Merge branch 'master' into markers_backup

# Conflicts:
#	OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java
This commit is contained in:
Vitaliy 2020-11-07 21:47:46 +02:00
commit bb10f31f86
65 changed files with 2191 additions and 1318 deletions

View file

@ -106,9 +106,11 @@ public class TspAnt {
// Allocates all memory.
// Adds 1 to edge lengths to ensure no zero length edges.
public TspAnt readGraph(List<LatLon> intermediates, LatLon start, LatLon end) {
boolean keepEndPoint = end != null;
List<LatLon> l = new ArrayList<LatLon>();
l.add(start);
boolean keepEndPoint = end != null;
List<LatLon> l = new ArrayList<LatLon>();
if (start != null) {
l.add(start);
}
l.addAll(intermediates);
if (keepEndPoint) {
l.add(end);

View file

@ -267,4 +267,8 @@
<string name="location_sharing_status">مشاركة: %1$s</string>
<string name="shared_string_enabled">مفعل</string>
<string name="duration_ago">%1$s منذ</string>
<string name="shared_string_export">تصدير</string>
<string name="logcat_buffer">لوجكات العازلة</string>
<string name="logcat_buffer_descr">تحقق من السجلات التفصيلية للتطبيق وشاركها</string>
<string name="send_report">إرسال تقرير</string>
</resources>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:icon="@mipmap/icon_free"
android:label="@string/app_name_free"
tools:replace="android:icon">
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="fb131313131043971"/>
<activity
android:name="net.osmand.plus.activities.MapActivity"
android:theme="@style/FirstSplashScreenCustom"
tools:replace="android:theme"/>
<service
android:name="net.osmand.plus.NavigationService"
android:process="net.osmand.freecustom"
tools:replace="android:process"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="net.osmand.freecustom.fileprovider"
tools:replace="android:authorities"/>
</application>
</manifest>

View file

@ -119,9 +119,6 @@ android {
full {
java.srcDirs = ["src-google"]
}
fulldev {
java.srcDirs = ["src-google"]
}
free {
java.srcDirs = ["src-google"]
manifest.srcFile "AndroidManifest-free.xml"
@ -130,10 +127,6 @@ android {
java.srcDirs = ["src-google"]
manifest.srcFile "AndroidManifest-freedev.xml"
}
freecustom {
java.srcDirs = ["src-google"]
manifest.srcFile "AndroidManifest-freecustom.xml"
}
freehuawei {
java.srcDirs = ["src-huawei"]
manifest.srcFile "AndroidManifest-freehuawei.xml"
@ -188,38 +181,24 @@ android {
dimension "version"
applicationId "net.osmand"
}
freeres {
dimension "version"
applicationId "net.osmand"
resConfig "en"
}
freecustom {
dimension "version"
applicationId "net.osmand.freecustom"
}
full {
dimension "version"
applicationId "net.osmand.plus"
}
fulldev {
dimension "version"
applicationId "net.osmand.plus"
resConfig "en"
// resConfigs "xxhdpi", "nodpi"
}
freehuawei {
dimension "version"
applicationId "net.osmand.huawei"
}
// CoreVersion
// Build that doesn't include 3D OpenGL
legacy {
dimension "coreversion"
}
// Build that includes 3D OpenGL release
qtcore {
dimension "coreversion"
}
// Build that includes 3D OpenGL debug
qtcoredebug {
dimension "coreversion"
}

View file

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M8,9.95C8,13.0979 9.799,16.5395 11.3078,18.9347C11.535,19.2955 11.7687,19.6502 12.0081,20H5V4H10.6499C9.0278,5.4317 8,7.5291 8,9.95Z"
android:strokeAlpha="0.3"
android:fillColor="#727272"
android:fillAlpha="0.3"/>
<path
android:pathData="M10.6499,4H5L5,20H12.0081C12.4759,20.6835 12.9654,21.3479 13.4706,22H5C3.8954,22 3,21.1046 3,20V4C3,2.8954 3.8954,2 5,2H15C15.112,2 15.2218,2.0092 15.3288,2.0269C13.5444,2.1705 11.9161,2.8824 10.6499,4Z"
android:strokeAlpha="0.7"
android:fillColor="#727272"
android:fillAlpha="0.7"/>
<path
android:pathData="M9.6612,16H8C7.4477,16 7,16.4477 7,17C7,17.5523 7.4477,18 8,18H10.7383C10.3718,17.3767 10.0035,16.7041 9.6612,16Z"
android:strokeAlpha="0.7"
android:fillColor="#727272"
android:fillAlpha="0.7"/>
<path
android:pathData="M6,6C6,5.4477 6.4477,5 7,5C7.5523,5 8,5.4477 8,6C8,6.5523 7.5523,7 7,7C6.4477,7 6,6.5523 6,6Z"
android:strokeAlpha="0.5"
android:fillColor="#727272"
android:fillAlpha="0.5"/>
<path
android:pathData="M16,22C16,22 22,15 22,9.95C22,6.55 19.3137,4 16,4C12.6863,4 10,6.55 10,9.95C10,15 16,22 16,22ZM16,12C17.1046,12 18,11.1046 18,10C18,8.8954 17.1046,8 16,8C14.8954,8 14,8.8954 14,10C14,11.1046 14.8954,12 16,12Z"
android:fillColor="#727272"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M5,4H15V20H5V4Z"
android:fillColor="#237BFF"/>
<path
android:pathData="M15,4H5L5,20H15V4ZM5,2C3.8954,2 3,2.8954 3,4V20C3,21.1046 3.8954,22 5,22H15C16.1046,22 17,21.1046 17,20V4C17,2.8954 16.1046,2 15,2H5Z"
android:fillColor="#264573"
android:fillType="evenOdd"/>
<path
android:pathData="M7,18C7,17.4477 7.4477,17 8,17H12C12.5523,17 13,17.4477 13,18C13,18.5523 12.5523,19 12,19H8C7.4477,19 7,18.5523 7,18Z"
android:fillColor="#66A3FF"/>
<path
android:pathData="M6,6C6,5.4477 6.4477,5 7,5C7.5523,5 8,5.4477 8,6C8,6.5523 7.5523,7 7,7C6.4477,7 6,6.5523 6,6Z"
android:fillColor="#002357"/>
<path
android:pathData="M17,5.3265C16.3744,5.1142 15.7013,5 15,5C11.6863,5 9,7.55 9,10.95C9,14.8397 12.441,19.8863 14.0222,22H15C15.4484,22 15.8623,21.8525 16.1958,21.6033C16.4203,21.2762 16.672,20.9017 16.9394,20.4904C16.979,20.3335 17,20.1692 17,20V5.3265Z"
android:strokeAlpha="0.1"
android:fillColor="#000000"
android:fillAlpha="0.1"/>
<path
android:pathData="M22,9.95C22,13.9575 17.1491,20.7132 16.3199,21.8388C16.243,21.9431 16.1239,22 15.9943,22V22C15.8712,22 15.7575,21.9487 15.6805,21.8526C14.8522,20.8194 10,14.569 10,9.95C10,6.55 12.6863,4 16,4C19.3137,4 22,6.55 22,9.95Z"
android:fillColor="#FF8800"/>
<path
android:pathData="M16,10m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#B35F00"/>
</vector>

View file

@ -26,9 +26,9 @@
android:id="@+id/common_graphs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
android:orientation="vertical">
tools:visibility="visible">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/line_chart"
@ -42,17 +42,22 @@
android:id="@+id/custom_graphs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
android:orientation="vertical">
tools:visibility="visible">
<com.github.mikephil.charting.charts.HorizontalBarChart
android:id="@+id/horizontal_chart"
android:layout_width="match_parent"
android:layout_height="@dimen/route_info_chart_height" />
<LinearLayout
android:id="@+id/route_items"
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/list_divider" />
<FrameLayout
android:id="@+id/route_legend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@ -65,48 +70,106 @@
android:id="@+id/message_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/content_padding_small"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding_small"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
android:orientation="horizontal">
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/message_icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:tint="?attr/default_icon_color"
tools:src="@drawable/ic_action_info_dark" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/card_button_progress_size"
android:layout_height="@dimen/card_button_progress_size"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:indeterminate="true"
android:visibility="gone" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/message_text"
android:layout_width="0dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:letterSpacing="@dimen/description_letter_spacing"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it." />
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/content_padding_small"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/message_icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:tint="?attr/default_icon_color"
tools:src="@drawable/ic_action_info_dark" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/card_button_progress_size"
android:layout_height="@dimen/card_button_progress_size"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:indeterminate="true"
android:visibility="gone" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/message_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:letterSpacing="@dimen/description_letter_spacing"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it." />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/btn_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_padding"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal">
<View
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:visibility="invisible"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="@dimen/bottom_sheet_list_item_height"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/divider_color_basic" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="@color/preference_category_title"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="@string/route_between_points" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -84,26 +84,11 @@
</ScrollView>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/inapp_descr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/list_content_padding_large"
android:layout_marginRight="@dimen/list_content_padding_large"
android:layout_marginTop="@dimen/title_padding"
android:gravity="center"
android:text="@string/osm_live_payment_desc"
android:textColor="?attr/card_description_text_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
android:layout_marginStart="@dimen/list_content_padding_large"
android:layout_marginEnd="@dimen/list_content_padding_large" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/card_padding"
android:layout_marginTop="@dimen/card_padding">
android:layout_marginTop="@dimen/title_padding">
<include layout="@layout/purchase_dialog_card_shadow_button"/>

View file

@ -2218,4 +2218,10 @@
<string name="all_previous_segments">Всички предишни сегменти</string>
<string name="only_selected_segment_recalc">Само избраният сегмент ще бъде преизчислен съобразно избрания профил.</string>
<string name="all_next_segments_will_be_recalc">Всички следващи сегменти ще бъдат преизчислени с помощта на избрания профил.</string>
<string name="plan_route_change_route_type_after">Промяна типа на маршрута след</string>
<string name="simplified_track">Опростена пътека</string>
<string name="simplified_track_description">Само линията на маршрута ще бъде запазена, точките ще бъдат изтрити.</string>
<string name="shared_string_file_name">Име на файл</string>
<string name="number_of_gpx_files_selected_pattern">Избрани файлове за проследяване: %s</string>
<string name="monitoring_control_start">REC</string>
</resources>

View file

@ -226,14 +226,14 @@
<string name="poi_pension_fund">صندوق بازنشستگی</string>
<string name="poi_migration">سازمان مهاجرت</string>
<string name="poi_tax_inspection">اداره مالیات</string>
<string name="poi_office_administrative">دفتر اجرایی</string>
<string name="poi_office_administrative">اداره حکومتی</string>
<string name="poi_customs">گمرک</string>
<string name="poi_city">شهر</string>
<string name="poi_town">شهر</string>
<string name="poi_village">روستا</string>
<string name="poi_hamlet">دهکده</string>
<string name="poi_isolated_dwelling">سکونتگاه دورافتاده</string>
<string name="poi_suburb">حومه شهر</string>
<string name="poi_suburb">منطقه شهری</string>
<string name="poi_neighbourhood">محله</string>
<string name="poi_locality">محل</string>
<string name="poi_place_allotments"/>
@ -346,7 +346,7 @@
<string name="poi_artwork">اثر هنری</string>
<string name="poi_archaeological_site">مرکز باستان شناسی</string>
<string name="poi_battlefield">میدان جنگ</string>
<string name="poi_boundary_stone">سنگ یادبود</string>
<string name="poi_boundary_stone">سنگ مرز</string>
<string name="poi_historic_cannon">توپ تاریخی</string>
<string name="poi_castle">قلعه</string>
<string name="poi_city_gate">دروازه شهر</string>
@ -1452,4 +1452,5 @@
<string name="poi_access_bus">دسترسی اتوبوس</string>
<string name="poi_climbing_crag">بله</string>
<string name="poi_protected_area">منطقهٔ حفاظت‌شده</string>
<string name="poi_badminton">بدمینتون</string>
</resources>

View file

@ -222,7 +222,7 @@
<string name="version_index_is_not_supported">La versione dell\'indice \'\'{0}\'\' non è supportata</string>
<string name="osmand_routing_experimental">Il routing offline di OsmAnd è sperimentale e non funziona per distanze superiori ai 20 km.
\n
\nNavigazione eseguita temporaneamente tramite servizio online CloudMade.</string>
\nNavigazione temporaneamente spostata sul servizio online CloudMade.</string>
<string name="specified_dir_doesnt_exist">Impossibile trovare la cartella specificata.</string>
<string name="application_dir">Cartella salvataggio dei dati</string>
<string name="osmand_net_previously_installed">Tutti i dati offline nella vecchia app verranno supportati dalla nuova, ma i Preferiti devono essere esportati dalla vecchia app e importati nella nuova.</string>
@ -313,13 +313,13 @@
<string name="gps_provider">GPS</string>
<string name="int_seconds">secondi</string>
<string name="int_min">min.</string>
<string name="background_service_int_descr">Intervallo di tempo di risveglio usato dal servizio in background:</string>
<string name="background_service_int_descr">Intervallo di risveglio usato dal servizio in background:</string>
<string name="background_service_int">Intervallo di risveglio GPS</string>
<string name="background_service_provider_descr">Fornitore di posizione utilizzato dal servizio in background:</string>
<string name="background_service_provider_descr">Metodo di localizzazione utilizzato dal servizio in background:</string>
<string name="background_service_provider">Fornitore di posizionamento</string>
<string name="background_router_service_descr">Traccia la posizione mentre lo schermo è spento.</string>
<string name="background_router_service">Esegui OsmAnd in background</string>
<string name="off_router_service_no_gps_available">Il servizio di navigazione in background di OsmAnd necessita del sistema di posizionamento attivato.</string>
<string name="off_router_service_no_gps_available">Il servizio di navigazione in background necessita di un sistema di posizionamento attivo.</string>
<string name="hide_poi_filter">Nascondi filtro</string>
<string name="show_poi_filter">Visualizza filtro</string>
<string name="search_poi_filter">Filtro</string>
@ -2695,13 +2695,13 @@
<string name="wiki_article_search_text">Sto cercando larticolo Wiki corrispondente</string>
<string name="wiki_article_not_found">Articolo non trovato</string>
<string name="how_to_open_wiki_title">Come aprire gli articoli di Wikipedia?</string>
<string name="osmand_plus_extended_description_part2">Navigazione
\n • Funziona online (veloce) o offline (nessuna tariffa di roaming quando sei all\'estero)
\n • Guida vocale svolta-dopo-svolta (voci registrate e sintetizzate)
\n • Guida sulla corsia opzionale, visualizzazione dei nomi delle strade, e tempo di arrivo stimato
\n • Supporto per punti intermedi del tuo itinerario
\n • Rielaborazione del percorso automatico ogni volta che si devia
\n • Ricerca di posti per indirizzo, per tipo (es: ristorante, albergo, stazione di servizio, museo) o per coordinate geografiche
<string name="osmand_plus_extended_description_part2">Navigazione
\n • Funziona online (veloce) o offline (nessuna tariffa di roaming quando sei all\'estero)
\n • Guida vocale svolta-per-svolta (voci registrate e sintetizzate)
\n • Indicazioni di corsia opzionali, visualizzazione dei nomi delle strade, e tempo di arrivo stimato
\n • Supporto dei punti intermedi del tuo itinerario
\n • Rielaborazione del percorso automatico ogni volta che si devia
\n • Ricerca di luoghi per indirizzo, per tipo (es: ristorante, albergo, stazione di servizio, museo) o per coordinate geografiche
\n</string>
<string name="hide_full_description">Nascondi la descrizione completa</string>
<string name="show_full_description">Mostra la descrizione completa</string>
@ -2743,12 +2743,12 @@
\n • Condividi la tua posizione in modo tale che i tuoi amici possano trovarti
\n</string>
<string name="osmand_plus_extended_description_part6">Funzioni per pedoni e velocipedi
\n • Mostra percorsi per pedoni, escursionisti e biciclette, ottimo per le attività allaperto
\n • Mostra percorsi per pedoni, escursionisti e biciclette, ottimo per le attività all\'aperto
\n • Navigazione e visualizzazione speciali per pedoni e velocipedi
\n • Scegli se mostrare le fermate del trasporto pubblico (bus, tram e treno) inclusi i nomi delle linee
\n • Scegli se registrare la tua visita su un file GPX locale oppure su di un servizio online
\n • Scegli se mostrare la velocità e laltitudine
\n • Mostra le curve di livello e lombreggiatura dei rilievi</string>
\n • Mostra le curve di livello e lombreggiatura dei rilievi (attraverso il componente aggiuntivo)</string>
<string name="unirs_render_descr">Modifica dello stile standard per un maggior contrasto delle strade pedonali e piste ciclabili. Utilizza i colori della versione vecchia di Mapnik.</string>
<string name="access_intermediate_arrival_time">Orario di arrivo intermedio</string>
<string name="map_widget_intermediate_time">Orario intermedio</string>
@ -3940,7 +3940,7 @@
<string name="icon_group_amenity">Amenità</string>
<string name="icon_group_special">Speciale</string>
<string name="icon_group_transport">Trasporto</string>
<string name="icon_group_service">Servizio</string>
<string name="icon_group_service">Servizi</string>
<string name="icon_group_symbols">Simboli</string>
<string name="icon_group_sport">Sport</string>
<string name="icon_group_emergency">Emergenza</string>

View file

@ -3675,7 +3675,7 @@
<string name="osmand_purchases_item">Zakupy w OsmAnd</string>
<string name="osm_live_payment_subscription_management">Opłata zostanie pobrana z twojego konta Google Play przy potwierdzeniu zakupu.
\n
\nSubskrypcja automatycznie odnawia się, chyba że anulujesz ją przed dniem odnowienia. Twoje konto zostanie obciążone za okres odnowienia (miesiąc/trzy miesiące/rok) dopiero w dniu odnowienia.
\nSubskrypcja automatycznie odnawia się, chyba że anulujesz ją przed dniem odnowienia. Twoje konto zostanie obciążone za okres odnowienia (miesiąc/trzy miesiące/rok) dopiero w dniu odnowienia.
\n
\nMożesz zarządzać subskrypcjami i anulować je w ustawieniach Google Play.</string>
<string name="release_3_7">• Nowe mapy stoków offline
@ -3802,7 +3802,7 @@
<string name="use_volume_buttons_as_zoom">Przyciski głośności jako powiększenie</string>
<string name="app_mode_wheelchair">Wózek inwalidzki</string>
<string name="app_mode_go_cart">Gokart</string>
<string name="osm_edit_closed_note">Zamknięta notatka OSM</string>
<string name="osm_edit_closed_note">Zamknięta uwaga OSM</string>
<string name="set_working_days_to_continue">Aby kontynuować, musisz ustawić dni robocze</string>
<string name="route_between_points">Droga pomiędzy punktami</string>
<string name="plan_a_route">Zaplanuj trasę</string>
@ -3844,7 +3844,7 @@
<string name="plan_route_exit_dialog_descr">Czy na pewno chcesz odrzucić wszystkie zmiany w zaplanowanej trasie, zamykając ją\?</string>
<string name="in_case_of_reverse_direction">W przypadku odwrotnego kierunku</string>
<string name="navigate_to_track_descr">Przejdź z mojej lokalizacji na trasę</string>
<string name="app_mode_wheelchair_forward">Wózek naprzód</string>
<string name="app_mode_wheelchair_forward">Wózek inwalidzki naprzód</string>
<string name="show_gpx">Ślady</string>
<string name="follow_track">Podążanie za śladem</string>
<string name="import_track_descr">Wybierz plik śladu do śledzenia lub zaimportuj go z urządzenia.</string>
@ -3913,7 +3913,7 @@
<string name="sort_name_ascending">Nazwa: A Z</string>
<string name="what_is_new">Co nowego</string>
<string name="start_finish_icons">Ikony start/koniec</string>
<string name="contour_lines_thanks">Dziękujemy za zakup linii konturowych</string>
<string name="contour_lines_thanks">Dziękujemy za zakup \"Linii konturowych\"</string>
<string name="osm_live_payment_desc_hw">Subskrypcja naliczona za wybrany okres. Anuluj ją w AppGallery w dowolnym momencie.</string>
<string name="osm_live_payment_subscription_management_hw">Płatność zostanie pobrana z konta AppGallery po potwierdzeniu zakupu.
\n
@ -3935,4 +3935,21 @@
<string name="osm_edit_logout_success">Wylogowanie powiodło się</string>
<string name="file_already_imported">Plik jest już zaimportowany do OsmAnd</string>
<string name="ltr_or_rtl_combine_via_dash">%1$s — %2$s</string>
<string name="navigate_point_mgrs">MGRS</string>
<string name="navigate_point_format_mgrs">MGRS</string>
<string name="use_two_phase_routing">Użyj 2-fazowego algorytmu routingu A *</string>
<string name="shared_string_graph">Wykres</string>
<string name="message_need_calculate_route_before_show_graph">%1$s dane dostępne tylko na drogach, aby je uzyskać, musisz obliczyć trasę za pomocą opcji „Trasa między punktami”.</string>
<string name="message_graph_will_be_available_after_recalculation">Poczekaj na ponowne obliczenie trasy.
\nWykres będzie dostępny po ponownym obliczeniu.</string>
<string name="shared_string_local_maps">Mapy lokalne</string>
<string name="app_mode_gap">Luka</string>
<string name="icon_group_amenity">Udogodnienie</string>
<string name="icon_group_special">Specjalne</string>
<string name="icon_group_transport">Transport</string>
<string name="icon_group_service">Usługi</string>
<string name="icon_group_symbols">Symbole</string>
<string name="icon_group_sport">Sport</string>
<string name="icon_group_emergency">Służby ratunkowe</string>
<string name="icon_group_travel">Podróże</string>
</resources>

View file

@ -3833,4 +3833,6 @@
<string name="poi_parking_sheds">Barracão</string>
<string name="poi_parking_rooftop">Telhado</string>
<string name="poi_gpx_point">Ponto GPX</string>
<string name="poi_radar_tower">Torre de radar</string>
<string name="poi_parking_layby">Área de repouso</string>
</resources>

View file

@ -3835,4 +3835,5 @@
<string name="poi_fuel_lng">СПГ</string>
<string name="poi_parking_sheds">Навесы</string>
<string name="poi_gpx_point">Точка GPX</string>
<string name="poi_radar_tower">Радиолокационная вышка</string>
</resources>

View file

@ -3937,4 +3937,8 @@
<string name="icon_group_sport">Спорт</string>
<string name="icon_group_emergency">Экстренные службы</string>
<string name="icon_group_travel">Путешествие</string>
<string name="navigate_point_mgrs">MGRS</string>
<string name="navigate_point_format_mgrs">MGRS</string>
<string name="mgrs_format_descr">OsmAnd использует MGRS, который похож на формат UTM NATO.</string>
<string name="use_native_pt">Развитие местного общественного транспорта</string>
</resources>

View file

@ -19,6 +19,7 @@
<attr name="expandable_list_background" format="color"/>
<attr name="bg_color" format="reference" />
<attr name="bg_circle" format="reference"/>
<attr name="bg_circle_contour" format="reference" />
<attr name="btn_round" format="reference" />
<attr name="btn_round_border" format="reference" />
<attr name="btn_round_border_2" format="reference" />

View file

@ -11,6 +11,12 @@
Thx - Hardy
-->
<string name="subscription_on_hold_title">OsmAnd Live subscription is on hold</string>
<string name="subscription_paused_title">OsmAnd Live subscription has been paused</string>
<string name="subscription_expired_title">OsmAnd Live subscription has been expired</string>
<string name="subscription_payment_issue_title">There is a problem with your subscription. Click the button to go to the Google Play subscription settings to fix your payment method.</string>
<string name="manage_subscription">Manage subscription</string>
<string name="message_you_need_add_two_points_to_show_graphs">You must add at least two points.</string>
<string name="icon_group_travel">Travel</string>
<string name="icon_group_emergency">Emergency</string>
<string name="icon_group_sport">Sport</string>

View file

@ -92,6 +92,7 @@
<item name="actionBarStyle">@style/Widget.Styled.ActionBarLight</item>
<item name="bg_color">@color/list_background_color_light</item>
<item name="bg_circle">@drawable/circle_background_light</item>
<item name="bg_circle_contour">@drawable/circle_contour_bg_light</item>
<item name="btn_round">@drawable/btn_round_light</item>
<item name="btn_round_border">@drawable/btn_round_border_light</item>
<item name="btn_round_border_2">@drawable/btn_round_border_light_2</item>
@ -394,6 +395,7 @@
<item name="actionBarStyle">@style/Widget.Styled.ActionBarDark</item>
<item name="bg_color">@color/list_background_color_dark</item>
<item name="bg_circle">@drawable/circle_background_dark</item>
<item name="bg_circle_contour">@drawable/circle_contour_bg_dark</item>
<item name="btn_round">@drawable/btn_round_dark</item>
<item name="btn_round_border">@drawable/btn_round_border_dark</item>
<item name="btn_round_border_2">@drawable/btn_round_border_dark_2</item>

View file

@ -21,8 +21,10 @@ import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState;
import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.util.BillingManager;
import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.settings.backend.OsmandPreference;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.srtmplugin.SRTMPlugin;
@ -33,6 +35,7 @@ import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map.Entry;
public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
@ -139,6 +142,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
for (Purchase p : purchases) {
skuSubscriptions.add(p.getSku());
}
skuSubscriptions.addAll(subscriptionStateMap.keySet());
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
@ -291,7 +295,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
}
// Listener that's called when we finish querying the items and subscriptions we own
private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
private final SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
@NonNull
private List<String> getAllOwnedSubscriptionSkus() {
@ -304,6 +308,15 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
}
}
}
for (Entry<String, SubscriptionState> entry : subscriptionStateMap.entrySet()) {
SubscriptionState state = entry.getValue();
if (state == SubscriptionState.PAUSED || state == SubscriptionState.ON_HOLD) {
String sku = entry.getKey();
if (!result.contains(sku)) {
result.add(sku);
}
}
}
return result;
}
@ -399,35 +412,28 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
// Do we have the live updates?
boolean subscribedToLiveUpdates = false;
List<Purchase> liveUpdatesPurchases = new ArrayList<>();
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
Purchase purchase = getPurchase(p.getSku());
if (purchase != null) {
liveUpdatesPurchases.add(purchase);
for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) {
Purchase purchase = getPurchase(s.getSku());
if (purchase != null || s.getState().isActive()) {
if (purchase != null) {
liveUpdatesPurchases.add(purchase);
}
if (!subscribedToLiveUpdates) {
subscribedToLiveUpdates = true;
}
}
}
OsmandPreference<Long> subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME;
if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) {
if (subscriptionCancelledTime.get() == 0) {
subscriptionCancelledTime.set(System.currentTimeMillis());
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
} else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) {
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false);
if (!isDepthContoursPurchased(ctx)) {
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false);
}
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false);
if (!isDepthContoursPurchased(ctx)) {
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false);
}
} else if (subscribedToLiveUpdates) {
subscriptionCancelledTime.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
}
lastValidationCheckTime = System.currentTimeMillis();
logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE")
+ " live updates purchased.");
logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + " live updates purchased.");
OsmandSettings settings = ctx.getSettings();
settings.INAPPS_READ.set(true);
@ -490,12 +496,24 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
}
}
if (inAppPurchase instanceof InAppSubscription) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
SubscriptionState state = subscriptionStateMap.get(inAppPurchase.getSku());
s.setState(state == null ? SubscriptionState.UNDEFINED : state);
CommonPreference<String> statePref = ctx.getSettings().registerStringPreference(
s.getSku() + "_state", SubscriptionState.UNDEFINED.getStateStr()).makeGlobal();
s.setPrevState(SubscriptionState.getByStateStr(statePref.get()));
statePref.set(s.getState().getStateStr());
if (s.getState().isGone() && s.hasStateChanged()) {
ctx.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.set(0L);
}
String introductoryPrice = skuDetails.getIntroductoryPrice();
String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod();
String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles();
long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros();
if (!Algorithms.isEmpty(introductoryPrice)) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
try {
s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles));

