diff --git a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java index c8ffd2d96b..3e4cd5af55 100644 --- a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java +++ b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java @@ -77,4 +77,6 @@ public class IndexConstants { public static final String TEMP_DIR = "temp/"; public static final String ROUTING_PROFILES_DIR = "routing/"; public static final String PLUGINS_DIR = "plugins/"; + + public static final String VOICE_PROVIDER_SUFFIX = "-tts"; } diff --git a/OsmAnd-java/src/main/java/net/osmand/TspAnt.java b/OsmAnd-java/src/main/java/net/osmand/TspAnt.java index 815bddb7ef..6964a3facd 100644 --- a/OsmAnd-java/src/main/java/net/osmand/TspAnt.java +++ b/OsmAnd-java/src/main/java/net/osmand/TspAnt.java @@ -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 intermediates, LatLon start, LatLon end) { - boolean keepEndPoint = end != null; - List l = new ArrayList(); - l.add(start); + boolean keepEndPoint = end != null; + List l = new ArrayList(); + if (start != null) { + l.add(start); + } l.addAll(intermediates); if (keepEndPoint) { l.add(end); diff --git a/OsmAnd-telegram/res/values-ar-rSA/strings.xml b/OsmAnd-telegram/res/values-ar-rSA/strings.xml index da6f0efd1b..f799745114 100644 --- a/OsmAnd-telegram/res/values-ar-rSA/strings.xml +++ b/OsmAnd-telegram/res/values-ar-rSA/strings.xml @@ -267,4 +267,8 @@ مشاركة: %1$s مفعل %1$s منذ + تصدير + لوجكات العازلة + تحقق من السجلات التفصيلية للتطبيق وشاركها + إرسال تقرير \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-fa/strings.xml b/OsmAnd-telegram/res/values-fa/strings.xml index 13e67eef49..092218d713 100644 --- a/OsmAnd-telegram/res/values-fa/strings.xml +++ b/OsmAnd-telegram/res/values-fa/strings.xml @@ -78,4 +78,8 @@ آخرین پاسخ: %1$s آخرین به‌روزرسانی تلگرام: %1$s خطا + ارسال گزارش + برون‌برد + بافر لاگ‌کت + لاگ‌های جزئی برنامه را بررسی و هم‌رسانی کنید \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-is/strings.xml b/OsmAnd-telegram/res/values-is/strings.xml index a6b3daec93..94bb15851c 100644 --- a/OsmAnd-telegram/res/values-is/strings.xml +++ b/OsmAnd-telegram/res/values-is/strings.xml @@ -1,2 +1,211 @@ - \ No newline at end of file + + Einingar og snið þeirra + Setja upp OsmAnd + Lykilorð + Meðalhæð + Fela + sendi (%1$d í biðminni) + Endursenda staðsetningu + Staðsetning… + Meðalhraði + Athugaðu og deildu nákvæmum atvikaskrám úr forritinu + Mílur/metrar + Lykilorð í Telegram + Deila staðsetningu sem + Deila staðsetningu + Sía: Stilltu lágmarksfjarlægð frá síðustu staðsetningu þar sem punktur er tekinn í skráningu + Til baka + Slökkt + Engin internettenging + Stilla tíma + Samþykkja + sek + %1$d mín + Mílur/fet + Vélmenni + Meðferð persónuupplýsinga í OsmAnd + Sendi staðsetningu + Tegund milliþjóns (proxy) + Breyta einingum sem notaðar eru við lengdarmælingar. + Staðsetningaferill + Merki OsmAnd + Gat ekki bætt við nýju tæki + Staða + Stillingar milliþjóns (proxy) + Gátt + Eftir nafni + Tímabelti + mín/km + m + Skráning staðsetningar virk + Tenging + Kílómetrar/metrar + Síðasta uppfærsla frá Telegram: Fyrir %1$s síðan + Vöktun er óvirk + Staða OsmAnd-rekjara + OsmAnd rekjari + Hópur + mín/ml + Netþjónn + Slökkva á öllu + Til baka í OsmAnd + Heiti tækis er of langt + Loka + %1$d klst %2$d mín + Mínútur á mílu + mín + Byrja + Vista + Landakort og texti + Lykilorð + %1$s bætt við. + Birta í OsmAnd + Síðasta tiltæka staðsetning + Sent + Auðkenningarkóði + Leita í tengiliðum + Opna OsmAnd + Endar + Settu inn auðkenningarkóða + Staðsetning mín + Skrá út + Birta á korti + Staða deilingar + Hætta við + Bíð eftir svari frá Telegram + Tókst að senda og uppfæra + Velja + Metrar á sekúndu + Heiti tækis + Velkomin + Hvernig það virkar + Stefna + Milliþjónn + Engin GPS-tenging + Senda staðsetningu sem + %1$d punktar + yd + Gera óvirkt + Tími á ferðinni + Sjómílur á klukkustund (hnútar) + %1$d klst + Lykill + Bakgrunnsvinna + Setja upp + Bæta við + Raða eftir + Síðasta svar: %1$s + Allt + Hæð + Birta GPS-punkta + Heiti tækis getur ekki verið tómt + Leita + Eftir vegalengd + Sjómílur + m/sek + Þú ert ekki skráð/ur inn + Ræsing + Lágmarksnákvæmni skráningar + Loka + Lágmarkshraði skráninga + OsmAnd rakningarþjónusta + Telegram + Kveikja á \"Staðsetning\"\? + Tengdur aðgangur + mi + Notandanafn + Heimild + Lengdareiningar + GPX-stillingar + Nýskráning í Telegram + Vegalengd + klst + km/klst + Skrá inn + Síðasta svar + Deila staðsetningu + ft + Nafn + %1$s síðan + Veldu notendur eða hópa til að deila með staðsetningu þinni. + Ég er ekki með Telegram-aðgang + Vöktun er virk + Rennur út + Gagnaleynd + Notandaaðgangur + Virkt + Nákvæmni + VILL + Ekki á ferð + Halda áfram + Staða + Upphafs — Endadagsetning + Aftengt + sml + Uppfæra + Settu inn lykilorð + Deila + Logcat biðminni + Settu inn símanúmer + Mínútur á kílómetra + Hætta + Útlit + Dagsetning + Síðar + Stefna + Hraðaeining + Landakort + Sía: Engin skráning punkta fyrir neðan þennan hraða + í %1$s + Leit: Hópur eða tengiliður + hnútar + km + Tímalína + Vistuð skilaboð + GPS-punktar + Virkja + Lágmarksfjarlægð skráninga + Síðasta uppfærsla frá Telegram + Safnað + Skrái út + Mílur/yardar + Veldu tíma sem á að birta + Auðkenni + Kílómetrar á klukkustund + Síðasta svar: Fyrir %1$s síðan + Tengist internetinu + Senda skýrslu + Flytja út + Mílur á klukkustund + Í lagi + Virkja + Persónuverndarstefna Telegram + Virkt spjall + Senda staðsetningu mína + Stungið upp á + Fara í stillingar + mi/klst + Deiling í bakgrunni + Deiling: %1$s + Síðasta uppfærsla frá Telegram: %1$s + Byrja + Raða + Engin gögn + Eftir hópi + Bæta við tæki + Texti + Stillingar + Birta notendur á kortinu + Tengt + Símanúmer + Upphafsdagsetning + Skilgreindu einingu fyrir hraða. + síðan + Settu inn kóða + Gera vöktun óvirka + Forritið hefur ekki heimildir til að nota staðsetningargögn. + Lokadagsetning + Bakgrunnshamur + OsmAnd-rakning keyrir í bakgrunni á meðan slökkt er á skjá. + \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-it/strings.xml b/OsmAnd-telegram/res/values-it/strings.xml index 04ca50389d..05cf56a8da 100644 --- a/OsmAnd-telegram/res/values-it/strings.xml +++ b/OsmAnd-telegram/res/values-it/strings.xml @@ -266,4 +266,5 @@ Ultimo aggiornamento da Telegram: %1$s ERR Controlla e condividi i log dettagliati dell\'applicazione + Logcat buffer \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-tzm/strings.xml b/OsmAnd-telegram/res/values-tzm/strings.xml new file mode 100644 index 0000000000..e2a8f81966 --- /dev/null +++ b/OsmAnd-telegram/res/values-tzm/strings.xml @@ -0,0 +1,52 @@ + + + Aɣul + Bḍu + Ssekcem uṭṭun n utilifun + Akk + Abut + Usrid + g %1$s + Amiḍan + Rgel + Tarabbut + aya + Ffeɣ + Fren s + Fren + S trabbut + S isem + Isem + Bḍu adɣar am + Rnu allal + Ḥḍu + Addad + Ddu ɣer tesɣal + Azan n udɣar + Ssentel + Rnu + Taleqqemt tameggarut seg Tiligṛam + Takaṛḍa d uḍṛiṣ + Aḍṛiṣ + Takaṛḍa + Azen adɣar am + Asakud n usenti + Asakud + Ssedɣi + Tiligṛam + WAX + Rzu + Tanila + Tinnutla + Apṛuksi + Tisɣal n Upṛuksi + Izdey + Anaw n upṛuksi + Taguri n uzray + Tasarut + Tisɣal n GPX + Stey + Ssenti + Aɣul ɣer OsmAnd + %1$s aya + \ No newline at end of file diff --git a/OsmAnd/AndroidManifest-freecustom.xml b/OsmAnd/AndroidManifest-freecustom.xml deleted file mode 100644 index 66ced1602a..0000000000 --- a/OsmAnd/AndroidManifest-freecustom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - diff --git a/OsmAnd/assets/bundled_assets.xml b/OsmAnd/assets/bundled_assets.xml index 30d1422d0f..9b69d7250c 100644 --- a/OsmAnd/assets/bundled_assets.xml +++ b/OsmAnd/assets/bundled_assets.xml @@ -12,6 +12,7 @@ + diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index db678db5c5..04fb414cdb 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -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" } diff --git a/OsmAnd/res/drawable/ic_action_device_location.xml b/OsmAnd/res/drawable/ic_action_device_location.xml new file mode 100644 index 0000000000..c804224052 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_device_location.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_device_location_colored.xml b/OsmAnd/res/drawable/ic_action_device_location_colored.xml new file mode 100644 index 0000000000..2bceb2d67e --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_device_location_colored.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/OsmAnd/res/layout/colors_card.xml b/OsmAnd/res/layout/colors_card.xml new file mode 100644 index 0000000000..bef11dc500 --- /dev/null +++ b/OsmAnd/res/layout/colors_card.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/fragment_measurement_tool_graph.xml b/OsmAnd/res/layout/fragment_measurement_tool_graph.xml index 3d14187712..555c1b6928 100644 --- a/OsmAnd/res/layout/fragment_measurement_tool_graph.xml +++ b/OsmAnd/res/layout/fragment_measurement_tool_graph.xml @@ -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"> + tools:visibility="visible"> - + + + tools:visibility="visible"> - - - - - + 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"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/osmlive_cancelled_dialog_fragment.xml b/OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml similarity index 81% rename from OsmAnd/res/layout/osmlive_cancelled_dialog_fragment.xml rename to OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml index a936b85350..1a2a28f1c6 100644 --- a/OsmAnd/res/layout/osmlive_cancelled_dialog_fragment.xml +++ b/OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml @@ -84,26 +84,11 @@ - - + android:layout_marginTop="@dimen/title_padding"> diff --git a/OsmAnd/res/layout/point_editor_fragment_new.xml b/OsmAnd/res/layout/point_editor_fragment_new.xml index 21c6396c53..9be9841750 100644 --- a/OsmAnd/res/layout/point_editor_fragment_new.xml +++ b/OsmAnd/res/layout/point_editor_fragment_new.xml @@ -444,9 +444,9 @@ osmand:typeface="@string/font_roboto_medium" /> - رياضة الطوارئ سفر + النظام المرجعي للشبكة العسكرية + النظام المرجعي للشبكة العسكرية + OsmAnd يستخدم MGRS، وهو مشابه لتنسيق UTM NATO . \ No newline at end of file diff --git a/OsmAnd/res/values-bg/strings.xml b/OsmAnd/res/values-bg/strings.xml index 8a5ad86232..f225fb42df 100644 --- a/OsmAnd/res/values-bg/strings.xml +++ b/OsmAnd/res/values-bg/strings.xml @@ -1710,7 +1710,7 @@ POI ще бъдат изтрити, след като качите Вашите промени Моля, кажете ни какви промени искате в това приложение. Вашето мнение е важно за нас. - Преизчисляване само началната част от дълги маршрути + Преизчисляване само на началото на маршрута. Може да се ползва за дълги маршрути. Онлайн OSM класификация на картите с изображения Показва използваните безплатни файлове за изтегляне Записването в GPX може да бъде включвано и изключвано от основния екран @@ -2186,4 +2186,42 @@ Спорт Аварийни служби Пътуване + Изчистете маркера OAuth на OpenStreetMap + Всички предишни сегменти ще бъдат преизчислени спрямо избрания профил. + Отваряне на записан трак + е записан + Моля, добавете поне две точки. + Повтаряне + Последна промяна + Име: Я - А + Име: А - Я + Икони за начало/край + Благодарим ви, че закупихте \"Контурни линии\" + Абонаментът се таксува за избрания период. Можете да го отмените в AppGallery по всяко време. + Заплащането ще ви бъде приспаднато от вашия акаунт в AppGallery при потвърждаване на покупката. +\n +\nАбонаментът се удължава автоматично, ако не бъде отменен преди датата на подновяване. От вашата сметка се удържат пари само в датата на подновяване. +\n +\nМожете да управлявате или да прекъснете абонаментите си в настройките на AppGallery. + Избягване на пътеки + Избягване на пътеки + Разработка + Данни на OsmAnd Live + Данни на OsmAnd Live + Двустъпково изчисляване на маршрута за автомобилна навигация. + Превключете на Java (безопасен режим) изчисляването на маршрути за обществения транспорт + Влезте с OAuth, за да ползвате всички функции на osmedit + Вход с OAuth + Системна настройка + Всички следващи сегменти + Предишен сегмент + Всички предишни сегменти + Само избраният сегмент ще бъде преизчислен съобразно избрания профил. + Всички следващи сегменти ще бъдат преизчислени с помощта на избрания профил. + Промяна типа на маршрута след + Опростена пътека + Само линията на маршрута ще бъде запазена, точките ще бъдат изтрити. + Име на файл + Избрани файлове за проследяване: %s + REC \ No newline at end of file diff --git a/OsmAnd/res/values-ca/strings.xml b/OsmAnd/res/values-ca/strings.xml index eb412908d1..04ad6f0150 100644 --- a/OsmAnd/res/values-ca/strings.xml +++ b/OsmAnd/res/values-ca/strings.xml @@ -184,7 +184,7 @@ Seleccioneu una veu i comproveu les seves indicacions: Canvia el zoom del mapa seguint el desplaçament horitzontal del ratolí de bola. Utilitza el ratolí de bola per controlar el zoom - Es van pujar {0} PDI/notes + S\'han pujat {0} PDI/notes PDIs/notes d\'OSM desades al dispositiu Mostra i gestiona els PDI/notes d\'OSM de la vostra base de dades local. Informació de dia/nit @@ -3859,4 +3859,26 @@ Abasta l\'àrea: %1$s x %2$s Control de pantalla Desenvolupament %1$s — %2$s + Canvieu el càlcul de rutes de Transport Public a Java (segur) + Icones d\'Inici/Final + Gràcies per comprar \'Corbes de nivell\' + Evita els camins per a vianants + Evita els camins per a vianants + Dades d\'OsmAnd Live + Dades d\'OsmAnd Live + Enrutament en dues fases per navegació amb cotxe. + Inicieu la sessió mitjançant OAuth per fer servir les funcionalitats osmedit + Inicieu la sessió via OAuth + Neteja el testimoni OAuth d\'OpenStreetMap + La sessió s\'ha tancat correctament + El fitxer ja s\'ha importat a OsmAnd + Mapes locals + Especial + Transport + Servei + Símbols + Esport + Emergència + Desenvolupament local de Transport Public + Discontinu \ No newline at end of file diff --git a/OsmAnd/res/values-cs/phrases.xml b/OsmAnd/res/values-cs/phrases.xml index b64efe0702..fc5bfd5892 100644 --- a/OsmAnd/res/values-cs/phrases.xml +++ b/OsmAnd/res/values-cs/phrases.xml @@ -3849,4 +3849,5 @@ Přístřešky Střešní Bod GPX + Radarová věž \ No newline at end of file diff --git a/OsmAnd/res/values-cs/strings.xml b/OsmAnd/res/values-cs/strings.xml index 67044dc34e..5c5a1a7d25 100644 --- a/OsmAnd/res/values-cs/strings.xml +++ b/OsmAnd/res/values-cs/strings.xml @@ -3589,4 +3589,69 @@ Zobrazená oblast: %1$s x %2$s Počkejte na přepočet trasy. \nGraf bude dostupný po přepočtu. %1$s — %2$s + Pseudo-Mercatorovo zobrazení + Jeden obrazový soubor pro každou dlaždici + Soubor SQLiteDB + Zadejte název online zdroje. + Zadejte nebo vložte URL online zdroje. + Opravdu chcete odstranit %d rychlých akcí\? + Časový limit obrazovky + Odinstalovat rychlostní radary + Právní informace + Body rychlostních radarů + V některých zemích nebo regionech je zákonem zakázáno používat aplikace varující před rychlostními radary. +\n +\nMusíte se rozhodnout podle zákonů vaší země. +\n +\nZvolíte-li %1$s, budete dostávat varování před rychlostními radary. +\n +\nZvolíte-li %2$s, všechna data o rychlostních radarech budou odstraněna do té doby, než OsmAnd kompletně přeinstalujete. + Ponechat aktivní + Odinstalovat + V některých zemích je varování před rychlostními radary zákonem zakázané. + Je-li \"%1$s\" zapnuto, čas aktivity bude na tom záviset. + Výchozí časový limit obrazovky + tuny + metry + Zobrazit nebo skrýt další detaily na mapě + Noční mapa + Přidat online zdroj + Použitím těchto změn se vymažou uložené dlaždice pro tento zdroj + Nastavit výšku plavidla + Můžete nastavit výšku plavidla, aby bylo možné se vyhnout nízkým mostům. Mějte na paměti, že pro pohyblivé mosty se zohledňuje výška v otevřeném stavu. + Plné + Přepsat stopu + Uložit jako novou stopu + Otočit trasu + Celá stopa bude přepočítána pomocí zvoleného profilu. + Pouze následující úsek bude přepočítaný pomocí zvoleného profilu. + Zvolte, jakým způsobem spojit body – přímou čarou, nebo výpočtem trasy mezi nimi, viz níže. + Celá stopa + Další úsek + Předplatné bude účtováno za zvolené období. Můžete jej kdykoliv zrušit na AppGallery. + Platba se strhne z vašeho účtu AppGallery po potvrzení nákupu. +\n +\nPředplatné bude automaticky obnoveno, pokud jej před datem obnovení nezrušíte. Platba za další období (měsíc/čtvrtletí/rok) bude stržena pouze v den obnovení. +\n +\nPředplatné můžete spravovat a zrušit v nastavení AppGallery. + Vyhnout se chodníkům + Vyhnout se chodníkům + Vývoj + Údaje OsmAnd Live + Údaje OsmAnd Live + Dvoufázový výpočet trasy pro navigaci auta. + Nativní hromadná doprava (ve vývoji) + Přepnout na výpočet trasy hromadné dopravy v Javě (bezpečné) + Lokální mapy + Občanská vybavenost + Speciální + Doprava + Služby + Symboly + Sport + Nouzová situace + Cestování + Nechat obrazovku zapnutou + Nechat obrazovku vypnutou + Dále, pro použití této možnosti připojte vaši stopu k nejbližší povolené cestě s některým z vašich navigačních profilů. \ No newline at end of file diff --git a/OsmAnd/res/values-de/phrases.xml b/OsmAnd/res/values-de/phrases.xml index 011706fbf0..4999a997a0 100644 --- a/OsmAnd/res/values-de/phrases.xml +++ b/OsmAnd/res/values-de/phrases.xml @@ -3852,4 +3852,5 @@ Überdachter Parkplatz Dachparkplätze GPX-Wegpunkt + Radarturm \ No newline at end of file diff --git a/OsmAnd/res/values-de/strings.xml b/OsmAnd/res/values-de/strings.xml index 559fb70a06..ef1285f3f1 100644 --- a/OsmAnd/res/values-de/strings.xml +++ b/OsmAnd/res/values-de/strings.xml @@ -3937,4 +3937,17 @@ 2-Phasen-A*-Routing-Algorithmus verwenden %1$s Daten sind nur auf den Straßen verfügbar, Sie müssen eine Route mit \"Route zwischen Punkten\" berechnen, um sie zu erhalten. %1$s — %2$s + MGRS + Warte auf die Neuberechnung der Route. +\nDie Graphik wird nach der Neuberechnung verfügbar sein. + Einrichtung + ÖPNV + Dienstleistung + Reisen + Sport + Notfall + Graph + Symbole + Lokale Karten + Spezial \ No newline at end of file diff --git a/OsmAnd/res/values-eo/phrases.xml b/OsmAnd/res/values-eo/phrases.xml index 2eae52044b..d1757bea9d 100644 --- a/OsmAnd/res/values-eo/phrases.xml +++ b/OsmAnd/res/values-eo/phrases.xml @@ -3842,4 +3842,5 @@ privataj garaĝ-budoj tegmento GPX-punkto + Radar-turo \ No newline at end of file diff --git a/OsmAnd/res/values-eo/strings.xml b/OsmAnd/res/values-eo/strings.xml index c469f7f4bd..93ff65c8f2 100644 --- a/OsmAnd/res/values-eo/strings.xml +++ b/OsmAnd/res/values-eo/strings.xml @@ -3934,4 +3934,16 @@ \nDiagramo estos videbla post rekalkulado. %1$s — %2$s Manko + Lokaj mapoj + Oportunaĵo + Specialaj + Transporto + Servo + Simboloj + Sporto + Vivsavo + Vojaĝo + MGRS + MGRS + OsmAnd uzas MGRS, kiu estas simila al la formo UTM NATO. \ No newline at end of file diff --git a/OsmAnd/res/values-es-rAR/phrases.xml b/OsmAnd/res/values-es-rAR/phrases.xml index 4a15cc5cdd..20712137c0 100644 --- a/OsmAnd/res/values-es-rAR/phrases.xml +++ b/OsmAnd/res/values-es-rAR/phrases.xml @@ -3047,7 +3047,7 @@ Shawarma;Carne a las brasas en asador horizontal Estación de combustible náutica;Gasolinera náutica Cebadero para animales - Artículos de cotillón;Artículos para fiestas + Artículos para fiestas;Cotillón Artículos de electricidad Cerrajería Artículos de iluminación @@ -3206,7 +3206,7 @@ Cañón (desfiladero) Couloir (barranco estrecho y empinado) Área montañosa - Polvo de ladrillo + Polvo de ladrillo (arcilla) Tartán Césped artificial Decoturf (superficie dura para tenis) @@ -3811,7 +3811,7 @@ Contrastado Primitivo Incorrecto - Señal para encontrar el poste + Conexión a Internet: clientes Nivel del agua: parcialmente sumergido Nivel del agua: sumergido Nivel de agua: seco @@ -3852,4 +3852,9 @@ Panal de abejas Frutos secos Gas natural licuado + Apartadero + Cobertizos + Azotea + Punto GPX + Torre de radar \ No newline at end of file diff --git a/OsmAnd/res/values-es-rAR/strings.xml b/OsmAnd/res/values-es-rAR/strings.xml index ad60cd64f0..e29831c83a 100644 --- a/OsmAnd/res/values-es-rAR/strings.xml +++ b/OsmAnd/res/values-es-rAR/strings.xml @@ -3121,22 +3121,24 @@ Error OsmAnd falló la última vez. Por favor, ayúdanos a mejorar OsmAnd compartiendo el mensaje de error. OVNI - • Perfiles de aplicación: crea un perfil personalizado según tus necesidades, con un icono y color personalizado -\n -\n • Ahora puedes personalizar las velocidades predefinidas, máximas y mínimas de cualquier perfil -\n -\n • Se ha añadido un widget para las coordenadas actuales -\n -\n • Se añadieron opciones para mostrar la brújula y una regla radial en el mapa -\n -\n • Se ha corregido la grabación de trazas en segundo plano -\n -\n • Se mejoraron las descargas de mapas en segundo plano -\n -\n • Se ha corregido la selección de idioma de Wikipedia -\n -\n • Se ha corregido el comportamiento de los botones de la brújula durante la navegación -\n + • Perfiles de aplicación: crea un perfil personalizado según tus necesidades, con un icono y color personalizado. +\n +\n • Ahora puedes personalizar las velocidades predefinidas, máximas y mínimas de cualquier perfil +\n +\n • Se ha añadido un widget para las coordenadas actuales +\n +\n • Se añadieron opciones para mostrar la brújula y una regla radial en el mapa +\n +\n • Se ha corregido la grabación de trazas en segundo plano +\n +\n • Se mejoraron las descargas de mapas en segundo plano +\n +\n • Se ha vuelto a añadir la opción «Encender pantalla» +\n +\n • Se ha corregido la selección de idioma de Wikipedia +\n +\n • Se ha corregido el comportamiento de los botones de la brújula durante la navegación +\n \n • Otras correcciones de errores \n \n @@ -3551,7 +3553,7 @@ Guardando el nuevo perfil No se pudo respaldar el perfil. ¿Borrar los datos registrados\? - Importando de datos de «%1$s» + Importando datos de «%1$s» Importando OsmAnd comprueba %1$s para ver si hay duplicados con los elementos existentes en la aplicación. \n @@ -3829,7 +3831,7 @@ Ruta inversa Recalcular sólo el siguiente segmento usando el perfil elegido. Recalcular toda la traza usando el perfil elegido. - Marca cómo conectar los puntos, con una línea recta o calcular una ruta entre ellos como se detalla a continuación. + Elige cómo conectar los puntos, con una línea recta o calcular una ruta entre ellos como se detalla a continuación. Traza completa Siguiente segmento A continuación, ajusta la traza al camino permitido más cercano con un perfil de navegación. @@ -3937,4 +3939,16 @@ Espera el recálculo de la ruta. \nEl gráfico estará disponible después del recálculo. %1$s — %2$s + Mapas locales + Comodidad + Especial + Transporte + Servicio + Símbolos + Deporte + Emergencia + Viaje + MGRS + MGRS + OsmAnd usa MGRS, similar al formato UTM de la OTAN. \ No newline at end of file diff --git a/OsmAnd/res/values-es-rUS/phrases.xml b/OsmAnd/res/values-es-rUS/phrases.xml index 8c0dbc2414..89b147ca4a 100644 --- a/OsmAnd/res/values-es-rUS/phrases.xml +++ b/OsmAnd/res/values-es-rUS/phrases.xml @@ -3568,8 +3568,8 @@ Radioterapia Advertencia de peligro Categoría de dificultad - н/к (sin categoría) - н/к* (sin categoría, posible peligro) + s/c (sin categoría) + s/c* (sin categoría, posible peligro) 1A 1A* 1B @@ -3807,7 +3807,7 @@ Tipo de cabina No - Señal para encontrar el poste + Conexión a Internet: clientes Sólo se permite al caminar Contrastado Primitivo @@ -3852,4 +3852,9 @@ Frutos secos Panal de abejas Gas natural licuado + Torre de radar + Cobertizos + Azotea + Apartadero + Punto GPX \ No newline at end of file diff --git a/OsmAnd/res/values-es-rUS/strings.xml b/OsmAnd/res/values-es-rUS/strings.xml index 8cbb5a9aeb..746343005d 100644 --- a/OsmAnd/res/values-es-rUS/strings.xml +++ b/OsmAnd/res/values-es-rUS/strings.xml @@ -3551,14 +3551,14 @@ Guardando el nuevo perfil No se pudo respaldar el perfil. ¿Borrar los datos registrados\? - Importación de datos de %1$s - Importación + Importando datos de «%1$s» + Importando OsmAnd comprueba %1$s para ver si hay duplicados con los elementos existentes en la aplicación. \n \nPuede llevar algún tiempo. Elementos añadidos Importación completa - Todos los datos de %1$s son importados, puedes usar los botones de abajo para abrir la parte necesaria de la aplicación para manejarla. + Todos los datos de «%1$s» fueron importados. Puedes usar los botones de abajo para gestionar la sección necesaria de la aplicación. Terreno Mapa de sombreado usando sombras oscuras para mostrar las laderas, picos y tierras bajas. La pendiente utiliza colores para visualizar la inclinación del terreno. @@ -3838,7 +3838,7 @@ Recalcular toda la traza usando el perfil elegido. A continuación, ajusta la traza al camino permitido más cercano con un perfil de navegación. Recalcular sólo el siguiente segmento usando el perfil elegido. - Marca cómo conectar los puntos, con una línea recta o calcular una ruta entre ellos como se detalla a continuación. + Elige cómo conectar los puntos, con una línea recta o calcular una ruta entre ellos como se detalla a continuación. Elige un archivo de traza al que se añadirá un nuevo segmento. Ruta inversa Añadir punto de referencia de la traza @@ -3926,4 +3926,24 @@ Desarrollo Navegación bifásica en automóvil. Vaciar llave OAuth de OpenStreetMap + Usar el algoritmo de navegación A* bifásica + Para caminos y senderos exclusivos de motos de nieve. + Mapas locales + Gráfico + MGRS + MGRS + OsmAnd usa MGRS, similar al formato UTM de la OTAN. + %1$s datos disponibles sólo en los caminos, necesitas calcular una ruta usando «Ruta entre puntos» para obtenerla. + Espera el recálculo de la ruta. +\nEl gráfico estará disponible después del recálculo. + %1$s — %2$s + Viaje + Transporte + Símbolos + Deporte + Especial + Servicio + Emergencia + Comodidad + El archivo ya fue importado en OsmAnd \ No newline at end of file diff --git a/OsmAnd/res/values-es/phrases.xml b/OsmAnd/res/values-es/phrases.xml index cac7b4a36d..925fc45bb3 100644 --- a/OsmAnd/res/values-es/phrases.xml +++ b/OsmAnd/res/values-es/phrases.xml @@ -1868,7 +1868,7 @@ 100LL (con plomo, para aviones) Autogas (Etanol libre de plomo) Jet A-1 (diésel) - Líquido de escape de diesel + Aditivo para escapes de Diesel (AdBlue) Combustible: madera Combustible: carbón vegetal Combustible: carbón @@ -3562,8 +3562,8 @@ Radioterapia Advertencia de peligro Categoría de dificultad - н/к (sin categoría) - н/к* (sin categoría, posible peligro) + s/c (sin categoría) + s/c* (sin categoría, posible peligro) 1А* 1B @@ -3808,7 +3808,7 @@ Nivel de agua: parcialmente sumergido Incorrecto Sólo se permite al caminar - Señal para encontrar el poste + Acceso a Internet: clientes Tipo de cabina Cabina Centro de buceo @@ -3845,4 +3845,10 @@ Pequeños electrodomésticos Tablero de salidas Relleno de agua potable + Torre de radar + Apartadero + Cobertizos + Azotea + Punto GPX + Gas natural licuado \ No newline at end of file diff --git a/OsmAnd/res/values-es/strings.xml b/OsmAnd/res/values-es/strings.xml index 6041f2a95e..efe9166106 100644 --- a/OsmAnd/res/values-es/strings.xml +++ b/OsmAnd/res/values-es/strings.xml @@ -609,18 +609,18 @@ Usa mapas en línea (descarga y guarda teselas en la tarjeta de memoria). Mapas en línea Elige las fuentes de teselas de mapas en línea o en caché. - "Accede a muchos tipos de mapas en línea (llamados teselas o ráster), desde teselas predefinidas de OpenStreetMap (como Mapnik) a imágenes satelitales y capas de propósito especial como mapas meteorológicos, mapas del clima, mapas geológicos, capas de sombreado, etc. + Accede a muchos tipos de mapas en línea (llamados teselas o ráster), desde teselas predefinidas de OpenStreetMap (como Mapnik) a imágenes satelitales y capas de propósito especial como mapas meteorológicos, mapas del clima, mapas geológicos, capas de sombreado, etc. \n \nCualquiera de estos mapas puede mostrarse como el mapa (base) principal, o como una superposición o subyacencia de otro mapa base (como los mapas estándar de OsmAnd sin conexión). Ciertos elementos de los mapas vectoriales OsmAnd se pueden ocultar a través del menú «Configurar mapa» para hacer más visible cualquier mapa subyacente. \n -\nDescarga los mapas de teselas directamente a través de fuentes en línea, o prepararlos para tu uso sin conexión (copiándolos manualmente en la carpeta de datos OsmAnd) como una base de datos SQLite que puede generarse con varias herramientas de preparación de mapas de terceros." +\nDescarga los mapas de teselas directamente a través de fuentes en línea, o prepararlos para tu uso sin conexión (copiándolos manualmente en la carpeta de datos OsmAnd) como una base de datos SQLite que puede generarse con varias herramientas de preparación de mapas de terceros. Muestra los ajustes para activar en modo reposo el seguimiento y la navegación, activando periódicamente el dispositivo GPS (con la pantalla apagada). Activa las funciones de accesibilidad del dispositivo directamente en OsmAnd. Facilita por ejemplo, el ajuste de la velocidad de la voz para voces de texto a voz, la configuración de navegación de pantalla con el panel direccional, usando la rueda de desplazamiento para el control del zoom, o la retroalimentación de texto a voz, por ejemplo, para anunciar la ubicación automáticamente. Este complemento muestra los ajustes de funciones de desarrollo y depuración para probar la simulación de rutas, el rendimiento del renderizado, o las indicaciones por voz. Estos ajustes están destinados para los desarrolladores y no son necesarios para el usuario general. Complementos Los complementos activan ajustes avanzados y funcionalidades adicionales. Complementos - Este complemento puede usarse para hacer contribuciones en OSM, como el crear o modificar objetos PDI, abrir o comentar notas de OSM y contribuir con archivos de trazas GPX. OSM es un proyecto de mapeo global impulsado por la comunidad y de dominio público. Para más detalles, consulta https://openstreetmap.org. Se aprecia la participación activa, y las contribuciones pueden hacerse directamente desde OsmAnd, especificando las credenciales personales de OSM en la aplicación. + Haz contribuciones en OSM, como el crear o modificar objetos PDI, abrir o comentar notas de OSM y contribuir con archivos de trazas GPX grabados en OsmAnd, proporcionando tu nombre de usuario y contraseña. OpenStreetMap.org es un proyecto de mapeo de dominio público, global, libre e impulsado por la comunidad. Los mapas vectoriales pueden mostrarse más rápido. Puede no funcionar correctamente en algunos dispositivos. Elige una voz y reproduce pruebas de los avisos: Desarrollo de OsmAnd @@ -890,8 +890,8 @@ Tomar notas de audio/foto/video durante un viaje, usando un botón de mapa o un menú contextual de la ubicación. Notas audio/vídeo Complemento OsmAnd para curvas de nivel sin conexión - Este complemento proporciona una capa superpuesta de curvas de nivel y una capa (de relieve) sombreada, que se pueden visualizar sobre los mapas descargados de OsmAnd. Esta funcionalidad será muy apreciada por atletas, caminantes, excursionistas, y cualquiera interesado en la estructura de relieve de un paisaje. -\n + Proporciona una capa superpuesta de curvas de nivel y una capa (de relieve) sombreada, que se pueden visualizar sobre los mapas descargados de OsmAnd. Esta funcionalidad será muy apreciada por atletas, caminantes, excursionistas, y cualquiera interesado en la estructura de relieve de un paisaje. +\n \nLos datos globales (entre 70° norte y 70° sur) se basan en mediciones de SRTM (Shuttle Radar Topography Mission, o en español Misión de Topografía por Radar desde Transbordador) y ASTER (Advanced Spaceborne Thermal Emission and Reflection Radiometer, o en español Radiómetro Espacial Avanzado de Emisión Térmica y Reflexión), un instrumento de captura de imágenes a bordo de Terra, el satélite insignia del Sistema de Observación de la Tierra de la NASA. ASTER es un esfuerzo cooperativo entre la NASA, el Ministerio de Economía, Comercio e Industria de Japón (METI) y Sistemas Espaciales de Japón (J-spacesystems). Medición de distancia Pulsa «Usar ubicación…» para añadir una nota a la ubicación. @@ -3014,22 +3014,24 @@ Buscando GPS Widget de coordenadas OVNI - • Perfiles de aplicación: crea un perfil personalizado según tus necesidades, con un icono y color personalizado. -\n -\n • Ahora puedes personalizar las velocidades predefinidas, máximas y mínimas de cualquier perfil -\n -\n • Se ha añadido un widget para las coordenadas actuales -\n -\n • Se añadieron opciones para mostrar la brújula y una regla radial en el mapa -\n -\n • Se ha corregido la grabación de trazas en segundo plano -\n -\n • Se mejoraron las descargas de mapas en segundo plano -\n -\n • Se ha corregido la selección de idioma de Wikipedia -\n -\n • Se ha corregido el comportamiento de los botones de la brújula durante la navegación -\n + • Perfiles de aplicación: crea un perfil personalizado según tus necesidades, con un icono y color personalizado. +\n +\n • Ahora puedes personalizar las velocidades predefinidas, máximas y mínimas de cualquier perfil +\n +\n • Se ha añadido un widget para las coordenadas actuales +\n +\n • Se añadieron opciones para mostrar la brújula y una regla radial en el mapa +\n +\n • Se ha corregido la grabación de trazas en segundo plano +\n +\n • Se mejoraron las descargas de mapas en segundo plano +\n +\n • Se ha vuelto a añadir la opción «Encender pantalla» +\n +\n • Se ha corregido la selección de idioma de Wikipedia +\n +\n • Se ha corregido el comportamiento de los botones de la brújula durante la navegación +\n \n • Otras correcciones de errores \n \n @@ -3840,7 +3842,7 @@ Ruta inversa La traza completa será recalculada utilizando el perfil seleccionado. Sólo el siguiente segmento será recalculado usando el perfil seleccionado. - Selecciona cómo conectar puntos, con línea recta, o calcula una ruta entre ellos como se especifica a continuación. + Elija cómo conectar los puntos, en línea recta o calcular una ruta entre ellos como se especifica a continuación. Traza completa Siguiente segmento A continuación, ajusta tu traza a la carretera permitida más cercana con uno de tus perfiles de navegación para usar esta opción. diff --git a/OsmAnd/res/values-et/phrases.xml b/OsmAnd/res/values-et/phrases.xml index 4e06dceeb0..3989f35f69 100644 --- a/OsmAnd/res/values-et/phrases.xml +++ b/OsmAnd/res/values-et/phrases.xml @@ -3831,4 +3831,5 @@ Parkla kiirtee ääres Parkimine varjualustes Parkla katusel + Radarimast \ No newline at end of file diff --git a/OsmAnd/res/values-et/strings.xml b/OsmAnd/res/values-et/strings.xml index 2a0af4fbbf..f1fbad6379 100644 --- a/OsmAnd/res/values-et/strings.xml +++ b/OsmAnd/res/values-et/strings.xml @@ -3795,4 +3795,7 @@ Sümbolid Teenused Transport + OsmAnd kasutab MGRS-vormingut, mis on sarnane NATO UTM-vormingule. + MGRS + MGRS \ No newline at end of file diff --git a/OsmAnd/res/values-fa/phrases.xml b/OsmAnd/res/values-fa/phrases.xml index dd6c374db3..28eb63e08b 100644 --- a/OsmAnd/res/values-fa/phrases.xml +++ b/OsmAnd/res/values-fa/phrases.xml @@ -226,14 +226,14 @@ صندوق بازنشستگی سازمان مهاجرت اداره مالیات - دفتر اجرایی + اداره حکومتی گمرک شهر شهر روستا دهکده سکونتگاه دورافتاده - حومه شهر + منطقه شهری محله محل @@ -346,7 +346,7 @@ اثر هنری مرکز باستان شناسی میدان جنگ - سنگ یادبود + سنگ مرز توپ تاریخی قلعه دروازه شهر @@ -1452,4 +1452,5 @@ دسترسی اتوبوس بله منطقهٔ حفاظت‌شده + بدمینتون \ No newline at end of file diff --git a/OsmAnd/res/values-fa/strings.xml b/OsmAnd/res/values-fa/strings.xml index c7674bb67d..46a00d0720 100644 --- a/OsmAnd/res/values-fa/strings.xml +++ b/OsmAnd/res/values-fa/strings.xml @@ -3482,7 +3482,7 @@ ردهای ضبط‌شدهٔ شما در %1$s یا در پوشهٔ OsmAnd قرار دارند. یادداشت‌های OSMای شما در %1$s قرار دارند. ویرایش‌ها یا یادداشت‌های OSMای خود که هنوز بارگذاری نشده‌اند را در %1$s ببینید. نقاط بارگذاری‌شده را دیگر نمی‌بینید. - لاگ‌های جزئی برنامه را ببینید و هم‌رسانی کنید + لاگ‌های جزئی برنامه را بررسی و هم‌رسانی کنید استفاده از برنامهٔ سیستم صدای شاتر دوربین احراز هویت موفقیت‌آمیز بود @@ -3630,8 +3630,8 @@ زبان همهٔ زبان‌ها تعداد اقلام «کشو»، «پیکربندی نقشه» و «منوی زمینه» را به‌دلخواه تنظیم کنید. -\n -\nافزونه‌های بی‌استفاده را غیرفعال کنید تا همهٔ کنترل‌های آن‌ها از برنامه پنهان شود. +\n +\nافزونه‌های بی‌استفاده را غیرفعال کنید تا همهٔ کنترل‌های آن‌ها از برنامه پنهان شود. %1$s. پنهان‌سازی تنظیمات آن‌ها را به حالت اولیه بازنشانی می‌کند. این اقلام از منو پنهان می‌شوند، اما گزینه‌ها یا افزونه‌های متناظر همچنان کار می‌کنند. پنهان @@ -3942,4 +3942,12 @@ مسیریابی دومرحله‌ای برای ناوبری خودرویی. مسیریابی پیچیده برای مسیریابی حمل‌ونقل عمومی از محاسبات جاوا (امن) استفاده می‌کند + نقشه‌های محلی + ویژه + حمل‌ونقل + خدمات + نمادها + ورزش + اورژانس + سفر \ No newline at end of file diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index 18fd7f8009..621bcc1627 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -3923,4 +3923,7 @@ Sport Urgence Voyage + OsmAnd utilise le système de référence MDRS de l\'OTAN dérivé des formats UTM et UPS. + MGRS + MGRS \ No newline at end of file diff --git a/OsmAnd/res/values-gl/phrases.xml b/OsmAnd/res/values-gl/phrases.xml index b974255f35..b377d6ec35 100644 --- a/OsmAnd/res/values-gl/phrases.xml +++ b/OsmAnd/res/values-gl/phrases.xml @@ -72,14 +72,14 @@ Aparcadoiro de motos Entrada de aparcadoiro Garaxes - Paraxe de transporte público - Paraxe de bus - Paraxe de trolebus - Paraxe do tranvía + Parada de transporte público + Parada de bus + Parada de trolebus + Parada de tranvía Estación de transporte público Entrada de metro Si - Paraxe de taxis + Parada de taxis Aeroporto Heliporto Faro @@ -428,8 +428,8 @@ Metanol Hidróxeno líquido Electricidade - Paraxe de bus - Paraxe do tranvía + Parada de bus + Parada de tranvía Estación de autobuses Estación de trens Terminal dos ferris @@ -1352,7 +1352,7 @@ OV-Chipkaart Cafetaría Tipo - Punto de paraxe do transporte público + Lugar de parada de transporte público Vértice xeodésico Instalación social Asesoramento fiscal @@ -1403,7 +1403,7 @@ Tipo de glaciar Tipo de posto de control Tenda de ultramarinos - Paraxe de trens + Parada de tren Fin da vía Estabelecemento de comidas Boia de instalación @@ -3554,18 +3554,18 @@ Categoría da dificultade Radioterapia Perigo - н/к - н/к* - - 1А* + n/c + n/c* + 1A + 1A* 1B 1B* - - 2А* + 2A + 2A* 2B 2B* - - 3А* + 3A + 3A* 3B 3B* Chama de gas;Queimadura de facho @@ -3793,7 +3793,7 @@ Tipo de cabina Si Non - Sinal para atopar o poste + Acceso á internet: clientes Só é permitido ó camiñar Contrastado Primitivo @@ -3838,4 +3838,8 @@ Taboleiro de saídas Recarga de auga potábel GNL + Apartadoiro + Pendellos + Terrazo + Punto GPX \ No newline at end of file diff --git a/OsmAnd/res/values-gl/strings.xml b/OsmAnd/res/values-gl/strings.xml index 447bdc7bbf..e422eecf62 100644 --- a/OsmAnd/res/values-gl/strings.xml +++ b/OsmAnd/res/values-gl/strings.xml @@ -500,15 +500,15 @@ Deter a música Mapas e navegación OsmAnd Visualización e navegación móbil de mapas globais do OSM con e sen conexión - OsmAnd (Indicacións de navegación automatizadas do OSM) -\n + OsmAnd (Indicacións de navegación automatizadas do OSM) +\n \n O OsmAnd é unha aplicación de navegación de código aberto con acceso a unha ampla variedade de datos globais do OSM. Tódolos datos do mapa (mapas vectoriais ou teselas) poden ser almacenados no cartón de memoria do teléfono móbil para empregalos sen conexión. O OsmAnd tamen fornece a funcionalidade de cálculo de rotas sen conexión, incluíndo a guía por voz paso a paso. \n \n Algunhas das funcións principais: \n - Funcionalidade completa sen conexión (os mapas vectoriais ou de teselas baixados almacénanse no cartafol que se escolla) \n - Mapas vectoriais compactos sen conexión para todo o mundo \n - Pódense baixar mapas de países ou rexións de xeito directo dende a aplicación -\n - É posíbel sobrepor varias capas de mapas, coma pistas de GPX ou de navegación, puntos de interese (PDI), favoritos, curvas do nivel, paraxes de transporte público ou mapas adicionais con transparencia regulábel +\n - É posíbel sobrepor varias capas de mapas, coma pistas de GPX ou de navegación, puntos de interese (PDI), favoritos, curvas do nivel, paradas de transporte público ou mapas adicionais con transparencia regulábel \n - Procura sen conexión dos enderezos e lugares (PDI) \n - Cálculo de rotas sen conexión para distancias medias \n - Modos para automóbil, bicicleta e peóns, opcionalmente con: @@ -517,29 +517,29 @@ \n - Aliñamento do mapa dependendo do compás ou da dirección do movemento \n - Guía de faixas ou carrís da estrada, límites de velocidade, voces gravadas e TTS \n -\n Limitacións desta versión de balde do OsmAnd: -\n - O número de mapas a baixar está limitado -\n - Non hai acceso ós puntos de interese (PDI) da Wikipedia sen conexión -\n +\n Limitacións desta versión de balde do OsmAnd: +\n - O número de mapas a baixar está limitado +\n - Non hai acceso ós puntos de interese (PDI) da Wikipedia sen conexión +\n \n O OsmAnd está a ser desenvolvido activamente e o noso proxecto e o seu progreso futuro depende da contribución financeira para financia-lo desenvolvemento e as probas das funcionalidades novas. Coida a posibilidade de mercar o OsmAnd+ ou de financiar funcións novas específicas ou de facer unha doazón xeral no osmand.net. Mapas e navegación de OsmAnd+ Visualización e navegación móbil de mapas globais para mapas de OSM con e sen conexión - O OsmAnd+ (Indicacións da navegación automatizadas do OSM) é un aplicativo de navegación de código aberto con acceso a unha ampla variedade de datos globais do OSM. Tódolos mapas dos datos (mapas vectoriais ou teselas) poden seren almacenados no cartón de memoria do teléfono para empregalos sen conexión. O OsmAnd tamén fornece funcionalidade de cálculo de rotas sen conexión, incluíndo a guía por voz paso a paso. -\n -\nO OsmAnd+ é a versión de pagamento, mais ó mercala estás a apoiar o proxecto, financiar o desenvolvemento de funcións novas e tes a oportunidade de recibir as últimas actualizacións. -\n -\nAlgunhas das funcións principais son: -\n-Funcionalidade completa sen conexión (os mapas vectoriais ou de teselas baixados almacénanse no cartafol que se escolleu) -\n-Mapas vectoriais compactos sen conexión para todo o mundo. -\n-Pódense baixar mapas de países ou rexións de xeito directo dende a aplicación. -\n-É posíbel sobrepór varias capas de mapas, coma pistas de GPX ou de navegación, puntos de interese (PDI), favoritos, curvas do nivel, paraxes de transporte público ou mapas adicionais con transparencia regulábel. -\n -\n- Procura sen conexión de enderezos e lugares (PDI). -\n-Cálculo de rotas sen conexión para distancias curtas (distancias medias) -\n- Modos para automóbil, bicicleta e peóns, con: -\n-Vistas de noite e día automatizadas opcionais. -\n-Achegamento opcional do mapa dependendo da velocidade -\n-Aliñamento opcional do mapa dependendo do compás ou da dirección do movemento. + O OsmAnd+ (Indicacións da navegación automatizadas do OSM) é un aplicativo de navegación de código aberto con acceso a unha ampla variedade de datos globais do OSM. Tódolos mapas dos datos (mapas vectoriais ou teselas) poden seren almacenados no cartón de memoria do teléfono para empregalos sen conexión. O OsmAnd tamén fornece funcionalidade de cálculo de rotas sen conexión, incluíndo a guía por voz paso a paso. +\n +\nO OsmAnd+ é a versión de pagamento, mais ó mercala estás a apoiar o proxecto, financiar o desenvolvemento de funcións novas e tes a oportunidade de recibir as últimas actualizacións. +\n +\nAlgunhas das funcións principais son: +\n-Funcionalidade completa sen conexión (os mapas vectoriais ou de teselas baixados almacénanse no cartafol que se escolleu) +\n-Mapas vectoriais compactos sen conexión para todo o mundo. +\n-Pódense baixar mapas de países ou rexións de xeito directo dende a aplicación. +\n-É posíbel sobrepór varias capas de mapas, coma pistas de GPX ou de navegación, puntos de interese (PDI), favoritos, curvas do nivel, paradas de transporte público ou mapas adicionais con transparencia regulábel. +\n +\n- Procura sen conexión de enderezos e lugares (PDI). +\n-Cálculo de rotas sen conexión para distancias curtas (distancias medias) +\n- Modos para automóbil, bicicleta e peóns, con: +\n-Vistas de noite e día automatizadas opcionais. +\n-Achegamento opcional do mapa dependendo da velocidade +\n-Aliñamento opcional do mapa dependendo do compás ou da dirección do movemento. \n-Guía opcional de faixas ou carrís da estrada, límites de velocidade, voces gravadas e TTS \n Crear un filtro de PDI @@ -1043,7 +1043,7 @@ Non foi posíbel atopar ficheiros GPX no cartafol de pistas Non foi posíbel ler os datos do GPX. Mapas vectoriais sen conexión - Procurar transporte na paraxe + Procurar transporte na parada Modificar o PDI Eliminar PDI Dirección do compás @@ -1126,19 +1126,19 @@ Engadir unha regra nova Liñas Stop - paraxes + paradas Itinerario posterior Itinerario anterior Rematar a procura - Escolle a paraxe na que baixar + Escolle a parada na que baixar distancia anterior distancia posterior - paraxes a pasar + paradas a pasar Distancia do itinerario Transporte Feito - Amosar paraxes do transporte público no mapa. - Amosar paraxes do transporte + Amosar paradas de transporte público no mapa. + Amosar paradas de transporte OsmAnd, aplicación de navegación Os datos do PDI foron actualizados ({0} foron carregados) Non podes atualizar a listaxe local de PDI. @@ -1986,7 +1986,7 @@ Lon %2$s Empregar o camiño con aforro no combustíbel (normalmente máis curto). Mudar Comezar - %1$s paraxes antes + %1$s paradas antes Inserir o enderezo do destino mediante vibración. Activar a navegación por modificacións do OsmAnd ao Vivo. Non recalcular a ruta ó saírme da mesma @@ -2362,8 +2362,8 @@ Lon %2$s Mapa importado Converter isto no punto de partida Actual - Engade unha paraxe intermedia - Engade a primeira paraxe do percorrido + Engade unha parada intermedia + Engade a primeira parada do percorrido Move o destino cara enriba e crea un novo Amosar/agochar as notas do OSM no mapa. GPX - axeitado para exportar a JOSM ou outros editores do OSM. @@ -2430,7 +2430,7 @@ Lon %2$s Primeiro especificar a cidade, vila ou lugar Restaurar Manter no mapa as marcaxes desbotadas - Máis transportes dispoñíbeis dende esta paraxe. + Máis transportes dispoñíbeis dende esta parada. Para proseguer, permite acceder á localización ó OsmAnd. Grazas polos teus comentarios Non foi posíbel atopar o nó ou a vía. @@ -2757,11 +2757,11 @@ Lon %2$s \n• Olla a velocidade e altitude \n• A opción da gravación GPX, permite gravar o viaxe e partillalo ou compartilo \n• Mediante un plugin adicional, podes activar as curvas de nivel e o asombreado dos outeiros ou colinas - Camiñar, sendeirismo, turismo de cidade -\n • O mapa amosa as rotas para camiñar e de sendeirismo -\n • Wikipedia na lingua de preferencia, pode dicir moito durante unha visita turística -\n • As paraxes do transporte público (autobús, tranvía, tren), incluíndo os nomes de liña, axuda a navigar nunha nova cidade -\n • Navegación GPS no modo peón, constrúe a rota empregando rotas de sendeirismo + Camiñar, sendeirismo, turismo de cidade +\n • O mapa amosa as rotas para camiñar e de sendeirismo +\n • Wikipedia na lingua de preferencia, pode dicir moito durante unha visita turística +\n • As paradas do transporte público (autobús, tranvía, tren), incluíndo os nomes de liña, axuda a navigar nunha nova cidade +\n • Navegación GPS no modo peón, constrúe a rota empregando rotas de sendeirismo \n • Soba e sigua unha rota GPX ou grave e partille ou comparta as súas propias rotas \n Contribúe co OSM @@ -2897,7 +2897,7 @@ Lon %2$s Tódolos cruzamentos (xiro a xiro) Tipos de estrada Saír en - Embarcar na paraxe + Embarcar na parada Trocar Amosar máis Pistas amosadas @@ -3879,7 +3879,7 @@ Lon %2$s Ruta inversa Recalcular toda a pista empregando o perfil escollido. Recalcular só o seguinte segmento empregando o perfil escollido. - Marca de que xeito conectar os puntos, cunha liña recta ou calcular unha ruta entre eles como se detalla deseguido. + Escolle de que xeito conectar os puntos, cunha liña recta ou calcular unha ruta entre eles como se detalla deseguido. Pista enteira Seguinte segmento Deseguido, axusta a pista á estrada permitida máis preto cun perfil de navegación. @@ -3955,4 +3955,20 @@ Lon %2$s Usar algoritmo de enrutamento A* de 2 fases Gráfica %1$s — %2$s + MGRS + MGRS + O OsmAnd emprega MGRS, que é semellante ó formato UTM NATO. + %1$s datos dispoñíbeis só nas estrada, necesitas calcular unha ruta empregando \"Ruta entre puntos\" para obtela. + Agarda polo recálculo da ruta. +\nO gráfico estará dispoñíbel após o recálculo. + Mapas locais + Salto + Instalación + Especial + Transporte + Servizo + Símbolos + Deporte + Emerxencia + Viaxe \ No newline at end of file diff --git a/OsmAnd/res/values-hu/phrases.xml b/OsmAnd/res/values-hu/phrases.xml index 99f11bb034..c20dc74cd4 100644 --- a/OsmAnd/res/values-hu/phrases.xml +++ b/OsmAnd/res/values-hu/phrases.xml @@ -3840,4 +3840,6 @@ Fedett parkolóhely Tető Internetcsatlakozás: ügyfeleknek + Radartorony + GPX-pont \ No newline at end of file diff --git a/OsmAnd/res/values-hu/strings.xml b/OsmAnd/res/values-hu/strings.xml index f3762a3fd1..5d656a709f 100644 --- a/OsmAnd/res/values-hu/strings.xml +++ b/OsmAnd/res/values-hu/strings.xml @@ -3935,4 +3935,7 @@ Sport Vészhelyzet Utazás + MGRS + MGRS + Az OsmAnd a NATO által is használt MGRS (Military Grid Reference System) koordinátákat alkamazza, amely hasonlít az UTM-formátumhoz. \ No newline at end of file diff --git a/OsmAnd/res/values-is/strings.xml b/OsmAnd/res/values-is/strings.xml index 3414adba28..0682b0030e 100644 --- a/OsmAnd/res/values-is/strings.xml +++ b/OsmAnd/res/values-is/strings.xml @@ -106,15 +106,15 @@ Veldu flokk Hraðaeining sml - sjómílur - kílómetrar á klukkustund - mílur á klukkustund - metrar á sekúndu - mínútur á kílómetra - mínútur á mílu + Sjómílur + Kílómetrar á klukkustund + Mílur á klukkustund + Metrar á sekúndu + Mínútur á kílómetra + Mínútur á mílu Sjómílur á klukkustund (hnútar) hnútar - mín/m + mín/ml mín/km m/sek Leiðsögn @@ -238,7 +238,7 @@ Sýna allt Fella saman Birta á korti - Kort + Landakort Eftirlæti Eftirlæti Heimilisfang @@ -2109,7 +2109,7 @@ Nafnið inniheldur of marga hástafi. Viltu halda áfram? Tvítekið nafn á flýtiaðgerð Flokkur til að vista eftirlætisstað í: - Setja inn + Setja upp Ljósmyndir á netinu Engar myndir hér. Breyttu leitarorðum eða stækkaðu radíus leitarinnar. diff --git a/OsmAnd/res/values-it/strings.xml b/OsmAnd/res/values-it/strings.xml index e950feca34..33315de637 100644 --- a/OsmAnd/res/values-it/strings.xml +++ b/OsmAnd/res/values-it/strings.xml @@ -222,7 +222,7 @@ La versione dell\'indice \'\'{0}\'\' non è supportata Il routing offline di OsmAnd è sperimentale e non funziona per distanze superiori ai 20 km. \n -\nNavigazione eseguita temporaneamente tramite servizio online CloudMade. +\nNavigazione temporaneamente spostata sul servizio online CloudMade. Impossibile trovare la cartella specificata. Cartella salvataggio dei dati Tutti i dati offline nella vecchia app verranno supportati dalla nuova, ma i Preferiti devono essere esportati dalla vecchia app e importati nella nuova. @@ -313,13 +313,13 @@ GPS secondi min. - Intervallo di tempo di risveglio usato dal servizio in background: + Intervallo di risveglio usato dal servizio in background: Intervallo di risveglio GPS - Fornitore di posizione utilizzato dal servizio in background: + Metodo di localizzazione utilizzato dal servizio in background: Fornitore di posizionamento Traccia la posizione mentre lo schermo è spento. Esegui OsmAnd in background - Il servizio di navigazione in background di OsmAnd necessita del sistema di posizionamento attivato. + Il servizio di navigazione in background necessita di un sistema di posizionamento attivo. Nascondi filtro Visualizza filtro Filtro @@ -2695,13 +2695,13 @@ Sto cercando l’articolo Wiki corrispondente Articolo non trovato Come aprire gli articoli di Wikipedia? - 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 + 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 Nascondi la descrizione completa Mostra la descrizione completa @@ -2743,12 +2743,12 @@ \n • Condividi la tua posizione in modo tale che i tuoi amici possano trovarti \n Funzioni per pedoni e velocipedi -\n • Mostra percorsi per pedoni, escursionisti e biciclette, ottimo per le attività all’aperto +\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 l’altitudine -\n • Mostra le curve di livello e l’ombreggiatura dei rilievi +\n • Mostra le curve di livello e l’ombreggiatura dei rilievi (attraverso il componente aggiuntivo) Modifica dello stile standard per un maggior contrasto delle strade pedonali e piste ciclabili. Utilizza i colori della versione vecchia di Mapnik. Orario di arrivo intermedio Orario intermedio @@ -3933,4 +3933,16 @@ \nIl grafico sarà disponibile dopo il ricalcolo. %1$s — %2$s Buco + MGRS + MGRS + OsmAnd utilizza MGRS, che è simile al formato UTM NATO. + Mappe locali + Amenità + Speciale + Trasporto + Servizi + Simboli + Sport + Emergenza + Viaggio \ No newline at end of file diff --git a/OsmAnd/res/values-iw/strings.xml b/OsmAnd/res/values-iw/strings.xml index c82e5e3b8d..b27a8291d6 100644 --- a/OsmAnd/res/values-iw/strings.xml +++ b/OsmAnd/res/values-iw/strings.xml @@ -3948,4 +3948,7 @@ ספורט חירום טיול + ב־OsmAnd נעשה שימוש ב־MGRS, שדומה לתצורת UTM NATO. + MGRS + MGRS \ No newline at end of file diff --git a/OsmAnd/res/values-ja/phrases.xml b/OsmAnd/res/values-ja/phrases.xml index 3ddebc7b5a..c4199b91d4 100644 --- a/OsmAnd/res/values-ja/phrases.xml +++ b/OsmAnd/res/values-ja/phrases.xml @@ -361,7 +361,7 @@ 法律事務所 通信会社事務所 NGO・非政府組織 - 町役場・市庁舎 + 役場・庁舎 職業紹介所 研究所 IT関連事務所 @@ -370,7 +370,7 @@ 広告代理店 教育団体事務所 テレビ・ラジオ・録音スタジオ - 賭博場 + ブックメーカー スタジアム 総合スポーツ場 ゴルフ場 @@ -3839,4 +3839,5 @@ 待避所 車庫 屋上 + レーダー塔 \ No newline at end of file diff --git a/OsmAnd/res/values-ja/strings.xml b/OsmAnd/res/values-ja/strings.xml index be920969b3..ab453f2545 100644 --- a/OsmAnd/res/values-ja/strings.xml +++ b/OsmAnd/res/values-ja/strings.xml @@ -827,7 +827,7 @@ POIの更新は利用できません ヘルプ アクセシビリティモード アクセシビリティ(ユーザー補助)機能を設定します - ON + On OFF デバイス側のユーザー補助設定に従う メニューに戻る @@ -1547,7 +1547,7 @@ POIの更新は利用できません マップストレージ コピー OSMの編集を削除 - 無効化 + 無効 ログオフ 国境を越えて別の国に入るようなルート設定を避けます。 高さ制限 @@ -1808,7 +1808,7 @@ POIの更新は利用できません 項目の削除 項目は削除されました すべて元に戻す - タイプ + 種類 出発地点 指定しない レポート @@ -2853,7 +2853,7 @@ POIの更新は利用できません %1$s を利用 道路種別 入れ替え - 詳細を見る + さらに表示 マップ上の経路 GPX経路の表示/非表示 マップ上にある選択したGPX経路の表示/非表示を切り替えるボタンです。 @@ -2898,7 +2898,7 @@ POIの更新は利用できません %1$.2f %2$s 支払い方法の選択: 寄付金はOSMの地図製作に役立てられます。 - 360°イメージのみ表示 + 360°画像のみを表示 編集 %1$s, 合計 %2$s mBTC 起動 グアラニー語 @@ -3048,9 +3048,9 @@ POIの更新は利用できません マップ上に低排出ゾーン(CO2排出量の多い車両に課税するエリア)を表示します。ルーティングには影響しません。 低排出ゾーンの表示 デフォルト - 出口: + 降車 %1$d回の乗り換え - 歩行 + 徒歩 ログの送信 %1$d個のファイルを移動しました。(%2$s) %1$d個のファイルをコピーしました。(%2$s) @@ -3106,12 +3106,12 @@ POIの更新は利用できません 開始時に表示するプロファイルの選択 カスタムプロファイルは標準のアプリケーションプロファイルを元に作成します。ウィジェットなどの表示設定や速度と距離の単位などの標準設定をプロファイルごとに定義可能です。これらは標準のアプリプロファイルを基本としており、今後それらと共に拡張される可能性があります: ナビゲーションタイプの選択 - 乗用車,トラック,オートバイ - マウンテンバイク、モペッド(ペダル付き原動機付自転車)、馬 - 徒歩,ハイキング,ランニング + 乗用車、トラック、オートバイ + マウンテンバイク、モペッド、馬 + 徒歩、ハイキング、ランニング 公共交通機関の種類 - エンジン付き船舶,漕ぎボート,帆船 - 航空機,グライダー + 船舶、漕ぎボート、帆船 + 航空機、グライダー ジオコーディング 直線 BRouter(オフライン) @@ -3206,7 +3206,7 @@ POIの更新は利用できません マップ外観 インストール済みプラグインリスト ナビゲーション設定 - アプリテーマ、ユニット、リージョン + アプリテーマ、単位、地域 プロファイルの構築 プロファイル切替 アプリケーションプロファイルが\"%s\"に切り替えられました @@ -3809,8 +3809,8 @@ POIの更新は利用できません オンラインソースを追加 これらの変更を適用すると、このタイルソースのキャッシュデータがクリアされます 船の高さを設定 - 低い橋を避けるために船の高さを調整できます。ブリッジが可動式の場合は、開いた状態の高さが参照されます。 - 低い橋を避けるために船の高さを設定します。注:ブリッジが可動式の場合は、開いた状態の高さが参照されます。 + 低い橋を避けるために船の高さを調整できます。橋が可動式の場合は、開いた状態の高さが参照されます。 + 低い橋を避けるために船の高さを設定します。注:橋が可動式の場合は、開いた状態の高さが参照されます。 狭い橋を避けるために船の幅を設定します Mapillaryの表示切替 Mapillaryを非表示 diff --git a/OsmAnd/res/values-nl/strings.xml b/OsmAnd/res/values-nl/strings.xml index 129ae33b02..3c40568702 100644 --- a/OsmAnd/res/values-nl/strings.xml +++ b/OsmAnd/res/values-nl/strings.xml @@ -3233,7 +3233,7 @@ \'Freeride\' en \'Off-piste\' zijn officieuze routes en passages. Meestal onverzorgd en niet onderhouden, en niet \'s avonds gecontroleerd. Betreden op eigen risico. Verzamelde data Laatste OsmAnd uitvoering gecrasht. Help ons alstublieft OsmAnd te verbeteren door de foutmelding te delen. - Persoonlijke transporteur + Persoonlijk transport Offroad WunderLINQ Wegtype @@ -3797,11 +3797,11 @@ \n" "• Profielen: nu kunt u de volgorde wijzigen, het pictogram voor de kaart instellen, alle instellingen voor basisprofielen wijzigen en ze terugzetten naar de standaardinstellingen \n -\n • Exitnummer toegevoegd in de navigatie +\n • Exitnummer toegevoegd in de navigatie \n -\n • Herwerkte plug-in instellingen +\n • Herwerkte plug-in instellingen \n -\n • Herwerkt instellingenscherm voor snelle toegang tot alle profielen +\n • Herwerkt instellingenscherm voor snelle toegang tot alle profielen \n \n • Optie toegevoegd om instellingen van een ander profiel te kopiëren \n diff --git a/OsmAnd/res/values-pl/strings.xml b/OsmAnd/res/values-pl/strings.xml index 13ddd2c001..274f6b544a 100644 --- a/OsmAnd/res/values-pl/strings.xml +++ b/OsmAnd/res/values-pl/strings.xml @@ -3675,7 +3675,7 @@ Zakupy w OsmAnd 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. • Nowe mapy stoków offline @@ -3802,7 +3802,7 @@ Przyciski głośności jako powiększenie Wózek inwalidzki Gokart - Zamknięta notatka OSM + Zamknięta uwaga OSM Aby kontynuować, musisz ustawić dni robocze Droga pomiędzy punktami Zaplanuj trasę @@ -3844,7 +3844,7 @@ Czy na pewno chcesz odrzucić wszystkie zmiany w zaplanowanej trasie, zamykając ją\? W przypadku odwrotnego kierunku Przejdź z mojej lokalizacji na trasę - Wózek naprzód + Wózek inwalidzki naprzód Ślady Podążanie za śladem Wybierz plik śladu do śledzenia lub zaimportuj go z urządzenia. @@ -3913,7 +3913,7 @@ Nazwa: A – Z Co nowego Ikony start/koniec - Dziękujemy za zakup linii konturowych + Dziękujemy za zakup \"Linii konturowych\" Subskrypcja naliczona za wybrany okres. Anuluj ją w AppGallery w dowolnym momencie. Płatność zostanie pobrana z konta AppGallery po potwierdzeniu zakupu. \n @@ -3935,4 +3935,21 @@ Wylogowanie powiodło się Plik jest już zaimportowany do OsmAnd %1$s — %2$s + MGRS + MGRS + Użyj 2-fazowego algorytmu routingu A * + Wykres + %1$s dane dostępne tylko na drogach, aby je uzyskać, musisz obliczyć trasę za pomocą opcji „Trasa między punktami”. + Poczekaj na ponowne obliczenie trasy. +\nWykres będzie dostępny po ponownym obliczeniu. + Mapy lokalne + Luka + Udogodnienie + Specjalne + Transport + Usługi + Symbole + Sport + Służby ratunkowe + Podróże \ No newline at end of file diff --git a/OsmAnd/res/values-pt-rBR/phrases.xml b/OsmAnd/res/values-pt-rBR/phrases.xml index 068381b014..f9bb017fe2 100644 --- a/OsmAnd/res/values-pt-rBR/phrases.xml +++ b/OsmAnd/res/values-pt-rBR/phrases.xml @@ -3847,4 +3847,7 @@ GNL Galpões Ponto GPX + Torre de radar + Tapume + Terraço \ No newline at end of file diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index 0d88da8148..8d0920e8af 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -3938,4 +3938,7 @@ Esporte Emergência Viagem + MGRS + MGRS + OsmAnd usa MGRS, que é semelhante ao formato UTM NATO. \ No newline at end of file diff --git a/OsmAnd/res/values-pt/phrases.xml b/OsmAnd/res/values-pt/phrases.xml index 05bf64827a..2386f2f08a 100644 --- a/OsmAnd/res/values-pt/phrases.xml +++ b/OsmAnd/res/values-pt/phrases.xml @@ -3786,7 +3786,7 @@ Primitivo Contrastado Somente quando é permitido caminhar - Sinal para encontrar o poste + Acesso à Internet: clientes Não Sim Tipo de cabine @@ -3830,4 +3830,9 @@ Colmeia Loja de nozes GNL + Barracão + Telhado + Ponto GPX + Torre de radar + Área de repouso \ No newline at end of file diff --git a/OsmAnd/res/values-pt/strings.xml b/OsmAnd/res/values-pt/strings.xml index 23fbc0cbcc..587dd8b707 100644 --- a/OsmAnd/res/values-pt/strings.xml +++ b/OsmAnd/res/values-pt/strings.xml @@ -3829,7 +3829,7 @@ Rota inversa O trilho inteiro será recalculado a usar o perfil selecionado. Somente o próximo segmento será recalculado a usar o perfil selecionado. - Selecione como ligar pontos, por uma linha reta ou a calcular uma rota entre eles como especificado abaixo. + Escolha como ligar os pontos por uma linha reta ou calcule uma rota entre eles como especificado abaixo. Trilho inteiro Próximo segmento Em seguida, encaixe a sua pista na estrada mais próxima permitida com um dos seus perfis de navegação para usar esta opção. @@ -3937,4 +3937,16 @@ \nO gráfico estará disponível após o recalculo. %1$s — %2$s Lacuna + MGRS + MGRS + O OsmAnd utiliza o MGRS, que é semelhante ao formato UTM NATO. + Mapas locais + Amenidade + Especial + Transporte + Serviço + Símbolos + Desporto + Emergência + Viagem \ No newline at end of file diff --git a/OsmAnd/res/values-ru/phrases.xml b/OsmAnd/res/values-ru/phrases.xml index 36b7e66d59..c75c0ac4ef 100644 --- a/OsmAnd/res/values-ru/phrases.xml +++ b/OsmAnd/res/values-ru/phrases.xml @@ -3835,4 +3835,5 @@ СПГ Навесы Точка GPX + Радиолокационная вышка \ No newline at end of file diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index ef0b614441..d873a03ba5 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -3937,4 +3937,8 @@ Спорт Экстренные службы Путешествие + MGRS + MGRS + OsmAnd использует MGRS, который похож на формат UTM NATO. + Развитие местного общественного транспорта \ No newline at end of file diff --git a/OsmAnd/res/values-sc/strings.xml b/OsmAnd/res/values-sc/strings.xml index 8c9aa3a3ec..2245af22f2 100644 --- a/OsmAnd/res/values-sc/strings.xml +++ b/OsmAnd/res/values-sc/strings.xml @@ -3932,4 +3932,11 @@ Iseta su càrculu nou de s\'àndala. \nSu gràficu at a èssere a disponimentu a pustis de su càrculu. %1$s — %2$s + Apretu/Emergèntzia + Ispetziales + Trasportu + Servìtziu + Sìmbulos + Isport + Biàgiu \ No newline at end of file diff --git a/OsmAnd/res/values-sk/strings.xml b/OsmAnd/res/values-sk/strings.xml index 1ae947c024..4ff057ec8f 100644 --- a/OsmAnd/res/values-sk/strings.xml +++ b/OsmAnd/res/values-sk/strings.xml @@ -3944,4 +3944,7 @@ Šport Núdzová situácia/Záchrana Cestovanie + MGRS + MGRS + OsmAnd používa MGSR, ktorý je podobný ako formát UTM NATO. \ No newline at end of file diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index 54debf38a3..3fd6a52de9 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -3783,7 +3783,7 @@ Ters güzergah Tüm yol, seçilen profil kullanılarak yeniden hesaplanacaktır. Sadece sonraki bölüm, seçilen profil kullanılarak yeniden hesaplanacaktır. - Düz bir çizgi ile noktaları nasıl birleştireceğinizi veya aşağıda belirtildiği gibi aralarında nasıl güzergah hesaplayacağınızı seçin. + Düz bir çizgi ile noktaların nasıl birleştirileceğini veya aşağıda belirtildiği gibi aralarında nasıl güzergah hesaplanacağını seçin. Tüm yol Sonraki bölüm Eşik mesafesi @@ -3888,4 +3888,18 @@ %1$s verileri yalnızca yollarda kullanılabilir, elde etmek için “Noktalar arasındaki güzergah” kullanarak bir rota hesaplamanız gerekir. Güzergahın yeniden hesaplanmasını bekleyin. \nGrafik yeniden hesaplandıktan sonra kullanılabilir olacak. + Boşluk + MGRS + MGRS + OsmAnd, UTM NATO biçimine benzer olan MGRS\'yi kullanmaktadır. + Yerel haritalar + %1$s — %2$s + Tesis + Özel + Ulaşım + Hizmet + Semboller + Spor + Acil + Seyahat \ No newline at end of file diff --git a/OsmAnd/res/values-tzm/phrases.xml b/OsmAnd/res/values-tzm/phrases.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/OsmAnd/res/values-tzm/phrases.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/OsmAnd/res/values-tzm/strings.xml b/OsmAnd/res/values-tzm/strings.xml index a6b3daec93..1ae1b1ba84 100644 --- a/OsmAnd/res/values-tzm/strings.xml +++ b/OsmAnd/res/values-tzm/strings.xml @@ -1,2 +1,22 @@ - \ No newline at end of file + + Takaṛḍa tadɣarant + %1$s — %2$s + Tanafut + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/values-uk/phrases.xml b/OsmAnd/res/values-uk/phrases.xml index 0faed8c3ad..e3387de728 100644 --- a/OsmAnd/res/values-uk/phrases.xml +++ b/OsmAnd/res/values-uk/phrases.xml @@ -3837,4 +3837,5 @@ Навіси Дах Точка GPX + Радіолокаційна вежа \ No newline at end of file diff --git a/OsmAnd/res/values-uk/strings.xml b/OsmAnd/res/values-uk/strings.xml index f75ae53dfc..1e88f7ad89 100644 --- a/OsmAnd/res/values-uk/strings.xml +++ b/OsmAnd/res/values-uk/strings.xml @@ -3940,4 +3940,7 @@ Спорт Аварійні служби Мандрівка + MGRS + MGRS + OsmAnd використовує MGRS, який подібний формату UTM NATO. \ No newline at end of file diff --git a/OsmAnd/res/values-zh-rTW/phrases.xml b/OsmAnd/res/values-zh-rTW/phrases.xml index 10415b42a8..8244b4067d 100644 --- a/OsmAnd/res/values-zh-rTW/phrases.xml +++ b/OsmAnd/res/values-zh-rTW/phrases.xml @@ -3848,4 +3848,5 @@ 車棚 屋頂 GPX 點 + 雷達塔 \ No newline at end of file diff --git a/OsmAnd/res/values-zh-rTW/strings.xml b/OsmAnd/res/values-zh-rTW/strings.xml index 980ed0c113..9e00acd9a5 100644 --- a/OsmAnd/res/values-zh-rTW/strings.xml +++ b/OsmAnd/res/values-zh-rTW/strings.xml @@ -3939,4 +3939,7 @@ 運動 警急 旅行 + MGRS + MGRS + OsmAnd 使用 MGRS,其類似於 UTM NATO 格式。 \ No newline at end of file diff --git a/OsmAnd/res/values/attrs.xml b/OsmAnd/res/values/attrs.xml index 5ffd847b3a..8e8c8aee32 100644 --- a/OsmAnd/res/values/attrs.xml +++ b/OsmAnd/res/values/attrs.xml @@ -19,6 +19,7 @@ + diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index 04b193b923..0ca39e1de6 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -4265,4 +4265,6 @@ Sheds Layby + Radar tower + diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 7861393b31..c0d8bb8721 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,12 @@ Thx - Hardy --> + OsmAnd Live subscription is on hold + OsmAnd Live subscription has been paused + OsmAnd Live subscription has been expired + There is a problem with your subscription. Click the button to go to the Google Play subscription settings to fix your payment method. + Manage subscription + You must add at least two points. Travel Emergency Sport diff --git a/OsmAnd/res/values/styles.xml b/OsmAnd/res/values/styles.xml index ea305b19d2..d96c5fa196 100644 --- a/OsmAnd/res/values/styles.xml +++ b/OsmAnd/res/values/styles.xml @@ -92,6 +92,7 @@ @style/Widget.Styled.ActionBarLight @color/list_background_color_light @drawable/circle_background_light + @drawable/circle_contour_bg_light @drawable/btn_round_light @drawable/btn_round_border_light @drawable/btn_round_border_light_2 @@ -394,6 +395,7 @@ @style/Widget.Styled.ActionBarDark @color/list_background_color_dark @drawable/circle_background_dark + @drawable/circle_contour_bg_dark @drawable/btn_round_dark @drawable/btn_round_border_dark @drawable/btn_round_border_dark_2 diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index 5bd9f2cb23..d5ff6fcedf 100644 --- a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -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 getAllOwnedSubscriptionSkus() { @@ -304,6 +308,15 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } } } + for (Entry 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 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 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 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)); diff --git a/OsmAnd/src/net/osmand/plus/Version.java b/OsmAnd/src/net/osmand/plus/Version.java index 48dd9b1feb..8e23518613 100644 --- a/OsmAnd/src/net/osmand/plus/Version.java +++ b/OsmAnd/src/net/osmand/plus/Version.java @@ -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) { diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 8cb6e832c5..2d33be5f82 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -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 { diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveCancelledDialog.java b/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveCancelledDialog.java deleted file mode 100644 index a62ffcd296..0000000000 --- a/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveCancelledDialog.java +++ /dev/null @@ -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 firstTimeShown = app.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN; - OsmandPreference 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); - } - } -} diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveGoneDialog.java b/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveGoneDialog.java new file mode 100644 index 0000000000..fdbce9a076 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveGoneDialog.java @@ -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 firstTimeShownTime = app.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME; + OsmandPreference 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); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/dialogs/FavoriteDialogs.java b/OsmAnd/src/net/osmand/plus/dialogs/FavoriteDialogs.java index b6817fab05..0690952bdd 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/FavoriteDialogs.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/FavoriteDialogs.java @@ -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(); diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java b/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java index 95631c9c8c..b516d73d2c 100644 --- a/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java +++ b/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java @@ -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()); diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java b/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java index 7e0aadfa3b..3355d67a32 100644 --- a/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java +++ b/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java @@ -138,10 +138,10 @@ public class DownloadIndexesThread { String setTts = null; for (String s : OsmandSettings.TTS_AVAILABLE_VOICES) { if (lng.startsWith(s)) { - setTts = s + "-tts"; + setTts = s + IndexConstants.VOICE_PROVIDER_SUFFIX; break; } else if (lng.contains("," + s)) { - setTts = s + "-tts"; + setTts = s + IndexConstants.VOICE_PROVIDER_SUFFIX; } } if (setTts != null) { @@ -544,7 +544,7 @@ public class DownloadIndexesThread { // validate enough space if (asz != -1 && cs > asz) { String breakDownloadMessage = app.getString(R.string.download_files_not_enough_space, - cs, asz); + String.valueOf(cs), String.valueOf(asz)); publishProgress(breakDownloadMessage); return false; } diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java b/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java index 47b3015d56..f6381b3552 100644 --- a/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java +++ b/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java @@ -8,9 +8,7 @@ import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.zip.GZIPInputStream; import net.osmand.AndroidUtils; @@ -159,10 +157,13 @@ public class DownloadOsmandIndexesHelper { List mapping = getBundledAssets(amanager); for (AssetEntry asset : mapping) { String target = asset.destination; - if (target.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) && target.startsWith("voice/") && target.contains("-tts")) { - String lang = target.substring("voice/".length(), target.indexOf("-tts")); - File destFile = new File(voicePath, target.substring("voice/".length(), - target.indexOf("/", "voice/".length())) + "/" + lang + "_tts.js"); + if (target.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) + && target.startsWith(IndexConstants.VOICE_INDEX_DIR) + && target.contains(IndexConstants.VOICE_PROVIDER_SUFFIX)) { + String lang = target.substring(IndexConstants.VOICE_INDEX_DIR.length(), + target.indexOf(IndexConstants.VOICE_PROVIDER_SUFFIX)); + File destFile = new File(voicePath, target.substring(IndexConstants.VOICE_INDEX_DIR.length(), + target.indexOf("/", IndexConstants.VOICE_INDEX_DIR.length())) + "/" + lang + "_tts.js"); result.add(new AssetIndexItem(lang + "_" + IndexConstants.TTSVOICE_INDEX_EXT_JS, "voice", date, dateModified, "0.1", destFile.length(), asset.source, destFile.getPath(), DownloadActivityType.VOICE_FILE)); diff --git a/OsmAnd/src/net/osmand/plus/download/IndexItem.java b/OsmAnd/src/net/osmand/plus/download/IndexItem.java index 7173dfca66..650ab0679b 100644 --- a/OsmAnd/src/net/osmand/plus/download/IndexItem.java +++ b/OsmAnd/src/net/osmand/plus/download/IndexItem.java @@ -143,9 +143,9 @@ public class IndexItem implements Comparable { 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(); } diff --git a/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java b/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java index 2fb12e2ea4..4cf4f1154a 100644 --- a/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java +++ b/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java @@ -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 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() { diff --git a/OsmAnd/src/net/osmand/plus/helpers/CustomBarChartRenderer.java b/OsmAnd/src/net/osmand/plus/helpers/CustomBarChartRenderer.java new file mode 100644 index 0000000000..3321399dbf --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/helpers/CustomBarChartRenderer.java @@ -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; + } +} diff --git a/OsmAnd/src/net/osmand/plus/helpers/FileNameTranslationHelper.java b/OsmAnd/src/net/osmand/plus/helpers/FileNameTranslationHelper.java index 539eceb622..e366d1e8bb 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/FileNameTranslationHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/FileNameTranslationHelper.java @@ -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){ @@ -85,10 +93,10 @@ public class FileNameTranslationHelper { public static String getVoiceName(Context ctx, String fileName) { try { String nm = fileName.replace('-', '_').replace(' ', '_'); - if (nm.endsWith("_tts") || nm.endsWith("-tts")) { + if (nm.endsWith("_tts") || nm.endsWith(IndexConstants.VOICE_PROVIDER_SUFFIX)) { nm = nm.substring(0, nm.length() - 4); } - Field f = R.string.class.getField("lang_"+nm); + Field f = R.string.class.getField("lang_" + nm); if (f != null) { Integer in = (Integer) f.get(null); return ctx.getString(in); diff --git a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java index 6e9e61d4dd..2b91e0a6b7 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java @@ -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 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 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 { diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index 543c3a819d..c7464c6b1f 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -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 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 { + private class RequestInventoryTask extends AsyncTask { RequestInventoryTask() { } @Override - protected String doInBackground(Void... params) { + protected String[] doInBackground(Void... params) { try { Map 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 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); diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java index 5004e97165..7cc9a8fbee 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java @@ -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 expiredSubscriptions = new ArrayList<>(); + for (InAppSubscription s : getAllSubscriptions()) { + if (s.getState().isGone()) { + expiredSubscriptions.add(s); + } + } + Collections.sort(expiredSubscriptions, new Comparator() { + @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; diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java index c3da090ef9..26c81f03a7 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java @@ -495,7 +495,7 @@ public abstract class ImageCard extends AbstractCard { } } } catch (Exception e) { - e.printStackTrace(); + LOG.error(e); } if (listener != null) { listener.onPostProcess(result); diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java index e8adf8e088..e59ce02373 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java @@ -22,6 +22,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -52,6 +53,10 @@ import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.ColorDialogs; import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener; +import net.osmand.plus.track.ColorsCard; +import net.osmand.plus.track.CustomColorBottomSheet.ColorPickerListener; import net.osmand.plus.widgets.FlowLayout; import net.osmand.util.Algorithms; @@ -72,7 +77,7 @@ import static net.osmand.data.FavouritePoint.DEFAULT_UI_ICON_ID; import static net.osmand.plus.FavouritesDbHelper.FavoriteGroup.PERSONAL_CATEGORY; import static net.osmand.plus.FavouritesDbHelper.FavoriteGroup.isPersonalCategoryDisplayName; -public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { +public abstract class PointEditorFragmentNew extends BaseOsmAndFragment implements ColorPickerListener, CardListener { public static final String TAG = PointEditorFragmentNew.class.getSimpleName(); @@ -101,6 +106,7 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { private EditText descriptionEdit; private EditText addressEdit; private int layoutHeightPrevious = 0; + private ColorsCard colorsCard; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -458,48 +464,49 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { } private void createColorSelector() { - FlowLayout selectColor = view.findViewById(R.id.select_color); - for (int color : ColorDialogs.pallette) { - selectColor.addView(createColorItemView(color, selectColor), new FlowLayout.LayoutParams(0, 0)); - } - int customColor = getPointColor(); - if (!ColorDialogs.isPaletteColor(customColor)) { - selectColor.addView(createColorItemView(customColor, selectColor), new FlowLayout.LayoutParams(0, 0)); + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + List colors = new ArrayList<>(); + for (int color : ColorDialogs.pallette) { + colors.add(color); + } + int customColor = getPointColor(); + if (!ColorDialogs.isPaletteColor(customColor)) { + colors.add(customColor); + } + colorsCard = new ColorsCard(mapActivity, selectedColor, this, colors); + colorsCard.setListener(this); + LinearLayout selectColor = view.findViewById(R.id.select_color); + selectColor.addView(colorsCard.build(view.getContext())); } } - private View createColorItemView(@ColorInt final int color, final FlowLayout rootView) { - FrameLayout colorItemView = (FrameLayout) UiUtilities.getInflater(getContext(), nightMode) - .inflate(R.layout.point_editor_button, rootView, false); - ImageView outline = colorItemView.findViewById(R.id.outline); - outline.setImageDrawable( - UiUtilities.tintDrawable(AppCompatResources.getDrawable(app, R.drawable.bg_point_circle_contour), - ContextCompat.getColor(app, - nightMode ? R.color.stroked_buttons_and_links_outline_dark - : R.color.stroked_buttons_and_links_outline_light))); - ImageView backgroundCircle = colorItemView.findViewById(R.id.background); - backgroundCircle.setImageDrawable(UiUtilities.tintDrawable(AppCompatResources.getDrawable(app, R.drawable.bg_point_circle), color)); - backgroundCircle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - updateColorSelector(color, rootView); - } - }); - colorItemView.setTag(color); - return colorItemView; + @Override + public void onColorSelected(Integer prevColor, int newColor) { + colorsCard.onColorSelected(prevColor, newColor); + int color = colorsCard.getSelectedColor(); + updateColorSelector(color, view); + } + + @Override + public void onCardLayoutNeeded(@NonNull BaseCard card) { + + } + + @Override + public void onCardPressed(@NonNull BaseCard card) { + if (card instanceof ColorsCard) { + int color = ((ColorsCard) card).getSelectedColor(); + updateColorSelector(color, view); + } + } + + @Override + public void onCardButtonPressed(@NonNull BaseCard card, int buttonIndex) { + } private void updateColorSelector(int color, View rootView) { - View oldColor = rootView.findViewWithTag(selectedColor); - if (oldColor != null) { - oldColor.findViewById(R.id.outline).setVisibility(View.INVISIBLE); - ImageView icon = oldColor.findViewById(R.id.icon); - icon.setImageDrawable(UiUtilities.tintDrawable(icon.getDrawable(), R.color.icon_color_default_light)); - } - View newColor = rootView.findViewWithTag(color); - if (newColor != null) { - newColor.findViewById(R.id.outline).setVisibility(View.VISIBLE); - } ((TextView) view.findViewById(R.id.color_name)).setText(ColorDialogs.getColorName(color)); selectedColor = color; setColor(color); @@ -935,7 +942,7 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { } } - private void exitEditing() { + public void exitEditing() { cancelled = true; dismiss(); } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/TrackDetailsMenu.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/TrackDetailsMenu.java index 1e441071a4..a45bfb27e8 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/TrackDetailsMenu.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/TrackDetailsMenu.java @@ -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 getXAxisPoints(LineChart chart) { float[] entries = chart.getXAxis().mEntries; LineData lineData = chart.getLineData(); diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/GraphsCard.java b/OsmAnd/src/net/osmand/plus/measurementtool/GraphsCard.java index 6b55bbffdd..a0f74d34db 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/GraphsCard.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/GraphsCard.java @@ -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 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 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 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 routeSegments = editingCtx.getAllRouteSegments(); - List routeStatistics = calculateRouteStatistics(routeSegments); + List 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 getDataSets(CommonGraphType type, LineChart chart, GPXTrackAnalysis analysis) { - List dataSets = new ArrayList<>(); - if (chart != null && analysis != null) { - OsmandApplication app = getMyApplication(); - switch (type) { - case OVERVIEW: { - List 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() { - @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 calculateRouteStatistics(List route) { + private List calculateRouteStatistics() { OsmandApplication app = getMyApplication(); - if (route == null || app == null) return null; - return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode); + List 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 { + 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 { + + 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 dataSets = GpxUiHelper.getDataSets(commonGraphAdapter.getChart(), + app, analysis, firstType, secondType, false); + return !Algorithms.isEmpty(dataSets) ? new LineData(dataSets) : null; + } + } + + private class CustomGraphType extends GraphType { + + 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; } } } diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java index 91a1d6e163..5d18f52669 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java @@ -398,6 +398,10 @@ public class MeasurementEditingContext { return before.points.size(); } + public boolean isPointsEnoughToCalculateRoute() { + return getPointsCount() >= 2; + } + public List getAllRouteSegments() { List allSegments = new ArrayList<>(); for (Pair key : getOrderedRoadSegmentDataKeys()) { diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index 3212ef60b8..16c8e2309c 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -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; diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java b/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java index 9d14598856..f3b55473bc 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java @@ -24,7 +24,9 @@ public class PointsCard extends BaseCard implements OnUpdateAdditionalInfoListen @Override public void onUpdateAdditionalInfo() { - adapter.notifyDataSetChanged(); + if (adapter != null) { + adapter.notifyDataSetChanged(); + } } @Override diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/graph/BaseGraphAdapter.java b/OsmAnd/src/net/osmand/plus/measurementtool/graph/BaseGraphAdapter.java new file mode 100644 index 0000000000..5f9e1fdc29 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/measurementtool/graph/BaseGraphAdapter.java @@ -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(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/graph/CommonGraphAdapter.java b/OsmAnd/src/net/osmand/plus/measurementtool/graph/CommonGraphAdapter.java new file mode 100644 index 0000000000..2b4a2bb479 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/measurementtool/graph/CommonGraphAdapter.java @@ -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 { + + private Highlight highlight; + private Map 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; + } +} diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/graph/CustomGraphAdapter.java b/OsmAnd/src/net/osmand/plus/measurementtool/graph/CustomGraphAdapter.java new file mode 100644 index 0000000000..1bc763b165 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/measurementtool/graph/CustomGraphAdapter.java @@ -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 { + + 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 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 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 getSegmentsList() { + return getStatistics() != null ? new ArrayList<>(getStatistics().partition.values()) : null; + } + + private RouteStatistics getStatistics() { + return additionalData; + } +} diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/graph/GraphAdapterHelper.java b/OsmAnd/src/net/osmand/plus/measurementtool/graph/GraphAdapterHelper.java new file mode 100644 index 0000000000..94b8a45d24 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/measurementtool/graph/GraphAdapterHelper.java @@ -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 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); + } +} diff --git a/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java index ce3151920d..e53b51cf39 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java @@ -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 getDataSets(GPXTabItemType tabType, LineChart chart) { + private List getDataSets(LineChart chart, + GPXTabItemType tabType, + LineGraphType firstType, + LineGraphType secondType) { List 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 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 ds = getDataSets(tabType, null); + List 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++) { diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java index ae123b2c8b..a87d5004bd 100644 --- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java +++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java @@ -413,7 +413,7 @@ public class ResourceManager { java.text.DateFormat dateFormat = getDateFormat(); for (File f : lf) { if (f.isDirectory()) { - String lang = f.getName().replace("-tts", ""); + String lang = f.getName().replace(IndexConstants.VOICE_PROVIDER_SUFFIX, ""); File conf = new File(f, lang + "_" + IndexConstants.TTSVOICE_INDEX_EXT_JS); if (!conf.exists()) { conf = new File(f, "_config.p"); @@ -454,9 +454,10 @@ public class ResourceManager { if (appPath.canWrite()) { for (AssetEntry asset : assets) { File jsFile = new File(appPath, asset.destination); - if (asset.destination.contains("-tts") && asset.destination + if (asset.destination.contains(IndexConstants.VOICE_PROVIDER_SUFFIX) && asset.destination .endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) { - File oggFile = new File(appPath, asset.destination.replace("-tts", "")); + File oggFile = new File(appPath, asset.destination.replace( + IndexConstants.VOICE_PROVIDER_SUFFIX, "")); if (oggFile.getParentFile().exists() && !oggFile.exists()) { copyAssets(context.getAssets(), asset.source, oggFile); } diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java index 1f29dfd1b7..780186b068 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java @@ -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 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 getRouteInfoCardsGraphAdapters() { + List adapters = new ArrayList<>(); + for (RouteInfoCard card : routeInfoCards) { + BaseGraphAdapter adapter = card.getGraphAdapter(); + if (adapter != null) { + adapters.add(adapter); + } + } + return adapters; + } + public static List calculateRouteStatistics(OsmandApplication app, List 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; - } - } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/BaseCard.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/BaseCard.java index 245412fe34..d4101e58ec 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/BaseCard.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/BaseCard.java @@ -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) { diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteInfoCard.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteInfoCard.java index 01f894cf93..124687f2f7 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteInfoCard.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteInfoCard.java @@ -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; public class RouteInfoCard extends BaseCard { - - private static final int MINIMUM_CONTRAST_RATIO = 3; - - 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 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 partition = routeStatistics.partition; - List> 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 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; } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteStatisticCard.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteStatisticCard.java index ef37797f0a..e25c242469 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteStatisticCard.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/RouteStatisticCard.java @@ -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 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); diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java index 591e5ae55b..91d5493217 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java @@ -14,5 +14,7 @@ public enum ExportSettingsType { OSM_NOTES, OSM_EDITS, OFFLINE_MAPS, - FAVORITES + FAVORITES, + TTS_VOICE, + VOICE } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java index 4c91964f0d..d24dd23ee6 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java @@ -1104,9 +1104,8 @@ public class OsmandSettings { public final OsmandPreference BILLING_PURCHASE_TOKEN_SENT = new BooleanPreference(this, "billing_purchase_token_sent", false).makeGlobal(); public final OsmandPreference BILLING_PURCHASE_TOKENS_SENT = new StringPreference(this, "billing_purchase_tokens_sent", "").makeGlobal(); public final OsmandPreference LIVE_UPDATES_PURCHASED = new BooleanPreference(this, "billing_live_updates_purchased", false).makeGlobal(); - public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_TIME = new LongPreference(this, "live_updates_purchase_cancelled_time", 0).makeGlobal(); - public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_first_dlg_shown", false).makeGlobal(); - public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_second_dlg_shown", false).makeGlobal(); + public final OsmandPreference LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_first_dlg_shown_time", 0).makeGlobal(); + public final OsmandPreference LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_second_dlg_shown_time", 0).makeGlobal(); public final OsmandPreference FULL_VERSION_PURCHASED = new BooleanPreference(this, "billing_full_version_purchased", false).makeGlobal(); public final OsmandPreference DEPTH_CONTOURS_PURCHASED = new BooleanPreference(this, "billing_sea_depth_purchased", false).makeGlobal(); public final OsmandPreference CONTOUR_LINES_PURCHASED = new BooleanPreference(this, "billing_srtm_purchased", false).makeGlobal(); @@ -2543,7 +2542,7 @@ public class OsmandSettings { Configuration config = ctx.getResources().getConfiguration(); for (String lang : TTS_AVAILABLE_VOICES) { if (lang.equals(config.locale.getLanguage())) { - return lang + "-tts"; + return lang + IndexConstants.VOICE_PROVIDER_SUFFIX; } } return "en-tts"; diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java index d68325fe5f..d040f20411 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java @@ -64,7 +64,7 @@ public class DataSettingsItem extends StreamSettingsItem { SettingsItemReader getReader() { return new StreamSettingsItemReader(this) { @Override - public void readFromStream(@NonNull InputStream inputStream) 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]; diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FavoritesSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FavoritesSettingsItem.java index 8e8c4c6f36..817ec38a49 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FavoritesSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FavoritesSettingsItem.java @@ -145,7 +145,7 @@ public class FavoritesSettingsItem extends CollectionSettingsItem return new SettingsItemReader(this) { @Override - public void readFromStream(@NonNull InputStream inputStream) 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()))); diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java index 097e64b0d2..563a04fc75 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java @@ -1,5 +1,6 @@ package net.osmand.plus.settings.backend.backup; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -18,35 +19,38 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +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), - 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; + return this == OBF_MAP || this == WIKI_MAP || this == SRTM_MAP || this == TILES_MAP || this == ROAD_MAP; } public String getSubtypeName() { @@ -57,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)) { @@ -66,6 +75,11 @@ public class FileSettingsItem extends StreamSettingsItem { return null; } + public static FileSubtype getSubtypeByPath(@NonNull OsmandApplication app, @NonNull String fileName) { + fileName = fileName.replace(app.getAppPath(null).getPath(), ""); + return getSubtypeByFileName(fileName); + } + public static FileSubtype getSubtypeByFileName(@NonNull String fileName) { String name = fileName; if (fileName.startsWith(File.separator)) { @@ -91,6 +105,11 @@ public class FileSettingsItem extends StreamSettingsItem { return subtype; } break; + case TTS_VOICE: + if (name.startsWith(subtype.subtypeFolder) && name.endsWith(IndexConstants.VOICE_PROVIDER_SUFFIX)) { + return subtype; + } + break; default: if (name.startsWith(subtype.subtypeFolder)) { return subtype; @@ -109,7 +128,7 @@ public class FileSettingsItem extends StreamSettingsItem { } protected File file; - private File appPath; + private final File appPath; protected FileSubtype subtype; private long size; @@ -191,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) { @@ -213,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; } } } @@ -231,13 +268,17 @@ public class FileSettingsItem extends StreamSettingsItem { SettingsItemReader getReader() { return new StreamSettingsItemReader(this) { @Override - public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + public void readFromStream(@NonNull InputStream inputStream, String entryName) throws IOException, IllegalArgumentException { OutputStream output; - File dest = FileSettingsItem.this.file; + 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); @@ -258,12 +299,42 @@ public class FileSettingsItem extends StreamSettingsItem { @Nullable @Override public SettingsItemWriter getWriter() { - 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); + 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); + } + return super.getWriter(); + } else { + return new StreamSettingsItemWriter(this) { + + @Override + public void writeEntry(String fileName, @NonNull ZipOutputStream zos) throws IOException { + writeDirWithFiles(file, 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); + } + } + } + }; } - return super.getWriter(); } } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java index 0267e69471..1fd27e192f 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java @@ -29,7 +29,7 @@ public abstract class OsmandSettingsItemReader ext @NonNull JSONObject json) throws JSONException; @Override - public void readFromStream(@NonNull InputStream inputStream) 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")); diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java index ccc23b5416..38c637e0c7 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java @@ -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 items; private Map 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 writer = item.getWriter(); if (writer != null) { @@ -67,17 +78,24 @@ class SettingsExporter { if (Algorithms.isEmpty(fileName)) { fileName = item.getDefaultFileName(); } - ZipEntry entry = new ZipEntry(fileName); - zos.putNextEntry(entry); - writer.writeToStream(zos); - zos.closeEntry(); + 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 param : additionalParams.entrySet()) { json.put(param.getKey(), param.getValue()); } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java index 7d79ef3481..9c47589674 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java @@ -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; @@ -23,6 +25,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; @@ -34,6 +37,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; @@ -42,6 +46,8 @@ 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; @@ -49,6 +55,7 @@ import java.util.Set; import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; import static net.osmand.plus.activities.LocalIndexHelper.LocalIndexType; +import static net.osmand.plus.settings.backend.backup.FileSettingsItem.FileSubtype; /* Usage: @@ -105,6 +112,8 @@ public class SettingsHelper { public interface SettingsExportListener { void onSettingsExportFinished(@NonNull File file, boolean succeed); + + void onSettingsExportProgressUpdate(int value); } public enum ImportType { @@ -133,6 +142,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); } @@ -194,11 +211,15 @@ public class SettingsHelper { } } - @SuppressLint("StaticFieldLeak") - private class ExportAsyncTask extends AsyncTask { + public interface ExportProgressListener { + void updateProgress(int value); + } + + @SuppressLint("StaticFieldLeak") + public class ExportAsyncTask extends AsyncTask { - private SettingsExporter exporter; private File file; + private SettingsExporter exporter; private SettingsExportListener listener; ExportAsyncTask(@NonNull File settingsFile, @@ -206,7 +227,7 @@ public class SettingsHelper { @NonNull List 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); } @@ -225,6 +246,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); @@ -232,6 +260,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") @@ -532,28 +575,48 @@ public class SettingsHelper { dataList.put(ExportSettingsType.OSM_EDITS, editsPointList); } } - List files = getLocalMapFiles(); - if (!files.isEmpty()) { - dataList.put(ExportSettingsType.OFFLINE_MAPS, files); - } List favoriteGroups = app.getFavorites().getFavoriteGroups(); if (!favoriteGroups.isEmpty()) { dataList.put(ExportSettingsType.FAVORITES, favoriteGroups); } + List localIndexInfoList = getLocalIndexData(); + List 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); + if (!files.isEmpty()) { + dataList.put(ExportSettingsType.TTS_VOICE, files); + } + files = getFilesByType(localIndexInfoList, LocalIndexType.VOICE_DATA); + if (!files.isEmpty()) { + dataList.put(ExportSettingsType.VOICE, files); + } return dataList; } - private List getLocalMapFiles() { - List files = new ArrayList<>(); - LocalIndexHelper helper = new LocalIndexHelper(app); - List localMapFileList = helper.getLocalIndexData(new AbstractLoadLocalIndexTask() { + private List getLocalIndexData() { + return new LocalIndexHelper(app).getLocalIndexData(new AbstractLoadLocalIndexTask() { @Override public void loadFile(LocalIndexInfo... loaded) { } }); - for (LocalIndexInfo map : localMapFileList) { + } + + private List getFilesByType(List localVoiceFileList, LocalIndexType... localIndexType) { + List files = new ArrayList<>(); + for (LocalIndexInfo map : localVoiceFileList) { File file = new File(map.getPathToData()); - if (file != null && file.exists() && map.getType() != LocalIndexType.TTS_VOICE_DATA) { + boolean filtered = false; + for (LocalIndexType type : localIndexType) { + if (map.getType() == type) { + filtered = true; + break; + } + } + if (file.exists() && filtered) { files.add(file); } } @@ -638,6 +701,8 @@ public class SettingsHelper { List renderFilesList = new ArrayList<>(); List multimediaFilesList = new ArrayList<>(); List tracksFilesList = new ArrayList<>(); + List ttsVoiceFilesList = new ArrayList<>(); + List voiceFilesList = new ArrayList<>(); List mapFilesList = new ArrayList<>(); List avoidRoads = new ArrayList<>(); List globalSettingsItems = new ArrayList<>(); @@ -652,19 +717,20 @@ public class SettingsHelper { break; case FILE: FileSettingsItem fileItem = (FileSettingsItem) item; - if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { + if (fileItem.getSubtype() == FileSubtype.RENDERING_STYLE) { renderFilesList.add(fileItem.getFile()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { + } else if (fileItem.getSubtype() == FileSubtype.ROUTING_CONFIG) { routingFilesList.add(fileItem.getFile()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.MULTIMEDIA_NOTES) { + } else if (fileItem.getSubtype() == FileSubtype.MULTIMEDIA_NOTES) { multimediaFilesList.add(fileItem.getFile()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.GPX) { + } else if (fileItem.getSubtype() == FileSubtype.GPX) { tracksFilesList.add(fileItem.getFile()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.OBF_MAP - || fileItem.getSubtype() == FileSettingsItem.FileSubtype.WIKI_MAP - || fileItem.getSubtype() == FileSettingsItem.FileSubtype.SRTM_MAP - || fileItem.getSubtype() == FileSettingsItem.FileSubtype.TILES_MAP) { + } else if (fileItem.getSubtype().isMap()) { mapFilesList.add(fileItem); + } else if (fileItem.getSubtype() == FileSubtype.TTS_VOICE) { + ttsVoiceFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSubtype.VOICE) { + voiceFilesList.add(fileItem.getFile()); } break; case QUICK_ACTIONS: @@ -769,6 +835,26 @@ public class SettingsHelper { if (!favoriteGroups.isEmpty()) { settingsToOperate.put(ExportSettingsType.FAVORITES, favoriteGroups); } + if (!ttsVoiceFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.TTS_VOICE, ttsVoiceFilesList); + } + if (!voiceFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.VOICE, voiceFilesList); + } return settingsToOperate; } + + private void sortLocalFiles(List files) { + final Collator collator = OsmAndCollator.primaryCollator(); + Collections.sort(files, new Comparator() { + @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()); + } + }); + } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java index a03f363542..7f72761066 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java @@ -25,7 +25,7 @@ import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; class SettingsImporter { - private OsmandApplication app; + private final OsmandApplication app; SettingsImporter(@NonNull OsmandApplication app) { this.app = app; @@ -124,7 +124,7 @@ class SettingsImporter { try { SettingsItemReader reader = item.getReader(); if (reader != null) { - reader.readFromStream(ois); + reader.readFromStream(ois, fileName); } } catch (IllegalArgumentException e) { item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java index c80eb4cec2..afe776db09 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java @@ -12,6 +12,7 @@ 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; @@ -88,7 +89,7 @@ public abstract class SettingsItem { public boolean applyFileName(@NonNull String fileName) { String n = getFileName(); - return n != null && n.endsWith(fileName); + return n != null && (n.endsWith(fileName) || fileName.startsWith(n + File.separator)); } public boolean shouldReadOnCollecting() { @@ -168,7 +169,7 @@ public abstract class SettingsItem { SettingsItemReader getJsonReader() { return new SettingsItemReader(this) { @Override - public void readFromStream(@NonNull InputStream inputStream) 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")); diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java index daa66f979a..12af0ee3c5 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java @@ -13,5 +13,5 @@ public abstract class SettingsItemReader { this.item = item; } - public abstract void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException; + public abstract void readFromStream(@NonNull InputStream inputStream, String entryName) throws IOException, IllegalArgumentException; } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java index 9e3cf61377..090767a493 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java @@ -4,6 +4,8 @@ import androidx.annotation.NonNull; import java.io.IOException; import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; public abstract class SettingsItemWriter { @@ -18,4 +20,11 @@ public abstract class SettingsItemWriter { } public abstract boolean writeToStream(@NonNull OutputStream outputStream) throws IOException; + + public void writeEntry(String fileName, @NonNull ZipOutputStream zos) throws IOException { + ZipEntry entry = new ZipEntry(fileName); + zos.putNextEntry(entry); + writeToStream(zos); + zos.closeEntry(); + } } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java index d90f4b7429..efda7de984 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java @@ -15,6 +15,7 @@ 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; @@ -34,6 +35,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 { private static final Log LOG = PlatformUtil.getLog(DuplicatesSettingsAdapter.class.getName()); @@ -75,12 +78,11 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter> 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 items = prepareSettingsItemsForExport(); + progress.setMax(getMaxProgress(items)); + app.getSettingsHelper().exportSettings(tempDir, fileName, getSettingsExportListener(), items, true); } } + private int getMaxProgress(List 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.setTitle(app.getString(R.string.export_profile)); + 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(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java index 8fafa4b4c0..14d89803e2 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java @@ -17,6 +17,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import androidx.core.view.ViewCompat; import androidx.core.widget.NestedScrollView; +import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -25,19 +26,21 @@ import com.google.android.material.appbar.CollapsingToolbarLayout; import net.osmand.AndroidUtils; import net.osmand.map.ITileSource; -import net.osmand.plus.AppInitializer; import net.osmand.plus.FavouritesDbHelper.FavoriteGroup; import net.osmand.plus.OsmandApplication; 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; import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportAsyncTask; import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportType; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsImportListener; import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.view.ComplexButton; @@ -45,14 +48,11 @@ 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; -public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View.OnClickListener { +public class ImportDuplicatesFragment extends BaseOsmAndFragment { public static final String TAG = ImportDuplicatesFragment.class.getSimpleName(); private OsmandApplication app; @@ -69,8 +69,9 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View private SettingsHelper settingsHelper; public static void showInstance(@NonNull FragmentManager fm, List duplicatesList, - List settingsItems, File file) { + List settingsItems, File file, Fragment targetFragment) { ImportDuplicatesFragment fragment = new ImportDuplicatesFragment(); + fragment.setTargetFragment(targetFragment, 0); fragment.setDuplicatesList(duplicatesList); fragment.setSettingsItems(settingsItems); fragment.setFile(file); @@ -97,7 +98,11 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View if (file == null) { file = importTask.getFile(); } - importTask.setImportListener(getImportListener()); + Fragment target = getTargetFragment(); + if (target instanceof ImportSettingsFragment) { + SettingsImportListener importListener = ((ImportSettingsFragment) target).getImportListener(); + importTask.setImportListener(importListener); + } } } @@ -125,8 +130,18 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View ? getResources().getColor(R.color.active_buttons_and_links_text_dark) : getResources().getColor(R.color.active_buttons_and_links_text_light)) ); - keepBothBtn.setOnClickListener(this); - replaceAllBtn.setOnClickListener(this); + keepBothBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + importItems(false); + } + }); + replaceAllBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + importItems(true); + } + }); list = root.findViewById(R.id.list); ViewCompat.setNestedScrollingEnabled(list, false); ViewTreeObserver treeObserver = buttonsContainer.getViewTreeObserver(); @@ -180,6 +195,11 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View List trackFilesList = new ArrayList<>(); List avoidRoads = new ArrayList<>(); List favoriteGroups = new ArrayList<>(); + List osmNotesPointList = new ArrayList<>(); + List osmEditsPointList = new ArrayList<>(); + List ttsVoiceFilesList = new ArrayList<>(); + List voiceFilesList = new ArrayList<>(); + List mapFilesList = new ArrayList<>(); for (Object object : duplicatesList) { if (object instanceof ApplicationMode.ApplicationModeBean) { @@ -192,19 +212,30 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View 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()) { @@ -247,21 +278,27 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View duplicates.add(getString(R.string.shared_string_favorites)); duplicates.addAll(favoriteGroups); } - return duplicates; - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.keep_both_btn: { - importItems(false); - break; - } - case R.id.replace_all_btn: { - importItems(true); - break; - } + 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; } @Override @@ -275,7 +312,11 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View for (SettingsItem item : settingsItems) { item.setShouldReplace(shouldReplace); } - settingsHelper.importSettings(file, settingsItems, "", 1, getImportListener()); + Fragment target = getTargetFragment(); + if (target instanceof ImportSettingsFragment) { + SettingsImportListener importListener = ((ImportSettingsFragment) target).getImportListener(); + settingsHelper.importSettings(file, settingsItems, "", 1, importListener); + } } } @@ -290,22 +331,6 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View buttonsContainer.setVisibility(View.GONE); } - private SettingsHelper.SettingsImportListener getImportListener() { - return new SettingsHelper.SettingsImportListener() { - @Override - public void onSettingsImportFinished(boolean succeed, @NonNull List items) { - if (succeed) { - app.getRendererRegistry().updateExternalRenderers(); - AppInitializer.loadRoutingFiles(app, null); - FragmentManager fm = getFragmentManager(); - if (fm != null && file != null) { - ImportCompleteFragment.showInstance(fm, items, file.getName()); - } - } - } - }; - } - private void setupToolbar(Toolbar toolbar) { toolbar.setTitle(R.string.import_duplicates_title); toolbar.setNavigationIcon(getPaintedContentIcon( diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java index 5cffbf5898..f9960db436 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java @@ -74,8 +74,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class ImportSettingsFragment extends BaseOsmAndFragment - implements View.OnClickListener { +public class ImportSettingsFragment extends BaseOsmAndFragment { public static final String TAG = ImportSettingsFragment.class.getSimpleName(); public static final Log LOG = PlatformUtil.getLog(ImportSettingsFragment.class.getSimpleName()); @@ -138,12 +137,28 @@ public class ImportSettingsFragment extends BaseOsmAndFragment progressBar = root.findViewById(R.id.progress_bar); setupToolbar(toolbar); ViewCompat.setNestedScrollingEnabled(expandableList, true); - View header = inflater.inflate(R.layout.list_item_description_header, null); + View header = inflater.inflate(R.layout.list_item_description_header, container, false); description = header.findViewById(R.id.description); description.setText(R.string.select_data_to_import); expandableList.addHeaderView(header); - continueBtn.setOnClickListener(this); - selectBtn.setOnClickListener(this); + continueBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (adapter.getData().isEmpty()) { + app.showShortToastMessage(getString(R.string.shared_string_nothing_selected)); + } else { + importItems(); + } + } + }); + selectBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + allSelected = !allSelected; + selectBtn.setText(allSelected ? R.string.shared_string_deselect_all : R.string.shared_string_select_all); + adapter.selectAll(allSelected); + } + }); if (Build.VERSION.SDK_INT >= 21) { AndroidUtils.addStatusBarPadding21v(app, root); } @@ -225,26 +240,6 @@ public class ImportSettingsFragment extends BaseOsmAndFragment } } - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.select_button: { - allSelected = !allSelected; - selectBtn.setText(allSelected ? R.string.shared_string_deselect_all : R.string.shared_string_select_all); - adapter.selectAll(allSelected); - break; - } - case R.id.continue_button: { - if (adapter.getData().isEmpty()) { - app.showShortToastMessage(getString(R.string.shared_string_nothing_selected)); - } else { - importItems(); - } - break; - } - } - } - private void updateUi(int toolbarTitleRes, int descriptionRes) { if (file != null) { String fileName = file.getName(); @@ -268,15 +263,15 @@ public class ImportSettingsFragment extends BaseOsmAndFragment } } - private SettingsHelper.SettingsImportListener getImportListener() { + public SettingsHelper.SettingsImportListener getImportListener() { return new SettingsHelper.SettingsImportListener() { @Override public void onSettingsImportFinished(boolean succeed, @NonNull List items) { - FragmentManager fm = getFragmentManager(); if (succeed) { app.getRendererRegistry().updateExternalRenderers(); AppInitializer.loadRoutingFiles(app, null); reloadIndexes(items); + FragmentManager fm = getFragmentManager(); if (fm != null && file != null) { ImportCompleteFragment.showInstance(fm, items, file.getName()); } @@ -351,7 +346,7 @@ public class ImportSettingsFragment extends BaseOsmAndFragment } settingsHelper.importSettings(file, items, "", 1, getImportListener()); } else if (fm != null && !isStateSaved()) { - ImportDuplicatesFragment.showInstance(fm, duplicates, items, file); + ImportDuplicatesFragment.showInstance(fm, duplicates, items, file, this); } } } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java index d50b00ae6f..3eec01930b 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java @@ -120,7 +120,7 @@ public class ImportedSettingsItemsAdapter extends break; case OSM_EDITS: holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_info_dark, activeColorRes)); - holder.title.setText(R.string.osm_edit_modified_poi); + holder.title.setText(R.string.osm_edits); break; case FAVORITES: holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_favorite, activeColorRes)); @@ -128,7 +128,15 @@ public class ImportedSettingsItemsAdapter extends break; case OFFLINE_MAPS: holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_map, activeColorRes)); - holder.title.setText(R.string.shared_string_local_maps); + holder.title.setText(R.string.shared_string_maps); + break; + case TTS_VOICE: + holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_volume_up, activeColorRes)); + holder.title.setText(R.string.local_indexes_cat_tts); + break; + case VOICE: + holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_volume_up, activeColorRes)); + holder.title.setText(R.string.local_indexes_cat_voice); break; case GLOBAL: holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_settings, activeColorRes)); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java index 1bdbc38b19..bc3092d963 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java @@ -735,6 +735,11 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment { app.showToastMessage(R.string.profile_backup_failed); } } + + @Override + public void onSettingsExportProgressUpdate(int value) { + + } }; } return exportListener; diff --git a/OsmAnd/src/net/osmand/plus/track/ColorsCard.java b/OsmAnd/src/net/osmand/plus/track/ColorsCard.java new file mode 100644 index 0000000000..9fee9408c4 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/track/ColorsCard.java @@ -0,0 +1,244 @@ +package net.osmand.plus.track; + +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.ColorUtils; +import androidx.fragment.app.Fragment; + +import com.google.android.material.internal.FlowLayout; + +import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.track.CustomColorBottomSheet.ColorPickerListener; +import net.osmand.util.Algorithms; + +import org.apache.commons.logging.Log; + +import java.util.ArrayList; +import java.util.List; + +public class ColorsCard extends BaseCard implements ColorPickerListener { + + public static final int MAX_CUSTOM_COLORS = 6; + public static final int MINIMUM_CONTRAST_RATIO = 3; + + private static final Log log = PlatformUtil.getLog(TrackColoringCard.class); + + public static final int INVALID_VALUE = -1; + + private Fragment targetFragment; + + private List colors; + private List customColors; + + private int selectedColor; + + @Override + public int getCardLayoutId() { + return R.layout.colors_card; + } + + public ColorsCard(MapActivity mapActivity, int selectedColor, Fragment targetFragment, List colors) { + super(mapActivity); + this.targetFragment = targetFragment; + this.selectedColor = selectedColor; + this.colors = colors; + customColors = getCustomColors(app); + } + + public int getSelectedColor() { + return selectedColor; + } + + public void setSelectedColor(int selectedColor) { + this.selectedColor = selectedColor; + } + + @Override + public void onColorSelected(Integer prevColor, int newColor) { + if (prevColor != null) { + int index = customColors.indexOf(prevColor); + if (index != INVALID_VALUE) { + customColors.set(index, newColor); + } + if (selectedColor == prevColor) { + selectedColor = newColor; + } + } else if (customColors.size() < MAX_CUSTOM_COLORS) { + customColors.add(newColor); + selectedColor = newColor; + } + saveCustomColors(); + updateContent(); + } + + @Override + protected void updateContent() { + createColorSelector(); + updateColorSelector(selectedColor, view); + } + + private void createColorSelector() { + FlowLayout selectColor = view.findViewById(R.id.select_color); + selectColor.removeAllViews(); + + for (int color : customColors) { + selectColor.addView(createColorItemView(color, selectColor, true)); + } + if (customColors.size() < 6) { + selectColor.addView(createAddCustomColorItemView(selectColor)); + } + selectColor.addView(createDividerView(selectColor)); + + for (int color : colors) { + selectColor.addView(createColorItemView(color, selectColor, false)); + } + updateColorSelector(selectedColor, selectColor); + } + + private void updateColorSelector(int color, View rootView) { + View oldColor = rootView.findViewWithTag(selectedColor); + if (oldColor != null) { + oldColor.findViewById(R.id.outline).setVisibility(View.INVISIBLE); + ImageView icon = oldColor.findViewById(R.id.icon); + icon.setImageDrawable(UiUtilities.tintDrawable(icon.getDrawable(), R.color.icon_color_default_light)); + } + View newColor = rootView.findViewWithTag(color); + if (newColor != null) { + newColor.findViewById(R.id.outline).setVisibility(View.VISIBLE); + } + } + + private View createColorItemView(@ColorInt final int color, final FlowLayout rootView, boolean customColor) { + View colorItemView = createCircleView(rootView); + + ImageView backgroundCircle = colorItemView.findViewById(R.id.background); + + Drawable transparencyIcon = getTransparencyIcon(app, color); + Drawable colorIcon = app.getUIUtilities().getPaintedIcon(R.drawable.bg_point_circle, color); + Drawable layeredIcon = UiUtilities.getLayeredIcon(transparencyIcon, colorIcon); + double contrastRatio = ColorUtils.calculateContrast(color, ContextCompat.getColor(app, nightMode ? R.color.card_and_list_background_dark : R.color.card_and_list_background_light)); + if (contrastRatio < MINIMUM_CONTRAST_RATIO) { + backgroundCircle.setBackgroundResource(nightMode ? R.drawable.circle_contour_bg_dark : R.drawable.circle_contour_bg_light); + } + backgroundCircle.setImageDrawable(layeredIcon); + backgroundCircle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + updateColorSelector(color, rootView); + selectedColor = color; + + CardListener listener = getListener(); + if (listener != null) { + listener.onCardPressed(ColorsCard.this); + } + } + }); + if (customColor) { + backgroundCircle.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + CustomColorBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), targetFragment, color); + } + return false; + } + }); + } + colorItemView.setTag(color); + return colorItemView; + } + + private View createAddCustomColorItemView(FlowLayout rootView) { + View colorItemView = createCircleView(rootView); + ImageView backgroundCircle = colorItemView.findViewById(R.id.background); + + int bgColorId = nightMode ? R.color.activity_background_color_dark : R.color.activity_background_color_light; + Drawable backgroundIcon = app.getUIUtilities().getIcon(R.drawable.bg_point_circle, bgColorId); + + ImageView icon = colorItemView.findViewById(R.id.icon); + icon.setVisibility(View.VISIBLE); + int activeColorResId = nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light; + icon.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_plus, activeColorResId)); + + backgroundCircle.setImageDrawable(backgroundIcon); + backgroundCircle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + CustomColorBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), targetFragment, null); + } + } + }); + return colorItemView; + } + + private View createDividerView(FlowLayout rootView) { + LayoutInflater themedInflater = UiUtilities.getInflater(view.getContext(), nightMode); + View divider = themedInflater.inflate(R.layout.simple_divider_item, rootView, false); + + LinearLayout dividerContainer = new LinearLayout(view.getContext()); + dividerContainer.addView(divider); + dividerContainer.setPadding(0, AndroidUtils.dpToPx(app, 1), 0, AndroidUtils.dpToPx(app, 5)); + + return dividerContainer; + } + + private View createCircleView(ViewGroup rootView) { + LayoutInflater themedInflater = UiUtilities.getInflater(view.getContext(), nightMode); + View circleView = themedInflater.inflate(R.layout.point_editor_button, rootView, false); + ImageView outline = circleView.findViewById(R.id.outline); + int colorId = nightMode ? R.color.stroked_buttons_and_links_outline_dark : R.color.stroked_buttons_and_links_outline_light; + Drawable contourIcon = app.getUIUtilities().getIcon(R.drawable.bg_point_circle_contour, colorId); + outline.setImageDrawable(contourIcon); + return circleView; + } + + private Drawable getTransparencyIcon(OsmandApplication app, @ColorInt int color) { + int colorWithoutAlpha = UiUtilities.removeAlpha(color); + int transparencyColor = UiUtilities.getColorWithAlpha(colorWithoutAlpha, 0.8f); + return app.getUIUtilities().getPaintedIcon(R.drawable.ic_bg_transparency, transparencyColor); + } + + public static List getCustomColors(@NonNull OsmandApplication app) { + List colors = new ArrayList<>(); + List colorNames = app.getSettings().CUSTOM_TRACK_COLORS.getStringsList(); + if (colorNames != null) { + for (String colorHex : colorNames) { + try { + if (!Algorithms.isEmpty(colorHex)) { + int color = Algorithms.parseColor(colorHex); + colors.add(color); + } + } catch (IllegalArgumentException e) { + log.error(e); + } + } + } + return colors; + } + + private void saveCustomColors() { + List colorNames = new ArrayList<>(); + for (Integer color : customColors) { + String colorHex = Algorithms.colorToString(color); + colorNames.add(colorHex); + } + app.getSettings().CUSTOM_TRACK_COLORS.setStringsList(colorNames); + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java index 0c7cfce551..f8a9ebbfc3 100644 --- a/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java @@ -38,6 +38,8 @@ import net.osmand.plus.UiUtilities.DialogButtonType; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.base.ContextMenuScrollFragment; import net.osmand.plus.dialogs.GpxAppearanceAdapter; +import net.osmand.plus.dialogs.GpxAppearanceAdapter.AppearanceListItem; +import net.osmand.plus.dialogs.GpxAppearanceAdapter.GpxAppearanceAdapterType; import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener; @@ -58,6 +60,7 @@ import static net.osmand.plus.activities.TrackActivity.TRACK_FILE_NAME; import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR; import static net.osmand.plus.dialogs.GpxAppearanceAdapter.TRACK_WIDTH_BOLD; import static net.osmand.plus.dialogs.GpxAppearanceAdapter.TRACK_WIDTH_MEDIUM; +import static net.osmand.plus.dialogs.GpxAppearanceAdapter.getAppearanceItems; public class TrackAppearanceFragment extends ContextMenuScrollFragment implements CardListener, ColorPickerListener { @@ -80,6 +83,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement private TrackWidthCard trackWidthCard; private SplitIntervalCard splitIntervalCard; private TrackColoringCard trackColoringCard; + private ColorsCard colorsCard; private boolean showStartFinishIconsInitialValue; private ImageView trackIcon; @@ -339,7 +343,9 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement if (mapActivity != null) { if (card instanceof SplitIntervalCard) { SplitIntervalBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), trackDrawInfo, this); - } else if (card instanceof TrackColoringCard) { + } else if (card instanceof ColorsCard) { + int color = ((ColorsCard) card).getSelectedColor(); + trackDrawInfo.setColor(color); updateColorItems(); } else if (card instanceof TrackWidthCard) { updateAppearanceIcon(); @@ -356,7 +362,15 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement @Override public void onColorSelected(Integer prevColor, int newColor) { - trackColoringCard.onColorSelected(prevColor, newColor); + if (prevColor != null) { + List customColors = ColorsCard.getCustomColors(app); + int index = customColors.indexOf(prevColor); + if (index != ColorsCard.INVALID_VALUE) { + saveCustomColorsToTracks(prevColor, newColor); + } + } + trackDrawInfo.setColor(newColor); + colorsCard.onColorSelected(prevColor, newColor); updateColorItems(); } @@ -475,6 +489,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement AndroidUiHelper.updateVisibility(saveButton, true); AndroidUiHelper.updateVisibility(view.findViewById(R.id.buttons_divider), true); } + private void showShadowButton() { buttonsShadow.setVisibility(View.VISIBLE); buttonsShadow.animate() @@ -511,11 +526,30 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement AndroidUiHelper.updateVisibility(buttonsShadow, scrollToBottomAvailable); } + private void saveCustomColorsToTracks(int prevColor, int newColor) { + List gpxDataItems = app.getGpxDbHelper().getItems(); + for (GpxDataItem dataItem : gpxDataItems) { + if (prevColor == dataItem.getColor()) { + app.getGpxDbHelper().updateColor(dataItem, newColor); + } + } + List files = app.getSelectedGpxHelper().getSelectedGPXFiles(); + for (SelectedGpxFile selectedGpxFile : files) { + if (prevColor == selectedGpxFile.getGpxFile().getColor(0)) { + selectedGpxFile.getGpxFile().setColor(newColor); + } + } + } + private void updateColorItems() { updateAppearanceIcon(); if (trackWidthCard != null) { trackWidthCard.updateItems(); } + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + mapActivity.refreshMap(); + } } private void saveTrackInfo() { @@ -627,6 +661,8 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement trackColoringCard.setListener(this); cardsContainer.addView(trackColoringCard.build(mapActivity)); + setupColorsCard(cardsContainer); + trackWidthCard = new TrackWidthCard(mapActivity, trackDrawInfo, new OnNeedScrollListener() { @Override @@ -647,6 +683,26 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement } } + private void setupColorsCard(ViewGroup cardsContainer) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + List colors = getTrackColors(); + colorsCard = new ColorsCard(mapActivity, trackDrawInfo.getColor(), this, colors); + colorsCard.setListener(this); + cardsContainer.addView(colorsCard.build(mapActivity)); + } + } + + private List getTrackColors() { + List colors = new ArrayList<>(); + for (AppearanceListItem appearanceListItem : getAppearanceItems(app, GpxAppearanceAdapterType.TRACK_COLOR)) { + if (!colors.contains(appearanceListItem.getColor())) { + colors.add(appearanceListItem.getColor()); + } + } + return colors; + } + public List getGpxDisplayGroups() { GPXFile gpxFile = selectedGpxFile.getGpxFile(); if (gpxFile == null) { diff --git a/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java b/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java index 99571dad6c..0a17f5e06b 100644 --- a/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java +++ b/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java @@ -1,53 +1,36 @@ package net.osmand.plus.track; -import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; 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.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.appcompat.content.res.AppCompatResources; import androidx.core.content.ContextCompat; -import androidx.core.graphics.ColorUtils; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.internal.FlowLayout; - import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; -import net.osmand.plus.GPXDatabase.GpxDataItem; -import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; -import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.dialogs.GpxAppearanceAdapter; -import net.osmand.plus.dialogs.GpxAppearanceAdapter.AppearanceListItem; -import net.osmand.plus.dialogs.GpxAppearanceAdapter.GpxAppearanceAdapterType; import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.routepreparationmenu.cards.BaseCard; -import net.osmand.plus.track.CustomColorBottomSheet.ColorPickerListener; -import net.osmand.util.Algorithms; import org.apache.commons.logging.Log; import java.util.ArrayList; import java.util.List; -public class TrackColoringCard extends BaseCard implements ColorPickerListener { +public class TrackColoringCard extends BaseCard { private static final int MINIMUM_CONTRAST_RATIO = 3; - public static final int INVALID_VALUE = -1; - private final static String SOLID_COLOR = "solid_color"; private static final Log log = PlatformUtil.getLog(TrackColoringCard.class); @@ -57,7 +40,6 @@ public class TrackColoringCard extends BaseCard implements ColorPickerListener { private TrackAppearanceItem selectedAppearanceItem; private List appearanceItems; - private List customColors; private Fragment target; public TrackColoringCard(MapActivity mapActivity, TrackDrawInfo trackDrawInfo, Fragment target) { @@ -65,7 +47,6 @@ public class TrackColoringCard extends BaseCard implements ColorPickerListener { this.target = target; this.trackDrawInfo = trackDrawInfo; appearanceItems = getGradientAppearanceItems(); - customColors = getCustomColors(); } @Override @@ -76,8 +57,6 @@ public class TrackColoringCard extends BaseCard implements ColorPickerListener { @Override protected void updateContent() { updateHeader(); - createColorSelector(); - updateColorSelector(); // coloringAdapter = new TrackColoringAdapter(appearanceItems); // RecyclerView groupRecyclerView = view.findViewById(R.id.recycler_view); @@ -87,25 +66,6 @@ public class TrackColoringCard extends BaseCard implements ColorPickerListener { AndroidUiHelper.updateVisibility(view.findViewById(R.id.top_divider), isShowDivider()); } - private List getCustomColors() { - List colors = new ArrayList<>(); - List colorNames = app.getSettings().CUSTOM_TRACK_COLORS.getStringsList(); - if (colorNames != null) { - for (String colorHex : colorNames) { - try { - if (!Algorithms.isEmpty(colorHex)) { - int color = Algorithms.parseColor(colorHex); - colors.add(color); - } - } catch (IllegalArgumentException e) { - log.error(e); - } - } - } - - return colors; - } - private List getGradientAppearanceItems() { List items = new ArrayList<>(); items.add(new TrackAppearanceItem(SOLID_COLOR, app.getString(R.string.track_coloring_solid), R.drawable.ic_action_circle)); @@ -117,138 +77,6 @@ public class TrackColoringCard extends BaseCard implements ColorPickerListener { return items; } - private void createColorSelector() { - FlowLayout selectColor = view.findViewById(R.id.select_color); - selectColor.removeAllViews(); - - for (int color : customColors) { - selectColor.addView(createColorItemView(color, selectColor, true)); - } - if (customColors.size() < 6) { - selectColor.addView(createAddCustomColorItemView(selectColor)); - } - selectColor.addView(createDividerView(selectColor)); - - List colors = new ArrayList<>(); - for (AppearanceListItem appearanceListItem : GpxAppearanceAdapter.getAppearanceItems(app, GpxAppearanceAdapterType.TRACK_COLOR)) { - if (!colors.contains(appearanceListItem.getColor())) { - colors.add(appearanceListItem.getColor()); - } - } - for (int color : colors) { - selectColor.addView(createColorItemView(color, selectColor, false)); - } - updateColorSelector(trackDrawInfo.getColor(), selectColor); - } - - private View createColorItemView(@ColorInt final int color, final FlowLayout rootView, boolean customColor) { - View colorItemView = createCircleView(rootView); - - ImageView backgroundCircle = colorItemView.findViewById(R.id.background); - - Drawable transparencyIcon = getTransparencyIcon(app, color); - Drawable colorIcon = app.getUIUtilities().getPaintedIcon(R.drawable.bg_point_circle, color); - Drawable layeredIcon = UiUtilities.getLayeredIcon(transparencyIcon, colorIcon); - double contrastRatio = ColorUtils.calculateContrast(color, ContextCompat.getColor(app, nightMode ? R.color.card_and_list_background_dark : R.color.card_and_list_background_light)); - if (contrastRatio < MINIMUM_CONTRAST_RATIO) { - backgroundCircle.setBackgroundResource(nightMode ? R.drawable.circle_contour_bg_dark : R.drawable.circle_contour_bg_light); - } - backgroundCircle.setImageDrawable(layeredIcon); - backgroundCircle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - updateColorSelector(color, rootView); -// coloringAdapter.notifyDataSetChanged(); - trackDrawInfo.setColor(color); - - CardListener listener = getListener(); - if (listener != null) { - listener.onCardPressed(TrackColoringCard.this); - } - } - }); - if (customColor) { - backgroundCircle.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - CustomColorBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), target, color); - } - return false; - } - }); - } - colorItemView.setTag(color); - return colorItemView; - } - - private Drawable getTransparencyIcon(OsmandApplication app, @ColorInt int color) { - int colorWithoutAlpha = UiUtilities.removeAlpha(color); - int transparencyColor = UiUtilities.getColorWithAlpha(colorWithoutAlpha, 0.8f); - return app.getUIUtilities().getPaintedIcon(R.drawable.ic_bg_transparency, transparencyColor); - } - - private View createAddCustomColorItemView(FlowLayout rootView) { - View colorItemView = createCircleView(rootView); - ImageView backgroundCircle = colorItemView.findViewById(R.id.background); - - int bgColorId = nightMode ? R.color.activity_background_color_dark : R.color.activity_background_color_light; - Drawable backgroundIcon = app.getUIUtilities().getIcon(R.drawable.bg_point_circle, bgColorId); - - ImageView icon = colorItemView.findViewById(R.id.icon); - icon.setVisibility(View.VISIBLE); - int activeColorResId = nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light; - icon.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_plus, activeColorResId)); - - backgroundCircle.setImageDrawable(backgroundIcon); - backgroundCircle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - CustomColorBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), target, null); - } - } - }); - return colorItemView; - } - - private View createDividerView(FlowLayout rootView) { - LayoutInflater themedInflater = UiUtilities.getInflater(view.getContext(), nightMode); - View divider = themedInflater.inflate(R.layout.simple_divider_item, rootView, false); - - LinearLayout dividerContainer = new LinearLayout(view.getContext()); - dividerContainer.addView(divider); - dividerContainer.setPadding(0, AndroidUtils.dpToPx(app, 1), 0, AndroidUtils.dpToPx(app, 5)); - - return dividerContainer; - } - - private View createCircleView(ViewGroup rootView) { - LayoutInflater themedInflater = UiUtilities.getInflater(view.getContext(), nightMode); - View circleView = themedInflater.inflate(R.layout.point_editor_button, rootView, false); - ImageView outline = circleView.findViewById(R.id.outline); - int colorId = nightMode ? R.color.stroked_buttons_and_links_outline_dark : R.color.stroked_buttons_and_links_outline_light; - Drawable contourIcon = app.getUIUtilities().getIcon(R.drawable.bg_point_circle_contour, colorId); - outline.setImageDrawable(contourIcon); - return circleView; - } - - private void updateColorSelector(int color, View rootView) { - View oldColor = rootView.findViewWithTag(trackDrawInfo.getColor()); - if (oldColor != null) { - oldColor.findViewById(R.id.outline).setVisibility(View.INVISIBLE); - ImageView icon = oldColor.findViewById(R.id.icon); - icon.setImageDrawable(UiUtilities.tintDrawable(icon.getDrawable(), R.color.icon_color_default_light)); - } - View newColor = rootView.findViewWithTag(color); - if (newColor != null) { - newColor.findViewById(R.id.outline).setVisibility(View.VISIBLE); - } - mapActivity.refreshMap(); - } - private TrackAppearanceItem getSelectedAppearanceItem() { if (selectedAppearanceItem == null) { GradientScaleType scaleType = trackDrawInfo.getGradientScaleType(); @@ -293,49 +121,6 @@ public class TrackColoringCard extends BaseCard implements ColorPickerListener { updateColorSelector(); } - @Override - public void onColorSelected(Integer prevColor, int newColor) { - if (prevColor != null) { - int index = customColors.indexOf(prevColor); - if (index != INVALID_VALUE) { - customColors.set(index, newColor); - saveCustomColorsToTracks(prevColor, newColor); - } - if (trackDrawInfo.getColor() == prevColor) { - trackDrawInfo.setColor(newColor); - } - } else if (customColors.size() < 6) { - customColors.add(newColor); - trackDrawInfo.setColor(newColor); - } - saveCustomColors(); - updateContent(); - } - - private void saveCustomColorsToTracks(int prevColor, int newColor) { - List gpxDataItems = app.getGpxDbHelper().getItems(); - for (GpxDataItem dataItem : gpxDataItems) { - if (prevColor == dataItem.getColor()) { - app.getGpxDbHelper().updateColor(dataItem, newColor); - } - } - List files = app.getSelectedGpxHelper().getSelectedGPXFiles(); - for (SelectedGpxFile selectedGpxFile : files) { - if (prevColor == selectedGpxFile.getGpxFile().getColor(0)) { - selectedGpxFile.getGpxFile().setColor(newColor); - } - } - } - - private void saveCustomColors() { - List colorNames = new ArrayList<>(); - for (Integer color : customColors) { - String colorHex = Algorithms.colorToString(color); - colorNames.add(colorHex); - } - app.getSettings().CUSTOM_TRACK_COLORS.setStringsList(colorNames); - } - private class TrackColoringAdapter extends RecyclerView.Adapter { private List items; diff --git a/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java index 8491f24a67..f966b7d897 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java @@ -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; diff --git a/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java b/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java index 44f301945f..fe17c52ab5 100644 --- a/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java +++ b/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java @@ -95,7 +95,8 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer, Stat language = ((Struct) langVal).getName(); } } else { - language = voiceProvider.replace("-tts", "").replace("-formal", ""); + language = voiceProvider.replace(IndexConstants.VOICE_PROVIDER_SUFFIX, "") + .replace("-formal", ""); } } diff --git a/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java index e0c0018605..bf8d9803bf 100644 --- a/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java +++ b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java @@ -28,8 +28,8 @@ public class JSTTSCommandPlayerImpl extends TTSCommandPlayerImpl { jsScope = context.initSafeStandardObjects(); try { BufferedReader br = new BufferedReader(new FileReader(new File( - app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() + - "/" + voiceProvider + "/" + voiceProvider.replace("-tts", "_tts") + ".js"))); + app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() + "/" + voiceProvider + "/" + + voiceProvider.replace(IndexConstants.VOICE_PROVIDER_SUFFIX, "_tts") + ".js"))); context.evaluateReader(jsScope, br, "JS", 1, null); br.close(); } catch (Exception e) {