View file

@ -134,11 +134,11 @@ public class Version {
}
public static boolean isDeveloperVersion(OsmandApplication ctx){
return getAppName(ctx).contains("~") || ctx.getPackageName().equals(FREE_DEV_VERSION_NAME);
return false;//getAppName(ctx).contains("~") || ctx.getPackageName().equals(FREE_DEV_VERSION_NAME);
}
public static boolean isDeveloperBuild(OsmandApplication ctx){
return getAppName(ctx).contains("~");
return false;//getAppName(ctx).contains("~");
}
public static String getVersionForTracker(OsmandApplication ctx) {

View file

@ -214,7 +214,7 @@ public class LocalIndexHelper {
return result;
}
public void loadVoiceData(File voiceDir, List<LocalIndexInfo> result, boolean backup, AbstractLoadLocalIndexTask loadTask) {
private void loadVoiceData(File voiceDir, List<LocalIndexInfo> result, boolean backup, AbstractLoadLocalIndexTask loadTask) {
if (voiceDir.canRead()) {
//First list TTS files, they are preferred
for (File voiceF : listFilesSorted(voiceDir)) {

View file

@ -83,7 +83,7 @@ import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.base.ContextMenuFragment;
import net.osmand.plus.base.FailSafeFuntions;
import net.osmand.plus.base.MapViewTrackingUtilities;
import net.osmand.plus.chooseplan.OsmLiveCancelledDialog;
import net.osmand.plus.chooseplan.OsmLiveGoneDialog;
import net.osmand.plus.dashboard.DashBaseFragment;
import net.osmand.plus.dashboard.DashboardOnMap;
import net.osmand.plus.dialogs.CrashBottomSheetDialogFragment;
@ -845,8 +845,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
getSupportFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, new FirstUsageWelcomeFragment(),
FirstUsageWelcomeFragment.TAG).commitAllowingStateLoss();
} else if (!isFirstScreenShowing() && OsmLiveCancelledDialog.shouldShowDialog(app)) {
OsmLiveCancelledDialog.showInstance(getSupportFragmentManager());
} else if (SendAnalyticsBottomSheetDialogFragment.shouldShowDialog(app)) {
SendAnalyticsBottomSheetDialogFragment.showInstance(app, getSupportFragmentManager(), null);
}
@ -2290,6 +2288,9 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
@Override
public void onInAppPurchaseGetItems() {
DiscountHelper.checkAndDisplay(this);
if (!isFirstScreenShowing() && OsmLiveGoneDialog.shouldShowDialog(app)) {
OsmLiveGoneDialog.showInstance(app, getSupportFragmentManager());
}
}
public enum ShowQuickSearchMode {

View file

@ -1,245 +0,0 @@
package net.osmand.plus.chooseplan;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ProgressBar;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.Version;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandPreference;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.BaseOsmAndDialogFragment;
import net.osmand.plus.chooseplan.ChoosePlanDialogFragment.OsmAndFeature;
import net.osmand.plus.inapp.InAppPurchaseHelper;
import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener;
import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType;
import net.osmand.plus.widgets.TextViewEx;
import org.apache.commons.logging.Log;
import static net.osmand.plus.inapp.InAppPurchaseHelper.SUBSCRIPTION_HOLDING_TIME_MSEC;
public class OsmLiveCancelledDialog extends BaseOsmAndDialogFragment implements InAppPurchaseListener {
public static final String TAG = OsmLiveCancelledDialog.class.getSimpleName();
private static final Log LOG = PlatformUtil.getLog(OsmLiveCancelledDialog.class);
private OsmandApplication app;
private InAppPurchaseHelper purchaseHelper;
private boolean nightMode;
private View osmLiveButton;
private final OsmAndFeature[] osmLiveFeatures = {
OsmAndFeature.DAILY_MAP_UPDATES,
OsmAndFeature.UNLIMITED_DOWNLOADS,
OsmAndFeature.WIKIPEDIA_OFFLINE,
OsmAndFeature.WIKIVOYAGE_OFFLINE,
OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS,
OsmAndFeature.SEA_DEPTH_MAPS,
OsmAndFeature.UNLOCK_ALL_FEATURES,
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = getMyApplication();
purchaseHelper = app.getInAppPurchaseHelper();
nightMode = isNightMode(getMapActivity() != null);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Activity ctx = requireActivity();
int themeId = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
Dialog dialog = new Dialog(ctx, themeId);
Window window = dialog.getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
if (!getSettings().DO_NOT_USE_ANIMATIONS.get()) {
window.getAttributes().windowAnimations = R.style.Animations_Alpha;
}
if (Build.VERSION.SDK_INT >= 21) {
window.setStatusBarColor(ContextCompat.getColor(ctx, getStatusBarColor()));
}
}
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Context ctx = getContext();
if (ctx == null) {
return null;
}
int themeRes = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
View view = LayoutInflater.from(new ContextThemeWrapper(getContext(), themeRes))
.inflate(R.layout.osmlive_cancelled_dialog_fragment, container, false);
view.findViewById(R.id.button_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
TextViewEx infoDescr = (TextViewEx) view.findViewById(R.id.info_description);
StringBuilder descr = new StringBuilder();
descr.append(getString(R.string.purchase_cancelled_dialog_descr));
for (OsmAndFeature feature : osmLiveFeatures) {
descr.append("\n").append("").append(feature.toHumanString(ctx));
}
infoDescr.setText(descr);
TextViewEx inappDescr = (TextViewEx) view.findViewById(R.id.inapp_descr);
inappDescr.setText(Version.isHuawei(app) ? R.string.osm_live_payment_desc_hw : R.string.osm_live_payment_desc);
osmLiveButton = view.findViewById(R.id.card_button);
return view;
}
@Nullable
public MapActivity getMapActivity() {
Activity activity = getActivity();
if (activity instanceof MapActivity) {
return (MapActivity) activity;
}
return null;
}
@Override
public void onResume() {
super.onResume();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.disableDrawer();
}
boolean requestingInventory = purchaseHelper != null && purchaseHelper.getActiveTask() == InAppPurchaseTaskType.REQUEST_INVENTORY;
setupOsmLiveButton(requestingInventory);
OsmandPreference<Boolean> firstTimeShown = app.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN;
OsmandPreference<Boolean> secondTimeShown = app.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN;
if (!firstTimeShown.get()) {
firstTimeShown.set(true);
} else if (!secondTimeShown.get()) {
secondTimeShown.set(true);
}
}
@Override
public void onPause() {
super.onPause();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.enableDrawer();
}
}
@ColorRes
protected int getStatusBarColor() {
return nightMode ? R.color.status_bar_wikivoyage_dark : R.color.status_bar_wikivoyage_light;
}
@Override
public void onError(InAppPurchaseTaskType taskType, String error) {
if (taskType == InAppPurchaseTaskType.REQUEST_INVENTORY) {
setupOsmLiveButton(false);
}
}
@Override
public void onGetItems() {
}
@Override
public void onItemPurchased(String sku, boolean active) {
}
@Override
public void showProgress(InAppPurchaseTaskType taskType) {
if (taskType == InAppPurchaseTaskType.REQUEST_INVENTORY) {
setupOsmLiveButton(true);
}
}
@Override
public void dismissProgress(InAppPurchaseTaskType taskType) {
if (taskType == InAppPurchaseTaskType.REQUEST_INVENTORY) {
setupOsmLiveButton(false);
}
}
private void setupOsmLiveButton(boolean progress) {
if (osmLiveButton != null) {
ProgressBar progressBar = (ProgressBar) osmLiveButton.findViewById(R.id.card_button_progress);
TextViewEx buttonTitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_title);
TextViewEx buttonSubtitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_subtitle);
buttonTitle.setText(getString(R.string.osm_live_plan_pricing));
buttonSubtitle.setVisibility(View.GONE);
if (progress) {
buttonTitle.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
osmLiveButton.setOnClickListener(null);
} else {
buttonTitle.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
osmLiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
FragmentActivity activity = getActivity();
if (activity != null) {
ChoosePlanDialogFragment.showOsmLiveInstance(activity.getSupportFragmentManager());
}
}
});
}
}
}
public static boolean shouldShowDialog(OsmandApplication app) {
OsmandSettings settings = app.getSettings();
long cancelledTime = settings.LIVE_UPDATES_PURCHASE_CANCELLED_TIME.get();
boolean firstTimeShown = settings.LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.get();
boolean secondTimeShown = settings.LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.get();
return cancelledTime > 0
&& (!firstTimeShown
|| (System.currentTimeMillis() - cancelledTime > SUBSCRIPTION_HOLDING_TIME_MSEC
&& !secondTimeShown));
}
public static void showInstance(@NonNull FragmentManager fm) {
try {
if (fm.findFragmentByTag(OsmLiveCancelledDialog.TAG) == null) {
OsmLiveCancelledDialog fragment = new OsmLiveCancelledDialog();
fragment.show(fm, OsmLiveCancelledDialog.TAG);
}
} catch (RuntimeException e) {
LOG.error("showInstance", e);
}
}
}

View file

@ -0,0 +1,334 @@
package net.osmand.plus.chooseplan;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.BaseOsmAndDialogFragment;
import net.osmand.plus.chooseplan.ChoosePlanDialogFragment.OsmAndFeature;
import net.osmand.plus.inapp.InAppPurchaseHelper;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.settings.backend.OsmandPreference;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.widgets.TextViewEx;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
public abstract class OsmLiveGoneDialog extends BaseOsmAndDialogFragment {
public static final String TAG = OsmLiveGoneDialog.class.getName();
private static final Log LOG = PlatformUtil.getLog(OsmLiveGoneDialog.class);
private static final long TIME_BETWEEN_DIALOGS_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days
private OsmandApplication app;
private boolean nightMode;
private View osmLiveButton;
private final OsmAndFeature[] osmLiveFeatures = {
OsmAndFeature.DAILY_MAP_UPDATES,
OsmAndFeature.UNLIMITED_DOWNLOADS,
OsmAndFeature.WIKIPEDIA_OFFLINE,
OsmAndFeature.WIKIVOYAGE_OFFLINE,
OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS,
OsmAndFeature.SEA_DEPTH_MAPS,
OsmAndFeature.UNLOCK_ALL_FEATURES,
};
public static class OsmLiveOnHoldDialog extends OsmLiveGoneDialog {
public static final String TAG = OsmLiveOnHoldDialog.class.getSimpleName();
@Override
protected OsmLiveButtonType getOsmLiveButtonType() {
return OsmLiveButtonType.MANAGE_SUBSCRIPTION;
}
@Override
protected String getTitle() {
return getString(R.string.subscription_on_hold_title);
}
@Override
protected String getSubscriptionDescr() {
return getString(R.string.subscription_payment_issue_title);
}
}
public static class OsmLivePausedDialog extends OsmLiveGoneDialog {
public static final String TAG = OsmLivePausedDialog.class.getSimpleName();
@Override
protected OsmLiveButtonType getOsmLiveButtonType() {
return OsmLiveButtonType.MANAGE_SUBSCRIPTION;
}
@Override
protected String getTitle() {
return getString(R.string.subscription_paused_title);
}
}
public static class OsmLiveExpiredDialog extends OsmLiveGoneDialog {
public static final String TAG = OsmLiveExpiredDialog.class.getSimpleName();
@Override
protected String getTitle() {
return getString(R.string.subscription_expired_title);
}
}
protected enum OsmLiveButtonType {
PURCHASE_SUBSCRIPTION,
MANAGE_SUBSCRIPTION
}
protected OsmLiveButtonType getOsmLiveButtonType() {
return OsmLiveButtonType.PURCHASE_SUBSCRIPTION;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = getMyApplication();
nightMode = isNightMode(getMapActivity() != null);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Activity ctx = requireActivity();
int themeId = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
Dialog dialog = new Dialog(ctx, themeId);
Window window = dialog.getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
if (!getSettings().DO_NOT_USE_ANIMATIONS.get()) {
window.getAttributes().windowAnimations = R.style.Animations_Alpha;
}
if (Build.VERSION.SDK_INT >= 21) {
window.setStatusBarColor(ContextCompat.getColor(ctx, getStatusBarColor()));
}
}
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Context ctx = getContext();
if (ctx == null) {
return null;
}
int themeRes = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
View view = LayoutInflater.from(new ContextThemeWrapper(getContext(), themeRes))
.inflate(R.layout.osmlive_gone_dialog_fragment, container, false);
view.findViewById(R.id.button_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
TextViewEx title = (TextViewEx) view.findViewById(R.id.title);
title.setText(getTitle());
TextViewEx infoDescr = (TextViewEx) view.findViewById(R.id.info_description);
StringBuilder descr = new StringBuilder();
String subscriptionDescr = getSubscriptionDescr();
if (!Algorithms.isEmpty(subscriptionDescr)) {
descr.append(subscriptionDescr).append("\n\n");
}
descr.append(getString(R.string.purchase_cancelled_dialog_descr));
for (OsmAndFeature feature : osmLiveFeatures) {
descr.append("\n").append("").append(feature.toHumanString(ctx));
}
infoDescr.setText(descr);
osmLiveButton = view.findViewById(R.id.card_button);
return view;
}
protected abstract String getTitle();
protected String getSubscriptionDescr() {
return null;
}
@Nullable
public MapActivity getMapActivity() {
Activity activity = getActivity();
if (activity instanceof MapActivity) {
return (MapActivity) activity;
}
return null;
}
@Override
public void onResume() {
super.onResume();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.disableDrawer();
}
setupOsmLiveButton();
OsmandPreference<Long> firstTimeShownTime = app.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME;
OsmandPreference<Long> secondTimeShownTime = app.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME;
if (firstTimeShownTime.get() == 0) {
firstTimeShownTime.set(System.currentTimeMillis());
} else if (secondTimeShownTime.get() == 0) {
secondTimeShownTime.set(System.currentTimeMillis());
}
}
@Override
public void onPause() {
super.onPause();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.enableDrawer();
}
}
@ColorRes
protected int getStatusBarColor() {
return nightMode ? R.color.status_bar_wikivoyage_dark : R.color.status_bar_wikivoyage_light;
}
private void setupOsmLiveButton() {
if (osmLiveButton != null) {
TextViewEx buttonTitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_title);
TextViewEx buttonSubtitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_subtitle);
switch (getOsmLiveButtonType()) {
case PURCHASE_SUBSCRIPTION:
buttonTitle.setText(getString(R.string.osm_live_plan_pricing));
osmLiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
FragmentActivity activity = getActivity();
if (activity != null) {
ChoosePlanDialogFragment.showOsmLiveInstance(activity.getSupportFragmentManager());
}
}
});
break;
case MANAGE_SUBSCRIPTION:
buttonTitle.setText(getString(R.string.manage_subscription));
osmLiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
FragmentActivity activity = getActivity();
if (activity != null) {
InAppSubscription expiredSubscription = getExpiredSubscription((OsmandApplication) activity.getApplication());
if (expiredSubscription != null) {
manageSubscription(expiredSubscription.getSku());
}
}
}
});
break;
}
buttonSubtitle.setVisibility(View.GONE);
buttonTitle.setVisibility(View.VISIBLE);
osmLiveButton.findViewById(R.id.card_button_progress).setVisibility(View.GONE);
}
}
private void manageSubscription(@Nullable String sku) {
Context ctx = getContext();
if (ctx != null) {
String url = "https://play.google.com/store/account/subscriptions?package=" + ctx.getPackageName();
if (!Algorithms.isEmpty(sku)) {
url += "&sku=" + sku;
}
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
}
@Nullable
private static InAppSubscription getExpiredSubscription(@NonNull OsmandApplication app) {
if (!app.getSettings().LIVE_UPDATES_PURCHASED.get()) {
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
return purchaseHelper.getLiveUpdates().getTopExpiredSubscription();
}
return null;
}
public static boolean shouldShowDialog(@NonNull OsmandApplication app) {
InAppSubscription expiredSubscription = getExpiredSubscription(app);
if (expiredSubscription == null) {
return false;
}
OsmandSettings settings = app.getSettings();
long firstTimeShownTime = settings.LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.get();
long secondTimeShownTime = settings.LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.get();
return firstTimeShownTime == 0
|| (System.currentTimeMillis() - firstTimeShownTime > TIME_BETWEEN_DIALOGS_MSEC && secondTimeShownTime == 0);
}
public static void showInstance(@NonNull OsmandApplication app, @NonNull FragmentManager fm) {
try {
InAppSubscription expiredSubscription = getExpiredSubscription(app);
if (expiredSubscription == null) {
return;
}
String tag = null;
DialogFragment fragment = null;
switch (expiredSubscription.getState()) {
case ON_HOLD:
tag = OsmLiveOnHoldDialog.TAG;
if (fm.findFragmentByTag(tag) == null) {
fragment = new OsmLiveOnHoldDialog();
}
break;
case PAUSED:
tag = OsmLivePausedDialog.TAG;
if (fm.findFragmentByTag(tag) == null) {
fragment = new OsmLivePausedDialog();
}
break;
case EXPIRED:
tag = OsmLiveExpiredDialog.TAG;
if (fm.findFragmentByTag(tag) == null) {
fragment = new OsmLiveExpiredDialog();
}
break;
}
if (fragment != null) {
fragment.show(fm, tag);
}
} catch (RuntimeException e) {
LOG.error("showInstance", e);
}
}
}

View file

@ -20,6 +20,7 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import net.osmand.AndroidUtils;
import net.osmand.data.FavouritePoint;
@ -32,6 +33,8 @@ import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.FavoritesListFragment.FavouritesAdapter;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.editors.FavoritePointEditor;
import net.osmand.plus.mapcontextmenu.editors.FavoritePointEditorFragmentNew;
import java.text.MessageFormat;
import java.util.ArrayList;
@ -79,20 +82,24 @@ public class FavoriteDialogs {
@Override
public void onClick(DialogInterface dialog, int which) {
if(dlgHolder != null && dlgHolder.length > 0 && dlgHolder[0] != null) {
if (dlgHolder != null && dlgHolder.length > 0 && dlgHolder[0] != null) {
dlgHolder[0].dismiss();
}
FavouritePoint point = (FavouritePoint) args.getSerializable(KEY_FAVORITE);
if (helper.editFavourite(fp, point.getLatitude(), point.getLongitude())) {
if (point != null && helper.editFavourite(fp, point.getLatitude(), point.getLongitude())) {
helper.deleteFavourite(point);
if (activity instanceof MapActivity) {
((MapActivity) activity).getContextMenu()
MapActivity mapActivity = (MapActivity) activity;
Fragment fragment = mapActivity.getSupportFragmentManager()
.findFragmentByTag(FavoritePointEditor.TAG);
if (fragment instanceof FavoritePointEditorFragmentNew) {
((FavoritePointEditorFragmentNew) fragment).exitEditing();
}
mapActivity.getContextMenu()
.show(new LatLon(point.getLatitude(), point.getLongitude()), fp.getPointDescription(activity), fp);
mapActivity.getMapView().refreshMap();
}
}
if (activity instanceof MapActivity) {
((MapActivity) activity).getMapView().refreshMap();
}
}
});
builder.show();

View file

@ -342,7 +342,7 @@ public class DownloadActivityType {
return FileNameTranslationHelper.getFontName(ctx, getBasename(indexItem));
}
final String basename = getBasename(indexItem);
if (basename.endsWith(FileNameTranslationHelper.WIKI_NAME)){
if (basename.endsWith(FileNameTranslationHelper.WIKI_NAME)) {
return FileNameTranslationHelper.getWikiName(ctx, basename);
}
// if (this == HILLSHADE_FILE){
@ -357,7 +357,7 @@ public class DownloadActivityType {
final int ind = basename.indexOf("addresses-nationwide");
String downloadName = basename.substring(0, ind - 1) + basename.substring(ind + "addresses-nationwide".length());
return osmandRegions.getLocaleName(downloadName, includingParent) +
" "+ ctx.getString(R.string.index_item_nation_addresses);
" " + ctx.getString(R.string.index_item_nation_addresses);
} else if (basename.startsWith("Depth_")) {
final int extInd = basename.indexOf("osmand_ext");
String downloadName = extInd == -1 ? basename.substring(6, basename.length()).replace('_', ' ')
@ -438,11 +438,11 @@ public class DownloadActivityType {
}
if (this == HILLSHADE_FILE) {
return fileName.substring(0, fileName.length() - IndexConstants.SQLITE_EXT.length())
.replace(FileNameTranslationHelper.HILL_SHADE, "");
.replace(FileNameTranslationHelper.HILL_SHADE + "_", "");
}
if (this == SLOPE_FILE) {
return fileName.substring(0, fileName.length() - IndexConstants.SQLITE_EXT.length())
.replace(FileNameTranslationHelper.SLOPE, "");
.replace(FileNameTranslationHelper.SLOPE + "_", "");
}
if (fileName.endsWith(IndexConstants.SQLITE_EXT)) {
return fileName.substring(0, fileName.length() - IndexConstants.SQLITE_EXT.length());

View file

@ -143,9 +143,9 @@ public class IndexItem implements Comparable<IndexItem> {
public String getTranslatedBasename() {
if (type == DownloadActivityType.HILLSHADE_FILE) {
return (FileNameTranslationHelper.HILL_SHADE + getBasename()).replace("_", " ");
return (FileNameTranslationHelper.HILL_SHADE + "_" + getBasename()).replace("_", " ");
} else if (type == DownloadActivityType.SLOPE_FILE) {
return (FileNameTranslationHelper.SLOPE + getBasename()).replace('_', ' ');
return (FileNameTranslationHelper.SLOPE + "_" + getBasename()).replace('_', ' ');
} else {
return getBasename();
}

View file

@ -7,8 +7,6 @@ import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -19,7 +17,6 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@ -45,7 +42,6 @@ import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuAdapter.ItemClickListener;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.SQLiteTileSource;
import net.osmand.plus.UiUtilities;
@ -127,19 +123,6 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement
if (asyncLoader == null || asyncLoader.getResult() == null) {
reloadData();
}
getExpandableListView().setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
long packedPos = ((ExpandableListContextMenuInfo) menuInfo).packedPosition;
int group = ExpandableListView.getPackedPositionGroup(packedPos);
int child = ExpandableListView.getPackedPositionChild(packedPos);
if (child >= 0 && group >= 0) {
final LocalIndexInfo point = listAdapter.getChild(group, child);
showContextMenu(point);
}
}
});
}
public void reloadData() {
@ -156,62 +139,6 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement
}
}
private void showContextMenu(final LocalIndexInfo info) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final ContextMenuAdapter adapter = new ContextMenuAdapter(getMyApplication());
basicFileOperation(info, adapter);
OsmandPlugin.onContextMenuActivity(getActivity(), null, info, adapter);
String[] values = adapter.getItemNames();
builder.setItems(values, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ContextMenuItem item = adapter.getItem(which);
if (item.getItemClickListener() != null) {
item.getItemClickListener().onContextMenuClick(null,
item.getTitleId(), which, false, null);
}
}
});
builder.show();
}
private void basicFileOperation(final LocalIndexInfo info, ContextMenuAdapter adapter) {
ItemClickListener listener = new ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int resId, int pos, boolean isChecked, int[] viewCoordinates) {
return performBasicOperation(resId, info);
}
};
if (info.getType() == LocalIndexType.MAP_DATA || info.getType() == LocalIndexType.SRTM_DATA ||
info.getType() == LocalIndexType.WIKI_DATA ) {
if (!info.isBackupedData()) {
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.local_index_mi_backup, getContext())
.setListener(listener)
.createItem());
}
}
if (info.isBackupedData()) {
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.local_index_mi_restore, getContext())
.setListener(listener)
.createItem());
}
if (info.getType() != LocalIndexType.TTS_VOICE_DATA && info.getType() != LocalIndexType.VOICE_DATA
&& info.getType() != LocalIndexType.FONT_DATA) {
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.shared_string_rename, getContext())
.setListener(listener)
.createItem());
}
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.shared_string_delete, getContext())
.setListener(listener)
.createItem());
}
private boolean performBasicOperation(int resId, final LocalIndexInfo info) {
if (resId == R.string.shared_string_rename) {
FileUtils.renameFile(getActivity(), new File(info.getPathToData()), new RenameCallback() {

View file

@ -0,0 +1,30 @@
package net.osmand.plus.helpers;
import android.graphics.RectF;
import androidx.annotation.NonNull;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer;
import net.osmand.AndroidUtils;
public class CustomBarChartRenderer extends HorizontalBarChartRenderer {
private float highlightHalfWidth;
public CustomBarChartRenderer(@NonNull BarChart chart) {
this(chart, AndroidUtils.dpToPx(chart.getContext(), 1f) / 2f);
}
public CustomBarChartRenderer(@NonNull BarChart chart, float highlightHalfWidth) {
super(chart, chart.getAnimator(), chart.getViewPortHandler());
this.highlightHalfWidth = highlightHalfWidth;
}
@Override
protected void setHighlightDrawPos(Highlight high, RectF bar) {
bar.left = high.getDrawX() - highlightHalfWidth;
bar.right = high.getDrawX() + highlightHalfWidth;
}
}

View file

@ -4,6 +4,7 @@ import android.content.Context;
import net.osmand.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.map.OsmandRegions;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.download.DownloadResources;
@ -18,10 +19,14 @@ import java.lang.reflect.Field;
public class FileNameTranslationHelper {
private static final Log LOG = PlatformUtil.getLog(FileNameTranslationHelper.class);
public static final String WIKI_NAME = "_wiki";
public static final String HILL_SHADE = "Hillshade_";
public static final String SLOPE = "Slope_";
public static final String HILL_SHADE = "Hillshade";
public static final String SLOPE = "Slope";
public static final String SEA_DEPTH = "Depth_";
public static String getFileNameWithRegion(OsmandApplication app, String fileName) {
return getFileName(app, app.getResourceManager().getOsmandRegions(), fileName);
}
public static String getFileName(Context ctx, OsmandRegions regions, String fileName) {
String basename = getBasename(fileName);
if (basename.endsWith(WIKI_NAME)) { //wiki files
@ -30,13 +35,15 @@ public class FileNameTranslationHelper {
return getVoiceName(ctx, fileName);
} else if (fileName.endsWith(IndexConstants.FONT_INDEX_EXT)) { //otf files
return getFontName(ctx, basename);
} else if (fileName.startsWith(HILL_SHADE)){
} else if (fileName.startsWith(HILL_SHADE)) {
basename = basename.replace(HILL_SHADE + " ", "");
return getTerrainName(ctx, regions, basename, R.string.download_hillshade_maps);
} else if (fileName.startsWith(SLOPE)) {
basename = basename.replace(SLOPE + " ", "");
return getTerrainName(ctx, regions, basename, R.string.download_slope_maps);
} else if (fileName.length() == 2) { //voice recorded files
try {
Field f = R.string.class.getField("lang_"+fileName);
Field f = R.string.class.getField("lang_" + fileName);
if (f != null) {
Integer in = (Integer) f.get(null);
return ctx.getString(in);
@ -62,9 +69,10 @@ public class FileNameTranslationHelper {
public static String getTerrainName(Context ctx, OsmandRegions regions, String basename,
int terrainNameRes) {
String terrain = ctx.getString(terrainNameRes) + " ";
basename = basename.replace(" ", "_");
String terrain = ctx.getString(terrainNameRes);
String locName = regions.getLocaleName(basename.trim(), true);
return terrain + locName;
return ctx.getString(R.string.ltr_or_rtl_combine_via_space, locName, "(" + terrain + ")");
}
public static String getWikiName(Context ctx, String basename){

View file

@ -76,6 +76,8 @@ import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxDbHelper.GpxDataItemCallback;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmAndConstants;
import net.osmand.plus.OsmAndFormatter;
@ -2045,6 +2047,91 @@ public class GpxUiHelper {
return gpx;
}
public enum LineGraphType {
ALTITUDE,
SLOPE,
SPEED;
}
public static List<ILineDataSet> getDataSets(LineChart chart,
OsmandApplication app,
GPXTrackAnalysis analysis,
@NonNull LineGraphType firstType,
@Nullable LineGraphType secondType,
boolean calcWithoutGaps) {
if (app == null || chart == null || analysis == null) {
return new ArrayList<>();
}
List<ILineDataSet> result = new ArrayList<>();
if (secondType == null) {
ILineDataSet dataSet = getDataSet(chart, app, analysis, calcWithoutGaps, false, firstType);
if (dataSet != null) {
result.add(dataSet);
}
} else {
OrderedLineDataSet dataSet1 = getDataSet(chart, app, analysis, calcWithoutGaps, false, firstType);
OrderedLineDataSet dataSet2 = getDataSet(chart, app, analysis, calcWithoutGaps, true, secondType);
if (dataSet1 == null && dataSet2 == null) {
return new ArrayList<>();
} else if (dataSet1 == null) {
result.add(dataSet2);
} else if (dataSet2 == null) {
result.add(dataSet1);
} else if (dataSet1.getPriority() < dataSet2.getPriority()) {
result.add(dataSet2);
result.add(dataSet1);
} else {
result.add(dataSet1);
result.add(dataSet2);
}
}
return result;
}
private static OrderedLineDataSet getDataSet(@NonNull LineChart chart,
@NonNull OsmandApplication app,
@NonNull GPXTrackAnalysis analysis,
boolean calcWithoutGaps,
boolean useRightAxis,
@NonNull LineGraphType type) {
OrderedLineDataSet dataSet = null;
switch (type) {
case ALTITUDE: {
if (analysis.hasElevationData) {
dataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, useRightAxis, true, calcWithoutGaps);
}
break;
}
case SLOPE:
if (analysis.hasElevationData) {
dataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, useRightAxis, true, calcWithoutGaps);
}
break;
case SPEED: {
if (analysis.hasSpeedData) {
dataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, useRightAxis, true, calcWithoutGaps);
}
break;
}
}
return dataSet;
}
public static GpxDisplayItem makeGpxDisplayItem(OsmandApplication app, GPXUtilities.GPXFile gpx) {
GpxDisplayItem gpxItem = null;
String groupName = app.getString(R.string.current_route);
GpxDisplayGroup group = app.getSelectedGpxHelper().buildGpxDisplayGroup(gpx, 0, groupName);
if (group != null && group.getModifiableList().size() > 0) {
gpxItem = group.getModifiableList().get(0);
if (gpxItem != null) {
gpxItem.route = true;
}
}
return gpxItem;
}
public static class GPXInfo {

View file

@ -17,13 +17,12 @@ import net.osmand.AndroidNetworkUtils.OnRequestsResultListener;
import net.osmand.AndroidNetworkUtils.RequestResponse;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandPreference;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
import net.osmand.plus.liveupdates.CountrySelectionFragment;
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
@ -50,11 +49,10 @@ public abstract class InAppPurchaseHelper {
private static final String TAG = InAppPurchaseHelper.class.getSimpleName();
private boolean mDebugLog = false;
public static final long SUBSCRIPTION_HOLDING_TIME_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days
protected InAppPurchases purchases;
protected long lastValidationCheckTime;
protected boolean inventoryRequested;
protected Map<String, SubscriptionState> subscriptionStateMap = new HashMap<>();
private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily
@ -375,21 +373,33 @@ public abstract class InAppPurchaseHelper {
final String sku, final String payload) throws UnsupportedOperationException;
@SuppressLint("StaticFieldLeak")
private class RequestInventoryTask extends AsyncTask<Void, Void, String> {
private class RequestInventoryTask extends AsyncTask<Void, Void, String[]> {
RequestInventoryTask() {
}
@Override
protected String doInBackground(Void... params) {
protected String[] doInBackground(Void... params) {
try {
Map<String, String> parameters = new HashMap<>();
parameters.put("androidPackage", ctx.getPackageName());
addUserInfo(parameters);
return AndroidNetworkUtils.sendRequest(ctx,
String activeSubscriptionsIds = AndroidNetworkUtils.sendRequest(ctx,
"https://osmand.net/api/subscriptions/active",
parameters, "Requesting active subscriptions...", false, false);
String subscriptionsState = null;
String userId = ctx.getSettings().BILLING_USER_ID.get();
String userToken = ctx.getSettings().BILLING_USER_TOKEN.get();
if (!Algorithms.isEmpty(userId) && !Algorithms.isEmpty(userToken)) {
parameters.put("userId", userId);
parameters.put("userToken", userToken);
subscriptionsState = AndroidNetworkUtils.sendRequest(ctx,
"https://osmand.net/api/subscriptions/get",
parameters, "Requesting subscriptions state...", false, false);
}
return new String[] { activeSubscriptionsIds, subscriptionsState };
} catch (Exception e) {
logError("sendRequest Error", e);
}
@ -397,12 +407,14 @@ public abstract class InAppPurchaseHelper {
}
@Override
protected void onPostExecute(String response) {
logDebug("Response=" + response);
if (response != null) {
protected void onPostExecute(String[] response) {
logDebug("Response=" + Arrays.toString(response));
String activeSubscriptionsIdsJson = response[0];
String subscriptionsStateJson = response[1];
if (activeSubscriptionsIdsJson != null) {
inventoryRequested = true;
try {
JSONObject obj = new JSONObject(response);
JSONObject obj = new JSONObject(activeSubscriptionsIdsJson);
JSONArray names = obj.names();
if (names != null) {
for (int i = 0; i < names.length(); i++) {
@ -418,6 +430,24 @@ public abstract class InAppPurchaseHelper {
logError("Json parsing error", e);
}
}
if (subscriptionsStateJson != null) {
inventoryRequested = true;
Map<String, SubscriptionState> subscriptionStateMap = new HashMap<>();
try {
JSONArray subArrJson = new JSONArray(subscriptionsStateJson);
for (int i = 0; i < subArrJson.length(); i++) {
JSONObject subObj = subArrJson.getJSONObject(i);
String sku = subObj.getString("sku");
String state = subObj.getString("state");
if (!Algorithms.isEmpty(sku) && !Algorithms.isEmpty(state)) {
subscriptionStateMap.put(sku, SubscriptionState.getByStateStr(state));
}
}
} catch (JSONException e) {
logError("Json parsing error", e);
}
InAppPurchaseHelper.this.subscriptionStateMap = subscriptionStateMap;
}
exec(InAppPurchaseTaskType.REQUEST_INVENTORY, getRequestInventoryCommand());
}
}
@ -467,9 +497,8 @@ public abstract class InAppPurchaseHelper {
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.set(0L);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
notifyItemPurchased(sku, active);

View file

@ -24,6 +24,8 @@ import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
@ -190,6 +192,28 @@ public abstract class InAppPurchases {
}
return null;
}
@Nullable
public InAppSubscription getTopExpiredSubscription() {
List<InAppSubscription> expiredSubscriptions = new ArrayList<>();
for (InAppSubscription s : getAllSubscriptions()) {
if (s.getState().isGone()) {
expiredSubscriptions.add(s);
}
}
Collections.sort(expiredSubscriptions, new Comparator<InAppSubscription>() {
@Override
public int compare(InAppSubscription s1, InAppSubscription s2) {
int orderS1 = s1.getState().ordinal();
int orderS2 = s2.getState().ordinal();
if (orderS1 != orderS2) {
return (orderS1 < orderS2) ? -1 : ((orderS1 == orderS2) ? 0 : 1);
}
return Double.compare(s1.getMonthlyPriceValue(), s2.getMonthlyPriceValue());
}
});
return expiredSubscriptions.isEmpty() ? null : expiredSubscriptions.get(0);
}
}
public abstract static class InAppPurchase {
@ -554,9 +578,49 @@ public abstract class InAppPurchases {
private String subscriptionPeriodString;
private Period subscriptionPeriod;
private boolean upgrade = false;
private SubscriptionState state = SubscriptionState.UNDEFINED;
private SubscriptionState prevState = SubscriptionState.UNDEFINED;
private InAppSubscriptionIntroductoryInfo introductoryInfo;
public enum SubscriptionState {
UNDEFINED("undefined"),
ACTIVE("active"),
CANCELLED("cancelled"),
IN_GRACE_PERIOD("in_grace_period"),
ON_HOLD("on_hold"),
PAUSED("paused"),
EXPIRED("expired");
private final String stateStr;
SubscriptionState(@NonNull String stateStr) {
this.stateStr = stateStr;
}
public String getStateStr() {
return stateStr;
}
@NonNull
public static SubscriptionState getByStateStr(@NonNull String stateStr) {
for (SubscriptionState state : SubscriptionState.values()) {
if (state.stateStr.equals(stateStr)) {
return state;
}
}
return UNDEFINED;
}
public boolean isGone() {
return this == ON_HOLD || this == PAUSED || this == EXPIRED;
}
public boolean isActive() {
return this == ACTIVE || this == CANCELLED || this == IN_GRACE_PERIOD;
}
}
InAppSubscription(@NonNull String skuNoVersion, int version) {
super(skuNoVersion + "_v" + version);
this.skuNoVersion = skuNoVersion;
@ -592,6 +656,28 @@ public abstract class InAppPurchases {
return upgrade;
}
@NonNull
public SubscriptionState getState() {
return state;
}
public void setState(@NonNull SubscriptionState state) {
this.state = state;
}
@NonNull
public SubscriptionState getPrevState() {
return prevState;
}
public void setPrevState(@NonNull SubscriptionState prevState) {
this.prevState = prevState;
}
public boolean hasStateChanged() {
return state != prevState;
}
public boolean isAnyPurchased() {
if (isPurchased()) {
return true;

View file

@ -495,7 +495,7 @@ public abstract class ImageCard extends AbstractCard {
}
}
} catch (Exception e) {
e.printStackTrace();
LOG.error(e);
}
if (listener != null) {
listener.onPostProcess(result);

View file

@ -942,7 +942,7 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment implemen
}
}
private void exitEditing() {
public void exitEditing() {
cancelled = true;
dismiss();
}

View file

@ -125,8 +125,7 @@ public class TrackDetailsMenu {
hidding = true;
fragment.dismiss(backPressed);
} else {
segment = null;
trackChartPoints = null;
reset();
}
}
@ -274,8 +273,7 @@ public class TrackDetailsMenu {
if (hidding) {
hidding = false;
visible = false;
segment = null;
trackChartPoints = null;
reset();
}
}
@ -532,6 +530,11 @@ public class TrackDetailsMenu {
fitTrackOnMap(chart, location, forceFit);
}
public void reset() {
segment = null;
trackChartPoints = null;
}
private List<LatLon> getXAxisPoints(LineChart chart) {
float[] entries = chart.getXAxis().mEntries;
LineData lineData = chart.getLineData();

View file

@ -1,11 +1,14 @@
package net.osmand.plus.measurementtool;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -16,15 +19,23 @@ import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.helpers.GpxUiHelper.LineGraphType;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.MeasurementToolFragment.OnUpdateAdditionalInfoListener;
import net.osmand.plus.measurementtool.graph.CommonGraphAdapter;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter.LegendViewType;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper.RefreshMapCallback;
import net.osmand.plus.routepreparationmenu.RouteDetailsFragment;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.router.RouteSegmentResult;
@ -32,48 +43,44 @@ import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static net.osmand.GPXUtilities.GPXFile;
import static net.osmand.GPXUtilities.GPXTrackAnalysis;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.ALTITUDE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SLOPE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SPEED;
import static net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem;
import static net.osmand.router.RouteStatisticsHelper.RouteStatistics;
public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListener {
private static String GRAPH_DATA_GPX_FILE_NAME = "graph_data_tmp";
private static int INVALID_ID = -1;
private MeasurementEditingContext editingCtx;
private MeasurementToolFragment fragment;
private GraphType visibleGraphType;
private List<GraphType> graphTypes = new ArrayList<>();
private TrackDetailsMenu trackDetailsMenu;
private RefreshMapCallback refreshMapCallback;
private GPXFile gpxFile;
private GPXTrackAnalysis analysis;
private GpxDisplayItem gpxItem;
private View commonGraphContainer;
private View customGraphContainer;
private View messageContainer;
private LineChart commonGraphChart;
private HorizontalBarChart customGraphChart;
private CommonGraphAdapter commonGraphAdapter;
private CustomGraphAdapter customGraphAdapter;
private RecyclerView graphTypesMenu;
private enum CommonGraphType {
OVERVIEW(R.string.shared_string_overview, false),
ALTITUDE(R.string.altitude, true),
SLOPE(R.string.shared_string_slope, true),
SPEED(R.string.map_widget_speed, false);
private GraphType visibleType;
private List<GraphType> graphTypes = new ArrayList<>();
CommonGraphType(int titleId, boolean canBeCalculated) {
this.titleId = titleId;
this.canBeCalculated = canBeCalculated;
}
final int titleId;
final boolean canBeCalculated;
}
public GraphsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) {
public GraphsCard(@NonNull MapActivity mapActivity,
TrackDetailsMenu trackDetailsMenu,
MeasurementToolFragment fragment) {
super(mapActivity);
this.trackDetailsMenu = trackDetailsMenu;
this.fragment = fragment;
}
@ -82,19 +89,27 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
if (mapActivity == null || fragment == null) return;
editingCtx = fragment.getEditingCtx();
graphTypesMenu = view.findViewById(R.id.graph_types_recycler_view);
graphTypesMenu.setLayoutManager(new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false));
commonGraphContainer = view.findViewById(R.id.common_graphs_container);
customGraphContainer = view.findViewById(R.id.custom_graphs_container);
messageContainer = view.findViewById(R.id.message_container);
commonGraphChart = (LineChart) view.findViewById(R.id.line_chart);
customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart);
updateGraphData();
LineChart lineChart = (LineChart) view.findViewById(R.id.line_chart);
HorizontalBarChart barChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart);
commonGraphAdapter = new CommonGraphAdapter(lineChart, true);
customGraphAdapter = new CustomGraphAdapter(barChart, true);
graphTypesMenu = view.findViewById(R.id.graph_types_recycler_view);
graphTypesMenu.setLayoutManager(
new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false));
customGraphAdapter.setLegendContainer((ViewGroup) view.findViewById(R.id.route_legend));
customGraphAdapter.setLayoutChangeListener(new BaseGraphAdapter.LayoutChangeListener() {
@Override
public void onLayoutChanged() {
setLayoutNeeded();
}
});
refreshGraphTypesSelectionMenu();
updateDataView();
GraphAdapterHelper.bindGraphAdapters(commonGraphAdapter, Collections.singletonList((BaseGraphAdapter) customGraphAdapter), (ViewGroup) view);
refreshMapCallback = GraphAdapterHelper.bindToMap(commonGraphAdapter, mapActivity, trackDetailsMenu);
fullUpdate();
}
@Override
@ -104,15 +119,32 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
@Override
public void onUpdateAdditionalInfo() {
if (!isRouteCalculating()) {
updateGraphData();
refreshGraphTypesSelectionMenu();
if (editingCtx != null) {
fullUpdate();
}
updateDataView();
}
private void refreshGraphTypesSelectionMenu() {
graphTypesMenu.removeAllViews();
private void fullUpdate() {
if (!isRouteCalculating()) {
updateData();
setupVisibleType();
updateMenu();
}
updateView();
updateChartOnMap();
}
private void updateMenu() {
if (!editingCtx.isPointsEnoughToCalculateRoute()) {
graphTypesMenu.setVisibility(View.GONE);
} else {
graphTypesMenu.setVisibility(View.VISIBLE);
graphTypesMenu.removeAllViews();
fillInMenu();
}
}
private void fillInMenu() {
OsmandApplication app = getMyApplication();
int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode);
@ -127,16 +159,16 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
}
}
adapter.setItems(items);
String selectedItemKey = visibleGraphType.getTitle();
String selectedItemKey = visibleType.getTitle();
adapter.setSelectedItemByTitle(selectedItemKey);
adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() {
adapter.setListener(new HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) {
public void onItemSelected(HorizontalSelectionItem item) {
adapter.setItems(items);
adapter.setSelectedItem(item);
GraphType chosenGraphType = (GraphType) item.getObject();
if (!isCurrentVisibleType(chosenGraphType)) {
setupVisibleGraphType(chosenGraphType);
GraphType chosenType = (GraphType) item.getObject();
if (!isVisibleType(chosenType)) {
changeVisibleType(chosenType);
}
}
});
@ -144,19 +176,19 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
adapter.notifyDataSetChanged();
}
private void setupVisibleGraphType(GraphType type) {
visibleGraphType = type;
updateDataView();
private void changeVisibleType(GraphType type) {
visibleType = type;
updateView();
}
private boolean isCurrentVisibleType(GraphType type) {
if (visibleGraphType != null && type != null) {
return Algorithms.objectEquals(visibleGraphType.getTitle(), type.getTitle());
private boolean isVisibleType(GraphType type) {
if (visibleType != null && type != null) {
return Algorithms.objectEquals(visibleType.getTitle(), type.getTitle());
}
return false;
}
private GraphType getFirstAvailableGraphType() {
private GraphType getFirstAvailableType() {
for (GraphType type : graphTypes) {
if (type.isAvailable()) {
return type;
@ -165,208 +197,181 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
return null;
}
private void updateDataView() {
if (isRouteCalculating()) {
showProgressMessage();
} else if (visibleGraphType.hasData()) {
private void updateView() {
hideAll();
if (!editingCtx.isPointsEnoughToCalculateRoute()) {
showMessage(app.getString(R.string.message_you_need_add_two_points_to_show_graphs));
} else if (isRouteCalculating()) {
showMessage(app.getString(R.string.message_graph_will_be_available_after_recalculation), true);
} else if (visibleType.hasData()) {
showGraph();
} else if (visibleGraphType.canBeCalculated()) {
showMessage();
} else if (visibleType.canBeCalculated()) {
showMessage(app.getString(R.string.message_need_calculate_route_before_show_graph,
visibleType.getTitle()), R.drawable.ic_action_altitude_average,
app.getString(R.string.route_between_points), new View.OnClickListener() {
@Override
public void onClick(View v) {
fragment.startSnapToRoad(false);
}
});
}
}
private void showProgressMessage() {
private void hideAll() {
commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.GONE);
}
private void showMessage(String text) {
showMessage(text, INVALID_ID, false, null, null);
}
private void showMessage(String text, @DrawableRes int iconResId, String btnTitle, View.OnClickListener btnListener) {
showMessage(text, iconResId, false, btnTitle, btnListener);
}
private void showMessage(String text, boolean showProgressBar) {
showMessage(text, INVALID_ID, showProgressBar, null, null);
}
private void showMessage(@NonNull String text,
@DrawableRes int iconResId,
boolean showProgressBar,
String btnTitle,
View.OnClickListener btnListener) {
messageContainer.setVisibility(View.VISIBLE);
TextView tvMessage = messageContainer.findViewById(R.id.message_text);
tvMessage.setText(text);
ImageView icon = messageContainer.findViewById(R.id.message_icon);
if (iconResId != INVALID_ID) {
icon.setVisibility(View.VISIBLE);
icon.setImageResource(iconResId);
} else {
icon.setVisibility(View.GONE);
}
ProgressBar pb = messageContainer.findViewById(R.id.progress_bar);
pb.setVisibility(View.VISIBLE);
icon.setVisibility(View.GONE);
tvMessage.setText(R.string.message_graph_will_be_available_after_recalculation);
pb.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
View btnContainer = messageContainer.findViewById(R.id.btn_container);
if (btnTitle != null) {
TextView tvBtnTitle = btnContainer.findViewById(R.id.btn_text);
tvBtnTitle.setText(btnTitle);
btnContainer.setVisibility(View.VISIBLE);
} else {
btnContainer.setVisibility(View.GONE);
}
if (btnListener != null) {
btnContainer.setOnClickListener(btnListener);
}
}
private void showGraph() {
if (visibleGraphType.isCustom()) {
customGraphChart.clear();
commonGraphContainer.setVisibility(View.GONE);
if (visibleType.isCustom()) {
CustomGraphType customGraphType = (CustomGraphType) visibleType;
customGraphContainer.setVisibility(View.VISIBLE);
messageContainer.setVisibility(View.GONE);
prepareCustomGraphView((BarData) visibleGraphType.getGraphData());
customGraphAdapter.setLegendViewType(LegendViewType.ONE_ELEMENT);
customGraphAdapter.updateContent(customGraphType.getChartData(), customGraphType.getStatistics());
} else {
commonGraphChart.clear();
CommonGraphType commonGraphType = (CommonGraphType) visibleType;
commonGraphContainer.setVisibility(View.VISIBLE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.GONE);
prepareCommonGraphView((LineData) visibleGraphType.getGraphData());
customGraphAdapter.setLegendViewType(LegendViewType.GONE);
commonGraphAdapter.updateContent(commonGraphType.getChartData(), gpxItem);
}
}
private void showMessage() {
commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.VISIBLE);
TextView tvMessage = messageContainer.findViewById(R.id.message_text);
ImageView icon = messageContainer.findViewById(R.id.message_icon);
ProgressBar pb = messageContainer.findViewById(R.id.progress_bar);
pb.setVisibility(View.GONE);
icon.setVisibility(View.VISIBLE);
tvMessage.setText(app.getString(
R.string.message_need_calculate_route_before_show_graph,
visibleGraphType.getTitle()));
icon.setImageResource(R.drawable.ic_action_altitude_average);
}
private void prepareCommonGraphView(LineData data) {
GpxUiHelper.setupGPXChart(commonGraphChart, 4, 24f, 16f, !nightMode, true);
commonGraphChart.setData(data);
}
private void prepareCustomGraphView(BarData data) {
OsmandApplication app = getMyApplication();
if (app == null) return;
GpxUiHelper.setupHorizontalGPXChart(app, customGraphChart, 5, 9, 24, true, nightMode);
customGraphChart.setExtraRightOffset(16);
customGraphChart.setExtraLeftOffset(16);
customGraphChart.setData(data);
}
private void updateGraphData() {
private void updateData() {
graphTypes.clear();
OsmandApplication app = getMyApplication();
GPXTrackAnalysis analysis = createGpxTrackAnalysis();
gpxFile = getGpxFile();
analysis = gpxFile != null ? gpxFile.getAnalysis(0) : null;
gpxItem = gpxFile != null ? GpxUiHelper.makeGpxDisplayItem(app, gpxFile) : null;
if (gpxItem != null) {
trackDetailsMenu.setGpxItem(gpxItem);
}
if (analysis == null) return;
// update common graph data
for (CommonGraphType commonType : CommonGraphType.values()) {
List<ILineDataSet> dataSets = getDataSets(commonType, commonGraphChart, analysis);
LineData data = null;
if (!Algorithms.isEmpty(dataSets)) {
data = new LineData(dataSets);
}
String title = app.getString(commonType.titleId);
graphTypes.add(new GraphType(title, false, commonType.canBeCalculated, data));
}
boolean hasElevationData = analysis.hasElevationData;
boolean hasSpeedData = analysis.isSpeedSpecified();
addCommonType(R.string.shared_string_overview, true, hasElevationData, ALTITUDE, SLOPE);
addCommonType(R.string.altitude, true, hasElevationData, ALTITUDE, null);
addCommonType(R.string.shared_string_slope, true, hasElevationData, SLOPE, null);
addCommonType(R.string.map_widget_speed, false, hasSpeedData, SPEED, null);
// update custom graph data
List<RouteSegmentResult> routeSegments = editingCtx.getAllRouteSegments();
List<RouteStatistics> routeStatistics = calculateRouteStatistics(routeSegments);
List<RouteStatistics> routeStatistics = calculateRouteStatistics();
if (analysis != null && routeStatistics != null) {
for (RouteStatistics statistics : routeStatistics) {
String title = AndroidUtils.getStringRouteInfoPropertyValue(app, statistics.name);
BarData data = null;
if (!Algorithms.isEmpty(statistics.elements)) {
data = GpxUiHelper.buildStatisticChart(
app, customGraphChart, statistics, analysis, true, nightMode);
}
graphTypes.add(new GraphType(title, true, false, data));
graphTypes.add(new CustomGraphType(title, statistics));
}
}
}
// update current visible graph type
if (visibleGraphType == null) {
visibleGraphType = getFirstAvailableGraphType();
private void updateChartOnMap() {
if (hasVisibleGraph()) {
trackDetailsMenu.reset();
refreshMapCallback.refreshMap(false);
}
}
private void addCommonType(int titleId,
boolean canBeCalculated,
boolean hasData,
LineGraphType firstType,
LineGraphType secondType) {
OsmandApplication app = getMyApplication();
String title = app.getString(titleId);
graphTypes.add(new CommonGraphType(title, canBeCalculated, hasData, firstType, secondType));
}
private void setupVisibleType() {
if (visibleType == null) {
visibleType = getFirstAvailableType();
} else {
for (GraphType type : graphTypes) {
if (isCurrentVisibleType(type)) {
visibleGraphType = type.isAvailable() ? type : getFirstAvailableGraphType();
if (isVisibleType(type)) {
visibleType = type.isAvailable() ? type : getFirstAvailableType();
break;
}
}
}
}
private List<ILineDataSet> getDataSets(CommonGraphType type, LineChart chart, GPXTrackAnalysis analysis) {
List<ILineDataSet> dataSets = new ArrayList<>();
if (chart != null && analysis != null) {
OsmandApplication app = getMyApplication();
switch (type) {
case OVERVIEW: {
List<OrderedLineDataSet> dataList = new ArrayList<>();
if (analysis.hasSpeedData) {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, true, true, false);
dataList.add(speedDataSet);
}
if (analysis.hasElevationData) {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);
dataList.add(elevationDataSet);
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false);
dataList.add(slopeDataSet);
}
if (dataList.size() > 0) {
Collections.sort(dataList, new Comparator<OrderedLineDataSet>() {
@Override
public int compare(OrderedLineDataSet o1, OrderedLineDataSet o2) {
return Float.compare(o1.getPriority(), o2.getPriority());
}
});
}
dataSets.addAll(dataList);
break;
}
case ALTITUDE: {
if (analysis.hasElevationData) {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
dataSets.add(elevationDataSet);
}
break;
}
case SLOPE:
if (analysis.hasElevationData) {
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false);
dataSets.add(slopeDataSet);
}
break;
case SPEED: {
if (analysis.hasSpeedData) {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
dataSets.add(speedDataSet);
}
break;
}
}
}
return dataSets;
}
private GPXTrackAnalysis createGpxTrackAnalysis() {
GPXFile gpx;
if (editingCtx.getGpxData() != null) {
gpx = editingCtx.getGpxData().getGpxFile();
private GPXFile getGpxFile() {
if (fragment.isTrackReadyToCalculate()) {
return editingCtx.exportGpx(GRAPH_DATA_GPX_FILE_NAME);
} else {
gpx = editingCtx.exportGpx(GRAPH_DATA_GPX_FILE_NAME);
GpxData gpxData = editingCtx.getGpxData();
return gpxData != null ? gpxData.getGpxFile() : null;
}
return gpx != null ? gpx.getAnalysis(0) : null;
}
private List<RouteStatistics> calculateRouteStatistics(List<RouteSegmentResult> route) {
private List<RouteStatistics> calculateRouteStatistics() {
OsmandApplication app = getMyApplication();
if (route == null || app == null) return null;
return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode);
List<RouteSegmentResult> route = editingCtx.getAllRouteSegments();
if (route != null && app != null) {
return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode);
}
return null;
}
private boolean isRouteCalculating() {
return fragment.isProgressBarVisible();
}
private static class GraphType {
private String title;
private boolean isCustom;
private boolean canBeCalculated;
private ChartData graphData;
public boolean hasVisibleGraph() {
return (commonGraphContainer != null && commonGraphContainer.getVisibility() == View.VISIBLE)
|| (customGraphContainer != null && customGraphContainer.getVisibility() == View.VISIBLE);
}
public GraphType(String title, boolean isCustom, boolean canBeCalculated, ChartData graphData) {
private abstract class GraphType<T extends ChartData> {
private String title;
private boolean canBeCalculated;
public GraphType(String title, boolean canBeCalculated) {
this.title = title;
this.isCustom = isCustom;
this.canBeCalculated = canBeCalculated;
this.graphData = graphData;
}
public String getTitle() {
@ -374,23 +379,80 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
}
public boolean isCustom() {
return isCustom;
return this instanceof CustomGraphType;
}
public boolean isAvailable() {
return hasData() || canBeCalculated();
return isPointsCountEnoughToCalculateRoute() && (hasData() || canBeCalculated());
}
private boolean isPointsCountEnoughToCalculateRoute() {
return editingCtx.getPointsCount() >= 2;
}
public boolean canBeCalculated() {
return canBeCalculated;
}
public boolean hasData() {
return getGraphData() != null;
public abstract boolean hasData();
public abstract T getChartData();
}
private class CommonGraphType extends GraphType<LineData> {
private boolean hasData;
private LineGraphType firstType;
private LineGraphType secondType;
public CommonGraphType(String title, boolean canBeCalculated, boolean hasData, @NonNull LineGraphType firstType, @Nullable LineGraphType secondType) {
super(title, canBeCalculated);
this.hasData = hasData;
this.firstType = firstType;
this.secondType = secondType;
}
public ChartData getGraphData() {
return graphData;
@Override
public boolean hasData() {
return hasData;
}
@Override
public LineData getChartData() {
GpxUiHelper.setupGPXChart(commonGraphAdapter.getChart(), 4, 24f, 16f, !nightMode, true);
List<ILineDataSet> dataSets = GpxUiHelper.getDataSets(commonGraphAdapter.getChart(),
app, analysis, firstType, secondType, false);
return !Algorithms.isEmpty(dataSets) ? new LineData(dataSets) : null;
}
}
private class CustomGraphType extends GraphType<BarData> {
private RouteStatistics statistics;
public CustomGraphType(String title, RouteStatistics statistics) {
super(title, false);
this.statistics = statistics;
}
public RouteStatistics getStatistics() {
return statistics;
}
@Override
public boolean hasData() {
return !Algorithms.isEmpty(statistics.elements);
}
@Override
public BarData getChartData() {
GpxUiHelper.setupHorizontalGPXChart(app, customGraphAdapter.getChart(), 5, 9, 24, true, nightMode);
BarData data = null;
if (!Algorithms.isEmpty(statistics.elements)) {
data = GpxUiHelper.buildStatisticChart(app, customGraphAdapter.getChart(),
statistics, analysis, true, nightMode);
}
return data;
}
}
}

View file

@ -398,6 +398,10 @@ public class MeasurementEditingContext {
return before.points.size();
}
public boolean isPointsEnoughToCalculateRoute() {
return getPointsCount() >= 2;
}
public List<RouteSegmentResult> getAllRouteSegments() {
List<RouteSegmentResult> allSegments = new ArrayList<>();
for (Pair<WptPt, WptPt> key : getOrderedRoadSegmentDataKeys()) {

View file

@ -50,6 +50,7 @@ import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.base.ContextMenuFragment.MenuState;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.GpxApproximationFragment.GpxApproximationFragmentListener;
import net.osmand.plus.measurementtool.OptionsBottomSheetDialogFragment.OptionsFragmentListener;
import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragment.RouteBetweenPointsDialogMode;
@ -113,8 +114,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
private TextView distanceToCenterTv;
private String pointsSt;
private View additionalInfoContainer;
private ViewGroup additionalInfoCardsContainer;
private BaseCard visibleAdditionalInfoCard;
private ViewGroup cardsContainer;
private BaseCard visibleCard;
private PointsCard pointsCard;
private GraphsCard graphsCard;
private LinearLayout customRadioButton;
private View mainView;
private ImageView upDownBtn;
@ -141,6 +144,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
private int cachedMapPosition;
private MeasurementEditingContext editingCtx = new MeasurementEditingContext();
private GraphDetailsMenu detailsMenu;
private LatLon initialPoint;
@ -160,6 +164,19 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
GRAPH
}
private class GraphDetailsMenu extends TrackDetailsMenu {
@Override
protected int getFragmentWidth() {
return mainView.getWidth();
}
@Override
protected int getFragmentHeight() {
return mainView.getHeight();
}
}
private void setEditingCtx(MeasurementEditingContext editingCtx) {
this.editingCtx = editingCtx;
}
@ -253,8 +270,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
mainView = view.findViewById(R.id.main_view);
AndroidUtils.setBackground(mapActivity, mainView, nightMode, R.drawable.bg_bottom_menu_light, R.drawable.bg_bottom_menu_dark);
detailsMenu = new GraphDetailsMenu();
additionalInfoContainer = mainView.findViewById(R.id.additional_info_container);
additionalInfoCardsContainer = mainView.findViewById(R.id.cards_container);
cardsContainer = mainView.findViewById(R.id.cards_container);
if (portrait) {
customRadioButton = mainView.findViewById(R.id.custom_radio_buttons);
@ -278,6 +296,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
}
});
}
pointsCard = new PointsCard(mapActivity, this);
graphsCard = new GraphsCard(mapActivity, detailsMenu, this);
if (progressBarVisible) {
showProgressBar();
@ -513,29 +533,33 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) {
MapActivity ma = getMapActivity();
if (ma == null) return;
currentAdditionalInfoType = type;
updateUpDownBtn();
OsmandApplication app = ma.getMyApplication();
BaseCard additionalInfoCard = null;
if (AdditionalInfoType.POINTS == type) {
additionalInfoCard = new PointsCard(ma, this);
visibleCard = pointsCard;
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START);
} else if (AdditionalInfoType.GRAPH == type) {
additionalInfoCard = new GraphsCard(ma, this);
visibleCard = graphsCard;
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END);
}
if (additionalInfoCard != null) {
visibleAdditionalInfoCard = additionalInfoCard;
additionalInfoCardsContainer.removeAllViews();
additionalInfoCardsContainer.addView(additionalInfoCard.build(ma));
additionalInfoExpanded = true;
}
cardsContainer.removeAllViews();
View cardView = visibleCard.getView() != null ? visibleCard.getView() : visibleCard.build(ma);
cardsContainer.addView(cardView);
currentAdditionalInfoType = type;
additionalInfoExpanded = true;
updateUpDownBtn();
}
}
private void updateAdditionalInfoView() {
if (visibleAdditionalInfoCard instanceof OnUpdateAdditionalInfoListener) {
((OnUpdateAdditionalInfoListener) visibleAdditionalInfoCard).onUpdateAdditionalInfo();
updateAdditionalInfoView(pointsCard);
updateAdditionalInfoView(graphsCard);
}
private void updateAdditionalInfoView(OnUpdateAdditionalInfoListener listener) {
if (listener != null) {
listener.onUpdateAdditionalInfo();
}
}
@ -588,6 +612,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
super.onResume();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
detailsMenu.setMapActivity(mapActivity);
mapActivity.getMapLayers().getMapControlsLayer().addThemeInfoProviderTag(TAG);
mapActivity.getMapLayers().getMapControlsLayer().showMapControlsIfHidden();
cachedMapPosition = mapActivity.getMapView().getMapPosition();
@ -603,6 +628,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (mapActivity != null) {
mapActivity.getMapLayers().getMapControlsLayer().removeThemeInfoProviderTag(TAG);
}
detailsMenu.onDismiss();
detailsMenu.setMapActivity(null);
setMapPosition(cachedMapPosition);
}
@ -669,7 +696,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
mainIcon.setImageDrawable(getActiveIcon(gpxData != null ? R.drawable.ic_action_polygom_dark : R.drawable.ic_action_ruler));
}
private void startSnapToRoad(boolean rememberPreviousTitle) {
public void startSnapToRoad(boolean rememberPreviousTitle) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
if (rememberPreviousTitle) {
@ -1189,7 +1216,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
final ApplicationMode appMode = editingCtx.getAppMode();
if (mapActivity != null) {
Drawable icon;
if (!editingCtx.isApproximationNeeded() || editingCtx.isNewData()) {
if (isTrackReadyToCalculate()) {
if (appMode == MeasurementEditingContext.DEFAULT_APP_MODE) {
icon = getActiveIcon(R.drawable.ic_action_split_interval);
} else {
@ -1204,6 +1231,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
}
}
public boolean isTrackReadyToCalculate() {
return !editingCtx.isApproximationNeeded() || editingCtx.isNewData();
}
private void hideSnapToRoadIcon() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
@ -1488,6 +1519,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
return type.equals(currentAdditionalInfoType);
}
public boolean hasVisibleGraph() {
return graphsCard != null && graphsCard.hasVisibleGraph();
}
private String getSuggestedFileName() {
GpxData gpxData = editingCtx.getGpxData();
String displayedName = null;

View file

@ -24,7 +24,9 @@ public class PointsCard extends BaseCard implements OnUpdateAdditionalInfoListen
@Override
public void onUpdateAdditionalInfo() {
adapter.notifyDataSetChanged();
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
@Override

View file

@ -0,0 +1,82 @@
package net.osmand.plus.measurementtool.graph;
import android.view.MotionEvent;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.ChartTouchListener;
import net.osmand.plus.OsmandApplication;
public abstract class BaseGraphAdapter<_Chart extends Chart, _ChartData extends ChartData, _Data> {
private Highlight lastKnownHighlight;
protected _Chart chart;
protected _ChartData chartData;
protected _Data additionalData;
protected boolean usedOnMap;
public BaseGraphAdapter(_Chart chart, boolean usedOnMap) {
this.chart = chart;
this.usedOnMap = usedOnMap;
prepareChartView();
}
protected void prepareChartView() {
chart.setExtraRightOffset(16);
chart.setExtraLeftOffset(16);
}
public _Chart getChart() {
return chart;
}
protected void updateHighlight() {
highlight(lastKnownHighlight);
}
public void highlight(Highlight h) {
this.lastKnownHighlight = h;
}
public void updateContent(_ChartData chartData, _Data data) {
updateData(chartData, data);
updateView();
}
public void updateData(_ChartData chartData, _Data data) {
this.chartData = chartData;
this.additionalData = data;
}
public abstract void updateView();
protected boolean isNightMode() {
OsmandApplication app = getMyApplication();
if (app != null) {
return usedOnMap ? app.getDaynightHelper().isNightModeForMapControls()
: !app.getSettings().isLightContent();
}
return false;
}
protected OsmandApplication getMyApplication() {
return (OsmandApplication) chart.getContext().getApplicationContext();
}
public interface ExternalValueSelectedListener {
void onValueSelected(Entry e, Highlight h);
void onNothingSelected();
}
public interface ExternalGestureListener {
void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture);
void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture, boolean hasTranslated);
}
public interface LayoutChangeListener {
void onLayoutChanged();
}
}

View file

@ -0,0 +1,138 @@
package net.osmand.plus.measurementtool.graph;
import android.graphics.Matrix;
import android.view.MotionEvent;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.ChartTouchListener;
import com.github.mikephil.charting.listener.OnChartGestureListener;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import java.util.HashMap;
import java.util.Map;
public class CommonGraphAdapter extends BaseGraphAdapter<LineChart, LineData, GpxDisplayItem> {
private Highlight highlight;
private Map<String, ExternalValueSelectedListener> externalValueSelectedListeners = new HashMap<>();
private ExternalGestureListener externalGestureListener;
public CommonGraphAdapter(LineChart chart, boolean usedOnMap) {
super(chart, usedOnMap);
}
@Override
protected void prepareChartView() {
super.prepareChartView();
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
highlight = h;
for (ExternalValueSelectedListener listener : externalValueSelectedListeners.values()) {
listener.onValueSelected(e, h);
}
}
@Override
public void onNothingSelected() {
for (ExternalValueSelectedListener listener : externalValueSelectedListeners.values()) {
listener.onNothingSelected();
}
}
});
chart.setOnChartGestureListener(new OnChartGestureListener() {
boolean hasTranslated = false;
float highlightDrawX = -1;
@Override
public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
hasTranslated = false;
if (chart.getHighlighted() != null && chart.getHighlighted().length > 0) {
highlightDrawX = chart.getHighlighted()[0].getDrawX();
} else {
highlightDrawX = -1;
}
if (externalGestureListener != null) {
externalGestureListener.onChartGestureStart(me, lastPerformedGesture);
}
}
@Override
public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
GpxDisplayItem gpxItem = getGpxItem();
gpxItem.chartMatrix = new Matrix(chart.getViewPortHandler().getMatrixTouch());
Highlight[] highlights = chart.getHighlighted();
if (highlights != null && highlights.length > 0) {
gpxItem.chartHighlightPos = highlights[0].getX();
} else {
gpxItem.chartHighlightPos = -1;
}
if (externalGestureListener != null) {
externalGestureListener.onChartGestureEnd(me, lastPerformedGesture, hasTranslated);
}
}
@Override
public void onChartLongPressed(MotionEvent me) {
}
@Override
public void onChartDoubleTapped(MotionEvent me) {
}
@Override
public void onChartSingleTapped(MotionEvent me) {
}
@Override
public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
}
@Override
public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
}
@Override
public void onChartTranslate(MotionEvent me, float dX, float dY) {
hasTranslated = true;
if (highlightDrawX != -1) {
Highlight h = chart.getHighlightByTouchPoint(highlightDrawX, 0f);
if (h != null) {
chart.highlightValue(h, true);
}
}
}
});
}
public void addValueSelectedListener(String key, ExternalValueSelectedListener listener) {
this.externalValueSelectedListeners.put(key, listener);
}
public void setExternalGestureListener(ExternalGestureListener listener) {
this.externalGestureListener = listener;
}
@Override
public void updateView() {
chart.setData(chartData);
updateHighlight();
}
@Override
public void highlight(Highlight h) {
super.highlight(h);
chart.highlightValue(highlight);
}
public GpxDisplayItem getGpxItem() {
return additionalData;
}
}

View file

@ -0,0 +1,183 @@
package net.osmand.plus.measurementtool.graph;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.graphics.ColorUtils;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.helpers.CustomBarChartRenderer;
import net.osmand.router.RouteStatisticsHelper;
import net.osmand.router.RouteStatisticsHelper.RouteStatistics;
import net.osmand.router.RouteStatisticsHelper.RouteSegmentAttribute;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static net.osmand.plus.track.ColorsCard.MINIMUM_CONTRAST_RATIO;
public class CustomGraphAdapter extends BaseGraphAdapter<HorizontalBarChart, BarData, RouteStatistics> {
private String selectedPropertyName;
private ViewGroup legendContainer;
private LegendViewType legendViewType;
private LayoutChangeListener layoutChangeListener;
public enum LegendViewType {
ONE_ELEMENT,
ALL_AS_LIST,
GONE
}
public CustomGraphAdapter(HorizontalBarChart chart, boolean usedOnMap) {
super(chart, usedOnMap);
}
@Override
protected void prepareChartView() {
super.prepareChartView();
legendViewType = LegendViewType.GONE;
chart.setRenderer(new CustomBarChartRenderer(chart));
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
if (getStatistics() == null) return;
List<RouteStatisticsHelper.RouteSegmentAttribute> elems = getStatistics().elements;
int i = h.getStackIndex();
if (i >= 0 && elems.size() > i) {
selectedPropertyName = elems.get(i).getPropertyName();
updateLegend();
}
}
@Override
public void onNothingSelected() {
selectedPropertyName = null;
updateLegend();
}
});
}
@Override
public void updateView() {
chart.setData(chartData);
updateHighlight();
updateLegend();
}
public void setLegendContainer(ViewGroup legendContainer) {
this.legendContainer = legendContainer;
}
public void setLegendViewType(LegendViewType legendViewType) {
this.legendViewType = legendViewType;
}
public void setLayoutChangeListener(LayoutChangeListener layoutChangeListener) {
this.layoutChangeListener = layoutChangeListener;
}
public void highlight(Highlight h) {
super.highlight(h);
Highlight bh = h != null ? chart.getHighlighter().getHighlight(1, h.getXPx()) : null;
if (bh != null) {
bh.setDraw(h.getXPx(), 0);
}
chart.highlightValue(bh, true);
}
private void updateLegend() {
if (legendContainer != null) {
legendContainer.removeAllViews();
attachLegend();
if (layoutChangeListener != null) {
layoutChangeListener.onLayoutChanged();
}
}
}
private void attachLegend() {
if (getSegmentsList() == null) return;
switch (legendViewType) {
case ONE_ELEMENT:
for (RouteSegmentAttribute segment : getSegmentsList()) {
if (segment.getPropertyName().equals(selectedPropertyName)) {
attachLegend(Collections.singletonList(segment), null);
break;
}
}
break;
case ALL_AS_LIST:
attachLegend(getSegmentsList(), selectedPropertyName);
break;
}
}
private void attachLegend(List<RouteSegmentAttribute> list,
String propertyNameToFullSpan) {
OsmandApplication app = getMyApplication();
LayoutInflater inflater = LayoutInflater.from(app);
for (RouteStatisticsHelper.RouteSegmentAttribute segment : list) {
View view = inflater.inflate(R.layout.route_details_legend, legendContainer, false);
int segmentColor = segment.getColor();
Drawable circle = app.getUIUtilities().getPaintedIcon(R.drawable.ic_action_circle, segmentColor);
ImageView legendIcon = (ImageView) view.findViewById(R.id.legend_icon_color);
legendIcon.setImageDrawable(circle);
double contrastRatio = ColorUtils.calculateContrast(segmentColor,
AndroidUtils.getColorFromAttr(app, R.attr.card_and_list_background_basic));
if (contrastRatio < MINIMUM_CONTRAST_RATIO) {
legendIcon.setBackgroundResource(AndroidUtils.resolveAttribute(app, R.attr.bg_circle_contour));
}
String propertyName = segment.getUserPropertyName();
String name = AndroidUtils.getRenderingStringPropertyName(app, propertyName, propertyName.replaceAll("_", " "));
boolean selected = segment.getPropertyName().equals(propertyNameToFullSpan);
Spannable text = getSpanLegend(name, segment, selected);
TextView legend = (TextView) view.findViewById(R.id.legend_text);
legend.setText(text);
legendContainer.addView(view);
}
}
private Spannable getSpanLegend(String title,
RouteSegmentAttribute segment,
boolean fullSpan) {
String formattedDistance = OsmAndFormatter.getFormattedDistance(segment.getDistance(), getMyApplication());
title = Algorithms.capitalizeFirstLetter(title);
SpannableStringBuilder spannable = new SpannableStringBuilder(title);
spannable.append(": ");
int startIndex = fullSpan ? -0 : spannable.length();
spannable.append(formattedDistance);
spannable.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
startIndex, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
private List<RouteSegmentAttribute> getSegmentsList() {
return getStatistics() != null ? new ArrayList<>(getStatistics().partition.values()) : null;
}
private RouteStatistics getStatistics() {
return additionalData;
}
}

View file

@ -0,0 +1,155 @@
package net.osmand.plus.measurementtool.graph;
import android.annotation.SuppressLint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.ChartTouchListener;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter.ExternalValueSelectedListener;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter.ExternalGestureListener;
import java.util.List;
public class GraphAdapterHelper {
public static final String BIND_GRAPH_ADAPTERS_KEY = "bind_graph_adapters_key";
public static final String BIND_TO_MAP_KEY = "bind_to_map_key";
public static void bindGraphAdapters(final CommonGraphAdapter mainGraphAdapter,
final List<BaseGraphAdapter> otherGraphAdapters,
final ViewGroup mainView) {
if (mainGraphAdapter == null || mainGraphAdapter.getChart() == null
|| otherGraphAdapters == null || otherGraphAdapters.size() == 0) {
return;
}
final LineChart mainChart = mainGraphAdapter.getChart();
View.OnTouchListener mainChartTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (mainView != null) {
mainView.requestDisallowInterceptTouchEvent(true);
}
for (BaseGraphAdapter adapter : otherGraphAdapters) {
if (adapter.getChart() != null) {
MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
adapter.getChart().dispatchTouchEvent(event);
}
}
return false;
}
};
mainChart.setOnTouchListener(mainChartTouchListener);
mainGraphAdapter.addValueSelectedListener(BIND_GRAPH_ADAPTERS_KEY,
new ExternalValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
for (BaseGraphAdapter adapter : otherGraphAdapters) {
adapter.highlight(h);
}
}
@Override
public void onNothingSelected() {
for (BaseGraphAdapter adapter : otherGraphAdapters) {
adapter.highlight(null);
}
}
}
);
View.OnTouchListener otherChartsTouchListener = new View.OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (ev.getSource() != 0) {
final MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
mainChart.dispatchTouchEvent(event);
return true;
}
return false;
}
};
for (BaseGraphAdapter adapter : otherGraphAdapters) {
if (adapter.getChart() != null) {
if (adapter.getChart() instanceof BarChart) {
// maybe we should find min and max axis from all charters
BarChart barChart = (BarChart) adapter.getChart();
barChart.getAxisRight().setAxisMinimum(mainChart.getXChartMin());
barChart.getAxisRight().setAxisMaximum(mainChart.getXChartMax());
barChart.setHighlightPerDragEnabled(false);
barChart.setHighlightPerTapEnabled(false);
}
adapter.getChart().setOnTouchListener(otherChartsTouchListener);
}
}
}
public static RefreshMapCallback bindToMap(@NonNull final CommonGraphAdapter graphAdapter,
@NonNull final MapActivity mapActivity,
@NonNull final TrackDetailsMenu detailsMenu) {
final RefreshMapCallback refreshMapCallback = new RefreshMapCallback() {
@Override
public void refreshMap(boolean forceFit) {
LineChart chart = graphAdapter.getChart();
OsmandApplication app = mapActivity.getMyApplication();
if (!app.getRoutingHelper().isFollowingMode()) {
detailsMenu.refreshChart(chart, forceFit);
mapActivity.refreshMap();
}
}
};
graphAdapter.addValueSelectedListener(BIND_TO_MAP_KEY,
new CommonGraphAdapter.ExternalValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
refreshMapCallback.refreshMap(false);
}
@Override
public void onNothingSelected() {
}
});
graphAdapter.setExternalGestureListener(new ExternalGestureListener() {
@Override
public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
}
@Override
public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture, boolean hasTranslated) {
if ((lastPerformedGesture == ChartTouchListener.ChartGesture.DRAG && hasTranslated) ||
lastPerformedGesture == ChartTouchListener.ChartGesture.X_ZOOM ||
lastPerformedGesture == ChartTouchListener.ChartGesture.Y_ZOOM ||
lastPerformedGesture == ChartTouchListener.ChartGesture.PINCH_ZOOM ||
lastPerformedGesture == ChartTouchListener.ChartGesture.DOUBLE_TAP ||
lastPerformedGesture == ChartTouchListener.ChartGesture.ROTATE) {
refreshMapCallback.refreshMap(true);
}
}
});
return refreshMapCallback;
}
public interface RefreshMapCallback {
void refreshMap(boolean forceFit);
}
}

View file

@ -62,6 +62,7 @@ import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.base.OsmAndListFragment;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.LineGraphType;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
@ -86,6 +87,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.ALTITUDE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SLOPE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SPEED;
public class TrackSegmentFragment extends OsmAndListFragment implements TrackBitmapDrawerListener {
private OsmandApplication app;
@ -424,64 +429,17 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
}
}
private List<ILineDataSet> getDataSets(GPXTabItemType tabType, LineChart chart) {
private List<ILineDataSet> getDataSets(LineChart chart,
GPXTabItemType tabType,
LineGraphType firstType,
LineGraphType secondType) {
List<ILineDataSet> dataSets = dataSetsMap.get(tabType);
if (dataSets == null && chart != null) {
dataSets = new ArrayList<>();
GPXTrackAnalysis analysis = gpxItem.analysis;
GpxDataItem gpxDataItem = getGpxDataItem();
boolean calcWithoutGaps = gpxItem.isGeneralTrack() && gpxDataItem != null && !gpxDataItem.isJoinSegments();
switch (tabType) {
case GPX_TAB_ITEM_GENERAL: {
OrderedLineDataSet speedDataSet = null;
OrderedLineDataSet elevationDataSet = null;
if (analysis.hasSpeedData) {
speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, true, true, calcWithoutGaps);
}
if (analysis.hasElevationData) {
elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, calcWithoutGaps);
}
if (speedDataSet != null) {
dataSets.add(speedDataSet);
if (elevationDataSet != null) {
dataSets.add(elevationDataSet.getPriority() < speedDataSet.getPriority()
? 1 : 0, elevationDataSet);
}
} else if (elevationDataSet != null) {
dataSets.add(elevationDataSet);
}
dataSetsMap.put(GPXTabItemType.GPX_TAB_ITEM_GENERAL, dataSets);
break;
}
case GPX_TAB_ITEM_ALTITUDE: {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, calcWithoutGaps);
if (elevationDataSet != null) {
dataSets.add(elevationDataSet);
}
if (analysis.hasElevationData) {
List<Entry> eleValues = elevationDataSet != null && !gpxItem.isGeneralTrack() ? elevationDataSet.getValues() : null;
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, eleValues, true, true, calcWithoutGaps);
if (slopeDataSet != null) {
dataSets.add(slopeDataSet);
}
}
dataSetsMap.put(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, dataSets);
break;
}
case GPX_TAB_ITEM_SPEED: {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, calcWithoutGaps);
if (speedDataSet != null) {
dataSets.add(speedDataSet);
}
dataSetsMap.put(GPXTabItemType.GPX_TAB_ITEM_SPEED, dataSets);
break;
}
}
dataSets = GpxUiHelper.getDataSets(chart, app, analysis, firstType, secondType, calcWithoutGaps);
dataSetsMap.put(tabType, dataSets);
}
return dataSets;
}
@ -702,7 +660,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
if (analysis != null) {
if (analysis.hasElevationData || analysis.hasSpeedData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(GPXTabItemType.GPX_TAB_ITEM_GENERAL, chart)));
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_GENERAL, ALTITUDE, SPEED)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
@ -820,7 +778,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
if (analysis != null) {
if (analysis.hasElevationData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, chart)));
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, ALTITUDE, SLOPE)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
@ -922,7 +880,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
if (analysis != null && analysis.isSpeedSpecified()) {
if (analysis.hasSpeedData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(GPXTabItemType.GPX_TAB_ITEM_SPEED, chart)));
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_SPEED, SPEED, null)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
@ -1188,7 +1146,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
LatLon location = null;
WptPt wpt = null;
gpxItem.chartTypes = null;
List<ILineDataSet> ds = getDataSets(tabType, null);
List<ILineDataSet> ds = getDataSets(null, tabType, null, null);
if (ds != null && ds.size() > 0) {
gpxItem.chartTypes = new GPXDataSetType[ds.size()];
for (int i = 0; i < ds.size(); i++) {

View file

@ -2,7 +2,6 @@ package net.osmand.plus.routepreparationmenu;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@ -14,11 +13,9 @@ import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
@ -30,15 +27,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.animation.ChartAnimator;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture;
import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer;
import com.github.mikephil.charting.utils.ViewPortHandler;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXFile;
@ -68,10 +57,13 @@ import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.mapcontextmenu.CollapsableView;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter;
import net.osmand.plus.measurementtool.graph.CommonGraphAdapter;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper.RefreshMapCallback;
import net.osmand.plus.render.MapRenderRepositories;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener;
import net.osmand.plus.routepreparationmenu.cards.CardChartListener;
import net.osmand.plus.routepreparationmenu.cards.PublicTransportCard;
import net.osmand.plus.routepreparationmenu.cards.PublicTransportCard.PublicTransportCardListener;
import net.osmand.plus.routepreparationmenu.cards.RouteDirectionsCard;
@ -98,8 +90,8 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class RouteDetailsFragment extends ContextMenuFragment implements PublicTransportCardListener,
CardListener, CardChartListener {
public class RouteDetailsFragment extends ContextMenuFragment
implements PublicTransportCardListener, CardListener {
public static final String ROUTE_ID_KEY = "route_id_key";
private static final float PAGE_MARGIN = 5f;
@ -122,6 +114,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
private RouteStatisticCard statisticCard;
private List<RouteInfoCard> routeInfoCards = new ArrayList<>();
private RouteDetailsMenu routeDetailsMenu;
private RefreshMapCallback refreshMapCallback;
public interface RouteDetailsFragmentListener {
void onNavigationRequested();
@ -311,24 +304,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
return;
}
OsmandApplication app = mapActivity.getMyApplication();
statisticCard = new RouteStatisticCard(mapActivity, gpx, new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent ev) {
LinearLayout mainView = getMainView();
if (mainView != null) {
mainView.requestDisallowInterceptTouchEvent(true);
}
for (RouteInfoCard card : routeInfoCards) {
final HorizontalBarChart ch = card.getChart();
if (ch != null) {
final MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
ch.dispatchTouchEvent(event);
}
}
return false;
}
}, new OnClickListener() {
statisticCard = new RouteStatisticCard(mapActivity, gpx, new OnClickListener() {
@Override
public void onClick(View v) {
openDetails();
@ -336,7 +312,6 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
});
statisticCard.setTransparentBackground(true);
statisticCard.setListener(this);
statisticCard.setChartListener(this);
menuCards.add(statisticCard);
cardsContainer.addView(statisticCard.build(mapActivity));
buildRowDivider(cardsContainer, false);
@ -359,13 +334,25 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
routeDetailsMenu.setGpxItem(gpxItem);
}
routeDetailsMenu.setMapActivity(mapActivity);
LineChart chart = statisticCard.getChart();
if (chart != null) {
chart.setExtraRightOffset(16);
chart.setExtraLeftOffset(16);
CommonGraphAdapter mainGraphAdapter = statisticCard.getGraphAdapter();
if (mainGraphAdapter != null) {
GraphAdapterHelper.bindGraphAdapters(mainGraphAdapter, getRouteInfoCardsGraphAdapters(), getMainView());
refreshMapCallback = GraphAdapterHelper.bindToMap(mainGraphAdapter, mapActivity, routeDetailsMenu);
}
}
private List<BaseGraphAdapter> getRouteInfoCardsGraphAdapters() {
List<BaseGraphAdapter> adapters = new ArrayList<>();
for (RouteInfoCard card : routeInfoCards) {
BaseGraphAdapter adapter = card.getGraphAdapter();
if (adapter != null) {
adapters.add(adapter);
}
}
return adapters;
}
public static List<RouteStatistics> calculateRouteStatistics(OsmandApplication app,
List<RouteSegmentResult> route,
boolean nightMode) {
@ -384,7 +371,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
protected void calculateLayout(View view, boolean initLayout) {
super.calculateLayout(view, initLayout);
if (!initLayout && getCurrentMenuState() != MenuState.FULL_SCREEN) {
refreshChart(false);
refreshMapCallback.refreshMap(false);
}
}
@ -401,48 +388,15 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
buildRowDivider(cardsContainer, false);
}
private OnTouchListener getChartTouchListener() {
return new OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (ev.getSource() != 0 && v instanceof HorizontalBarChart) {
if (statisticCard != null) {
LineChart ch = statisticCard.getChart();
if (ch != null) {
final MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
ch.dispatchTouchEvent(event);
}
}
return true;
}
return false;
}
};
}
@SuppressLint("ClickableViewAccessibility")
private void addRouteCard(final LinearLayout cardsContainer, RouteInfoCard routeInfoCard) {
private void addRouteCard(LinearLayout cardsContainer,
RouteInfoCard routeInfoCard) {
OsmandApplication app = requireMyApplication();
menuCards.add(routeInfoCard);
routeInfoCard.setListener(this);
cardsContainer.addView(routeInfoCard.build(app));
buildRowDivider(cardsContainer, false);
routeInfoCards.add(routeInfoCard);
HorizontalBarChart chart = routeInfoCard.getChart();
if (chart != null) {
LineChart mainChart = statisticCard.getChart();
if (mainChart != null) {
chart.getAxisRight().setAxisMinimum(mainChart.getXChartMin());
chart.getAxisRight().setAxisMaximum(mainChart.getXChartMax());
}
chart.setRenderer(new CustomBarChartRenderer(chart, chart.getAnimator(), chart.getViewPortHandler(), AndroidUtils.dpToPx(app, 1f) / 2f));
chart.setHighlightPerDragEnabled(false);
chart.setHighlightPerTapEnabled(false);
chart.setOnTouchListener(getChartTouchListener());
}
}
public Drawable getCollapseIcon(boolean collapsed) {
@ -1487,14 +1441,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
private void makeGpx() {
OsmandApplication app = requireMyApplication();
gpx = GpxUiHelper.makeGpxFromRoute(app.getRoutingHelper().getRoute(), app);
String groupName = getString(R.string.current_route);
GpxDisplayGroup group = app.getSelectedGpxHelper().buildGpxDisplayGroup(gpx, 0, groupName);
if (group != null && group.getModifiableList().size() > 0) {
gpxItem = group.getModifiableList().get(0);
if (gpxItem != null) {
gpxItem.route = true;
}
}
gpxItem = GpxUiHelper.makeGpxDisplayItem(app, gpx);
}
void openDetails() {
@ -1615,59 +1562,6 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
}
}
private void refreshChart(boolean forceFit) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null && routeDetailsMenu != null && statisticCard != null &&
!mapActivity.getMyApplication().getRoutingHelper().isFollowingMode()) {
LineChart chart = statisticCard.getChart();
if (chart != null) {
routeDetailsMenu.refreshChart(chart, forceFit);
mapActivity.refreshMap();
}
}
}
private void highlightRouteInfoCharts(@Nullable Highlight h) {
for (RouteInfoCard rc : routeInfoCards) {
HorizontalBarChart chart = rc.getChart();
if (chart != null) {
Highlight bh = h != null ? chart.getHighlighter().getHighlight(1, h.getXPx()) : null;
if (bh != null) {
bh.setDraw(h.getXPx(), 0);
}
chart.highlightValue(bh, true);
}
}
}
@Override
public void onValueSelected(BaseCard card, Entry e, Highlight h) {
refreshChart(false);
highlightRouteInfoCharts(h);
}
@Override
public void onNothingSelected(BaseCard card) {
highlightRouteInfoCharts(null);
}
@Override
public void onChartGestureStart(BaseCard card, MotionEvent me, ChartGesture lastPerformedGesture) {
}
@Override
public void onChartGestureEnd(BaseCard card, MotionEvent me, ChartGesture lastPerformedGesture, boolean hasTranslated) {
if ((lastPerformedGesture == ChartGesture.DRAG && hasTranslated) ||
lastPerformedGesture == ChartGesture.X_ZOOM ||
lastPerformedGesture == ChartGesture.Y_ZOOM ||
lastPerformedGesture == ChartGesture.PINCH_ZOOM ||
lastPerformedGesture == ChartGesture.DOUBLE_TAP ||
lastPerformedGesture == ChartGesture.ROTATE) {
refreshChart(true);
}
}
public static class CumulativeInfo {
public int distance;
public int time;
@ -1696,20 +1590,4 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
final int timeInSeconds = model.getExpectedTime();
return Algorithms.formatDuration(timeInSeconds, app.accessibilityEnabled());
}
private static class CustomBarChartRenderer extends HorizontalBarChartRenderer {
private float highlightHalfWidth;
CustomBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler, float highlightHalfWidth) {
super(chart, animator, viewPortHandler);
this.highlightHalfWidth = highlightHalfWidth;
}
@Override
protected void setHighlightDrawPos(Highlight high, RectF bar) {
bar.left = high.getDrawX() - highlightHalfWidth;
bar.right = high.getDrawX() + highlightHalfWidth;
}
}
}

View file

@ -31,7 +31,6 @@ public abstract class BaseCard {
protected boolean nightMode;
private CardListener listener;
private CardChartListener chartListener;
public interface CardListener {
void onCardLayoutNeeded(@NonNull BaseCard card);
@ -78,14 +77,6 @@ public abstract class BaseCard {
this.listener = listener;
}
public CardChartListener getChartListener() {
return chartListener;
}
public void setChartListener(CardChartListener chartListener) {
this.chartListener = chartListener;
}
public void setLayoutNeeded() {
CardListener listener = this.listener;
if (listener != null) {

View file

@ -1,54 +1,33 @@
package net.osmand.plus.routepreparationmenu.cards;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.router.RouteStatisticsHelper.RouteSegmentAttribute;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter.LegendViewType;
import net.osmand.router.RouteStatisticsHelper.RouteStatistics;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static net.osmand.plus.track.ColorsCard.MINIMUM_CONTRAST_RATIO;
public class RouteInfoCard extends BaseCard {
private RouteStatistics routeStatistics;
private RouteStatistics statistics;
private GPXTrackAnalysis analysis;
private String selectedPropertyName;
private CustomGraphAdapter graphAdapter;
private boolean showLegend;
public RouteInfoCard(MapActivity mapActivity, RouteStatistics routeStatistics, GPXTrackAnalysis analysis) {
public RouteInfoCard(MapActivity mapActivity, RouteStatistics statistics, GPXTrackAnalysis analysis) {
super(mapActivity);
this.routeStatistics = routeStatistics;
this.statistics = statistics;
this.analysis = analysis;
}
@ -59,112 +38,47 @@ public class RouteInfoCard extends BaseCard {
@Override
protected void updateContent() {
updateContent(routeStatistics);
}
@Nullable
public HorizontalBarChart getChart() {
return (HorizontalBarChart) view.findViewById(R.id.chart);
}
private void updateContent(final RouteStatistics routeStatistics) {
updateHeader();
final HorizontalBarChart chart = (HorizontalBarChart) view.findViewById(R.id.chart);
GpxUiHelper.setupHorizontalGPXChart(app, chart, 5, 9, 24, true, nightMode);
chart.setExtraRightOffset(16);
chart.setExtraLeftOffset(16);
BarData barData = GpxUiHelper.buildStatisticChart(app, chart, routeStatistics, analysis, true, nightMode);
chart.setData(barData);
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
List<RouteSegmentAttribute> elems = routeStatistics.elements;
int i = h.getStackIndex();
if (i >= 0 && elems.size() > i) {
selectedPropertyName = elems.get(i).getPropertyName();
if (showLegend) {
updateLegend(routeStatistics);
}
}
}
@Override
public void onNothingSelected() {
selectedPropertyName = null;
if (showLegend) {
updateLegend(routeStatistics);
}
}
});
LinearLayout container = (LinearLayout) view.findViewById(R.id.route_items);
container.removeAllViews();
if (showLegend) {
attachLegend(container, routeStatistics);
}
final ImageView iconViewCollapse = (ImageView) view.findViewById(R.id.up_down_icon);
iconViewCollapse.setImageDrawable(getCollapseIcon(!showLegend));
HorizontalBarChart chart = (HorizontalBarChart) view.findViewById(R.id.chart);
GpxUiHelper.setupHorizontalGPXChart(getMyApplication(), chart, 5, 9, 24, true, nightMode);
BarData barData = GpxUiHelper.buildStatisticChart(app, chart, statistics, analysis, true, nightMode);
graphAdapter = new CustomGraphAdapter(chart, true);
graphAdapter.setLegendContainer(container);
graphAdapter.updateData(barData, statistics);
updateView();
view.findViewById(R.id.info_type_details_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showLegend = !showLegend;
updateContent();
updateView();
setLayoutNeeded();
}
});
}
protected void updateLegend(RouteStatistics routeStatistics) {
LinearLayout container = (LinearLayout) view.findViewById(R.id.route_items);
container.removeAllViews();
attachLegend(container, routeStatistics);
setLayoutNeeded();
}
private Drawable getCollapseIcon(boolean collapsed) {
return collapsed ? getContentIcon(R.drawable.ic_action_arrow_down) : getActiveIcon(R.drawable.ic_action_arrow_up);
private void updateView() {
updateCollapseIcon();
graphAdapter.setLegendViewType(showLegend ? LegendViewType.ALL_AS_LIST : LegendViewType.GONE);
graphAdapter.updateView();
}
private void updateHeader() {
TextView title = (TextView) view.findViewById(R.id.info_type_title);
String name = AndroidUtils.getStringRouteInfoPropertyValue(app, routeStatistics.name);
String name = AndroidUtils.getStringRouteInfoPropertyValue(app, statistics.name);
title.setText(name);
}
private void attachLegend(ViewGroup container, RouteStatistics routeStatistics) {
Map<String, RouteSegmentAttribute> partition = routeStatistics.partition;
List<Map.Entry<String, RouteSegmentAttribute>> list = new ArrayList<>(partition.entrySet());
ContextThemeWrapper ctx = new ContextThemeWrapper(mapActivity, !nightMode ? R.style.OsmandLightTheme : R.style.OsmandDarkTheme);
LayoutInflater inflater = LayoutInflater.from(ctx);
for (Map.Entry<String, RouteSegmentAttribute> entry : list) {
RouteSegmentAttribute segment = entry.getValue();
View view = inflater.inflate(R.layout.route_details_legend, container, false);
int segmentColor = segment.getColor();
Drawable circle = app.getUIUtilities().getPaintedIcon(R.drawable.ic_action_circle, segmentColor);
ImageView legendIcon = (ImageView) view.findViewById(R.id.legend_icon_color);
legendIcon.setImageDrawable(circle);
double contrastRatio = ColorUtils.calculateContrast(segmentColor, ContextCompat.getColor(app, nightMode ? R.color.card_and_list_background_dark : R.color.card_and_list_background_light));
if (contrastRatio < MINIMUM_CONTRAST_RATIO) {
legendIcon.setBackgroundResource(nightMode ? R.drawable.circle_contour_bg_dark : R.drawable.circle_contour_bg_light);
}
String propertyName = segment.getUserPropertyName();
String name = AndroidUtils.getRenderingStringPropertyName(app, propertyName, propertyName.replaceAll("_", " "));
Spannable text = getSpanLegend(name, segment, segment.getUserPropertyName().equals(selectedPropertyName));
TextView legend = (TextView) view.findViewById(R.id.legend_text);
legend.setText(text);
container.addView(view);
}
private void updateCollapseIcon() {
ImageView ivCollapse = (ImageView) view.findViewById(R.id.up_down_icon);
Drawable drawable = showLegend ?
getContentIcon(R.drawable.ic_action_arrow_down) :
getActiveIcon(R.drawable.ic_action_arrow_up);
ivCollapse.setImageDrawable(drawable);
}
private Spannable getSpanLegend(String title, RouteSegmentAttribute segment, boolean selected) {
String formattedDistance = OsmAndFormatter.getFormattedDistance(segment.getDistance(), getMyApplication());
title = Algorithms.capitalizeFirstLetter(title);
SpannableStringBuilder spannable = new SpannableStringBuilder(title);
spannable.append(": ");
int startIndex = selected ? -0 : spannable.length();
spannable.append(formattedDistance);
spannable.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), startIndex, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
public CustomGraphAdapter getGraphAdapter() {
return graphAdapter;
}
}

View file

@ -1,13 +1,10 @@
package net.osmand.plus.routepreparationmenu.cards;
import android.graphics.Matrix;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@ -16,18 +13,12 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture;
import com.github.mikephil.charting.listener.OnChartGestureListener;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
@ -36,6 +27,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.measurementtool.graph.CommonGraphAdapter;
import net.osmand.plus.routing.RoutingHelper;
import java.util.ArrayList;
@ -52,16 +44,15 @@ public class RouteStatisticCard extends BaseCard {
private OrderedLineDataSet slopeDataSet;
@Nullable
private OrderedLineDataSet elevationDataSet;
private OnTouchListener onTouchListener;
private OnClickListener onAnalyseClickListener;
private CommonGraphAdapter graphAdapter;
public RouteStatisticCard(MapActivity mapActivity, GPXFile gpx, OnTouchListener onTouchListener,
public RouteStatisticCard(MapActivity mapActivity, GPXFile gpx,
OnClickListener onAnalyseClickListener) {
super(mapActivity);
this.gpx = gpx;
this.onTouchListener = onTouchListener;
this.onAnalyseClickListener = onAnalyseClickListener;
makeGpxDisplayItem();
this.gpxItem = GpxUiHelper.makeGpxDisplayItem(app, gpx);
}
@Nullable
@ -219,26 +210,15 @@ public class RouteStatisticCard extends BaseCard {
return elevationDataSet;
}
private void makeGpxDisplayItem() {
String groupName = getMyApplication().getString(R.string.current_route);
GpxSelectionHelper.GpxDisplayGroup group = getMyApplication().getSelectedGpxHelper().buildGpxDisplayGroup(gpx, 0, groupName);
if (group != null && group.getModifiableList().size() > 0) {
gpxItem = group.getModifiableList().get(0);
if (gpxItem != null) {
gpxItem.route = true;
}
}
}
@Nullable
public LineChart getChart() {
return (LineChart) view.findViewById(R.id.chart);
public CommonGraphAdapter getGraphAdapter() {
return graphAdapter;
}
private void buildHeader(GPXTrackAnalysis analysis) {
final LineChart mChart = (LineChart) view.findViewById(R.id.chart);
LineChart mChart = (LineChart) view.findViewById(R.id.chart);
GpxUiHelper.setupGPXChart(mChart, 4, 24f, 16f, !nightMode, true);
mChart.setOnTouchListener(onTouchListener);
graphAdapter = new CommonGraphAdapter(mChart, true);
if (analysis.hasElevationData) {
List<ILineDataSet> dataSets = new ArrayList<>();
@ -256,99 +236,7 @@ public class RouteStatisticCard extends BaseCard {
this.elevationDataSet = elevationDataSet;
this.slopeDataSet = slopeDataSet;
LineData data = new LineData(dataSets);
mChart.setData(data);
mChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onValueSelected(RouteStatisticCard.this, e, h);
}
}
@Override
public void onNothingSelected() {
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onNothingSelected(RouteStatisticCard.this);
}
}
});
mChart.setOnChartGestureListener(new OnChartGestureListener() {
boolean hasTranslated = false;
float highlightDrawX = -1;
@Override
public void onChartGestureStart(MotionEvent me, ChartGesture lastPerformedGesture) {
hasTranslated = false;
if (mChart.getHighlighted() != null && mChart.getHighlighted().length > 0) {
highlightDrawX = mChart.getHighlighted()[0].getDrawX();
} else {
highlightDrawX = -1;
}
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onChartGestureStart(RouteStatisticCard.this, me, lastPerformedGesture);
}
}
@Override
public void onChartGestureEnd(MotionEvent me, ChartGesture lastPerformedGesture) {
gpxItem.chartMatrix = new Matrix(mChart.getViewPortHandler().getMatrixTouch());
Highlight[] highlights = mChart.getHighlighted();
if (highlights != null && highlights.length > 0) {
gpxItem.chartHighlightPos = highlights[0].getX();
} else {
gpxItem.chartHighlightPos = -1;
}
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onChartGestureEnd(RouteStatisticCard.this, me, lastPerformedGesture, hasTranslated);
}
}
@Override
public void onChartLongPressed(MotionEvent me) {
}
@Override
public void onChartDoubleTapped(MotionEvent me) {
}
@Override
public void onChartSingleTapped(MotionEvent me) {
}
@Override
public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
}
@Override
public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
}
@Override
public void onChartTranslate(MotionEvent me, float dX, float dY) {
hasTranslated = true;
if (highlightDrawX != -1) {
Highlight h = mChart.getHighlightByTouchPoint(highlightDrawX, 0f);
if (h != null) {
/*
ILineDataSet set = mChart.getLineData().getDataSetByIndex(h.getDataSetIndex());
if (set != null && set.isHighlightEnabled()) {
Entry e = set.getEntryForXValue(h.getX(), h.getY());
MPPointD pix = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(e.getX(), e.getY());
h.setDraw((float) pix.x, (float) pix.y);
}
*/
mChart.highlightValue(h, true);
}
}
}
});
graphAdapter.updateContent(new LineData(dataSets), gpxItem);
mChart.setVisibility(View.VISIBLE);
} else {
mChart.setVisibility(View.GONE);

View file

@ -1104,9 +1104,8 @@ public class OsmandSettings {
public final OsmandPreference<Boolean> BILLING_PURCHASE_TOKEN_SENT = new BooleanPreference(this, "billing_purchase_token_sent", false).makeGlobal();
public final OsmandPreference<String> BILLING_PURCHASE_TOKENS_SENT = new StringPreference(this, "billing_purchase_tokens_sent", "").makeGlobal();
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASED = new BooleanPreference(this, "billing_live_updates_purchased", false).makeGlobal();
public final OsmandPreference<Long> LIVE_UPDATES_PURCHASE_CANCELLED_TIME = new LongPreference(this, "live_updates_purchase_cancelled_time", 0).makeGlobal();
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_first_dlg_shown", false).makeGlobal();
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_second_dlg_shown", false).makeGlobal();
public final OsmandPreference<Long> LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_first_dlg_shown_time", 0).makeGlobal();
public final OsmandPreference<Long> LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_second_dlg_shown_time", 0).makeGlobal();
public final OsmandPreference<Boolean> FULL_VERSION_PURCHASED = new BooleanPreference(this, "billing_full_version_purchased", false).makeGlobal();
public final OsmandPreference<Boolean> DEPTH_CONTOURS_PURCHASED = new BooleanPreference(this, "billing_sea_depth_purchased", false).makeGlobal();
public final OsmandPreference<Boolean> CONTOUR_LINES_PURCHASED = new BooleanPreference(this, "billing_srtm_purchased", false).makeGlobal();

View file

@ -64,7 +64,7 @@ public class DataSettingsItem extends StreamSettingsItem {
SettingsItemReader<? extends SettingsItem> getReader() {
return new StreamSettingsItemReader(this) {
@Override
public void readFromStream(@NonNull InputStream inputStream, File destination) throws IOException, IllegalArgumentException {
public void readFromStream(@NonNull InputStream inputStream, String entryName) throws IOException, IllegalArgumentException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[SettingsHelper.BUFFER];

View file

@ -16,7 +16,6 @@ import net.osmand.plus.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -146,7 +145,7 @@ public class FavoritesSettingsItem extends CollectionSettingsItem<FavoriteGroup>
return new SettingsItemReader<FavoritesSettingsItem>(this) {
@Override
public void readFromStream(@NonNull InputStream inputStream, File destination) throws IllegalArgumentException {
public void readFromStream(@NonNull InputStream inputStream, String entryName) throws IllegalArgumentException {
GPXFile gpxFile = GPXUtilities.loadGPXFile(inputStream);
if (gpxFile.error != null) {
warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType())));

View file

@ -1,5 +1,6 @@
package net.osmand.plus.settings.backend.backup;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -23,34 +24,33 @@ import java.util.zip.ZipOutputStream;
public class FileSettingsItem extends StreamSettingsItem {
public enum FileSubtype {
UNKNOWN("", null),
OTHER("other", ""),
ROUTING_CONFIG("routing_config", IndexConstants.ROUTING_PROFILES_DIR),
RENDERING_STYLE("rendering_style", IndexConstants.RENDERERS_DIR),
WIKI_MAP("wiki_map", IndexConstants.WIKI_INDEX_DIR),
SRTM_MAP("srtm_map", IndexConstants.SRTM_INDEX_DIR),
OBF_MAP("obf_map", IndexConstants.MAPS_PATH),
TILES_MAP("tiles_map", IndexConstants.TILES_INDEX_DIR),
GPX("gpx", IndexConstants.GPX_INDEX_DIR),
TTS_VOICE("tts_voice", IndexConstants.VOICE_INDEX_DIR),
VOICE("voice", IndexConstants.VOICE_INDEX_DIR),
TRAVEL("travel", IndexConstants.WIKIVOYAGE_INDEX_DIR),
MULTIMEDIA_NOTES("multimedia_notes", IndexConstants.AV_INDEX_DIR);
UNKNOWN("", null, R.drawable.ic_type_file),
OTHER("other", "", R.drawable.ic_type_file),
ROUTING_CONFIG("routing_config", IndexConstants.ROUTING_PROFILES_DIR, R.drawable.ic_action_route_distance),
RENDERING_STYLE("rendering_style", IndexConstants.RENDERERS_DIR, R.drawable.ic_action_map_style),
WIKI_MAP("wiki_map", IndexConstants.WIKI_INDEX_DIR, R.drawable.ic_plugin_wikipedia),
SRTM_MAP("srtm_map", IndexConstants.SRTM_INDEX_DIR, R.drawable.ic_plugin_srtm),
OBF_MAP("obf_map", IndexConstants.MAPS_PATH, R.drawable.ic_map),
TILES_MAP("tiles_map", IndexConstants.TILES_INDEX_DIR, R.drawable.ic_map),
ROAD_MAP("road_map", IndexConstants.ROADS_INDEX_DIR, R.drawable.ic_map),
GPX("gpx", IndexConstants.GPX_INDEX_DIR, R.drawable.ic_action_route_distance),
TTS_VOICE("tts_voice", IndexConstants.VOICE_INDEX_DIR, R.drawable.ic_action_volume_up),
VOICE("voice", IndexConstants.VOICE_INDEX_DIR, R.drawable.ic_action_volume_up),
TRAVEL("travel", IndexConstants.WIKIVOYAGE_INDEX_DIR, R.drawable.ic_plugin_wikipedia),
MULTIMEDIA_NOTES("multimedia_notes", IndexConstants.AV_INDEX_DIR, R.drawable.ic_action_photo_dark);
private String subtypeName;
private String subtypeFolder;
private final String subtypeName;
private final String subtypeFolder;
private final int iconId;
FileSubtype(String subtypeName, String subtypeFolder) {
FileSubtype(@NonNull String subtypeName, String subtypeFolder, @DrawableRes int iconId) {
this.subtypeName = subtypeName;
this.subtypeFolder = subtypeFolder;
this.iconId = iconId;
}
public boolean isMap() {
return this == OBF_MAP || this == WIKI_MAP || this == SRTM_MAP || this == TILES_MAP;
}
public boolean isDirectory() {
return this == TTS_VOICE || this == VOICE;
return this == OBF_MAP || this == WIKI_MAP || this == SRTM_MAP || this == TILES_MAP || this == ROAD_MAP;
}
public String getSubtypeName() {
@ -61,6 +61,11 @@ public class FileSettingsItem extends StreamSettingsItem {
return subtypeFolder;
}
@DrawableRes
public int getIconId() {
return iconId;
}
public static FileSubtype getSubtypeByName(@NonNull String name) {
for (FileSubtype subtype : FileSubtype.values()) {
if (name.equals(subtype.subtypeName)) {
@ -205,7 +210,12 @@ public class FileSettingsItem extends StreamSettingsItem {
}
public long getSize() {
return size;
if (size != 0) {
return size;
} else if (file != null && !file.isDirectory()) {
return file.length();
}
return 0;
}
public void setSize(long size) {
@ -227,15 +237,28 @@ public class FileSettingsItem extends StreamSettingsItem {
return file.exists();
}
private File renameFile(File file) {
private File renameFile(File oldFile) {
String oldPath = oldFile.getAbsolutePath();
String prefix;
if (file.isDirectory()) {
prefix = file.getAbsolutePath();
} else if (oldPath.endsWith(IndexConstants.BINARY_WIKI_MAP_INDEX_EXT)) {
prefix = oldPath.substring(0, oldPath.lastIndexOf(IndexConstants.BINARY_WIKI_MAP_INDEX_EXT));
} else if (oldPath.endsWith(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT)) {
prefix = oldPath.substring(0, oldPath.lastIndexOf(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT));
} else if (oldPath.endsWith(IndexConstants.BINARY_ROAD_MAP_INDEX_EXT)) {
prefix = oldPath.substring(0, oldPath.lastIndexOf(IndexConstants.BINARY_ROAD_MAP_INDEX_EXT));
} else {
prefix = oldPath.substring(0, oldPath.lastIndexOf("."));
}
String suffix = oldPath.replace(prefix, "");
int number = 0;
String path = file.getAbsolutePath();
while (true) {
number++;
String copyName = path.replaceAll(file.getName(), file.getName().replaceFirst("[.]", "_" + number + "."));
File copyFile = new File(copyName);
if (!copyFile.exists()) {
return copyFile;
String newName = prefix + "_" + number + suffix;
File newFile = new File(newName);
if (!newFile.exists()) {
return newFile;
}
}
}
@ -245,12 +268,17 @@ public class FileSettingsItem extends StreamSettingsItem {
SettingsItemReader<? extends SettingsItem> getReader() {
return new StreamSettingsItemReader(this) {
@Override
public void readFromStream(@NonNull InputStream inputStream, File dest) throws IOException, IllegalArgumentException {
public void readFromStream(@NonNull InputStream inputStream, String entryName) throws IOException, IllegalArgumentException {
OutputStream output;
File dest = FileSettingsItem.this.getFile();
if (dest.isDirectory()) {
dest = new File(dest, entryName.substring(fileName.length()));
}
if (dest.exists() && !shouldReplace) {
dest = renameFile(dest);
}
if (dest.getParentFile() != null && !dest.getParentFile().exists()) {
//noinspection ResultOfMethodCallIgnored
dest.getParentFile().mkdirs();
}
output = new FileOutputStream(dest);
@ -271,46 +299,42 @@ public class FileSettingsItem extends StreamSettingsItem {
@Nullable
@Override
public SettingsItemWriter<? extends SettingsItem> getWriter() {
try {
if (!file.isDirectory()) {
if (!file.isDirectory()) {
try {
setInputStream(new FileInputStream(file));
} catch (FileNotFoundException e) {
warnings.add(app.getString(R.string.settings_item_read_error, file.getName()));
SettingsHelper.LOG.error("Failed to set input stream from file: " + file.getName(), e);
}
} catch (FileNotFoundException e) {
warnings.add(app.getString(R.string.settings_item_read_error, file.getName()));
SettingsHelper.LOG.error("Failed to set input stream from file: " + file.getName(), e);
}
return new StreamSettingsItemWriter(this) {
return super.getWriter();
} else {
return new StreamSettingsItemWriter(this) {
@Override
public void writeEntry(String fileName, @NonNull ZipOutputStream zos) throws IOException {
if (getSubtype().isDirectory()) {
File file = getFile();
zipDirsWithFiles(file, zos);
} else {
super.writeEntry(fileName, zos);
@Override
public void writeEntry(String fileName, @NonNull ZipOutputStream zos) throws IOException {
writeDirWithFiles(file, zos);
}
}
public void zipDirsWithFiles(File f, ZipOutputStream zos)
throws IOException {
if (f == null) {
return;
}
if (f.isDirectory()) {
File[] fs = f.listFiles();
if (fs != null) {
for (File c : fs) {
zipDirsWithFiles(c, zos);
public void writeDirWithFiles(File file, ZipOutputStream zos) throws IOException {
if (file != null) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File subfolderFile : files) {
writeDirWithFiles(subfolderFile, zos);
}
}
} else {
String subtypeFolder = getSubtype().getSubtypeFolder();
String zipEntryName = Algorithms.isEmpty(subtypeFolder)
? file.getName()
: file.getPath().substring(file.getPath().indexOf(subtypeFolder) - 1);
setInputStream(new FileInputStream(file));
super.writeEntry(zipEntryName, zos);
}
}
} else {
String zipEntryName = Algorithms.isEmpty(getSubtype().getSubtypeFolder())
? f.getName()
: f.getPath().substring(f.getPath().indexOf(getSubtype().getSubtypeFolder()) - 1);
setInputStream(new FileInputStream(f));
super.writeEntry(zipEntryName, zos);
}
}
};
};
}
}
}

View file

@ -10,7 +10,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -30,7 +29,7 @@ public abstract class OsmandSettingsItemReader<T extends OsmandSettingsItem> ext
@NonNull JSONObject json) throws JSONException;
@Override
public void readFromStream(@NonNull InputStream inputStream, File destination) throws IOException, IllegalArgumentException {
public void readFromStream(@NonNull InputStream inputStream, String entryName) throws IOException, IllegalArgumentException {
StringBuilder buf = new StringBuilder();
try {
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

View file

@ -16,13 +16,19 @@ import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static net.osmand.plus.settings.backend.backup.SettingsHelper.*;
class SettingsExporter {
private Map<String, SettingsItem> items;
private Map<String, String> additionalParams;
private ExportProgressListener progressListener;
private boolean cancelled;
private boolean exportItemsFiles;
SettingsExporter(boolean exportItemsFiles) {
SettingsExporter(ExportProgressListener progressListener, boolean exportItemsFiles) {
this.progressListener = progressListener;
this.exportItemsFiles = exportItemsFiles;
items = new LinkedHashMap<>();
additionalParams = new LinkedHashMap<>();
@ -35,13 +41,17 @@ class SettingsExporter {
items.put(item.getName(), item);
}
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
void addAdditionalParam(String key, String value) {
additionalParams.put(key, value);
}
void exportSettings(File file) throws JSONException, IOException {
JSONObject json = createItemsJson();
OutputStream os = new BufferedOutputStream(new FileOutputStream(file), SettingsHelper.BUFFER);
OutputStream os = new BufferedOutputStream(new FileOutputStream(file), BUFFER);
ZipOutputStream zos = new ZipOutputStream(os);
try {
ZipEntry entry = new ZipEntry("items.json");
@ -60,6 +70,7 @@ class SettingsExporter {
}
private void writeItemFiles(ZipOutputStream zos) throws IOException {
int progress = 0;
for (SettingsItem item : items.values()) {
SettingsItemWriter<? extends SettingsItem> writer = item.getWriter();
if (writer != null) {
@ -69,13 +80,22 @@ class SettingsExporter {
}
writer.writeEntry(fileName, zos);
}
if (cancelled) {
return;
}
if (item instanceof FileSettingsItem) {
int size = (int) ((FileSettingsItem) item).getSize() / (1 << 20);
progress += size;
if (progressListener != null) {
progressListener.updateProgress(progress);
}
}
}
}
private JSONObject createItemsJson() throws JSONException {
JSONObject json = new JSONObject();
json.put("version", SettingsHelper.VERSION);
json.put("version", VERSION);
for (Map.Entry<String, String> param : additionalParams.entrySet()) {
json.put(param.getKey(), param.getValue());
}

View file

@ -7,7 +7,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.AndroidUtils;
import net.osmand.Collator;
import net.osmand.IndexConstants;
import net.osmand.OsmAndCollator;
import net.osmand.PlatformUtil;
import net.osmand.data.LatLon;
import net.osmand.map.ITileSource;
@ -24,6 +26,7 @@ import net.osmand.plus.audionotes.AudioVideoNotesPlugin;
import net.osmand.plus.audionotes.AudioVideoNotesPlugin.Recording;
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo;
import net.osmand.plus.helpers.FileNameTranslationHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXInfo;
import net.osmand.plus.osmedit.OpenstreetmapPoint;
@ -35,6 +38,7 @@ import net.osmand.plus.quickaction.QuickActionRegistry;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean;
import net.osmand.plus.settings.backend.ExportSettingsType;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import org.json.JSONException;
@ -43,14 +47,16 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT;
import static net.osmand.plus.settings.backend.backup.FileSettingsItem.*;
import static net.osmand.plus.activities.LocalIndexHelper.LocalIndexType;
import static net.osmand.plus.settings.backend.backup.FileSettingsItem.FileSubtype;
/*
Usage:
@ -107,6 +113,8 @@ public class SettingsHelper {
public interface SettingsExportListener {
void onSettingsExportFinished(@NonNull File file, boolean succeed);
void onSettingsExportProgressUpdate(int value);
}
public enum ImportType {
@ -135,6 +143,14 @@ public class SettingsHelper {
return importTask == null || importTask.isImportDone();
}
public boolean cancelExportForFile(File file) {
ExportAsyncTask exportTask = exportAsyncTasks.get(file);
if (exportTask != null && (exportTask.getStatus() == AsyncTask.Status.RUNNING)) {
return exportTask.cancel(true);
}
return false;
}
public boolean isFileExporting(File file) {
return exportAsyncTasks.containsKey(file);
}
@ -196,11 +212,15 @@ public class SettingsHelper {
}
}
@SuppressLint("StaticFieldLeak")
private class ExportAsyncTask extends AsyncTask<Void, Void, Boolean> {
public interface ExportProgressListener {
void updateProgress(int value);
}
@SuppressLint("StaticFieldLeak")
public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private SettingsExporter exporter;
private File file;
private SettingsExporter exporter;
private SettingsExportListener listener;
ExportAsyncTask(@NonNull File settingsFile,
@ -208,7 +228,7 @@ public class SettingsHelper {
@NonNull List<SettingsItem> items, boolean exportItemsFiles) {
this.file = settingsFile;
this.listener = listener;
this.exporter = new SettingsExporter(exportItemsFiles);
this.exporter = new SettingsExporter(getProgressListener(), exportItemsFiles);
for (SettingsItem item : items) {
exporter.addSettingsItem(item);
}
@ -227,6 +247,13 @@ public class SettingsHelper {
return false;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (listener != null) {
listener.onSettingsExportProgressUpdate(values[0]);
}
}
@Override
protected void onPostExecute(Boolean success) {
exportAsyncTasks.remove(file);
@ -234,6 +261,21 @@ public class SettingsHelper {
listener.onSettingsExportFinished(file, success);
}
}
@Override
protected void onCancelled() {
Algorithms.removeAllFiles(file);
}
private ExportProgressListener getProgressListener() {
return new ExportProgressListener() {
@Override
public void updateProgress(int value) {
exporter.setCancelled(isCancelled());
publishProgress(value);
}
};
}
}
@SuppressLint("StaticFieldLeak")
@ -538,11 +580,11 @@ public class SettingsHelper {
if (!favoriteGroups.isEmpty()) {
dataList.put(ExportSettingsType.FAVORITES, favoriteGroups);
}
List<LocalIndexInfo> localIndexInfoList = getVoiceIndexInfo();
List<File> files;
files = getFilesByType(localIndexInfoList, LocalIndexType.MAP_DATA, LocalIndexType.TILES_DATA,
List<LocalIndexInfo> localIndexInfoList = getLocalIndexData();
List<File> files = getFilesByType(localIndexInfoList, LocalIndexType.MAP_DATA, LocalIndexType.TILES_DATA,
LocalIndexType.SRTM_DATA, LocalIndexType.WIKI_DATA);
if (!files.isEmpty()) {
sortLocalFiles(files);
dataList.put(ExportSettingsType.OFFLINE_MAPS, files);
}
files = getFilesByType(localIndexInfoList, LocalIndexType.TTS_VOICE_DATA);
@ -560,7 +602,7 @@ public class SettingsHelper {
return dataList;
}
private List<LocalIndexInfo> getVoiceIndexInfo() {
private List<LocalIndexInfo> getLocalIndexData() {
return new LocalIndexHelper(app).getLocalIndexData(new AbstractLoadLocalIndexTask() {
@Override
public void loadFile(LocalIndexInfo... loaded) {
@ -820,4 +862,18 @@ public class SettingsHelper {
}
return settingsToOperate;
}
private void sortLocalFiles(List<File> files) {
final Collator collator = OsmAndCollator.primaryCollator();
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
return collator.compare(getNameToDisplay(lhs), getNameToDisplay(rhs));
}
private String getNameToDisplay(File item) {
return FileNameTranslationHelper.getFileNameWithRegion(app, item.getName());
}
});
}
}

View file

@ -124,7 +124,7 @@ class SettingsImporter {
try {
SettingsItemReader<? extends SettingsItem> reader = item.getReader();
if (reader != null) {
reader.readFromStream(ois, app.getAppPath(fileName));
reader.readFromStream(ois, fileName);
}
} catch (IllegalArgumentException e) {
item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName()));

View file

@ -169,7 +169,7 @@ public abstract class SettingsItem {
SettingsItemReader<? extends SettingsItem> getJsonReader() {
return new SettingsItemReader<SettingsItem>(this) {
@Override
public void readFromStream(@NonNull InputStream inputStream, File destination) throws IOException, IllegalArgumentException {
public void readFromStream(@NonNull InputStream inputStream, String entryName) throws IOException, IllegalArgumentException {
StringBuilder buf = new StringBuilder();
try {
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

View file

@ -2,7 +2,6 @@ package net.osmand.plus.settings.backend.backup;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -14,5 +13,5 @@ public abstract class SettingsItemReader<T extends SettingsItem> {
this.item = item;
}
public abstract void readFromStream(@NonNull InputStream inputStream, File destination) throws IOException, IllegalArgumentException;
public abstract void readFromStream(@NonNull InputStream inputStream, String entryName) throws IOException, IllegalArgumentException;
}

View file

@ -14,6 +14,11 @@ import net.osmand.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.map.ITileSource;
import net.osmand.plus.FavouritesDbHelper.FavoriteGroup;
import net.osmand.plus.audionotes.AudioVideoNotesPlugin;
import net.osmand.plus.helpers.FileNameTranslationHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean;
import net.osmand.plus.mapmarkers.MapMarkersGroup;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
@ -35,6 +40,8 @@ import org.apache.commons.logging.Log;
import java.io.File;
import java.util.List;
import static net.osmand.plus.settings.backend.backup.FileSettingsItem.*;
public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final Log LOG = PlatformUtil.getLog(DuplicatesSettingsAdapter.class.getName());
@ -76,12 +83,11 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView
if (holder instanceof HeaderViewHolder) {
HeaderViewHolder headerHolder = (HeaderViewHolder) holder;
headerHolder.title.setText((String) currentItem);
headerHolder.subTitle.setText(String.format(
app.getString(R.string.listed_exist),
(String) currentItem));
headerHolder.subTitle.setText(String.format(app.getString(R.string.listed_exist), currentItem));
headerHolder.divider.setVisibility(View.VISIBLE);
} else if (holder instanceof ItemViewHolder) {
ItemViewHolder itemHolder = (ItemViewHolder) holder;
itemHolder.subTitle.setVisibility(View.GONE);
if (currentItem instanceof ApplicationModeBean) {
ApplicationModeBean modeBean = (ApplicationModeBean) currentItem;
String profileName = modeBean.userProfileName;
@ -117,19 +123,17 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView
QuickAction action = (QuickAction) currentItem;
itemHolder.title.setText(action.getName(app));
itemHolder.icon.setImageDrawable(uiUtilities.getIcon(action.getIconRes(), activeColorRes));
itemHolder.subTitle.setVisibility(View.GONE);
} else if (currentItem instanceof PoiUIFilter) {
PoiUIFilter filter = (PoiUIFilter) currentItem;
itemHolder.title.setText(filter.getName());
int iconRes = RenderingIcons.getBigIconResourceId(filter.getIconId());
itemHolder.icon.setImageDrawable(uiUtilities.getIcon(iconRes != 0 ? iconRes : R.drawable.ic_action_user, activeColorRes));
itemHolder.subTitle.setVisibility(View.GONE);
} else if (currentItem instanceof ITileSource) {
itemHolder.title.setText(((ITileSource) currentItem).getName());
itemHolder.icon.setImageDrawable(uiUtilities.getIcon(R.drawable.ic_map, activeColorRes));
itemHolder.subTitle.setVisibility(View.GONE);
} else if (currentItem instanceof File) {
File file = (File) currentItem;
FileSubtype fileSubtype = FileSubtype.getSubtypeByPath(app, file.getPath());
itemHolder.title.setText(file.getName());
if (file.getAbsolutePath().contains(IndexConstants.RENDERERS_DIR)) {
itemHolder.icon.setImageDrawable(uiUtilities.getIcon(R.drawable.ic_action_map_style, activeColorRes));
@ -144,16 +148,18 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView
iconId = R.drawable.ic_action_photo_dark;
}
itemHolder.icon.setImageDrawable(uiUtilities.getIcon(iconId, activeColorRes));
} else if (fileSubtype.isMap()
|| fileSubtype == FileSubtype.TTS_VOICE
|| fileSubtype == FileSubtype.VOICE) {
itemHolder.title.setText(FileNameTranslationHelper.getFileNameWithRegion(app, file.getName()));
itemHolder.icon.setImageDrawable(uiUtilities.getIcon(fileSubtype.getIconId(), activeColorRes));
}
itemHolder.subTitle.setVisibility(View.GONE);
} else if (currentItem instanceof AvoidRoadInfo) {
itemHolder.title.setText(((AvoidRoadInfo) currentItem).name);
itemHolder.icon.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_alert, activeColorRes));
itemHolder.subTitle.setVisibility(View.GONE);
} else if (currentItem instanceof FavoriteGroup) {
itemHolder.title.setText(((FavoriteGroup) currentItem).getDisplayName(app));
itemHolder.icon.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_favorite, activeColorRes));
itemHolder.subTitle.setVisibility(View.GONE);
} else if (currentItem instanceof MapMarkersGroup) {
MapMarkersGroup markersGroup = (MapMarkersGroup) currentItem;
String groupName = markersGroup.getName();
@ -162,7 +168,6 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView
}
itemHolder.title.setText(groupName);
itemHolder.icon.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_flag, activeColorRes));
itemHolder.subTitle.setVisibility(View.GONE);
}
itemHolder.divider.setVisibility(shouldShowDivider(position) ? View.VISIBLE : View.GONE);
}
@ -182,7 +187,7 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView
}
}
private class HeaderViewHolder extends RecyclerView.ViewHolder {
private static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView title;
TextView subTitle;
View divider;
@ -195,7 +200,7 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView
}
}
private class ItemViewHolder extends RecyclerView.ViewHolder {
private static class ItemViewHolder extends RecyclerView.ViewHolder {
TextView title;
TextView subTitle;
ImageView icon;
@ -219,4 +224,4 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter<RecyclerView
return next instanceof String;
}
}
}
}

View file

@ -286,21 +286,9 @@ class ExportImportSettingsAdapter extends OsmandBaseExpandableListAdapter {
file = (File) currentItem;
size = file.length();
}
title.setText(FileNameTranslationHelper.getFileName(app,
app.getResourceManager().getOsmandRegions(),
file.getName()));
title.setText(FileNameTranslationHelper.getFileNameWithRegion(app, file.getName()));
FileSubtype subtype = FileSubtype.getSubtypeByPath(app, file.getPath());
switch (subtype) {
case SRTM_MAP:
iconId = R.drawable.ic_plugin_srtm;
break;
case WIKI_MAP:
iconId = R.drawable.ic_plugin_wikipedia;
break;
default:
iconId = R.drawable.ic_map;
}
setupIcon(icon, iconId, itemSelected);
setupIcon(icon, subtype.getIconId(), itemSelected);
subText.setText(AndroidUtils.formatSize(app, size));
subText.setVisibility(View.VISIBLE);
break;
@ -312,9 +300,7 @@ class ExportImportSettingsAdapter extends OsmandBaseExpandableListAdapter {
case TTS_VOICE:
case VOICE:
file = (File) currentItem;
title.setText(FileNameTranslationHelper.getFileName(app,
app.getResourceManager().getOsmandRegions(),
file.getName()));
title.setText(FileNameTranslationHelper.getFileNameWithRegion(app, file.getName()));
setupIcon(icon, R.drawable.ic_action_volume_up, itemSelected);
break;
case MARKERS:

View file

@ -2,6 +2,7 @@ package net.osmand.plus.settings.fragments;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
@ -33,6 +34,7 @@ import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.ExportSettingsType;
import net.osmand.plus.settings.backend.backup.FileSettingsItem;
import net.osmand.plus.settings.backend.backup.GlobalSettingsItem;
import net.osmand.plus.settings.backend.backup.ProfileSettingsItem;
import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsExportListener;
@ -47,6 +49,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
@ -60,8 +63,10 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
private static final String EXPORTING_PROFILE_KEY = "exporting_profile_key";
private static final String INCLUDE_ADDITIONAL_DATA_KEY = "include_additional_data_key";
private static final String INCLUDE_GLOBAL_SETTINGS_KEY = "include_global_settings_key";
private static final String PROGRESS_MAX_KEY = "progress_max_key";
private static final String PROGRESS_VALUE_KEY = "progress_value_key";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yy");
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yy", Locale.US);
private OsmandApplication app;
private Map<ExportSettingsType, List<?>> dataList = new HashMap<>();
@ -69,6 +74,8 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
private SettingsExportListener exportListener;
private ProgressDialog progress;
private int progressMax;
private int progressValue;
private long exportStartTime;
@ -87,6 +94,8 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
includeAdditionalData = savedInstanceState.getBoolean(INCLUDE_ADDITIONAL_DATA_KEY);
includeGlobalSettings = savedInstanceState.getBoolean(INCLUDE_GLOBAL_SETTINGS_KEY);
exportStartTime = savedInstanceState.getLong(EXPORT_START_TIME_KEY);
progressMax = savedInstanceState.getInt(PROGRESS_MAX_KEY);
progressValue = savedInstanceState.getInt(PROGRESS_VALUE_KEY);
}
dataList = app.getSettingsHelper().getAdditionalData(globalExport);
}
@ -99,6 +108,8 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
outState.putBoolean(INCLUDE_ADDITIONAL_DATA_KEY, includeAdditionalData);
outState.putBoolean(INCLUDE_GLOBAL_SETTINGS_KEY, includeGlobalSettings);
outState.putLong(EXPORT_START_TIME_KEY, exportStartTime);
outState.putInt(PROGRESS_MAX_KEY, progress.getMax());
outState.putInt(PROGRESS_VALUE_KEY, progress.getProgress());
}
@Override
@ -271,10 +282,22 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
showExportProgressDialog();
File tempDir = FileUtils.getTempDir(app);
String fileName = getFileName();
app.getSettingsHelper().exportSettings(tempDir, fileName, getSettingsExportListener(), prepareSettingsItemsForExport(), true);
List<SettingsItem> items = prepareSettingsItemsForExport();
progress.setMax(getMaxProgress(items));
app.getSettingsHelper().exportSettings(tempDir, fileName, getSettingsExportListener(), items, true);
}
}
private int getMaxProgress(List<SettingsItem> items) {
long maxProgress = 0;
for (SettingsItem item : items) {
if (item instanceof FileSettingsItem) {
maxProgress += ((FileSettingsItem) item).getSize();
}
}
return (int) maxProgress / (1 << 20);
}
private String getFileName() {
if (globalExport) {
if (exportStartTime == 0) {
@ -295,12 +318,31 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
progress.dismiss();
}
progress = new ProgressDialog(context);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setCancelable(true);
progress.setTitle(app.getString(R.string.shared_string_export));
progress.setMessage(app.getString(R.string.shared_string_preparing));
progress.setCancelable(false);
progress.setButton(DialogInterface.BUTTON_NEGATIVE, app.getString(R.string.shared_string_cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
cancelExport();
}
});
progress.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
cancelExport();
}
});
progress.show();
}
private void cancelExport() {
app.getSettingsHelper().cancelExportForFile(getExportFile());
progress.dismiss();
dismiss();
}
private SettingsExportListener getSettingsExportListener() {
if (exportListener == null) {
exportListener = new SettingsExportListener() {
@ -315,6 +357,11 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
app.showToastMessage(R.string.export_profile_failed);
}
}
@Override
public void onSettingsExportProgressUpdate(int value) {
progress.setProgress(value);
}
};
}
return exportListener;
@ -326,6 +373,8 @@ public class ExportProfileBottomSheet extends BasePreferenceBottomSheet {
boolean fileExporting = app.getSettingsHelper().isFileExporting(file);
if (fileExporting) {
showExportProgressDialog();
progress.setMax(progressMax);
progress.setProgress(progressValue);
app.getSettingsHelper().updateExportListener(file, getSettingsExportListener());
} else if (file.exists()) {
dismissExportProgressDialog();

View file

@ -32,6 +32,8 @@ import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo;
import net.osmand.plus.osmedit.OpenstreetmapPoint;
import net.osmand.plus.osmedit.OsmNotesPoint;
import net.osmand.plus.poi.PoiUIFilter;
import net.osmand.plus.quickaction.QuickAction;
import net.osmand.plus.settings.backend.ApplicationMode;
@ -46,10 +48,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static net.osmand.IndexConstants.AV_INDEX_DIR;
import static net.osmand.IndexConstants.GPX_INDEX_DIR;
import static net.osmand.IndexConstants.RENDERERS_DIR;
import static net.osmand.IndexConstants.ROUTING_PROFILES_DIR;
import static net.osmand.plus.settings.backend.backup.FileSettingsItem.*;
import static net.osmand.plus.settings.fragments.ImportSettingsFragment.IMPORT_SETTINGS_TAG;
@ -196,6 +195,11 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment {
List<File> trackFilesList = new ArrayList<>();
List<AvoidRoadInfo> avoidRoads = new ArrayList<>();
List<FavoriteGroup> favoriteGroups = new ArrayList<>();
List<OsmNotesPoint> osmNotesPointList = new ArrayList<>();
List<OpenstreetmapPoint> osmEditsPointList = new ArrayList<>();
List<File> ttsVoiceFilesList = new ArrayList<>();
List<File> voiceFilesList = new ArrayList<>();
List<File> mapFilesList = new ArrayList<>();
for (Object object : duplicatesList) {
if (object instanceof ApplicationMode.ApplicationModeBean) {
@ -208,19 +212,30 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment {
tileSources.add((ITileSource) object);
} else if (object instanceof File) {
File file = (File) object;
if (file.getAbsolutePath().contains(RENDERERS_DIR)) {
FileSubtype fileSubtype = FileSubtype.getSubtypeByPath(app, file.getPath());
if (fileSubtype == FileSubtype.RENDERING_STYLE) {
renderFilesList.add(file);
} else if (file.getAbsolutePath().contains(ROUTING_PROFILES_DIR)) {
} else if (fileSubtype == FileSubtype.ROUTING_CONFIG) {
routingFilesList.add(file);
} else if (file.getAbsolutePath().contains(AV_INDEX_DIR)) {
} else if (fileSubtype == FileSubtype.MULTIMEDIA_NOTES) {
multimediaFilesList.add(file);
} else if (file.getAbsolutePath().contains(GPX_INDEX_DIR)) {
} else if (fileSubtype == FileSubtype.GPX) {
trackFilesList.add(file);
} else if (fileSubtype.isMap()) {
mapFilesList.add(file);
} else if (fileSubtype == FileSubtype.TTS_VOICE) {
ttsVoiceFilesList.add(file);
} else if (fileSubtype == FileSubtype.VOICE) {
voiceFilesList.add(file);
}
} else if (object instanceof AvoidRoadInfo) {
avoidRoads.add((AvoidRoadInfo) object);
} else if (object instanceof FavoriteGroup) {
favoriteGroups.add((FavoriteGroup) object);
} else if (object instanceof OsmNotesPoint) {
osmNotesPointList.add((OsmNotesPoint) object);
} else if (object instanceof OpenstreetmapPoint) {
osmEditsPointList.add((OpenstreetmapPoint) object);
}
}
if (!profiles.isEmpty()) {
@ -263,6 +278,26 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment {
duplicates.add(getString(R.string.shared_string_favorites));
duplicates.addAll(favoriteGroups);
}
if (!osmNotesPointList.isEmpty()) {
duplicates.add(getString(R.string.osm_notes));
duplicates.addAll(osmNotesPointList);
}
if (!osmEditsPointList.isEmpty()) {
duplicates.add(getString(R.string.osm_edits));
duplicates.addAll(osmEditsPointList);
}
if (!mapFilesList.isEmpty()) {
duplicates.add(getString(R.string.shared_string_maps));
duplicates.addAll(mapFilesList);
}
if (!ttsVoiceFilesList.isEmpty()) {
duplicates.add(getString(R.string.local_indexes_cat_tts));
duplicates.addAll(ttsVoiceFilesList);
}
if (!voiceFilesList.isEmpty()) {
duplicates.add(getString(R.string.local_indexes_cat_voice));
duplicates.addAll(voiceFilesList);
}
return duplicates;
}

View file

@ -735,6 +735,11 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment {
app.showToastMessage(R.string.profile_backup_failed);
}
}
@Override
public void onSettingsExportProgressUpdate(int value) {
}
};
}
return exportListener;

View file

@ -26,7 +26,9 @@ import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.data.TransportStop;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.other.TrackChartPoints;
import net.osmand.plus.measurementtool.MeasurementToolFragment;
import net.osmand.plus.profiles.LocationIcon;
import net.osmand.plus.routing.RouteCalculationResult;
import net.osmand.plus.routing.RouteDirectionInfo;
@ -158,7 +160,8 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont
@Override
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
if ((helper.isPublicTransportMode() && transportHelper.getRoutes() != null) ||
(helper.getFinalLocation() != null && helper.getRoute().isCalculated())) {
(helper.getFinalLocation() != null && helper.getRoute().isCalculated()) ||
isPlanRouteGraphsAvailable()) {
updateAttrs(settings, tileBox);
@ -202,6 +205,17 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont
}
private boolean isPlanRouteGraphsAvailable() {
if (view.getContext() instanceof MapActivity) {
MapActivity mapActivity = (MapActivity) view.getContext();
MeasurementToolFragment fragment = mapActivity.getMeasurementToolFragment();
if (fragment != null) {
return fragment.hasVisibleGraph();
}
}
return false;
}
private void updateAttrs(DrawSettings settings, RotatedTileBox tileBox) {
boolean updatePaints = attrs.updatePaints(view.getApplication(), settings, tileBox);
attrs.isPaint3 = false;