diff --git a/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl b/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl index 72a3035b60..e25e4338de 100644 --- a/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl +++ b/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl @@ -873,4 +873,14 @@ interface IOsmAndAidlInterface { boolean setMapMargins(in MapMarginsParams params); boolean exportProfile(in ExportProfileParams params); + + /** + * Is any fragment open. + */ + boolean isFragmentOpen(); + + /** + * Is contect menu open. + */ + boolean isMenuOpen(); } \ No newline at end of file diff --git a/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java b/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java index 8b810a81b4..11ec2ce17f 100644 --- a/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java +++ b/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java @@ -3,18 +3,31 @@ package net.osmand.aidlapi.customization; import android.os.Bundle; import android.os.Parcel; +import androidx.annotation.Nullable; + import net.osmand.aidlapi.AidlParams; +import java.util.ArrayList; +import java.util.List; + public class MapMarginsParams extends AidlParams { - private String appModeKey; + public static final String LEFT_MARGIN_KEY = "leftMargin"; + public static final String TOP_MARGIN_KEY = "topMargin"; + public static final String RIGHT_MARGIN_KEY = "rightMargin"; + public static final String BOTTOM_MARGIN_KEY = "bottomMargin"; + public static final String APP_MODES_KEYS_KEY = "appModesKeys"; + private ArrayList appModesKeys = new ArrayList<>(); private int leftMargin; private int topMargin; private int rightMargin; private int bottomMargin; - public MapMarginsParams(String appModeKey, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { - this.appModeKey = appModeKey; + public MapMarginsParams(int leftMargin, int topMargin, int rightMargin, int bottomMargin, + @Nullable List appModesKeys) { + if (appModesKeys != null) { + this.appModesKeys.addAll(appModesKeys); + } this.leftMargin = leftMargin; this.topMargin = topMargin; this.rightMargin = rightMargin; @@ -37,8 +50,8 @@ public class MapMarginsParams extends AidlParams { } }; - public String getAppModeKey() { - return appModeKey; + public List getAppModesKeys() { + return appModesKeys; } public int getLeftMargin() { @@ -59,19 +72,19 @@ public class MapMarginsParams extends AidlParams { @Override public void writeToBundle(Bundle bundle) { - bundle.putString("appModeKey", appModeKey); - bundle.putInt("leftMargin", leftMargin); - bundle.putInt("topMargin", topMargin); - bundle.putInt("rightMargin", rightMargin); - bundle.putInt("bottomMargin", bottomMargin); + bundle.putInt(LEFT_MARGIN_KEY, leftMargin); + bundle.putInt(TOP_MARGIN_KEY, topMargin); + bundle.putInt(RIGHT_MARGIN_KEY, rightMargin); + bundle.putInt(BOTTOM_MARGIN_KEY, bottomMargin); + bundle.putStringArrayList(APP_MODES_KEYS_KEY, appModesKeys); } @Override protected void readFromBundle(Bundle bundle) { - appModeKey = bundle.getString("appModeKey"); - leftMargin = bundle.getInt("leftMargin"); - topMargin = bundle.getInt("topMargin"); - rightMargin = bundle.getInt("rightMargin"); - bottomMargin = bundle.getInt("bottomMargin"); + leftMargin = bundle.getInt(LEFT_MARGIN_KEY); + topMargin = bundle.getInt(TOP_MARGIN_KEY); + rightMargin = bundle.getInt(RIGHT_MARGIN_KEY); + bottomMargin = bundle.getInt(BOTTOM_MARGIN_KEY); + appModesKeys = bundle.getStringArrayList(APP_MODES_KEYS_KEY); } } \ No newline at end of file diff --git a/OsmAnd-java/src/main/java/net/osmand/IProgress.java b/OsmAnd-java/src/main/java/net/osmand/IProgress.java index 762dab727b..407cd735f3 100644 --- a/OsmAnd-java/src/main/java/net/osmand/IProgress.java +++ b/OsmAnd-java/src/main/java/net/osmand/IProgress.java @@ -45,7 +45,7 @@ public interface IProgress { public boolean isInterrupted() {return false;} @Override - public boolean isIndeterminate() {return false;} + public boolean isIndeterminate() {return true;} @Override public void finishTask() {} diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java b/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java index f535af8610..e273fa4e7f 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java @@ -823,7 +823,7 @@ public class MapPoiTypes { } String name = keyName; name = name.replace('_', ' '); - return Algorithms.capitalizeFirstLetterAndLowercase(name); + return Algorithms.capitalizeFirstLetter(name); } public boolean isRegisteredType(PoiCategory t) { diff --git a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java index e597386d47..0ae4314dd9 100644 --- a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java +++ b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java @@ -41,7 +41,7 @@ public class RenderingRule { public void init(Map attributes) { ArrayList props = new ArrayList(attributes.size()); intProperties = new int[attributes.size()]; - floatProperties = null; + floatProperties = new float[attributes.size()]; attributesRef = null; int i = 0; Iterator> it = attributes.entrySet().iterator(); @@ -58,14 +58,13 @@ public class RenderingRule { attributesRef[i] = storage.getRenderingAttributeRule(vl.substring(1)); } else if (property.isString()) { intProperties[i] = storage.getDictionaryValue(vl); - } else if (property.isFloat()) { - if (floatProperties == null) { - // lazy creates - floatProperties = new float[attributes.size()]; - } - floatProperties[i] = property.parseFloatValue(vl); - intProperties[i] = property.parseIntValue(vl); } else { + float floatVal = property.parseFloatValue(vl); +// if (floatProperties == null && floatVal != 0) { +// // lazy creates +// floatProperties = new float[attributes.size()]; + floatProperties[i] = floatVal; +// } intProperties[i] = property.parseIntValue(vl); } i++; @@ -95,7 +94,7 @@ public class RenderingRule { public float getFloatPropertyValue(String property) { int i = getPropertyIndex(property); - if(i >= 0 && floatProperties != null){ + if (i >= 0) { return floatProperties[i]; } return 0; diff --git a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java index 228430b01f..322a734980 100644 --- a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java +++ b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java @@ -155,12 +155,7 @@ public class RenderingRuleProperty { try { int colon = value.indexOf(':'); if(colon != -1) { - int c = 0; - if(colon > 0) { - c += (int) Float.parseFloat(value.substring(0, colon)); - } - c += (int) Float.parseFloat(value.substring(colon + 1)); - return c; + return (int) Float.parseFloat(value.substring(colon + 1)); } return (int) Float.parseFloat(value); } catch (NumberFormatException e) { @@ -190,30 +185,35 @@ public class RenderingRuleProperty { } catch (NumberFormatException e) { log.error("Rendering parse " + value + " in " + attrName); } - return -1; + return 0; } else { return -1; } } - public float parseFloatValue(String value){ - if(type == FLOAT_TYPE){ - try { + public float parseFloatValue(String value) { + try { + if (type == FLOAT_TYPE) { int colon = value.indexOf(':'); - if(colon != -1) { - if(colon > 0) { + if (colon != -1) { + if (colon > 0) { return Float.parseFloat(value.substring(0, colon)); - } + } return 0; } return Float.parseFloat(value); - } catch (NumberFormatException e) { - log.error("Rendering parse " + value + " in " + attrName); + + } else if (type == INT_TYPE) { + int colon = value.indexOf(':'); + if (colon != -1 && colon > 0) { + return Float.parseFloat(value.substring(0, colon)); + } + return 0; } - return -1; - } else { - return -1; + } catch (NumberFormatException e) { + log.error("Rendering parse " + value + " in " + attrName); } + return 0; } diff --git a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java index 7a43f1f624..3eafb803dd 100644 --- a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java +++ b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java @@ -244,8 +244,6 @@ public class RenderingRuleStorageProperties { R_TEXT_HALO_COLOR = registerRuleInternal(RenderingRuleProperty.createOutputColorProperty(TEXT_HALO_COLOR)); R_TEXT_SIZE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(TEXT_SIZE)); R_TEXT_ORDER = registerRuleInternal(RenderingRuleProperty.createOutputIntProperty(TEXT_ORDER)); - R_ICON_ORDER = registerRuleInternal(RenderingRuleProperty.createOutputIntProperty(ICON_ORDER)); - R_ICON_VISIBLE_SIZE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(ICON_VISIBLE_SIZE)); R_TEXT_MIN_DISTANCE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(TEXT_MIN_DISTANCE)); R_TEXT_SHIELD = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty(TEXT_SHIELD)); @@ -265,7 +263,9 @@ public class RenderingRuleStorageProperties { R_ICON_3 = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty("icon_3")); R_ICON_4 = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty("icon_4")); R_ICON_5 = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty("icon_5")); + R_ICON_ORDER = registerRuleInternal(RenderingRuleProperty.createOutputIntProperty(ICON_ORDER)); R_SHIELD = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty(SHIELD)); + R_ICON_VISIBLE_SIZE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(ICON_VISIBLE_SIZE)); // polygon/way R_COLOR = registerRuleInternal(RenderingRuleProperty.createOutputColorProperty(COLOR)); diff --git a/OsmAnd-telegram/res/values-be/strings.xml b/OsmAnd-telegram/res/values-be/strings.xml index e1c5661a33..18e71dcd2e 100644 --- a/OsmAnd-telegram/res/values-be/strings.xml +++ b/OsmAnd-telegram/res/values-be/strings.xml @@ -267,4 +267,8 @@ Апошні адказ: %1$s таму %1$s таму ERR + Даслаць справаздачу + Экспартаваць + Буфер logcat + Праверце і падзяліцеся падрабязнымі журналамі праграмы \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-da/strings.xml b/OsmAnd-telegram/res/values-da/strings.xml index a6e077e7f8..caefa87b94 100644 --- a/OsmAnd-telegram/res/values-da/strings.xml +++ b/OsmAnd-telegram/res/values-da/strings.xml @@ -269,4 +269,8 @@ Sidste svar: %1$s siden %1$s siden ERR + Eksporter + Logcat-buffer + Kontroller og del detaljerede logfiler for programmet + Send rapport \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-tr/strings.xml b/OsmAnd-telegram/res/values-tr/strings.xml index 87ba56717d..2ff160a4a4 100644 --- a/OsmAnd-telegram/res/values-tr/strings.xml +++ b/OsmAnd-telegram/res/values-tr/strings.xml @@ -233,7 +233,7 @@ OsmAnd Tracker, ekran kapalıyken arka planda çalışır. Konumu paylaş Konum paylaşılıyor - OsmAnd Tracker servisi + OsmAnd Tracker hizmeti OsmAnd logosu Önce OsmAnd\'ın ücretsiz veya ücretli sürümünü yüklemeniz gerekmektedir OsmAnd\'ı yükle diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 4e5395a04a..f108fc7dd5 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -53,7 +53,8 @@ android:icon="@mipmap/icon" android:label="@string/app_name" android:name="net.osmand.plus.OsmandApplication" android:configChanges="locale" android:theme="@style/OsmandDarkTheme" android:restoreAnyVersion="true" android:largeHeap="true" - android:supportsRtl="true" android:usesCleartextTraffic="true"> + android:supportsRtl="true" android:usesCleartextTraffic="true" + android:hasFragileUserData="true" android:requestLegacyExternalStorage="true"> @@ -246,6 +247,7 @@ + @@ -261,6 +263,7 @@ + @@ -465,6 +468,13 @@ + + + + + + + @@ -477,28 +487,11 @@ - - - - - - - - - - - - - - - diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index ee9d4dd684..3638a103ca 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -53,7 +53,7 @@ android { defaultConfig { minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 - targetSdkVersion 28 + targetSdkVersion 29 versionCode 390 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode multiDexEnabled true @@ -331,6 +331,9 @@ task collectHelpContentsAssets(type: Copy) { from("../../help/website/feature_articles") { include "*.html" } + from("../../help/website/blog_articles") { + include "osmand-3-8-released.html" + } into "assets/feature_articles" } diff --git a/OsmAnd/res/layout/fragment_measurement_tool.xml b/OsmAnd/res/layout/fragment_measurement_tool.xml index d4b7f7ea57..2fe4dc1416 100644 --- a/OsmAnd/res/layout/fragment_measurement_tool.xml +++ b/OsmAnd/res/layout/fragment_measurement_tool.xml @@ -130,33 +130,25 @@ tools:text="@string/add_point_after"/> - + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="gone" > - + - + android:layout_height="@dimen/content_padding_small" /> - - - - + android:layout_height="wrap_content" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/fragment_measurement_tool_points_list.xml b/OsmAnd/res/layout/fragment_measurement_tool_points_list.xml new file mode 100644 index 0000000000..a0684707ea --- /dev/null +++ b/OsmAnd/res/layout/fragment_measurement_tool_points_list.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/plugin.xml b/OsmAnd/res/layout/plugin.xml index b0436d95e5..056fc218f0 100644 --- a/OsmAnd/res/layout/plugin.xml +++ b/OsmAnd/res/layout/plugin.xml @@ -1,186 +1,204 @@ - + - + - + + + + + + + android:layout_height="wrap_content" + android:orientation="vertical" > - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_marginStart="@dimen/content_padding" + android:layout_marginLeft="@dimen/content_padding" + android:layout_marginTop="@dimen/content_padding" + android:layout_marginEnd="@dimen/content_padding" + android:layout_marginRight="@dimen/content_padding" + android:text="@string/shared_string_description" + android:textColor="?android:textColorSecondary" + android:textSize="@dimen/default_desc_text_size" + osmand:textAllCapsCompat="true" + osmand:typeface="@string/font_roboto_medium" /> - + - + - + - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/OsmAnd/res/layout/plugins.xml b/OsmAnd/res/layout/plugins.xml index 095bfee61c..66d220fce8 100644 --- a/OsmAnd/res/layout/plugins.xml +++ b/OsmAnd/res/layout/plugins.xml @@ -1,14 +1,24 @@ - + + + + + + - + android:dividerHeight="1dp" + android:drawSelectorOnTop="true" /> + + \ No newline at end of file diff --git a/OsmAnd/res/layout/plugins_list_item.xml b/OsmAnd/res/layout/plugins_list_item.xml index c616b0014f..4e8f369e0c 100644 --- a/OsmAnd/res/layout/plugins_list_item.xml +++ b/OsmAnd/res/layout/plugins_list_item.xml @@ -52,6 +52,7 @@ android:ellipsize="end" android:lines="2" android:maxLines="2" + android:scrollbars="none" android:text="@string/lorem_ipsum" android:textColor="?android:textColorSecondary" android:textSize="@dimen/default_desc_text_size" diff --git a/OsmAnd/res/layout/poi_tag_list_item.xml b/OsmAnd/res/layout/poi_tag_list_item.xml index c057b3f0f1..e8a9c8dbb5 100644 --- a/OsmAnd/res/layout/poi_tag_list_item.xml +++ b/OsmAnd/res/layout/poi_tag_list_item.xml @@ -17,13 +17,12 @@ android:layout_marginEnd="@dimen/content_padding"> @@ -34,12 +33,11 @@ android:layout_weight="1"> diff --git a/OsmAnd/res/layout/profile_preference_toolbar.xml b/OsmAnd/res/layout/profile_preference_toolbar.xml index b76eb61696..6776a7a70c 100644 --- a/OsmAnd/res/layout/profile_preference_toolbar.xml +++ b/OsmAnd/res/layout/profile_preference_toolbar.xml @@ -64,6 +64,13 @@ tools:text="Some description" /> + + \ No newline at end of file diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index c695a1c813..b60654e873 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -3901,4 +3901,23 @@ \nيمكنك إدارة وإلغاء الاشتراكات الخاصة بك عن طريق الانتقال إلى إعدادات AppGallery. تجنب الممرات تجنب الممرات + ما الجديد + تطوير + بيانات أوسماند لايف + بيانات أوسماند لايف + التوجيه المركب + التوجيه على مرحلتين لملاحة السيارة. + تطوير النقل العام المحلي + قم بالتبديل إلى Java (الآمن) حساب توجيه النقل العام + قم بإجراء تسجيل دخول إلى OAuth لاستخدام ميزات osmedit + تسجيل الدخول عبر OAuth + مسح رمز OpenStreetMap OAuth + تسجيل الخروج بنجاح + تم استيراد الملف بالفعل في أوسماند + استخدام خوارزمية توجيه من مرحلتين A* + %1$s البيانات المتوفرة فقط على الطرق ، تحتاج إلى حساب طريق باستخدام \"الطريق بين النقاط\" للحصول عليها. + في انتظار إعادة حساب الطريق +\nسيتوفر الرسم البياني بعد إعادة الحساب. + للقيادة على الجليد مع طرق ومسارات مخصصة. + رسم بياني \ No newline at end of file diff --git a/OsmAnd/res/values-be/phrases.xml b/OsmAnd/res/values-be/phrases.xml index f96eaef3ab..253f7eb98d 100644 --- a/OsmAnd/res/values-be/phrases.xml +++ b/OsmAnd/res/values-be/phrases.xml @@ -3844,4 +3844,5 @@ Маленькія электрапрыборы Вулей Крама арэхаў + \ No newline at end of file diff --git a/OsmAnd/res/values-be/strings.xml b/OsmAnd/res/values-be/strings.xml index 6839ec5d24..3d0aa6103d 100644 --- a/OsmAnd/res/values-be/strings.xml +++ b/OsmAnd/res/values-be/strings.xml @@ -1576,7 +1576,7 @@ Абмежаванне па вышыні Пазначыць вышыню транспартнага сродку для разліку маршруту. Разумны пераразлік маршруту - Пераразлічваць толькі пачатак маршруту для доўгіх паездак. + Пераразлічваць толькі пачатак маршруту. Падыходзіць для доўгіх паездак. Выйсці Выключана Афарбоўка па пешаходнаму сімвалу OSMC @@ -3957,4 +3957,27 @@ Апошняя змена Назва: Я — А Назва: А — Я + Што новага + Дзякуй за набыццё ўбудовы «Контурныя лініі» + Плата за падпіску спаганяецца за абраны перыяд. Скасаваць яе на AppGallery можна у любы момант. + Пры пацвярджэнні пакупкі аплата будзе спаганяцца з рахунка, звязанага з вашым акаўнтам AppGallery. +\n +\nПадпіска аўтаматычна працягваецца, калі вы не скасуеце яе да даты працягу. З вашага рахунка будзе адзін раз спаганяцца аплата за перыяд працягу (месяц/тры месяцы/год). +\n +\nВы можаце кіраваць падпіскамі і скасоўваць іх у наладах AppGallery. + Пазбягаць пешаходныя дарожкі + Пазбягаць пешаходныя дарожкі + Распрацоўка + Дзвюхфазная аўтанавігацыя. + Натыўны грамадскі транспарт (у распрацоўцы) + Увайсці праз OAuth + Выкарыстоўваць 2-фазны алгарытм маршрутызацыі A * + Перайсці на разлік маршруту грамадскага транспарту на Java (бяспечны) + Файл ужо імпартаваны ў OsmAnd + Значкі старту і фінішу + Увайдзіце праз OAuth, каб выкарыстоўваць функцыі osmedit + Ачысціць токен OpenStreetMap OAuth + Выхад выкананы + Даныя OsmAnd Live + Даныя OsmAnd Live \ No newline at end of file diff --git a/OsmAnd/res/values-de/strings.xml b/OsmAnd/res/values-de/strings.xml index fbf1ccc31f..dec24d8f5d 100644 --- a/OsmAnd/res/values-de/strings.xml +++ b/OsmAnd/res/values-de/strings.xml @@ -3925,4 +3925,8 @@ Zwei-Phasen-Routenberechnung für die Autonavigation. Native ÖPNV Entwicklung Wechseln zu Java (sicher) Berechnung des ÖPNV-Routings + Abmeldung erfolgreich + Datei wurde bereits in OsmAnd importiert + Anmelden über OAuth + OpenStreetMap OAuth-Token löschen \ No newline at end of file diff --git a/OsmAnd/res/values-eo/phrases.xml b/OsmAnd/res/values-eo/phrases.xml index 8dc446b363..df01830535 100644 --- a/OsmAnd/res/values-eo/phrases.xml +++ b/OsmAnd/res/values-eo/phrases.xml @@ -3837,4 +3837,5 @@ prokrasto Tabulo de forveturoj Plenigi per trinkebla akvo + tergaso likva (LNG) \ No newline at end of file diff --git a/OsmAnd/res/values-eo/strings.xml b/OsmAnd/res/values-eo/strings.xml index 50bc2c4390..ece67180a8 100644 --- a/OsmAnd/res/values-eo/strings.xml +++ b/OsmAnd/res/values-eo/strings.xml @@ -73,7 +73,7 @@ Volapuko OsmAnd Mapoj kaj Navigado Inversa ordigo - Anstataŭigi komencpunkton per finpunkto + Anstataŭigi komencpunkton per celo Emblemoj de interesejoj Elemento forigita elementoj forigitaj @@ -742,7 +742,7 @@ Vidiga koloro tagoj Konekti - Prikalkuli kurson inter punktojn + Kalkuli kurson inter punktoj Ĉiam montri centrigitan pozicion Loko adresoj tutmondaj @@ -1853,7 +1853,7 @@ Erara formo: %s Vi devas esti konektita al la interreto por instali tiun ĉi kromprogramon. Inteligenta rekalkulado de kurso - Por longaj vojaĝoj, rekalkuli nur komencan parton de kurso. + Rekalkuli nur komencan parton de kurso. Uzebla por longaj kursoj. Taksi tiun ĉi aplikaĵon Via opinio estas grava por ni. Bonvolu taksi OsmAnd ĉe Google Play @@ -1890,7 +1890,7 @@ Ĉiuj nekonservitaj ŝanĝoj estos forigitaj. Ĉu pluigi? ankoraŭ %1$s elŝutoj Vojoj - Elŝutado - %1$s dosiero + Elŝutado – %1$d dosiero Montri reklamaĵon de senpaga versio Montri reklamaĵon de senpaga versio, eĉ se vi havas pagan version. Bonvolu aktivigi la kromprogramon “mara map-vido” @@ -2832,13 +2832,13 @@ Ĉiu-3-monate Ĉiujare %1$s / monato - %1$,2f %2$s / monato + %1$.2f %2$s / monato Ŝparu %1$s Nuna kotizo Reaboni ĉiumonate Reaboni ĉiukvaronjare Reaboni ĉiujare - %1$,2f %2$s + %1$.2f %2$s Intertempo de pagoj: Donacoj helpos fondi kartografion de OSM. De OsmAnd @@ -3357,7 +3357,7 @@ Eraro dum enporti %1$s: %2$s %1$s enportita. Blanka - Anstataŭigi %1$s per %2$s + Anstataŭigi: %1$s ⇄ %2$s Komencpunkto Anstataŭigi komencpunkton per celo Simuli vian pozicion uzante registritan GPX‑kurson. @@ -3651,7 +3651,7 @@ %1$s / %2$s La pago estos prenita el via konto Google Play post konfirmi aĉeton. \n -\n La abono aŭtomate renoviĝos escepte se ĝi estos nuligita antaŭ la dato de renoviĝo. La pago estos prenita por la renoviĝa periodo (monato/tri monatoj/jaro) nur je la data de renoviĝo. +\n La abono aŭtomate renoviĝos escepte se ĝi estos nuligita antaŭ la dato de renoviĝo. La pago estos prenita por la renoviĝa periodo (monato/tri monatoj/jaro) nur je la dato de renoviĝo. \n \n Vi povas administri kaj rezigni viajn abonojn per agordoj de Google Play. Miksi specojn de interesejoj el diversaj kategorioj. Frapetu ŝaltilon por elekti la tutan kategorion, frapetu ĉe maldekstre por elekti detala(j)n objekto(j)n el la kategorio. @@ -3894,7 +3894,7 @@ Simpligita spuro Nur la linio de kurso estos konservita, la navigadpunktoj estos forigitaj. Dosiernomo - %d dosieroj de spuroj elektitaj + %s dosieroj de spuroj elektitaj Paŭzigos registri spuron je halto de la aplikaĵo (per la menuo de lastaj aplikaĵoj). (Fona emblemo de OsmAnd malaperos de la androida sciiga zono.) Paŭzigi registri spuron Daŭrigi registri spuron @@ -3903,4 +3903,33 @@ Antaŭa segmento Ĉiuj antaŭaj segmentoj Nur la elektita segmento estos rekalkulita uzante la elektitan profilon. + Kio estas nova + La pago estos prenita el via konto AppGallery post konfirmi aĉeton. +\n +\n La abono aŭtomate renoviĝos escepte se ĝi estos nuligita antaŭ la dato de renoviĝo. La pago estos prenita por la renoviĝa periodo (monato/tri monatoj/jaro) nur je la dato de renoviĝo. +\n +\n Vi povas administri kaj rezigni viajn abonojn per la agordoj de AppGallery. + Emblemoj komenco/fino + Dankon al vi por aĉeti la kromprogramon “nivelkurboj” + Abonpago prenita por la elektita periodo. VI ĉiam povas rezigni abonon ĉe AppGallery. + Eviti irejojn + Sen trotuaroj + Programado + Datumoj OsmAnd Live + Datumoj OsmAnd Live + Komplika kurs-difinado + Du-faza difinado de kurso por aŭtomobila navigo. + Indiĝena metodo de publik-transporta navigo + Aktivigi (sekuran) metodon por kalkuli kursojn de publika transporto uzante programlingvon Java + Ensaluti uzante OAuth por redakti la mapon OSM + Ensaluti per OAuth + Forigi ĵetonon OpenStreetMap OAuth + Sukcese elsalutinta + Por veturi per motorsledo sur dediĉitaj vojoj. + Dosiero jam estas enportita al OsmAnd + Uzi 2-fazan A* algoritmon de navigo + Diagramo + Datumoj de %1$s estas disponeblaj nur por vojoj, vi devas kalkuli la kurson uzante “kalkuli kurson inter punktoj” por akiri ĝin. + Atendado ĝis la kurso estos rekalkulita. +\nDiagramo estos videbla post rekalkulado. \ 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 88c35ef6f9..4a15cc5cdd 100644 --- a/OsmAnd/res/values-es-rAR/phrases.xml +++ b/OsmAnd/res/values-es-rAR/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 diff --git a/OsmAnd/res/values-es-rAR/strings.xml b/OsmAnd/res/values-es-rAR/strings.xml index 5e5fae0268..b68eaba5e1 100644 --- a/OsmAnd/res/values-es-rAR/strings.xml +++ b/OsmAnd/res/values-es-rAR/strings.xml @@ -3929,4 +3929,11 @@ Ingresar a través de OAuth Vaciar llave OAuth de OpenStreetMap Sesión finalizada + Para caminos y senderos exclusivos de motos de nieve. + El archivo ya fue importado en OsmAnd + Usar el algoritmo de enrutamiento A* de 2 fases + Gráfico + %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. \ No newline at end of file diff --git a/OsmAnd/res/values-et/phrases.xml b/OsmAnd/res/values-et/phrases.xml index 4cb42eee67..daafc22ac5 100644 --- a/OsmAnd/res/values-et/phrases.xml +++ b/OsmAnd/res/values-et/phrases.xml @@ -3559,20 +3559,20 @@ $0.5 mündid Laadimisjaam Oht - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + k/p + k/p* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Gaasi põletamine;Hõõglamp Kustutatud objekt Kiiritusravi diff --git a/OsmAnd/res/values-et/strings.xml b/OsmAnd/res/values-et/strings.xml index c580533de8..8ccdb58038 100644 --- a/OsmAnd/res/values-et/strings.xml +++ b/OsmAnd/res/values-et/strings.xml @@ -3593,7 +3593,7 @@ Kuna mõnedel marsruutidel võidakse kohaldada sõidukite kõrguspiiranguid, siis palun märkige oma sõiduki kõrgus. Kuna mõnedel marsruutidel võidakse kohaldada piiranguid pikkade sõidukite suhtes, siis palun märkige oma sõiduki pikkus. Kuna mõnedel marsruutidel võidakse kohaldada sõidukite kaalupiiranguid, siis palun märkige oma sõiduki kaal. - OsmAnd GPX faili vorming pole korrektne. Täpsemaks uurimiseks palun suhtle meie tugimeeskonnaga. + OsmAnd GPX-andmestiku vorming on vigane. Täpsemaks uurimiseks palun võta ühendust meie kasutajatoega. Sorteeri kategooria järgi Jätka Imporditavas profiilis leidub täiendavaid andmeid. Vajutades „Impordi“ imporditakse vaid profiili andmed, täiendavate andmete jaoks pead märkima vastava valiku. @@ -3775,4 +3775,11 @@ Arveldame tellimuse eest valitud ajavahemiku alusel. Seda saad sa vabalt valitud ajal tühistada AppGallery\'s. Keeruka teekonna koostamine Väljalogimine õnnestus + Arendus + Kustuta OpenStreetMap\'i OAuth\'i pääsuluba + Logi sisse OAuth abil + Kui sa soovid kasutada kaardi muutmise võimalusi, siis palun logi sisse OAuth abil + Meie uudised + Kasuta kahefaasilist A-klassi teekonna koostamise algoritmi + See fail on juba OsmAnd\'i imporditud \ No newline at end of file diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index 7df4716103..04e329659a 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -3900,4 +3900,16 @@ Connectez-vous avec OAuth Supprimer le jeton OAuth d\'OpenStreetMap Déconnexion réussie + Le fichier est déjà importé dans OsmAnd + Utiliser un algorithme de routage A* à 2 phases + Le paiement sera débité de votre compte AppGallery dès confirmation de l\'achat. +\n +\nA moins qu\'il ne soit annulé avant sa date de renouvellement, l\'abonnement sera automatiquement débité à chaque échéance (mensuelle / trimestrielle / annuelle). +\n +\nVous pouvez gérer et annuler vos abonnements dans vos paramètres AppGallery. + Seulement %1$s données disponibles sur les routes. Vous devez calculer l\'itinéraire via \"Itinéraire entre 2 points\". + Recalcul de l\'itinéraire en cours. +\nLe graphique sera disponible à l\'issue du calcul. + Pour la conduite en motoneige avec des routes et des pistes dédiées. + Graphique \ No newline at end of file diff --git a/OsmAnd/res/values-gl/phrases.xml b/OsmAnd/res/values-gl/phrases.xml index a840ae5d86..b974255f35 100644 --- a/OsmAnd/res/values-gl/phrases.xml +++ b/OsmAnd/res/values-gl/phrases.xml @@ -65,7 +65,7 @@ Lomba pequena de velocidade Rodas Inspección técnica de vehículos - Lavadura de automóbiles + Lavado de automóbiles Gasolineira;Estación de combustíbel;Estación de servizo Ar comprimido Aparcadoiro diff --git a/OsmAnd/res/values-gl/strings.xml b/OsmAnd/res/values-gl/strings.xml index 8e7a90f00a..0a65bf9b5a 100644 --- a/OsmAnd/res/values-gl/strings.xml +++ b/OsmAnd/res/values-gl/strings.xml @@ -3675,7 +3675,7 @@ Lon %2$s %1$s / %2$s "O pagamento será cobrado na túa conta da Google Play na confirmación da compra. \n -\n A subscrición é renovada de xeito automático, a menos que sexa desbotada antes da data de renovación. A túa conta será cobrada polo período de renovación (més/trimestre/ano) soamente na data de renovación. +\n A subscrición é renovada de xeito automático, a menos que sexa desbotada antes da data de renovación. A túa conta será cobrada polo período de renovación (mes/trimestre/ano) soamente na data de renovación. \n \n Podes xestionar e desbotar as túas subscricións entrando nos axustes da Google Play." Procurar tipos de PDI @@ -3945,4 +3945,9 @@ Lon %2$s Enrutamento de dúas fases para a navegación de automóbil Desenvolvemento do transporte público nativo Activar cálculo de enrutamento de transporte público do Java (seguro) + Novidades + Inicia sesión co OAuth para empregar as funcións de edición do OSM + Entrar polo OAuth + Limpar token do OpenStreetMap OAuth + Sesión rematada \ No newline at end of file diff --git a/OsmAnd/res/values-hu/phrases.xml b/OsmAnd/res/values-hu/phrases.xml index 0234cd76a2..eb5f1ef453 100644 --- a/OsmAnd/res/values-hu/phrases.xml +++ b/OsmAnd/res/values-hu/phrases.xml @@ -3553,20 +3553,20 @@ Sugárkezelés Veszély Nehézségi fok (az Orosz Túrasportszövetség skáláján) - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + N/A + N/A* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Gázfáklya Törölt objektum Sugárkezelés diff --git a/OsmAnd/res/values-hu/strings.xml b/OsmAnd/res/values-hu/strings.xml index be5f333f2a..ac040cd72f 100644 --- a/OsmAnd/res/values-hu/strings.xml +++ b/OsmAnd/res/values-hu/strings.xml @@ -3739,11 +3739,11 @@ Kiegészítő adatok átvétele Az importált profil kiegészítő adatokat is tartalmaz. Koppintson az \"Importál\" gombra kizálólag a profiladatok importálásához vagy válassza a kiegészítő adatokat. Összes adat importálva innen: %1$s, az alábbi gombokkal megnyithatja az alkalmazás megfelelő részét az adatok kezeléséhez. - A fizetés a Google Play fiókhoz lesz felszámítva a vásárlás megerősítésekor. + A fizetést a vásárlás visszaigazolásakor a Google Play számlájára terheljük. \n -\nAz előfizetés automatikusan megújul, kivéve ha a meghosszabbítás napja előtt lemondásra kerül. A fókjához az új időszak (hónap/három hónap/év) díja kizárólag a meghosszabítás napján lesz felszámolva. +\nAz előfizetés automatikusan megújul, kivéve, ha azt a megújítási dátum előtt lemondja. A megújítási időszakra (hónap / 3 hónap / év) vonatkozóan a számláját csak a megújítás napján terheljük meg. \n -\nAz előfizetéseit a Google Play beállításainál tudja kezelni és lemondani. +\nAz előfizetéseket a Google Play beállításai között kezelheti és törölheti. Törli az útvonal soron következő célpontját. Amennyiben ez a végző célpont, a navigáció megáll. Tudjon meg többet az érdekes pontokról a Wikipédiából. Ez az Ön offline zsebútikönyve – egyszerűen kapcsolja be a Wikipédia-bővítményt, és élvezze az Ön körüli objektumokról szóló cikkeket. Salakmotor @@ -3918,4 +3918,6 @@ Bejelentkezés OAuth segítségével OpenStreetMap OAuth token törlése Sikeresen kijelentkezett + Kétszakaszos A* útvonaltervezési algoritmus használata + A fájl már importálva van az OsmAndba \ No newline at end of file diff --git a/OsmAnd/res/values-is/phrases.xml b/OsmAnd/res/values-is/phrases.xml index 2c6a85bdb4..07ff354d7e 100644 --- a/OsmAnd/res/values-is/phrases.xml +++ b/OsmAnd/res/values-is/phrases.xml @@ -3827,4 +3827,7 @@ Lyfta Smærri raftæki Áfylling drykkjarvatns + LNG + Brottfaratafla: nei + Brottfaratafla \ No newline at end of file diff --git a/OsmAnd/res/values-is/strings.xml b/OsmAnd/res/values-is/strings.xml index d2404aaba2..c5ce396f03 100644 --- a/OsmAnd/res/values-is/strings.xml +++ b/OsmAnd/res/values-is/strings.xml @@ -530,7 +530,7 @@ Víðværar stillingar Almennt Bakgrunnshamur - Eyða \'%s\'? + Eyða %1$s\? Úthverfi Byggðakjarni Þorp @@ -709,8 +709,8 @@ Leyfa hraðbrautir. Wikipedia-greinar í nágrenninu Útreikningur leiðar - Þú ert ekki með neina GPX-ferla ennþá - Þú getur líka bætt GPX-skrám í möppuna + Þú ert ekki ennþá með neina ferla + Þú getur líka bætt ferilskrám í möppuna Virkja hraðskráningu ferðar Ferð Skráð @@ -723,7 +723,7 @@ Engin internettenging Nauðsynleg til að sækja kort. Leita að stað… - Gagnageymsla OsmAnd (fyrir kort, GPX-ferla, o.s.frv.): %1$s. + Gagnageymsla OsmAnd (fyrir kort, ferilskrár, o.s.frv.): %1$s. Gefa heimild Ekki sýna nýjar útgáfur Komast í gang @@ -1718,7 +1718,7 @@ Stærð leturs fyrir nöfn á kortinu: Aflúsunarupplýsingar myndgerðar Birta afköst myndgerðar. - Snúa við stefnu GPX + Snúa við stefnu ferils Frálag raddleiðsagnar Hljóð í margmiðlun/tónlist Nota rastakort fyrir allt undir þessu aðdráttarstigi. @@ -1746,7 +1746,7 @@ Stigvaxandi leit byggingar Upplýsingum um hnút var ekki hlaðið inn Snjöll endurreiknun leiðar - Einungis endurreikna upphafshluta leiðar fyrir langar ferðir. + Endurreiknar aðeins upphafshluta leiðar. Má nota fyrir langar ferðir. Kortið sem eingöngu er með vegum er ekki nauðsynlegt, þar sem þú ert þegar með staðlaða (fulla útgáfu) kortsins. Sækja það samt? Birta dýptarlínur og punkta. Dýptarlínur sjávar @@ -2116,7 +2116,7 @@ Gat ekki flutt skrána inn. Vinsamlegast athugið hvort OsmAnd hafi réttindi til að lesa skrána þar sem hún er. Opna Mapillary Mapillary græja - Götumyndir + Mapillary götumyndir Mapillary-mynd Mælistika út frá miðju hrings Heimildir @@ -2904,10 +2904,10 @@ Gerðir vega Fara útaf við Fara um borð við stöðvunina - Birta/Fela GPX-ferla - Hnappur til að birta eða fela valda GPX-ferla á kortinu. - Fela GPX-ferla - Birta GPX-ferla + Birta/Fela ferla + Hnappur til að birta eða fela valda ferla á kortinu. + Fela ferla + Birta ferla • Nýr skjár fyrir \'Leiðir\': Birtir leiðarhnappa fyrir \'Heim\' og \'Vinna\', flýtileið á fyrri leið, listi yfir virka GPX-ferla og merki, leitarferill \n \n • Viðbótarupplýsingar undir \'Nánar um leið\': gerð vegar, yfirborð, bratti, áferð @@ -3766,7 +3766,7 @@ Víxlhnappur til að birta eða fela Mapillary-lagið á kortinu. Stefna %1$s eytt - Endurræsing er nauðsynleg til að geta fjarlægt alveg gögn um hraðamyndavélar. + Endurræstu forritið til að fjarlægja öll gögn um hraðamyndavélar. Fjarlægja og endurræsa Fjarlægja Aðvaranir vegna hraðamyndavéla eru bannaðar með lögum í sumum löndum. @@ -3777,19 +3777,19 @@ \n \nVeldu %2$s: öllum gögnum sem tengjast hraðamyndavélum; t.d. aðvaranir, tilkynningar, staðsetningar o.fl. verður eytt þar til OsmAnd er sett inn aftur frá grunni. Veldu ferilskrá þar sem nýjum bút verður bætt inn. - Veldu hvernig eigi að tengja punkta; með beinni línu eða reikna leið milli þeirra með þessu sniði. + Veldu hvernig eigi að tengja punkta; með beinni línu eða reikna leið milli þeirra eins og tiltekið er hér að neðan. Allur ferillinn verður endurreiknaður með völdu sniði. Lokaður OSM-minnispunktur Allur ferillinn - Sýna tákn fyrir upphaf/enda + Sýna tákn fyrir upphaf og enda Þetta tæki er ekki með hraðamyndavélar. Gókart - Þú verður að skilgreina virka daga til að halda áfram + Skilgreindu virka daga til að halda áfram Eyða næsta markpunkti Leiðsagnarsnið Hjólastóll áfram Náðu í upplýsingar um merka staði frá Wikipedia. Þetta er þá orðið að vasaleiðsögn án nettengingar - bara virkjaðu Wikipedia-viðbótina og njóttu þess að geta lesið um hlutina í kringum þig. - Virkja þetta til að hægt sé að stýra aðdráttarstigi korts með hljóðstyrkshnöppum. + Stýrðu aðdráttarstigi korts með hljóðstyrkshnöppum tækisins. Tilgreindu lengd farartækis sem leyfð er á leiðum. Ef stefna er öfug Aðeins næsti bútur verður endurreiknaður með völdu sniði. @@ -3840,7 +3840,7 @@ \n Veldu bilið þar sem merki með tíma eða vegalengd á ferlinum verða birt. Tengja við vegina - Ertu viss um að þú viljir loka leiðaskipulagningu án þess að vista\? Þú munt tapa öllum breytingum. + Ertu viss um að þú viljir loka leiðaskipulagningu án þess að vista\? Vista sem ferilskrá Þolvik vegalengdar Skrifa feril í GPX-skrá @@ -3856,11 +3856,9 @@ Upphaf ferils Ferlar Settu inn heimilisfang - Veldu ferilskrá til að fylgja eða flyttu inn úr tæki. + Veldu ferilskrá til að fylgja eða flyttu hana inn úr tækinu þínu. Bæta í ferilskrá - Til að nota þennan valkost þarf OsmAnd að festa ferilinn þinn við vegi á kortinu. -\n -\nÍ næsta skrefi þarftu að velja leiðsagnarsnið svo hægt sé að finna hvaða vegir séu leyfilegir og hvaða þolvik vegalengdar eigi að miða við til að nálga ferilinn þinn við fyrirliggjandi vegi. + Í næsta skrefi þarftu að velja leiðsagnarsnið svo hægt sé að finna hvaða vegir séu leyfilegir og hvaða þolvik vegalengdar eigi að miða við til að nálga ferilinn þinn við fyrirliggjandi vegi. Bæta við ferilskrám Línuskautar Bæta við heimilisfangi @@ -3868,7 +3866,7 @@ Skráning ferðar Næsti punktur Ferlar - GPX + REC Klippa á undan Bæta við leiðarpunkti í feril Skipta um gerð leiðar eftir @@ -3879,4 +3877,41 @@ Skrá leið í feril Velja annan feril Skrifa feril sjálfkrafa í GPX-skrá á meðan leiðsögn stendur + Nýjungar + Einfaldaður ferill + Aðeins leiðarlínan verður vistuð, ferilpunktunum verður eytt. + Skráarheiti + Sjálfgefið í kerfinu + Allir bútar í kjölfarið + Fyrri bútur + Allir fyrri bútar + Aðeins valinn bútur verður endurreiknaður með völdu sniði. + Allir bútar sem á eftir fylgja verða endurreiknaðir með völdu sniði. + Allir fyrri bútar verða endurreiknaðir með völdu sniði. + Opna vistaðan feril + er vistað + Bættu við a.m.k. tveimur punktum. + Endurtaka + Síðast breytt + Nafn: Ö – A + Nafn: A – Ö + Tákn við upphaf/enda + Forðast gangstéttir + Forðast gangstéttir + Þróun + OsmAnd Live gögn + OsmAnd Live gögn + Flókin leiðagerð + Tveggja-þátta leiðagerð fyrir bílaleiðsögn. + Innbyggð þróun almenningssamgangna + Skipta yfir í Java (öruggt) útreikning fyrir almenningssamgöngur + Framkvæma OAuth-innskráningu til að nota osmedit-eiginleika + Skrá inn í gegnum OAuth + Hreinsa OAuth-teikn OpenStreetMap + Útskráning tókst + %s GPX-skrár valdar + Mun setja GPX-skráningu í bið þegar forritið er drepið (slökkt á því í gegnum skjáinn fyrir nýleg forrit - bakgrunnsvísir OsmAnd hverfur þar með úr tilkynningastiku Android-kerfisins.) + Veldu millibil skráninga í almenna leiðarskráningu (virkjað með viðmótshlutanum fyrir GPX-skráningu á kortinu). + Setja skráningu í bið + Halda áfram með skráningu \ No newline at end of file diff --git a/OsmAnd/res/values-iw/strings.xml b/OsmAnd/res/values-iw/strings.xml index 756dfe73dd..efe9cb3b27 100644 --- a/OsmAnd/res/values-iw/strings.xml +++ b/OsmAnd/res/values-iw/strings.xml @@ -3931,4 +3931,11 @@ להיכנס דרך OAuth למחוק את אסימון ה־OAuth של OpenStreetMap היציאה הצליחה + הקובץ כבר ייובא אל OsmAnd + להשתמש באלגוריתם חישוב מסלול דו־שלבי A*‎ + לנהיגה ברכבי שלג עם דרכים ומסלולים יעודיים. + הנתונים של %1$s זמינים בדרכים בלבד, עליך לחשב מסלול באמצעות „מסלול בין נקודות” כדי לקבל אותם. + תרשים + נא להמתין לחישוב המסלול מחדש. +\nהתרשים יהיה זמין לאחר החישוב מחדש. \ No newline at end of file diff --git a/OsmAnd/res/values-ja/phrases.xml b/OsmAnd/res/values-ja/phrases.xml index 66c5cc2a31..865fa35edc 100644 --- a/OsmAnd/res/values-ja/phrases.xml +++ b/OsmAnd/res/values-ja/phrases.xml @@ -50,8 +50,8 @@ インターネット有り レジャー クラブ - 食堂 - 軽食 + 飲食店 + カフェ・レストラン サービス 工芸 金融機関 @@ -568,9 +568,9 @@ 公園 レクリエーション広場 共有地 - 喫茶店・カフェ + カフェ ビアガーデン - レストラン・食堂 + レストラン ファーストフード バー・立ち呑み屋 フードコート @@ -1012,7 +1012,7 @@ 正面玄関 入り口 出口 - 高速道路の横断歩道 + 横断歩道 営業時間 収集時間 詳細 @@ -3834,4 +3834,5 @@ 行政区 ギブボックス(提供品置場) 簡易給水栓 + 液化天然ガス \ No newline at end of file diff --git a/OsmAnd/res/values-ja/strings.xml b/OsmAnd/res/values-ja/strings.xml index 35f9146fc7..86695fc95d 100644 --- a/OsmAnd/res/values-ja/strings.xml +++ b/OsmAnd/res/values-ja/strings.xml @@ -927,10 +927,10 @@ POIの更新は利用できません このOsmAnd 無料版はダウンロード数が%1$s個に制限されており、オフラインでのWikipedia記事利用もサポートしていません。 無料版 POIの説明文を表示 - 北米 + 北アメリカ アメリカ合衆国 - 中米 - 南米 + 中央アメリカ + 南アメリカ ヨーロッパ ヨーロッパ - フランス ヨーロッパ - ドイツ @@ -1553,7 +1553,7 @@ POIの更新は利用できません 高さ制限 ルート上で通行可能な車両の高さを指定します。 スマートなルート再計算 - 経路が長い場合、最初の部分のみ再計算します。 + ルートの最初の部分のみを再計算します。旅程(設定ルート)がとても長い場合に効果的です。 明色 暗色 ピエモンテ語 @@ -1936,7 +1936,7 @@ POIの更新は利用できません バス 鉄道 現在の経路 - バッテリーレベル + バッテリー残量 マーカーの位置を変更 マップ画面のドラッグでマーカー位置を調整できます @@ -3307,7 +3307,7 @@ POIの更新は利用できません アプリ全体に反映されます OsmAnd設定 他のプロファイルからコピー - 画面表示機能 + 画面の復帰設定 ナビゲーション中のマップ表示 ナビゲーション中のマップ表示 その他 @@ -3705,7 +3705,7 @@ POIの更新は利用できません グジャラート語 チュヴァシ語 OsmAnd GPXの形式が正しくありません。サポートチームに連絡しての調査をおすすめします。 - 画面のタイムアウト + 画面の消灯設定 画面復帰オプションを選択します(端末設定でロックされる場合は、OsmAndがバックグラウンド動作でないことを確認してください): 端末側の画面オフ(省電力)設定に従って画面を消灯します。 端末側の画面オフ設定を使用 @@ -3760,7 +3760,7 @@ POIの更新は利用できません 車両の重量を入力してください。一部のルートにおいては、大型車両では通行できない場合があります。 車の長さを入力してください。一部のルートにおいては、長い車両では通行できない場合があります。 常に - 画面制御 + 画面の制御 現在無効。 \'画面を表示する時間\'の下にある\'画面を常に表示\'への設定が必要です。 画面オフを維持 疑似メルカトル図法 @@ -3801,7 +3801,7 @@ POIの更新は利用できません アンインストール 一部の国では、スピードカメラの事前警告は法律で禁止されています。 \"%1$s\"がオンの場合、設定された動作時間はそちらに依存します。 - デフォルトの画面タイムアウト時間 + 端末設定の画面スリープ時間に従う トーン メートル 追加マップの詳細を表示または非表示にします @@ -3900,4 +3900,18 @@ POIの更新は利用できません 最終更新日 名称: 降順(Z-A) 名称: 昇順(A-Z) + Java(セーフモード)での公共交通機関ルーティング計算に切り替えます + OsmEdit機能を利用するには、OAuthでのログインが必要です。 + OAuthでログイン + OpenStreetMapOAuthトークンを消去する + ログアウトしました + 歩道を使わないようにします + 歩道を避ける + 開発 + OsmAnd Liveデータ + OsmAnd Liveデータ + 複雑なルート計算 + カーナビゲーション向けの2段階ルート計算です。 + 2段階 A*ルーティングアルゴリズムを使用 + ファイルはすでにOsmAndにインポートされています \ No newline at end of file diff --git a/OsmAnd/res/values-nb/strings.xml b/OsmAnd/res/values-nb/strings.xml index 97364e7f2d..85bfc9b423 100644 --- a/OsmAnd/res/values-nb/strings.xml +++ b/OsmAnd/res/values-nb/strings.xml @@ -3866,4 +3866,6 @@ Logg inn via OAuth Hva er nytt Utlogget + Bruk 2-stegs A*-rutingsalgoritme + Filen er allerede importert i OsmAnd \ 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 07c80c478d..bcb7d4a63f 100644 --- a/OsmAnd/res/values-pt-rBR/phrases.xml +++ b/OsmAnd/res/values-pt-rBR/phrases.xml @@ -3561,14 +3561,14 @@ Radioterapia Perigo Categoria de dificuldade - н/к - н/к* - - 1А* + n/c + n/c* + 1A + 1A* 1B 1B* - 2А* + 2A* 2B 2B* diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index 8f1641cca9..1ea7427da0 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -2821,7 +2821,7 @@ Renova anualmente Renova trimestralmente Por OsmAnd - %1$,2f %2$s + %1$.2f %2$s Período de pagamento: As doações ajudam a financiar a cartografia no OSM. • Navegação: Corrige barra de progresso, inversão rápida de início e fim de rota @@ -3921,4 +3921,11 @@ Entrar via OAuth Limpar token do OpenStreetMap OAuth Saída bem sucedida + O arquivo já foi importado para OsmAnd + Use o algoritmo de roteamento 2-phase A * + Para dirigir em motos de neve com estradas e trilhas exclusivas. + Gráfico + Dados de %1$s disponíveis apenas nas estradas, você precisa calcular uma rota usando “Rota entre pontos” para obtê-la. + Aguarde o recálculo da rota. +\nO gráfico estará disponível após o recálculo. \ No newline at end of file diff --git a/OsmAnd/res/values-pt/phrases.xml b/OsmAnd/res/values-pt/phrases.xml index fc6f81cb62..05bf64827a 100644 --- a/OsmAnd/res/values-pt/phrases.xml +++ b/OsmAnd/res/values-pt/phrases.xml @@ -3548,18 +3548,18 @@ Bonde Balsa Fonte de energia: biomassa - н/к - н/к* - - 1А* + n/c + n/c* + 1A + 1A* 1B 1B* - - 2А* + 2A + 2A* 2B 2B* - - 3А* + 3A + 3A* 3B 3B* Explosão de gás;Queimador de gás diff --git a/OsmAnd/res/values-pt/strings.xml b/OsmAnd/res/values-pt/strings.xml index 12f7810ed8..8179f41893 100644 --- a/OsmAnd/res/values-pt/strings.xml +++ b/OsmAnd/res/values-pt/strings.xml @@ -3928,4 +3928,11 @@ Fazer login via OAuth Limpar token do OpenStreetMap OAuth Logout bem sucedido + O ficheiro já é importado em OsmAnd + Usar algoritmo de roteamento de 2 fases A* + Para a condução de motos de neve com estradas e pistas dedicadas. + Gráfico + %1$s dados disponíveis apenas nas estradas, precisa calcular uma rota a usar \"Rota entre pontos\" para obtê-la. + Espere pelo recalculo da rota. +\nO gráfico estará disponível após o recalculo. \ No newline at end of file diff --git a/OsmAnd/res/values-ru/phrases.xml b/OsmAnd/res/values-ru/phrases.xml index d87b1f7c05..7b0db11a82 100644 --- a/OsmAnd/res/values-ru/phrases.xml +++ b/OsmAnd/res/values-ru/phrases.xml @@ -1737,7 +1737,7 @@ Тип приюта: для кошек Тип приюта: для собак и кошек Тип приюта: для лошадей - Исторический самолёт + Историческое воздушное судно Мёд С лифтом Без лифта diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index d2c1dfd578..1e5b9b274d 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -1336,7 +1336,7 @@ Данные о тайлах: %1$s Обзорная карта мира Время действия (в минутах) - Самолёт + Воздушное судно Лодка Пеший туризм Мотоцикл @@ -2269,7 +2269,7 @@ Удалить действие Вы уверены, что хотите удалить действие «%s»\? Показать избранные - Скрыть сохранённые + Скрыть избранные Показать/скрыть POI Показать %1$s Скрыть %1$s @@ -3242,8 +3242,8 @@ Буфер Logcat Настройки плагинов Язык и вывод - Переместить файлы данных OsmAnd в новое место назначения\? -\n%1$s > %2$s + Переместить файлы данных OsmAnd в новое место назначения\? +\n%1$s → %2$s По умолчанию %1$s • %2$s %1$s ГБ свободно (из %2$s ГБ) @@ -3349,7 +3349,7 @@ Выберите цвет Вы не можете удалить стандартные профили OsmAnd, но вы можете отключить их на предыдущем экране или переместить вниз. Редактировать профили - Режим навигации определяет правила расчета маршрутов. + Режим навигации определяет правила расчёта маршрутов. Внешний вид профиля Значок, цвет и имя Редактировать список профилей @@ -3580,7 +3580,7 @@ Примечание: проверка скорости > 0: большинство модулей GPS сообщают значение скорости только в том случае, если алгоритм определяет, что вы движетесь, и ничего, если вы не перемещаетесь. Следовательно, использование параметра > 0 в этом фильтре в некотором смысле приводит к обнаружению факта перемещения модуля GPS. Но даже если мы не производим данную фильтрацию во время записи, то всё равно эта функция используется при анализе GPX для определения скорректированного расстояния, то есть значение, отображаемое в этом поле, является расстоянием, записанным во время движения. Разделение записи Укажите веб-адрес со следующими параметрами: lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}. - "Будут записываться только точки, отвечающие по показателю минимальной точности (в метрах или футах — зависит от настроек системы). Точность — это близость измерений к истинному положению, и она не связана напрямую с точностью, которая представляет собой разброс повторных измерений." + Будут записываться только точки, отвечающие по показателю минимальной точности (в метрах или футах — зависит от настроек системы). Точность — это близость измерений к истинному положению, и она не связана напрямую с точностью, которая представляет собой разброс повторных измерений. Рекомендация: попробуйте сначала воспользоваться детектором движения через фильтр минимального смещения (B), что может дать лучшие результаты и вы потеряете меньше данных. Если треки остаются шумными на низких скоростях, попробуйте использовать ненулевые значения. Обратите внимание, что некоторые измерения могут вообще не указывать значения скорости (некоторые сетевые методы), и в этом случае ничего не будет записываться. Для визуализации крутизны рельефа используются цвета. Подробнее об уклонах можно прочитать в %1$s. @@ -3882,7 +3882,7 @@ Остановка записи GPX при принудительном закрытии (через последние приложения). (Из панели уведомлений Android исчезнет значок фонового режима.) сохранен Добавьте хотя бы две точки. - ПОВТОРИТЬ + Повторить • Обновлённая функция планирования маршрута позволяет применять к сегментам разные режимы навигации и настраивать привязку к дорогам \n \n • Новые настройки вида треков: выбор цвета и толщины линии, указатели направления, метки начала и конца маршрута @@ -3906,9 +3906,9 @@ Избегать пешеходных дорожек Избегать пешеходных дорожек Подписка взимается за выбранный период. Отмените её в AppGallery в любое время. - Оплата будет снята с вашей учетной записи AppGallery при подтверждении покупки. + Оплата будет снята с вашей учётной записи AppGallery при подтверждении покупки. \n -\nПодписка продлевается автоматически, если она не будет отменена до даты продления. С вашего счета будет взиматься плата за период продления (месяц/три месяца/год) только в дату продления. +\nПодписка продлевается автоматически, если она не будет отменена до даты продления. С вашего счёта будет взиматься плата за период продления (месяц/три месяца/год) только в дату продления. \n \nВы можете управлять своими подписками и отменять их, перейдя в настройки AppGallery. Данные OsmAnd Live @@ -3920,4 +3920,8 @@ Войти через OAuth Очистить токен OAuth OpenStreetMap Выход выполнен - \ No newline at end of file + График + Файл уже импортирован + Дождитесь пересчёта маршрута. +\nГрафик будет доступен после пересчёта. + diff --git a/OsmAnd/res/values-sc/phrases.xml b/OsmAnd/res/values-sc/phrases.xml index 62f4c1ece8..e0a15ffcdb 100644 --- a/OsmAnd/res/values-sc/phrases.xml +++ b/OsmAnd/res/values-sc/phrases.xml @@ -3557,20 +3557,20 @@ Radioterapia Perìgulu Categoria de dificultade - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + n/c + n/c* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Frama de gas;Tortza de brusiadura Ogetu iscantzelladu Radioterapia diff --git a/OsmAnd/res/values-sc/strings.xml b/OsmAnd/res/values-sc/strings.xml index ac6f4f667a..4a3d545b47 100644 --- a/OsmAnd/res/values-sc/strings.xml +++ b/OsmAnd/res/values-sc/strings.xml @@ -89,7 +89,7 @@ Preferèntzias de càrculu de s’àndala Imposta sa lestresa de sa boghe de sintetizatzione vocale (TTS). Lestresa de sa boghe - Càrculu lestru de s’àndala fallidu (%s), rinviu a su càlculu lentu. + Càrculu lestru de s’àndala fallidu (%s), rinviu a su càrculu lentu. Istuda su carculu de s’àndala in duas fases pro s’impreu in màchina. Istuta su carculu cumplessu de s’àndala Pidagnu @@ -1540,7 +1540,7 @@ Dislinda s’artària de su veìculu permìtida pro sos caminos. Non faghet rugrare sas fronteras intre sos istados Recàrculu intelligente de s’àndala - Pro biàgios longos, torra a carculare petzi su cantu initziale de s’àndala. + Torra a carculare petzi su cantu initziale de s’àndala. Podet èssere impreadu pro biàgios longos. Disabilitadu Essi Coloratzione a segunda de sa casta (afiliatzione) de àndala @@ -3824,8 +3824,8 @@ Subraiscrie sa rasta Sarva comente una rasta noa Fùrria s\'àndala - Sa rasta intrea at a èssere torrada a calculare impreende su profilu ischertadu. - Petzi su segmentu imbeniente at a èssere torradu a calculare impreende su profilu ischertadu. + Sa rasta intrea at a èssere torrada a carculare impreende su profilu ischertadu. + Petzi su segmentu imbeniente at a èssere torradu a carculare impreende su profilu ischertadu. Ischerta comente connètere sos puntos: cun una lìnia reta o calculende un\'àndala intre issos comente dislindadu inoghe in suta. Rasta intrea Segmentu imbeniente @@ -3913,4 +3913,22 @@ \nPodes amministrare e annullare sos abbonamentos tuos intrende in sas impostatziones de AppGallery tuas. Èvita sos martzapiedis Èvita sos martzapiedis + Ite b\'at de nou + Isvilupu + Datos de OsmAnd Live + Datos de OsmAnd Live + Càrculu de s\'àndala a duas fases pro sa navigatzione in màchina. + Isvilupu de sos trasportos pùblicos nativos + Cola a su càrculu de s\'àndala de sos trasportos pùblicos Java (seguru) + Intra cun OAuth pro impreare sas funtzionalidades osmedit + Intra impreende OAuth + Iscantzella su getone OAuth de OpenStreetMap + Essida fata chene problemas + Su documentu est giai importadu in OsmAnd + Imprea un\'algoritmu de càrculu de s\'àndala A* a duas fases + Pro sa ghia de motoislitas cun caminos e rastas dedicados. + Datos %1$s a disponimentu in sos caminos ebbia. Depes carculare un\'àndala impreende \"Àndala intre puntos\" pro los otènnere. + Gràficu + Iseta su càrculu nou de s\'àndala. +\nSu gràficu at a èssere a disponimentu a pustis de su càrculu. \ No newline at end of file diff --git a/OsmAnd/res/values-sk/phrases.xml b/OsmAnd/res/values-sk/phrases.xml index db5f8d31a6..7dc2cc7a4c 100644 --- a/OsmAnd/res/values-sk/phrases.xml +++ b/OsmAnd/res/values-sk/phrases.xml @@ -3653,4 +3653,15 @@ Nie Áno Kancelária taxislužby + Postbank + DecoTurf + Podológia + Zdravotná špecializácia: pôrodníctvo (cisársky rez): nie + Zdravotná špecializácia: sociálna pediatria: nie + Sociálna pediatria + Zdravotná špecializácia: pôrodníctvo (prenatálne): nie + Pôrodníctvo (prenatálne) + Zdravotná špecializácia: pôrodníctvo (postnatálne): nie + Rašelinisko + Slatina \ No newline at end of file diff --git a/OsmAnd/res/values-sk/strings.xml b/OsmAnd/res/values-sk/strings.xml index dc0af642be..6575698d3a 100644 --- a/OsmAnd/res/values-sk/strings.xml +++ b/OsmAnd/res/values-sk/strings.xml @@ -3919,11 +3919,18 @@ Údaje OsmAnd Live Komplexný výpočet trasy Dvojfázový výpočet trasy pre navigáciu auta. - Natívny vývoj hromadnej dopravy + Natívna hromadná doprava (vo vývoji) Prepnúť na výpočet trasy hromadnej dopravy v Jave (bezpečné) Čo je nové Vykonať prihlásenie cez OAuth pre použitie funkcií upravovania OSM Prihlásiť pomocou OAuth Vymazať token OpenStreetMap OAuth Odhlásenie úspešné + Pre jazdu na snežnom vozidle po na to určených cestách. + Súbor je už importovaný v OsmAnd + Použiť dvojfázový algoritmus A* na výpočet trasy + Graf + Údaje %1$s sú dostupné len na cestách, pre ich získanie musíte vypočítať trasu pomocou “Trasa medzi bodmi”. + Počkajte na prepočet trasy. +\nGraf bude dostupný po prepočte. \ No newline at end of file diff --git a/OsmAnd/res/values-tr/phrases.xml b/OsmAnd/res/values-tr/phrases.xml index 16005d8a23..a4b87bee1b 100644 --- a/OsmAnd/res/values-tr/phrases.xml +++ b/OsmAnd/res/values-tr/phrases.xml @@ -269,7 +269,7 @@ Otoyol kavşağı Birleşim Dinlenme alanı - Su kaynağı + Su kuyusu Yangın musluğu Su işleri Tersane @@ -366,7 +366,7 @@ Depolama Çöp bertaraf Çöp tenekesi - Sanayi Bölgesi + Sanayi bölgesi Taş ocağı Üzüm bağı Meyve bahçesi @@ -841,7 +841,7 @@ Düden Şelale Irmak - Akış + Dere Nehrin akıntılı yeri Değerli taş Pelerin @@ -1068,7 +1068,7 @@ Paket servisi Kokteyller Mikro bira imalathanesi - Servis + Hizmet Kabul edilen atık Şömine Mevsimlik @@ -1184,7 +1184,7 @@ Tırmanma kayalığı Evet Tarihi tank - Kar aracı erişimi + Kar arabası erişimi Otobüs erişimi Karavan erişimi Motokaravan erişimi @@ -1344,7 +1344,7 @@ Ziraat malzemeleri Döşeme malzemeleri Nüfus - Yeraltı + Yer altı Çok katlı Standlar Duvar döngüleri @@ -2801,18 +2801,18 @@ Danışma (uyuşturucu): evet Danışma (bağımlılık): evet Danışma (bağımlılık): evet - Sağlık çalışanının işi: psikolog + Sağlık çalışanının rolü: psikolog İlk yardım çantası Duvar - Yeraltı + Yer altı Sağlık merkezi türü: sahra hastanesi Sağlık merkezi türü: laboratuvar Sağlık hizmeti: aşılama: hayır Sağlık hizmeti: aşılama: evet Sağlık hizmeti: danışma: hayır Sağlık hizmeti: danışma: evet - Sağlık hizmeti: hasta bakıcılık: hayır - Sağlık hizmeti: hasta bakıcılık: evet + Sağlık hizmeti: hemşirelik: hayır + Sağlık hizmeti: hemşirelik: evet Geleneksel Tibet Geleneksel Moğol Geleneksel Çin @@ -2915,4 +2915,219 @@ Şarküteri Turta Çay dükkanı + Sağlık çalışanının rolü: büyücü + Sağlık çalışanının rolü: teknisyen + Sağlık çalışanının rolü: doktor asistanı + Sağlık çalışanının rolü: terapist + Sağlık çalışanının rolü: podolog + Sağlık çalışanının rolü: doktor + Sağlık çalışanının rolü: sağlık görevlisi + Sağlık çalışanının rolü: hemşire + Sağlık çalışanının rolü: ebe + Sağlık çalışanının rolü: şifacı + Sağlık çalışanının rolü: asistan + Emme noktası + Sağlık tesisi türü: destek grubu evi + Sağlık tesisi türü: huzurevi + Sağlık merkezi türü: bölüm + Sağlık hizmeti: test: hayır + Sağlık hizmeti: test: evet + Sağlık hizmeti: destek: hayır + Sağlık hizmeti: destek: evet + Sağlık hizmeti: önleme: hayır + Sağlık hizmeti: önleme: evet + Sağlık hizmeti: çocuk bakımı: hayır + Sağlık hizmeti: çocuk bakımı: evet + Sağlık hizmeti: muayene: hayır + Sağlık hizmeti: muayene: evet + Sağlık merkezi türü: ilk yardım + Sağlık merkezi türü: dispanser + Sağlık merkezi türü: terapi + Sağlık merkezi türü: danışma merkezi + Tıbbi ofis + Sert serbest uçuş: hayır + Sert + Yelken kanatla uçuş: hayır + Yelken kanatla uçuş + Yamaç paraşütü: hayır + Yamaç paraşütü + Resmi: hayır + Resmi: evet + Eğitim alanı + Çekme alanı + Zirve iniş alanı + İniş alanı + Kalkış alanı + Düğme ile etkinleştirilir: hayır + Düğme ile etkinleştirilir: evet + Serbest uçuş (spor) + Patlama: aygıt + Patlama: savaş başlığı + Patlama: krater çapı + Patlama salvosu: bir salvo testinin ikinci veya daha sonraki patlaması + Patlama salvosu: bir salvo testinin ilk patlaması + Vücut dalgası büyüklüğü + Patlama merkezinin yüksekliği + Patlama boyu + Patlama deliği + Patlama gücü + Patlama amacı: sanayi uygulaması, toprak kaydırma + Patlama amacı: sanayi uygulaması + Patlama amacı: sanayi uygulaması, petrol uyarımı + Patlama amacı: sanayi uygulaması, sismik sondaj + Patlama amacı: sanayi uygulaması, kazı çalışması + Patlama amacı: temel bilim + Patlama amacı: barışçıl uygulamalar için araştırma + Patlama amacı: güvenlik deneyi + Patlama amacı: silah etkileri + Patlama amacı: nükleer silahlarla ilgili + Patlama serisi + Kod adı (ingilizce) + Patlama türü: su altı + Patlama türü: uzay (80 km\'nin üzerindeki yükseklik) + Patlama türü: atmosferik, roket veya füze + Patlama türü: krater patlaması (sığ yüzey altı) + Patlama türü: atmosferik, su yüzeyi, mavna + Patlama türü: atmosferik, yüzey + Patlama türü: atmosferik, balon + Patlama türü: atmosferik, yüzey, kule + Patlama türü: atmosferik, havadan atma + Patlama türü: atmosferik + Patlama türü: yeraltı, kuyu + Patlama: alan + Koruma türü + Bandy + Sokak dolabı + Ateş çukuru + Ana + Musluk tarzı: wsh + Yer altı + Sokak + Park yeri + Şerit + Çimen + Kaldırım + Musluk akış kapasitesi + Musluk sayısı + Musluk basıncı + Musluk çapı + Ebe ofisi + Hemşirelik hizmeti + Psikolog ofisi + Şifacı ofisi + Podolog ofisi + Terapist ofisi + Doktor ofisi + Yatarak tedavi hizmetleri: yalnızca + Yatarak tedavi hizmetleri: hayır + Yatarak tedavi hizmetleri: evet + Danışma (şiddet): hayır + Danışma (şiddet): evet + Danışma (kurban): hayır + Danışma (kurban): evet + Danışma (cinsel istismar): hayır + Danışma (cinsel istismar): evet + Danışma (cinsellik): hayır + Danışma (cinsellik): evet + Danışma (rehabilitasyon): hayır + Danışma (rehabilitasyon): evet + Danışma (beslenme): hayır + Danışma (beslenme): evet + Danışma (evlilik): hayır + Danışma (evlilik): evet + Danışma (göçmen): hayır + Danışma (göçmen): evet + Danışma (eğitim): hayır + Danışma (eğitim): evet + Danışma (kriz): hayır + Danışma (kriz): evet + Danışma (çift): hayır + Danışma (çift): evet + Danışma (çocuk rehberliği): hayır + Danışma (çocuk rehberliği): evet + Danışma (doğum öncesi): evet + Danışma (doğum öncesi): hayır + Uzay üssü + Doğaya salma: hayır + Doğaya salma: evet + Sahiplenme: hayır + Sahiplenme: evet + Sahibi + Çocuk kampı + Fotoğraf stüdyosu + Beslenme takviyeleri + Hayvan besleme yeri + Destek: kule + Destek: çatı + Destek: askıda + Destek: tavan + Destek: reklam panosu + Destek: zemin + Destek: kaide + Destek: ağaç + Destek: duvar + Destek: direk + Tarih ekranı: hayır + Tarih ekranı + Pompa istasyonu + Çıkış: biyogaz + Biyogazın çıkış gücü + Çıkış: vakum + Çıkış: basınçlı hava + Basınçlı havanın çıkış gücü + Çıkış: soğuk su + Çıkış: sıcak hava + Çıkış: buhar + Çıkış: sıcak su + Sıcak suyun çıkış gücü + Çıkış (elektrik): hayır + Çıkış: elektrik + Çıkış gücü + Gerilim + Sera bahçeciliği + Yer çekimi + Meteorolojik + Kamu kullanımı için ölçekler + Konuk yönergeleri: hayır + Konuk yönergeleri: evet + Uçuşa yasak zaman (serbest uçuş) + Serbest uçuş alanı yönlendirmesi: KB + Serbest uçuş alanı yönlendirmesi: B + Serbest uçuş alanı yönlendirmesi: GB + Serbest uçuş alanı yönlendirmesi: G + Serbest uçuş alanı yönlendirmesi: GD + Serbest uçuş alanı yönlendirmesi: D + Serbest uçuş alanı yönlendirmesi: KD + Serbest uçuş alanı yönlendirmesi: K + Birden çok aile + Aile + Topluluk + Şişelenmiş su + Su deposu + Su tankeri + Sondaj + Pompa + Akan su + Boru hattı + Su kuyusu + Su arıtma tabletleri + Ters osmoz + Klor + Görünürlük: alan + Tartan + Sosyal hizmetler + Sosyal güvenlik + Elektronik tamir: alet + Güneş saati + Dijital ekran + Analog ekran + Ekran: hayır + Ekran: evet + Evet + Su deposu + Dere + Kuru varil + Sütun + Gölet + Gölet \ No newline at end of file diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index 38dec6907b..54debf38a3 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -8,7 +8,7 @@ Konum günlüğü hizmetlerini kullanmak için \"Yolculuk kaydı\" eklentisini etkinleştirin (GPX günlüğü, çevrim içi izleme) Uzak hedefler için tahmini rotayı hesapla Lütfen GPS\'yi ayarlardan açık konuma getirin - Log servisi + Günlük kayıt hizmetleri Rota yok Varış Noktasını kaldır Varış noktası %1$s @@ -78,7 +78,7 @@ Uygulamayı güvenli modda çalıştırın (yerel kod yerine daha yavaş Android kullanarak). Güvenli kip Uygulama güvenli modda çalışıyor (\'Ayarlar\'dan kapatın). - OsmAnd arka plan hizmeti hala çalışıyor. Onu da durdur\? + OsmAnd arka plan hizmeti hala çalışıyor. O da durdurulsun mu\? Ses/Video verisi Navigasyonu durdurmak istediğinizden emin misiniz\? Hedefi (ve ara hedefleri) temizlemek istediğinizden emin misiniz\? @@ -633,7 +633,7 @@ Yerel sürüm %1$d/%2$d öge devre dışı bırakıldı. %1$d/%2$d öge silindi. - %1$d/%2$d öge aktifleştirildi. + %1$d/%2$d öge etkinleştirildi. Harita dosyalarını yönetin. Aktifleştir Pasifleştir @@ -641,7 +641,7 @@ Adres Verisi Toplu taşıma verileri Harita Verisi - Pasifleştir + Devre dışı Sesli uyarılar (TTS) Sesli uyarılar (kaydedilmiş) POI Verisi @@ -837,7 +837,7 @@ Konum sağlayıcı Ekran kapalıyken konumunuzu izler. Arka planda Osmand başlat - Arka plan navigasyon hizmeti açık olması bir konum sağlayıcı gerektirir. + Arka plan navigasyon hizmeti, bir konum sağlayıcının açık olmasını gerektirir. Süzgeci gizle Süzgeci göster Süzgeç @@ -881,7 +881,7 @@ Çevrim içi arama: Ev numarası, sokak, şehir Çevrim dışı arama Toplam uzaklık %1$s, seyahat süresi %2$d s %3$d dak. - Çevrim içi veya çevrim dışı navigasyon servisi. + Çevrim içi veya çevrim dışı navigasyon hizmeti. Bellek kartındaki depolama klasörüne erişilemiyor! {0} - {1} indir ? {0} için çevrim dışı veri zaten var ({1}). ({2}) güncellensin mi\? @@ -1065,7 +1065,7 @@ Tekrar deneyin Eski uyumsuz Wikipedia verileriniz var. Arşivle\? Ekstra Wikipedia verilerini indir (%1$s MB)\? - Konum servisi kapalı. Aç\? + Konum hizmeti kapalı. Açılsın mı\? Ayrıntıları göster Devre dışı Ev kapı numaraları @@ -1457,18 +1457,18 @@ OSM notları (çevrim içi) Haritayı hareket ettirmek için bir izleme topu aygıtı kullanın. İzleme topu kullan - Arka plan servisi tarafından kullanılan uyanma aralığı: - Arka plan servisi tarafından kullanılan konum yöntemi: - Baş + Arka plan hizmeti tarafından kullanılan uyanma aralığı: + Arka plan hizmeti tarafından kullanılan konum yöntemi: + Düz gidin Ekran yönlendirme Hiçbir adres belirlenmedi Sesli uyarılar Durakta ulaşım aracı ara Harita yönlendirme \'\'{0}\'\' indeks sürümü desteklenmemektedir - OsmAnd çevrim dışı navigasyon deneysel bir özelliktir ve yaklaşık 20 km\'den daha uzun mesafelerde çalışmaz. -\n -\nNavigasyon geçici olarak çevrim içi CloudMade servisine geçti. + OsmAnd çevrim dışı navigasyon deneysel bir özelliktir ve yaklaşık 20 km\'den daha uzun mesafelerde çalışmaz. +\n +\nNavigasyon geçici olarak çevrim içi CloudMade hizmetine geçti. OsmAnd Yükle - {1} {2} üzerinden {0} MB \? Yakınlaştırma {0} indirmek {1} fayans ({2} MB) Önceden yükleme için en fazla yakınlaştırma @@ -1595,7 +1595,7 @@ Baskça Belarusça Boşnakça - Noktalar arasındaki rotayı hesaplamak + Noktalar arasındaki güzergahı hesapla Konumu sürekli ortada tut Ses Çeşitli @@ -1791,7 +1791,7 @@ Svahili dili İbranice İleri - GPX kaydı açıksa, izleme verilerini belirtilen bir web servisine gönder. + GPX kaydı açıksa, izleme verilerini belirtilen bir web hizmetine gönder. Online izleme (GPX gerekli) Online izleme başlat Online izleme durdurun @@ -2094,7 +2094,7 @@ Silindi Değiştirildi Eklendi - İşaretleyici %s aktifleştirildi. + %s işaretleyicisi etkinleştirildi. İçerik menüsünü açmadan, harita üzerinde bir belirtecin üzerine bas ve aktif belirteçlerin üzerine sürükle. \'Tek basış\' aktif Not alın! @@ -2282,7 +2282,7 @@ Geçilmiş-olanı göster Geçilmiş-olanı gizle Haritada harita işaretleyicilerine olan uzaklık ve yönün nasıl belirtileceğini seçin: - Harita oryantasyon eşiği + Harita yönlendirme eşiği Harita yönünün \'Hareket yönü\'nden \'Pusula yönü\'ne geçiş hızını aşağıdan seçin. Rota noktaları olarak kaydet Öncesinde nokta ekle @@ -2362,7 +2362,7 @@ Gezin İçerikler Sonuç - Seyehat + Seyahat rehberleri Toplam Tüm başlangıç noktalarını temizle Grup silindi @@ -3578,7 +3578,7 @@ Haritadaki Wikipedia makalelerinin dillerini seçin. Makaleyi okurken kullanılabilir herhangi bir dile geçiş yapın. Bazı Wikipedia makaleleri sizin dilinizde mevcut olmayabilir. Kantonca - Güney Min + Güney Mince Yorubaca Varayca Özbekçe @@ -3591,7 +3591,7 @@ Nepalce Napolice Birmanca - Minangkabau dili + Minangkabauca Malgaşça Kırgızca Kazakça @@ -3881,4 +3881,11 @@ OAuth ile oturum aç OpenStreetMap OAuth belirtecini temizle Oturum kapatma başarılı + Dosya zaten OsmAnd\'da içe aktarıldı + 2 aşamalı A* yönlendirme algoritması kullan + Ayrılmış yollar ve parkurlarla kar arabası sürüşü için. + Grafik + %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. \ No newline at end of file diff --git a/OsmAnd/res/values-tzm/strings.xml b/OsmAnd/res/values-tzm/strings.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/OsmAnd/res/values-tzm/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/OsmAnd/res/values-uk/strings.xml b/OsmAnd/res/values-uk/strings.xml index 8ba9a9906f..f93a7c0156 100644 --- a/OsmAnd/res/values-uk/strings.xml +++ b/OsmAnd/res/values-uk/strings.xml @@ -1147,7 +1147,7 @@ %1$s \nТрек %2$s З’єднатись - Розрахувати маршрут між точками + Обчислити маршрут між точками Відображати позицію завжди в центрі Голос Різне @@ -2293,17 +2293,17 @@ OsmAnd (OSM Automated Navigation Directions) — застосунок для мап і навігації з доступом до безкоштовних глобальних високоякісних даних OpenStreetMap (OSM). \n \nНасолоджуйтесь голосовою та візуальною навігацією, переглядом цікавих точок (англ. POI), створенням та керуванням GPX-треками, використовуючи відображення горизонталей та даних про висоту (за допомогою зовнішнього втулка), вибором між режимами автомобіліста, велосипедиста й пішохода, редагуванням OSM та ще багато чим іншим. - GPS навігація -\n • Вибір між автономним режимом (без зборів за роумінг за кордоном) або через Інтернет (швидше) -\n • Покрокові голосові підказки доставить Вас до місця призначення (записані чи синтезовані голоси) -\n • Повторна маршрутизація кожен раз після відхилення від маршруту -\n • Смуги руху, назви вулиць і приблизний час прибуття допоможуть Вам на шляху -\n • Для того, щоб зробити Вашу подорож безпечнішою, режим дня/ночі автоматично перемикається -\n • Відображення обмежень швидкості та попередження про її перевищення -\n • Мапа масштабується відповідно до Вашої швидкості -\n • Шукати місця за адресою, типом (наприклад, паркування, ресторан, готель, заправна станція, музей) чи географічними координатами -\n • Підтримка проміжних точок на Вашому маршруті -\n • Запис свого власного GPX-треку чи вивантаження готового і слідування за ним + GPS навігація +\n • Вибір між автономним режимом (без зборів за роумінг за кордоном) або через Інтернет (швидше) +\n • Покрокові голосові підказки доставить Вас до місця призначення (записані чи синтезовані голоси) +\n • Повторна маршрутизація кожен раз після відхилення від маршруту +\n • Смуги руху, назви вулиць і приблизний час прибуття допоможуть Вам на шляху +\n • Для того, щоб зробити Вашу подорож безпечнішою, режим дня/ночі автоматично перемикається +\n • Показ обмежень швидкості та попередження про її перевищення +\n • Мапа масштабується відповідно до Вашої швидкості +\n • Шукати місця за адресою, типом (наприклад, паркування, ресторан, готель, заправна станція, музей) чи географічними координатами +\n • Підтримка проміжних точок на Вашому маршруті +\n • Запис свого власного GPX-треку чи вивантаження готового і слідування за ним \n Мапа \n• Відображає POI (цікаві точки) навколо вас @@ -3922,4 +3922,11 @@ Уникати пішохідних шляхів Уникати пішохідних шляхів Розробка + Файл уже імпортовано до OsmAnd + Використання 2-фазного A* алгоритму маршрутизації + Для їзди на снігоходах із відведеними дорогами та трасами. + Графік + %1$s дані доступні лише для доріг, вам потрібно обчислити маршрут за допомогою «Маршрут між точками», щоб отримати його. + Дочекайтеся переобчислення маршруту. +\nГрафік буде доступний після переобчислення. \ 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 e36aa4414d..add05fe4a7 100644 --- a/OsmAnd/res/values-zh-rTW/phrases.xml +++ b/OsmAnd/res/values-zh-rTW/phrases.xml @@ -3561,20 +3561,20 @@ 危險 難度分類 放射治療 - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + n/c + n/c* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* 燃燒塔 已刪除的物件 攀岩 diff --git a/OsmAnd/res/values-zh-rTW/strings.xml b/OsmAnd/res/values-zh-rTW/strings.xml index a5eb5c24fb..d84b6fcd82 100644 --- a/OsmAnd/res/values-zh-rTW/strings.xml +++ b/OsmAnd/res/values-zh-rTW/strings.xml @@ -3921,4 +3921,11 @@ 透過 OAuth 登入 清除 OpenStreetMap OAuth 權杖 成功登出 + 適用於有專用道路與軌道的雪地摩托車駕駛。 + 檔案已在 OsmAnd 匯入 + 使用 2 相的 A* 路線演算法 + 圖表 + %1$s 資料僅供道路使用,您需要使用「兩點間的路線」來計算路線。 + 等待路線重新計算。 +\n重新計算後即可使用圖表。 \ No newline at end of file diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index b58d25a781..04b193b923 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -3935,20 +3935,20 @@ Tram Ferry - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + n/c + n/c* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Gas flare;Flare stack Deleted object @@ -4206,7 +4206,7 @@ Yes No - Signal to find the pole + Internet access: customers Only when walking is allowed Contrasted Primitive @@ -4259,5 +4259,10 @@ LNG + GPX point + + Rooftop + Sheds + Layby diff --git a/OsmAnd/res/values/sizes.xml b/OsmAnd/res/values/sizes.xml index f4eb13a389..69f711b6fb 100644 --- a/OsmAnd/res/values/sizes.xml +++ b/OsmAnd/res/values/sizes.xml @@ -324,6 +324,7 @@ 8dp 18dp 71dp + 120dp 40dp 48dp 18dp diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index e2e3d0a5ea..3132eb5971 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -13,6 +13,10 @@ --> Gap %1$s — %2$s + Wait for the route recalculation.\nGraph will be available after recalculation. + %1$s data available only on the roads, you need to calculate a route using “Route between points” to get it. + Graph + Use 2-phase A* routing algorithm File is already imported in OsmAnd Logout successful Clear OpenStreetMap OAuth token @@ -22,7 +26,6 @@ Native Public Transport development Recalculates only the initial part of the route. Can be used for long trips. Two-phase routing for car navigation. - Complex routing OsmAnd Live data OsmAnd Live data Development @@ -1018,6 +1021,7 @@ Bookmark Hide full description Show full description + For snowmobile driving with dedicated roads and tracks. For off-road driving based on \'Topo\' style and for use with green satellite images as an underlay. Reduced main road thickness, increased thickness of tracks, paths, bicycle and other routes. For nautical navigation. Features buoys, lighthouses, riverways, sea lanes and marks, harbors, seamark services, and depth contours. For skiing. Features pistes, ski-lifts, cross country tracks, etc. Dims secondary map objects. diff --git a/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java b/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java index b9ea4a62cf..eb806a5a33 100644 --- a/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java +++ b/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java @@ -8,9 +8,9 @@ import androidx.annotation.NonNull; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import java.io.IOException; import java.util.HashMap; @@ -66,13 +66,8 @@ public class AccessibilityPlugin extends OsmandPlugin { } @Override - public Class getSettingsActivity() { - return SettingsAccessibilityActivity.class; - } - - @Override - public Class getSettingsFragment() { - return AccessibilitySettingsFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.ACCESSIBILITY_SETTINGS; } @Override diff --git a/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java b/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java index 781a10bf41..609c43fcff 100644 --- a/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java +++ b/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java @@ -4,6 +4,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.widget.ImageView; @@ -13,21 +15,24 @@ import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.access.AccessibilityMode; import net.osmand.plus.access.RelativeDirectionStyle; +import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet.CopyAppModePrefsListener; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; -import net.osmand.plus.settings.fragments.OnPreferenceChanged; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet.ResetAppModePrefsListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.OnPreferenceChanged; import net.osmand.plus.settings.preferences.ListPreferenceEx; import net.osmand.plus.settings.preferences.SwitchPreferenceEx; +import static net.osmand.plus.activities.PluginInfoFragment.PLUGIN_INFO; + public class AccessibilitySettingsFragment extends BaseSettingsFragment implements OnPreferenceChanged, CopyAppModePrefsListener, ResetAppModePrefsListener { private static final String ACCESSIBILITY_OPTIONS = "accessibility_options"; @@ -36,6 +41,8 @@ public class AccessibilitySettingsFragment extends BaseSettingsFragment implemen private AccessibilityStateChangeListener accessibilityListener; + boolean showSwitchProfile = false; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -47,6 +54,28 @@ public class AccessibilitySettingsFragment extends BaseSettingsFragment implemen } } }; + + Bundle args = getArguments(); + if (args != null) { + showSwitchProfile = args.getBoolean(PLUGIN_INFO, false); + } + } + + @Override + protected void createToolbar(LayoutInflater inflater, View view) { + super.createToolbar(inflater, view); + + View switchProfile = view.findViewById(R.id.profile_button); + if (switchProfile != null) { + AndroidUiHelper.updateVisibility(switchProfile, showSwitchProfile); + } + } + + @Override + public Bundle buildArguments() { + Bundle args = super.buildArguments(); + args.putBoolean(PLUGIN_INFO, showSwitchProfile); + return args; } @Override diff --git a/OsmAnd/src/net/osmand/access/SettingsAccessibilityActivity.java b/OsmAnd/src/net/osmand/access/SettingsAccessibilityActivity.java deleted file mode 100644 index 719ed05863..0000000000 --- a/OsmAnd/src/net/osmand/access/SettingsAccessibilityActivity.java +++ /dev/null @@ -1,128 +0,0 @@ -package net.osmand.access; - - -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceGroup; -import android.preference.PreferenceScreen; - -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.access.AccessibilityMode; -import net.osmand.plus.access.RelativeDirectionStyle; -import net.osmand.plus.activities.SettingsBaseActivity; - -public class SettingsAccessibilityActivity extends SettingsBaseActivity { - - private ListPreference accessibilityModePreference; - private ListPreference directionStylePreference; - private ListPreference autoannouncePeriodPreference; - - - @Override - public void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - getToolbar().setTitle(R.string.shared_string_accessibility); - PreferenceScreen grp = getPreferenceScreen(); - - String[] entries = new String[AccessibilityMode.values().length]; - for (int i = 0; i < entries.length; i++) { - entries[i] = AccessibilityMode.values()[i].toHumanString(getMyApplication()); - } - accessibilityModePreference = createListPreference(settings.ACCESSIBILITY_MODE, entries, AccessibilityMode.values(), - R.string.accessibility_mode, R.string.accessibility_mode_descr); - accessibilityModePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - private final OnPreferenceChangeListener committer = accessibilityModePreference.getOnPreferenceChangeListener(); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (committer != null) - committer.onPreferenceChange(preference, newValue); - updateAllSettings(); - return true; - } - }); - addSpeechRateSetting(grp); - - grp.addPreference(accessibilityModePreference); - PreferenceCategory cat = new PreferenceCategory(this); - cat.setKey("accessibility_options"); - cat.setTitle(R.string.accessibility_options); - cat.setEnabled(getMyApplication().accessibilityEnabled()); - grp.addPreference(cat); - - entries = new String[RelativeDirectionStyle.values().length]; - for (int i = 0; i < entries.length; i++) { - entries[i] = RelativeDirectionStyle.values()[i].toHumanString(getMyApplication()); - } - directionStylePreference = createListPreference(settings.DIRECTION_STYLE, entries, RelativeDirectionStyle.values(), - R.string.settings_direction_style, R.string.settings_direction_style_descr); - directionStylePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - private final OnPreferenceChangeListener committer = directionStylePreference.getOnPreferenceChangeListener(); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (committer != null) - committer.onPreferenceChange(preference, newValue); - updateAllSettings(); - return true; - } - }); - cat.addPreference(directionStylePreference); - - cat.addPreference(createCheckBoxPreference(settings.ACCESSIBILITY_SMART_AUTOANNOUNCE, R.string.access_smart_autoannounce, - R.string.access_smart_autoannounce_descr)); - - final int[] seconds = new int[] {5, 10, 15, 20, 30, 45, 60, 90}; - final int[] minutes = new int[] {2, 3, 5}; - autoannouncePeriodPreference = createTimeListPreference(settings.ACCESSIBILITY_AUTOANNOUNCE_PERIOD, seconds, minutes, 1000, - R.string.access_autoannounce_period, R.string.access_autoannounce_period_descr); - autoannouncePeriodPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - private final OnPreferenceChangeListener committer = autoannouncePeriodPreference.getOnPreferenceChangeListener(); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (committer != null) - committer.onPreferenceChange(preference, newValue); - updateAllSettings(); - return true; - } - }); - cat.addPreference(autoannouncePeriodPreference); - cat.addPreference(createCheckBoxPreference(settings.DIRECTION_AUDIO_FEEDBACK, R.string.access_direction_audio_feedback, - R.string.access_direction_audio_feedback_descr)); - cat.addPreference(createCheckBoxPreference(settings.DIRECTION_HAPTIC_FEEDBACK, R.string.access_direction_haptic_feedback, - R.string.access_direction_haptic_feedback_descr)); - - } - - - protected void addSpeechRateSetting(PreferenceGroup grp) { - Float[] sprValues = new Float[] {0.5f, 0.75f, 1f, 1.25f, 1.5f, 2f} ; - String[] sprNames = new String[sprValues.length]; - for(int i = 0; i < sprNames.length; i++) { - sprNames[i] = (int)(sprValues[i] * 100) + " %"; - } - grp.addPreference(createListPreference(settings.SPEECH_RATE, sprNames, sprValues, R.string.speech_rate, R.string.speech_rate_descr)); - } - - - - public void updateAllSettings() { - super.updateAllSettings(); - PreferenceCategory accessibilityOptions = ((PreferenceCategory)(getPreferenceScreen().findPreference("accessibility_options"))); - if (accessibilityOptions != null) - accessibilityOptions.setEnabled(getMyApplication().accessibilityEnabled()); - if(accessibilityModePreference != null) { - accessibilityModePreference.setSummary(getString(R.string.accessibility_mode_descr) + " [" + settings.ACCESSIBILITY_MODE.get().toHumanString(getMyApplication()) + "]"); - } - if(directionStylePreference != null) { - directionStylePreference.setSummary(getString(R.string.settings_direction_style_descr) + " [" + settings.DIRECTION_STYLE.get().toHumanString(getMyApplication()) + "]"); - } - if(autoannouncePeriodPreference != null) { - autoannouncePeriodPreference.setSummary(getString(R.string.access_autoannounce_period_descr) + " [" + autoannouncePeriodPreference.getEntry() + "]"); - } - } - -} diff --git a/OsmAnd/src/net/osmand/aidl/ConnectedApp.java b/OsmAnd/src/net/osmand/aidl/ConnectedApp.java index be25ab931d..bbfa4f89b8 100644 --- a/OsmAnd/src/net/osmand/aidl/ConnectedApp.java +++ b/OsmAnd/src/net/osmand/aidl/ConnectedApp.java @@ -11,9 +11,6 @@ import android.widget.CompoundButton; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - import net.osmand.AndroidUtils; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.ContextMenuItem; @@ -21,15 +18,12 @@ import net.osmand.plus.OsmandApplication; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.layers.AidlMapLayer; import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget; import net.osmand.util.Algorithms; -import java.lang.reflect.Type; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -39,7 +33,6 @@ public class ConnectedApp implements Comparable { public static final String AIDL_LAYERS_PREFIX = "aidl_layers_"; public static final String AIDL_WIDGETS_PREFIX = "aidl_widgets_"; - public static final String AIDL_MARGINS_PREFIX = "aidl_margins_"; static final String AIDL_OBJECT_ID = "aidl_object_id"; static final String AIDL_PACKAGE_NAME = "aidl_package_name"; @@ -62,7 +55,6 @@ public class ConnectedApp implements Comparable { private Map mapLayers = new ConcurrentHashMap<>(); private CommonPreference layersPref; - private CommonPreference marginsPref; private String pack; private String name; @@ -76,7 +68,6 @@ public class ConnectedApp implements Comparable { this.pack = pack; this.enabled = enabled; layersPref = app.getSettings().registerBooleanPreference(AIDL_LAYERS_PREFIX + pack, true).cache(); - marginsPref = app.getSettings().registerStringPreference(AIDL_MARGINS_PREFIX + pack, null).cache(); } public boolean isEnabled() { @@ -134,45 +125,6 @@ public class ConnectedApp implements Comparable { } } - void updateMapMargins(@NonNull MapActivity mapActivity) { - String marginsJson = marginsPref.get(); - if (marginsJson != null) { - Type type = new TypeToken>() { - }.getType(); - Map margins = new Gson().fromJson(marginsJson, type); - if (margins != null) { - Integer leftMargin = margins.get("left"); - Integer topMargin = margins.get("top"); - Integer rightMargin = margins.get("right"); - Integer bottomMargin = margins.get("bottom"); - - int left = leftMargin != null ? leftMargin : 0; - int top = topMargin != null ? topMargin : 0; - int right = rightMargin != null ? rightMargin : 0; - int bottom = bottomMargin != null ? bottomMargin : 0; - - mapActivity.setMargins(left, top, right, bottom); - return; - } - } - mapActivity.setMargins(0, 0, 0, 0); - } - - public void setMargins(@NonNull MapActivity mapActivity, String appModeKey, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { - ApplicationMode mode = ApplicationMode.valueOfStringKey(appModeKey, null); - if (mode != null) { - Map margins = new HashMap<>(); - margins.put("left", leftMargin); - margins.put("top", topMargin); - margins.put("right", rightMargin); - margins.put("bottom", bottomMargin); - - String marginsJson = new Gson().toJson(margins); - marginsPref.setModeValue(mode, marginsJson); - updateMapMargins(mapActivity); - } - } - void registerLayerContextMenu(final ContextMenuAdapter menuAdapter, final MapActivity mapActivity) { ContextMenuAdapter.ItemClickListener listener = new ContextMenuAdapter.OnRowItemClick() { diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java index 6329d5db2e..21ed74a437 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java @@ -81,8 +81,10 @@ import net.osmand.plus.routing.VoiceRouter; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.settings.backend.ExportSettingsType; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.layers.AidlMapLayer; @@ -138,7 +140,7 @@ import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_DIRECTION_NAME; import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_DIRECTION_TURN; import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_DISTANCE; import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_IMMINENT; -import static net.osmand.plus.settings.backend.SettingsHelper.REPLACE_KEY; +import static net.osmand.plus.settings.backend.backup.SettingsHelper.REPLACE_KEY; public class OsmandAidlApi { @@ -200,13 +202,6 @@ public class OsmandAidlApi { private static final String AIDL_QUICK_ACTION_NUMBER = "aidl_quick_action_number"; private static final String AIDL_LOCK_STATE = "lock_state"; - private static final String AIDL_SET_MAP_MARGINS = "set_map_margins"; - private static final String AIDL_APP_MODE = "app_mode"; - private static final String AIDL_LEFT_MARGIN = "left_margin"; - private static final String AIDL_TOP_MARGIN = "top_margin"; - private static final String AIDL_RIGHT_MARGIN = "right_margin"; - private static final String AIDL_BOTTOM_MARGIN = "bottom_margin"; - private static final ApplicationMode DEFAULT_PROFILE = ApplicationMode.CAR; private static final ApplicationMode[] VALID_PROFILES = new ApplicationMode[]{ @@ -257,7 +252,6 @@ public class OsmandAidlApi { registerHideSqliteDbFileReceiver(mapActivity); registerExecuteQuickActionReceiver(mapActivity); registerLockStateReceiver(mapActivity); - registerMapMarginsReceiver(mapActivity); initOsmandTelegram(); app.getAppCustomization().addListener(mapActivity); this.mapActivity = mapActivity; @@ -379,28 +373,10 @@ public class OsmandAidlApi { registerReceiver(addMapWidgetReceiver, mapActivity, AIDL_ADD_MAP_WIDGET); } - private void registerMapMarginsReceiver(MapActivity mapActivity) { - final WeakReference mapActivityRef = new WeakReference<>(mapActivity); - BroadcastReceiver addMapWidgetReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - MapActivity mapActivity = mapActivityRef.get(); - String appModeKey = intent.getStringExtra(AIDL_APP_MODE); - String packName = intent.getStringExtra(AIDL_PACKAGE_NAME); - if (mapActivity != null && appModeKey != null && packName != null) { - ConnectedApp connectedApp = connectedApps.get(packName); - if (connectedApp != null) { - int leftMargin = intent.getIntExtra(AIDL_LEFT_MARGIN, 0); - int topMargin = intent.getIntExtra(AIDL_TOP_MARGIN, 0); - int bottomMargin = intent.getIntExtra(AIDL_RIGHT_MARGIN, 0); - int rightMargin = intent.getIntExtra(AIDL_BOTTOM_MARGIN, 0); - - connectedApp.setMargins(mapActivity, appModeKey, leftMargin, topMargin, rightMargin, bottomMargin); - } - } - } - }; - registerReceiver(addMapWidgetReceiver, mapActivity, AIDL_SET_MAP_MARGINS); + boolean setMapMargins(int left, int top, int right, int bottom, @Nullable List appModeKeys) { + app.getAppCustomization().setMapMargins(left, top, right, bottom, appModeKeys); + app.getAppCustomization().updateMapMargins(mapActivity); + return true; } private void registerAddContextMenuButtonsReceiver(MapActivity mapActivity) { @@ -919,12 +895,6 @@ public class OsmandAidlApi { } } - public void updateMapMargins(@NonNull MapActivity mapActivity) { - for (ConnectedApp connectedApp : connectedApps.values()) { - connectedApp.updateMapMargins(mapActivity); - } - } - private void refreshMap() { Intent intent = new Intent(); intent.setAction(AIDL_REFRESH_MAP); @@ -2330,19 +2300,6 @@ public class OsmandAidlApi { return true; } - public boolean setMapMargins(String packName, String appModeKey, int leftMargin, int topMargin, int bottomMargin, int rightMargin) { - Intent intent = new Intent(); - intent.setAction(AIDL_SET_MAP_MARGINS); - intent.putExtra(AIDL_PACKAGE_NAME, packName); - intent.putExtra(AIDL_APP_MODE, appModeKey); - intent.putExtra(AIDL_LEFT_MARGIN, leftMargin); - intent.putExtra(AIDL_TOP_MARGIN, topMargin); - intent.putExtra(AIDL_RIGHT_MARGIN, bottomMargin); - intent.putExtra(AIDL_BOTTOM_MARGIN, rightMargin); - app.sendBroadcast(intent); - return true; - } - public boolean exportProfile(String appModeKey, List settingsTypesKeys) { ApplicationMode appMode = ApplicationMode.valueOfStringKey(appModeKey, null); if (app != null && appMode != null) { @@ -2350,8 +2307,8 @@ public class OsmandAidlApi { for (String key : settingsTypesKeys) { settingsTypes.add(ExportSettingsType.valueOf(key)); } - List settingsItems = new ArrayList<>(); - settingsItems.add(new SettingsHelper.ProfileSettingsItem(app, appMode)); + List settingsItems = new ArrayList<>(); + settingsItems.add(new ProfileSettingsItem(app, appMode)); File exportDir = app.getSettings().getExternalStorageDirectory(); String fileName = appMode.toHumanString(); SettingsHelper settingsHelper = app.getSettingsHelper(); @@ -2362,6 +2319,14 @@ public class OsmandAidlApi { return false; } + public boolean isFragmentOpen() { + return mapActivity.isFragmentVisible(); + } + + public boolean isMenuOpen() { + return mapActivity.getContextMenu().isVisible(); + } + private static class FileCopyInfo { long startTime; long lastAccessTime; diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java index 7c69be1e94..78e7875d24 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java @@ -1327,9 +1327,30 @@ public class OsmandAidlServiceV2 extends Service implements AidlCallbackListener public boolean setMapMargins(MapMarginsParams params) { try { OsmandAidlApi api = getApi("setMapMargins"); - String packName = getCallingAppPackName(); - return api != null && api.setMapMargins(packName, params.getAppModeKey(), params.getLeftMargin(), - params.getTopMargin(), params.getBottomMargin(), params.getRightMargin()); + return api != null && api.setMapMargins(params.getLeftMargin(), params.getTopMargin(), + params.getBottomMargin(), params.getRightMargin(), params.getAppModesKeys()); + } catch (Exception e) { + handleException(e); + return false; + } + } + + @Override + public boolean isFragmentOpen() { + try { + OsmandAidlApi api = getApi("isFragmentOpen"); + return api != null && api.isFragmentOpen(); + } catch (Exception e) { + handleException(e); + return false; + } + } + + @Override + public boolean isMenuOpen() { + try { + OsmandAidlApi api = getApi("isMenuOpen"); + return api != null && api.isMenuOpen(); } catch (Exception e) { handleException(e); return false; diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index 78364460a7..58690a9a3b 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -55,7 +55,7 @@ import net.osmand.plus.routing.TransportRoutingHelper; import net.osmand.plus.search.QuickSearchHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.views.corenative.NativeCoreContext; import net.osmand.plus.voice.CommandPlayer; import net.osmand.plus.voice.CommandPlayerException; diff --git a/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java b/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java index e0cb743a64..9127fa0aef 100644 --- a/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java +++ b/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java @@ -18,15 +18,15 @@ import net.osmand.data.LatLon; import net.osmand.map.ITileSource; import net.osmand.map.WorldRegion; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.AvoidRoadsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.MapSourcesSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.PluginSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.PoiUiFiltersSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ProfileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.QuickActionsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsCollectListener; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.AvoidRoadsSettingsItem; +import net.osmand.plus.settings.backend.backup.MapSourcesSettingsItem; +import net.osmand.plus.settings.backend.backup.PluginSettingsItem; +import net.osmand.plus.settings.backend.backup.PoiUiFiltersSettingsItem; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.QuickActionsSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsCollectListener; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.download.DownloadActivityType; import net.osmand.plus.download.DownloadIndexesThread; import net.osmand.plus.download.DownloadResources; diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index 1a48650a4d..b57d94dc3a 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -75,7 +75,7 @@ import net.osmand.plus.search.QuickSearchHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.voice.CommandPlayer; import net.osmand.plus.wikivoyage.data.TravelDbHelper; import net.osmand.router.GeneralRouter; diff --git a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java index 4e9ca3d420..6d353713a0 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java +++ b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java @@ -46,10 +46,9 @@ import net.osmand.plus.quickaction.QuickActionType; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmandPreference; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.skimapsplugin.SkiMapsPlugin; import net.osmand.plus.srtmplugin.SRTMPlugin; import net.osmand.plus.views.OsmandMapTileView; @@ -112,11 +111,7 @@ public abstract class OsmandPlugin { return app.getUIUtilities().getIcon(getLogoResourceId()); } - public Class getSettingsActivity() { - return null; - } - - public Class getSettingsFragment() { + public SettingsScreenType getSettingsScreenType() { return null; } @@ -499,6 +494,9 @@ public abstract class OsmandPlugin { public void mapActivityResume(MapActivity activity) { } + public void mapActivityResumeOnTop(MapActivity activity) { + } + public void mapActivityPause(MapActivity activity) { } @@ -752,6 +750,12 @@ public abstract class OsmandPlugin { } } + public static void onMapActivityResumeOnTop(MapActivity activity) { + for (OsmandPlugin plugin : getEnabledPlugins()) { + plugin.mapActivityResumeOnTop(activity); + } + } + public static void onMapActivityPause(MapActivity activity) { for (OsmandPlugin plugin : getEnabledPlugins()) { plugin.mapActivityPause(activity); diff --git a/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java b/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java index 2d4a0c96c5..b7b3044f97 100644 --- a/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java @@ -173,7 +173,7 @@ public class HelpActivity extends OsmandActionBarActivity implements AdapterView contextMenuAdapter.addItem(createItem(R.string.versions_item, NULL_ID, "feature_articles/changes.html")); contextMenuAdapter.addItem(createItem(R.string.what_is_new, NULL_ID, - "feature_articles/blog.html")); + "feature_articles/osmand-3-8-released.html")); String releasedate = ""; if (!this.getString(R.string.app_edition).equals("")) { diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 122eb9ad7e..8cb6e832c5 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -74,9 +74,6 @@ import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndLocationSimulation; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.helpers.DayNightHelper; -import net.osmand.plus.settings.backend.CommonPreference; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.TargetPointsHelper; import net.osmand.plus.TargetPointsHelper.TargetPoint; @@ -87,6 +84,7 @@ 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.dashboard.DashBaseFragment; import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.dialogs.CrashBottomSheetDialogFragment; import net.osmand.plus.dialogs.ImportGpxBottomSheetDialogFragment; @@ -100,13 +98,14 @@ import net.osmand.plus.download.ui.DataStoragePlaceDialogFragment; import net.osmand.plus.firstusage.FirstUsageWelcomeFragment; import net.osmand.plus.firstusage.FirstUsageWizardFragment; import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.DayNightHelper; import net.osmand.plus.helpers.DiscountHelper; -import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.helpers.IntentHelper; import net.osmand.plus.helpers.LockHelper; import net.osmand.plus.helpers.LockHelper.LockUIAdapter; import net.osmand.plus.helpers.ScrollHelper; import net.osmand.plus.helpers.ScrollHelper.OnScrollEventListener; +import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.mapcontextmenu.AdditionalActionsBottomSheetDialogFragment; import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.mapcontextmenu.MenuController; @@ -119,6 +118,7 @@ import net.osmand.plus.measurementtool.GpxData; import net.osmand.plus.measurementtool.MeasurementEditingContext; import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.SnapTrackWarningFragment; +import net.osmand.plus.osmedit.OsmEditingFragment; import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; import net.osmand.plus.routepreparationmenu.ChooseRouteFragment; @@ -132,7 +132,9 @@ import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchTab; import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchType; import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmAndAppCustomization.OsmAndAppCustomizationListener; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.settings.fragments.ConfigureProfileFragment; @@ -885,6 +887,13 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven settings.USE_SYSTEM_SCREEN_TIMEOUT.addListener(useSystemScreenTimeoutListener); } + @Override + public void onTopResumedActivityChanged(boolean isTopResumedActivity) { + if (isTopResumedActivity) { + OsmandPlugin.onMapActivityResumeOnTop(this); + } + } + public void applyScreenOrientation() { if (settings.MAP_SCREEN_ORIENTATION.get() != getRequestedOrientation()) { setRequestedOrientation(settings.MAP_SCREEN_ORIENTATION.get()); @@ -1029,15 +1038,23 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven } public boolean isMapVisible() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment.isVisible()) { - return false; - } + if (isFragmentVisible()) { + return false; } return AndroidUtils.isActivityNotDestroyed(this) && settings.MAP_ACTIVITY_ENABLED.get() && !dashboardOnMap.isVisible(); } + public boolean isFragmentVisible() { + for (Fragment fragment : getSupportFragmentManager().getFragments()) { + if (!(fragment instanceof DashBaseFragment) && fragment.isVisible() + || dashboardOnMap.isVisible()) { + return true; + } + } + return false; + } + private void restartApp() { AlertDialog.Builder bld = new AlertDialog.Builder(this); bld.setMessage(R.string.storage_permission_restart_is_required); @@ -1421,7 +1438,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven }); getMapView().refreshMap(true); applyScreenOrientation(); - app.getAidlApi().updateMapMargins(this); + app.getAppCustomization().updateMapMargins(this); } public void updateNavigationBarColor() { @@ -2200,6 +2217,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(GpxApproximationFragment.TAG); } + public OsmEditingFragment getOsmEditingFragment() { + return getFragment(SettingsScreenType.OPEN_STREET_MAP_EDITING.fragmentName); + } + public SnapTrackWarningFragment getSnapTrackWarningBottomSheet() { return getFragment(SnapTrackWarningFragment.TAG); } diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java index d9137ee2d6..294b382ca4 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java @@ -974,10 +974,7 @@ public class MapActivityActions implements DialogProvider { @Override public boolean onContextMenuClick(ArrayAdapter adapter, int itemId, int pos, boolean isChecked, int[] viewCoordinates) { app.logEvent("drawer_plugins_open"); - Intent newIntent = new Intent(mapActivity, mapActivity.getMyApplication().getAppCustomization() - .getPluginsActivity()); - newIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - mapActivity.startActivity(newIntent); + PluginsFragment.showInstance(mapActivity.getSupportFragmentManager()); return true; } }).createItem()); diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginActivity.java b/OsmAnd/src/net/osmand/plus/activities/PluginActivity.java deleted file mode 100644 index e367f3cd8e..0000000000 --- a/OsmAnd/src/net/osmand/plus/activities/PluginActivity.java +++ /dev/null @@ -1,241 +0,0 @@ -package net.osmand.plus.activities; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.appcompat.content.res.AppCompatResources; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; - -import net.osmand.AndroidUtils; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; -import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog; -import net.osmand.plus.download.DownloadIndexesThread; -import net.osmand.plus.srtmplugin.SRTMPlugin; - -public class PluginActivity extends OsmandActionBarActivity implements DownloadIndexesThread.DownloadEvents, PluginInstalledBottomSheetDialog.PluginStateListener { - private static final String TAG = "PluginActivity"; - public static final String EXTRA_PLUGIN_ID = "plugin_id"; - - private OsmandPlugin plugin; - - @Override - protected void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - - Intent intent = getIntent(); - if (intent == null || !intent.hasExtra(EXTRA_PLUGIN_ID)) { - Log.e(TAG, "Required extra '" + EXTRA_PLUGIN_ID + "' is missing"); - finish(); - return; - } - String pluginId = intent.getStringExtra(EXTRA_PLUGIN_ID); - if (pluginId == null) { - Log.e(TAG, "Extra '" + EXTRA_PLUGIN_ID + "' is null"); - finish(); - return; - } - for (OsmandPlugin plugin : OsmandPlugin.getAvailablePlugins()) { - if (!plugin.getId().equals(pluginId)) - continue; - - this.plugin = plugin; - break; - } - if (plugin == null) { - Log.e(TAG, "Plugin '" + EXTRA_PLUGIN_ID + "' not found"); - finish(); - return; - } - - setContentView(R.layout.plugin); - //noinspection ConstantConditions - getSupportActionBar().setTitle(plugin.getName()); - Drawable pluginImage = plugin.getAssetResourceImage(); - if (pluginImage != null) { - ImageView img = (ImageView) findViewById(R.id.plugin_image); - img.setImageDrawable(pluginImage); - } else { - findViewById(R.id.plugin_image_placeholder).setVisibility(View.VISIBLE); - } - - TextView descriptionView = (TextView) findViewById(R.id.plugin_description); - descriptionView.setText(plugin.getDescription()); - - boolean light = getMyApplication().getSettings().isLightContent(); - int linkTextColor = ContextCompat.getColor(this, - light ? R.color.ctx_menu_bottom_view_url_color_light : R.color.ctx_menu_bottom_view_url_color_dark); - - descriptionView.setLinkTextColor(linkTextColor); - descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); - AndroidUtils.removeLinkUnderline(descriptionView); - - Button settingsButton = (Button) findViewById(R.id.plugin_settings); - settingsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(PluginActivity.this, plugin.getSettingsActivity())); - } - }); - - CompoundButton enableDisableButton = (CompoundButton)findViewById( - R.id.plugin_enable_disable); - enableDisableButton.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (plugin.isActive() == isChecked) { - return; - } - - boolean ok = OsmandPlugin.enablePlugin(PluginActivity.this, (OsmandApplication)getApplication(), - plugin, isChecked); - if (!ok) { - return; - } - updateState(); - } - }); - Button getButton = (Button)findViewById(R.id.plugin_get); - getButton.setText(plugin.isPaid() ? R.string.get_plugin : R.string.shared_string_install); - getButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - if (plugin instanceof SRTMPlugin) { - FragmentManager fragmentManager = getSupportFragmentManager(); - if (fragmentManager != null) { - ChoosePlanDialogFragment.showHillshadeSrtmPluginInstance(fragmentManager); - } - } else { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL()))); - } - } catch (Exception e) { - //ignored - } - } - }); - - updateState(); - } - - @Override - protected void onResume() { - super.onResume(); - OsmandApplication app = getMyApplication(); - OsmandPlugin.checkInstalledMarketPlugins(app, this); - app.getDownloadThread().setUiActivity(this); - updateState(); - } - - @Override - protected void onPause() { - super.onPause(); - getMyApplication().getDownloadThread().resetUiActivity(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); - switch (itemId) { - case android.R.id.home: - finish(); - return true; - - } - return false; - } - - @SuppressLint("NewApi") - private void updateState() { - CompoundButton enableDisableButton = (CompoundButton)findViewById( - R.id.plugin_enable_disable); - Button getButton = (Button)findViewById(R.id.plugin_get); - Button settingsButton = (Button)findViewById(R.id.plugin_settings); - settingsButton.setCompoundDrawablesWithIntrinsicBounds( - getMyApplication().getUIUtilities().getThemedIcon(R.drawable.ic_action_settings), - null, null, null); - View installHeader = findViewById(R.id.plugin_install_header); - - if (plugin.needsInstallation()) { - getButton.setVisibility(View.VISIBLE); - enableDisableButton.setVisibility(View.GONE); - settingsButton.setVisibility(View.GONE); - installHeader.setVisibility(View.VISIBLE); - View worldGlobeIcon = installHeader.findViewById(R.id.ic_world_globe); - Drawable worldGlobeDrawable = getMyApplication().getUIUtilities().getThemedIcon( - R.drawable.ic_world_globe_dark); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - worldGlobeIcon.setBackground(worldGlobeDrawable); - } else { - //noinspection deprecation - worldGlobeIcon.setBackgroundDrawable(worldGlobeDrawable); - } - } else { - getButton.setVisibility(View.GONE); - enableDisableButton.setVisibility(View.VISIBLE); - enableDisableButton.setChecked(plugin.isActive()); - - final Class settingsActivity = plugin.getSettingsActivity(); - if (settingsActivity == null || !plugin.isActive()) { - settingsButton.setVisibility(View.GONE); - } else { - settingsButton.setVisibility(View.VISIBLE); - } - - installHeader.setVisibility(View.GONE); - } - } - - // DownloadEvents - @Override - public void newDownloadIndexes() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).newDownloadIndexes(); - } - } - } - - @Override - public void downloadInProgress() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadInProgress(); - } - } - } - - @Override - public void downloadHasFinished() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadHasFinished(); - } - } - } - - @Override - public void onPluginStateChanged(OsmandPlugin plugin) { - updateState(); - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java b/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java new file mode 100644 index 0000000000..7deb85768c --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java @@ -0,0 +1,258 @@ +package net.osmand.plus.activities; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.activity.OnBackPressedCallback; +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.AndroidUtils; +import net.osmand.PlatformUtil; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; +import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog.PluginStateListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; +import net.osmand.plus.srtmplugin.SRTMPlugin; + +import org.apache.commons.logging.Log; + +public class PluginInfoFragment extends BaseOsmAndFragment implements PluginStateListener { + + private static final Log log = PlatformUtil.getLog(PluginInfoFragment.class); + + private static final String TAG = PluginInfoFragment.class.getName(); + + public static final String EXTRA_PLUGIN_ID = "plugin_id"; + public static final String PLUGIN_INFO = "plugin_info"; + + private OsmandPlugin plugin; + private OsmandApplication app; + + private View mainView; + private boolean nightMode; + + @Override + public int getStatusBarColorId() { + return nightMode ? R.color.status_bar_color_dark : R.color.status_bar_color_light; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FragmentActivity activity = requireMyActivity(); + activity.getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + public void handleOnBackPressed() { + dismiss(); + } + }); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + app = requireMyApplication(); + + Bundle args = getArguments(); + if (args == null || !args.containsKey(EXTRA_PLUGIN_ID)) { + log.error("Required extra '" + EXTRA_PLUGIN_ID + "' is missing"); + return null; + } + String pluginId = args.getString(EXTRA_PLUGIN_ID); + if (pluginId == null) { + log.error("Extra '" + EXTRA_PLUGIN_ID + "' is null"); + return null; + } + plugin = OsmandPlugin.getPlugin(pluginId); + if (plugin == null) { + log.error("Plugin '" + EXTRA_PLUGIN_ID + "' not found"); + return null; + } + + Context context = requireContext(); + nightMode = !app.getSettings().isLightContent(); + LayoutInflater themedInflater = UiUtilities.getInflater(context, nightMode); + mainView = themedInflater.inflate(R.layout.plugin, container, false); + AndroidUtils.addStatusBarPadding21v(context, mainView); + + TextView toolbarTitle = mainView.findViewById(R.id.toolbar_title); + toolbarTitle.setText(plugin.getName()); + + ImageView closeButton = mainView.findViewById(R.id.close_button); + closeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Activity activity = getMyActivity(); + if (activity != null) { + activity.onBackPressed(); + } + } + }); + UiUtilities.rotateImageByLayoutDirection(closeButton, AndroidUtils.getLayoutDirection(app)); + + Drawable pluginImage = plugin.getAssetResourceImage(); + if (pluginImage != null) { + ImageView img = mainView.findViewById(R.id.plugin_image); + img.setImageDrawable(pluginImage); + } else { + mainView.findViewById(R.id.plugin_image_placeholder).setVisibility(View.VISIBLE); + } + + TextView descriptionView = mainView.findViewById(R.id.plugin_description); + descriptionView.setText(plugin.getDescription()); + + int linkTextColorId = nightMode ? R.color.ctx_menu_bottom_view_url_color_dark : R.color.ctx_menu_bottom_view_url_color_light; + int linkTextColor = ContextCompat.getColor(context, linkTextColorId); + + descriptionView.setLinkTextColor(linkTextColor); + descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); + AndroidUtils.removeLinkUnderline(descriptionView); + + Button settingsButton = mainView.findViewById(R.id.plugin_settings); + settingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + FragmentActivity activity = getActivity(); + if (activity != null) { + SettingsScreenType settingsScreenType = plugin.getSettingsScreenType(); + if (settingsScreenType != null) { + Bundle args = new Bundle(); + args.putBoolean(PLUGIN_INFO, true); + BaseSettingsFragment.showInstance(activity, settingsScreenType, null, args); + } + } + } + }); + + CompoundButton enableDisableButton = mainView.findViewById(R.id.plugin_enable_disable); + enableDisableButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (plugin.isActive() == isChecked) { + return; + } + + boolean ok = OsmandPlugin.enablePlugin(getActivity(), app, plugin, isChecked); + if (!ok) { + return; + } + updateState(); + } + }); + Button getButton = mainView.findViewById(R.id.plugin_get); + getButton.setText(plugin.isPaid() ? R.string.get_plugin : R.string.shared_string_install); + getButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + if (plugin instanceof SRTMPlugin) { + FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); + if (fragmentManager != null) { + ChoosePlanDialogFragment.showHillshadeSrtmPluginInstance(fragmentManager); + } + } else { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL()))); + } + } catch (Exception e) { + //ignored + } + } + }); + + updateState(); + return mainView; + } + + @Override + public void onResume() { + super.onResume(); + OsmandPlugin.checkInstalledMarketPlugins(app, getActivity()); + updateState(); + } + + private void updateState() { + CompoundButton enableDisableButton = mainView.findViewById(R.id.plugin_enable_disable); + Button getButton = mainView.findViewById(R.id.plugin_get); + Button settingsButton = mainView.findViewById(R.id.plugin_settings); + settingsButton.setCompoundDrawablesWithIntrinsicBounds(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_settings), null, null, null); + View installHeader = mainView.findViewById(R.id.plugin_install_header); + + if (plugin.needsInstallation()) { + getButton.setVisibility(View.VISIBLE); + enableDisableButton.setVisibility(View.GONE); + settingsButton.setVisibility(View.GONE); + installHeader.setVisibility(View.VISIBLE); + View worldGlobeIcon = installHeader.findViewById(R.id.ic_world_globe); + Drawable worldGlobeDrawable = app.getUIUtilities().getThemedIcon(R.drawable.ic_world_globe_dark); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + worldGlobeIcon.setBackground(worldGlobeDrawable); + } else { + worldGlobeIcon.setBackgroundDrawable(worldGlobeDrawable); + } + } else { + getButton.setVisibility(View.GONE); + enableDisableButton.setVisibility(View.VISIBLE); + enableDisableButton.setChecked(plugin.isActive()); + + if (plugin.getSettingsScreenType() == null || !plugin.isActive()) { + settingsButton.setVisibility(View.GONE); + } else { + settingsButton.setVisibility(View.VISIBLE); + } + installHeader.setVisibility(View.GONE); + } + } + + @Override + public void onPluginStateChanged(OsmandPlugin plugin) { + updateState(); + } + + public void dismiss() { + FragmentActivity activity = getActivity(); + if (activity != null) { + try { + activity.getSupportFragmentManager().popBackStack(TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); + } catch (Exception e) { + log.error(e); + } + } + } + + public static boolean showInstance(FragmentManager fragmentManager, OsmandPlugin plugin) { + try { + Bundle args = new Bundle(); + args.putString(EXTRA_PLUGIN_ID, plugin.getId()); + + PluginInfoFragment fragment = new PluginInfoFragment(); + fragment.setArguments(args); + fragmentManager.beginTransaction() + .add(R.id.fragmentContainer, fragment, TAG) + .addToBackStack(TAG) + .commitAllowingStateLoss(); + return true; + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginsActivity.java b/OsmAnd/src/net/osmand/plus/activities/PluginsActivity.java deleted file mode 100644 index a0e731c7bc..0000000000 --- a/OsmAnd/src/net/osmand/plus/activities/PluginsActivity.java +++ /dev/null @@ -1,296 +0,0 @@ -package net.osmand.plus.activities; - -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.TypedArray; -import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; - -import net.osmand.AndroidUtils; -import net.osmand.aidl.ConnectedApp; -import net.osmand.plus.CustomOsmandPlugin; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog; -import net.osmand.plus.download.DownloadIndexesThread; - -import java.util.ArrayList; - -public class PluginsActivity extends OsmandListActivity implements DownloadIndexesThread.DownloadEvents, PluginInstalledBottomSheetDialog.PluginStateListener { - - public static final int ACTIVE_PLUGINS_LIST_MODIFIED = 1; - - private boolean listModified = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - getMyApplication().applyTheme(this); - super.onCreate(savedInstanceState); - setContentView(R.layout.plugins); - getSupportActionBar().setTitle(R.string.plugins_screen); - setListAdapter(new PluginsListAdapter()); - } - - @Override - public PluginsListAdapter getListAdapter() { - return (PluginsListAdapter) super.getListAdapter(); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Object tag = view.getTag(); - if (tag instanceof OsmandPlugin) { - Intent intent = new Intent(this, PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, ((OsmandPlugin) tag).getId()); - startActivity(intent); - } else if (tag instanceof ConnectedApp) { - switchEnabled((ConnectedApp) tag); - } - } - - @Override - protected void onResume() { - super.onResume(); - OsmandApplication app = getMyApplication(); - OsmandPlugin.checkInstalledMarketPlugins(app, this); - app.getDownloadThread().setUiActivity(this); - getListAdapter().notifyDataSetChanged(); - } - - @Override - protected void onPause() { - super.onPause(); - getMyApplication().getDownloadThread().resetUiActivity(this); - } - - private void enableDisablePlugin(OsmandPlugin plugin, boolean enable) { - OsmandApplication app = getMyApplication(); - if (OsmandPlugin.enablePlugin(this, app, plugin, enable)) { - if (!listModified) { - setResult(ACTIVE_PLUGINS_LIST_MODIFIED); - listModified = true; - } - getListAdapter().notifyDataSetChanged(); - } - } - - private void switchEnabled(@NonNull ConnectedApp app) { - getMyApplication().getAidlApi().switchEnabled(app); - getListAdapter().notifyDataSetChanged(); - } - - // DownloadEvents - @Override - public void newDownloadIndexes() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).newDownloadIndexes(); - } - } - } - - @Override - public void downloadInProgress() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadInProgress(); - } - } - } - - @Override - public void downloadHasFinished() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadHasFinished(); - } - } - } - - @Override - public void onPluginStateChanged(OsmandPlugin plugin) { - getListAdapter().notifyDataSetChanged(); - } - - protected class PluginsListAdapter extends ArrayAdapter { - PluginsListAdapter() { - super(PluginsActivity.this, R.layout.plugins_list_item, new ArrayList<>()); - addAll(getMyApplication().getAidlApi().getConnectedApps()); - addAll(OsmandPlugin.getVisiblePlugins()); - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View view = convertView; - if (view == null) { - view = getLayoutInflater().inflate(R.layout.plugins_list_item, parent, false); - } - - final Object item = getItem(position); - - boolean active = false; - int logoContDescId = R.string.shared_string_disable; - String name = ""; - boolean isLightTheme = getMyApplication().getSettings().isLightContent(); - - ImageButton pluginLogo = (ImageButton) view.findViewById(R.id.plugin_logo); - ImageView pluginOptions = (ImageView) view.findViewById(R.id.plugin_options); - TextView pluginDescription = (TextView) view.findViewById(R.id.plugin_description); - - if (item instanceof ConnectedApp) { - final ConnectedApp app = (ConnectedApp) item; - active = app.isEnabled(); - if (!active) { - logoContDescId = R.string.shared_string_enable; - } - name = app.getName(); - pluginDescription.setText(R.string.third_party_application); - pluginLogo.setImageDrawable(app.getIcon()); - pluginLogo.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - switchEnabled(app); - } - }); - pluginOptions.setVisibility(View.GONE); - pluginOptions.setOnClickListener(null); - view.setTag(app); - } else if (item instanceof OsmandPlugin) { - final OsmandPlugin plugin = (OsmandPlugin) item; - active = plugin.isActive(); - if (!active) { - logoContDescId = plugin.needsInstallation() - ? R.string.access_shared_string_not_installed : R.string.shared_string_enable; - } - name = plugin.getName(); - pluginDescription.setText(plugin.getDescription()); - - boolean light = getMyApplication().getSettings().isLightContent(); - int linkTextColor = ContextCompat.getColor(PluginsActivity.this, - light ? R.color.ctx_menu_bottom_view_url_color_light : R.color.ctx_menu_bottom_view_url_color_dark); - - pluginDescription.setLinkTextColor(linkTextColor); - pluginDescription.setMovementMethod(LinkMovementMethod.getInstance()); - AndroidUtils.removeLinkUnderline(pluginDescription); - - OsmandApplication app = getMyApplication(); - int color = AndroidUtils.getColorFromAttr(PluginsActivity.this, R.attr.list_background_color); - pluginLogo.setImageDrawable(UiUtilities.tintDrawable(plugin.getLogoResource(), color)); - pluginLogo.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (plugin.isActive() || !plugin.needsInstallation()) { - enableDisablePlugin(plugin, !plugin.isActive()); - } - } - }); - pluginOptions.setVisibility(View.VISIBLE); - pluginOptions.setImageDrawable(getMyApplication().getUIUtilities().getThemedIcon(R.drawable.ic_overflow_menu_white)); - pluginOptions.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showOptionsMenu(v, plugin); - } - }); - view.setTag(plugin); - } - - pluginLogo.setContentDescription(getString(logoContDescId)); - if (active) { - pluginLogo.setBackgroundResource(isLightTheme ? R.drawable.bg_plugin_logo_enabled_light : R.drawable.bg_plugin_logo_enabled_dark); - } else { - TypedArray attributes = getTheme().obtainStyledAttributes(new int[] {R.attr.bg_plugin_logo_disabled}); - pluginLogo.setBackgroundDrawable(attributes.getDrawable(0)); - attributes.recycle(); - } - - TextView pluginName = (TextView) view.findViewById(R.id.plugin_name); - pluginName.setText(name); - pluginName.setContentDescription(name + " " + getString(active - ? R.string.item_checked - : R.string.item_unchecked)); - - return view; - } - } - - private void showOptionsMenu(View v, final OsmandPlugin plugin) { - final Class settingsActivity = plugin.getSettingsActivity(); - - final PopupMenu optionsMenu = new PopupMenu(this, v); - if (plugin.isActive() || !plugin.needsInstallation()) { - MenuItem enableDisableItem = optionsMenu.getMenu().add( - plugin.isActive() ? R.string.shared_string_disable - : R.string.shared_string_enable); - enableDisableItem - .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - enableDisablePlugin(plugin, !plugin.isActive()); - optionsMenu.dismiss(); - return true; - } - }); - } - - if (settingsActivity != null && plugin.isActive()) { - MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_settings); - settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - startActivity(new Intent(PluginsActivity.this, settingsActivity)); - optionsMenu.dismiss(); - return true; - } - }); - } - - if (plugin instanceof CustomOsmandPlugin) { - MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_delete); - settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - showDeletePluginDialog((CustomOsmandPlugin) plugin); - optionsMenu.dismiss(); - return true; - } - }); - } - - optionsMenu.show(); - } - - private void showDeletePluginDialog(final CustomOsmandPlugin plugin) { - AlertDialog.Builder builder = new AlertDialog.Builder(PluginsActivity.this); - builder.setTitle(getString(R.string.delete_confirmation_msg, plugin.getName())); - builder.setMessage(R.string.are_you_sure); - builder.setNegativeButton(R.string.shared_string_cancel, null); - builder.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - OsmandApplication app = getMyApplication(); - OsmandPlugin.removeCustomPlugin(app, plugin); - getListAdapter().remove(plugin); - } - }); - builder.show(); - } -} diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginsFragment.java b/OsmAnd/src/net/osmand/plus/activities/PluginsFragment.java new file mode 100644 index 0000000000..783e7791c5 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/activities/PluginsFragment.java @@ -0,0 +1,338 @@ +package net.osmand.plus.activities; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; +import net.osmand.aidl.ConnectedApp; +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog.PluginStateListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; + +import org.apache.commons.logging.Log; + +import java.util.ArrayList; + +public class PluginsFragment extends BaseOsmAndFragment implements PluginStateListener { + + private static final Log log = PlatformUtil.getLog(PluginsFragment.class); + + public static final String TAG = PluginsFragment.class.getName(); + + public static final String OPEN_PLUGINS = "open_plugins"; + + private OsmandApplication app; + private PluginsListAdapter adapter; + + private LayoutInflater themedInflater; + private boolean nightMode; + + @Override + public int getStatusBarColorId() { + return nightMode ? R.color.status_bar_color_dark : R.color.status_bar_color_light; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FragmentActivity activity = requireMyActivity(); + activity.getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + public void handleOnBackPressed() { + FragmentActivity activity = getActivity(); + if (activity instanceof MapActivity) { + dismissImmediate(); + MapActivity mapActivity = (MapActivity) activity; + mapActivity.launchPrevActivityIntent(); + } + } + }); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + app = requireMyApplication(); + nightMode = !app.getSettings().isLightContent(); + + themedInflater = UiUtilities.getInflater(getContext(), nightMode); + View view = themedInflater.inflate(R.layout.plugins, container, false); + AndroidUtils.addStatusBarPadding21v(getContext(), view); + + TextView toolbarTitle = view.findViewById(R.id.toolbar_title); + toolbarTitle.setText(R.string.plugins_screen); + + ImageView closeButton = view.findViewById(R.id.close_button); + closeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Activity activity = getMyActivity(); + if (activity != null) { + activity.onBackPressed(); + } + } + }); + UiUtilities.rotateImageByLayoutDirection(closeButton, AndroidUtils.getLayoutDirection(app)); + + adapter = new PluginsListAdapter(requireContext()); + + ListView listView = view.findViewById(R.id.plugins_list); + listView.setAdapter(adapter); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Object tag = view.getTag(); + if (tag instanceof OsmandPlugin) { + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginInfoFragment.showInstance(activity.getSupportFragmentManager(), (OsmandPlugin) tag); + } + } else if (tag instanceof ConnectedApp) { + switchEnabled((ConnectedApp) tag); + } + } + }); + return view; + } + + @Override + public void onResume() { + super.onResume(); + OsmandPlugin.checkInstalledMarketPlugins(app, getActivity()); + adapter.notifyDataSetChanged(); + } + + private void enableDisablePlugin(OsmandPlugin plugin, boolean enable) { + if (OsmandPlugin.enablePlugin(getActivity(), app, plugin, enable)) { + adapter.notifyDataSetChanged(); + } + } + + private void switchEnabled(@NonNull ConnectedApp connectedApp) { + app.getAidlApi().switchEnabled(connectedApp); + adapter.notifyDataSetChanged(); + } + + @Override + public void onPluginStateChanged(OsmandPlugin plugin) { + adapter.notifyDataSetChanged(); + } + + protected class PluginsListAdapter extends ArrayAdapter { + + PluginsListAdapter(Context context) { + super(context, R.layout.plugins_list_item, new ArrayList<>()); + addAll(app.getAidlApi().getConnectedApps()); + addAll(OsmandPlugin.getVisiblePlugins()); + } + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + View view = convertView; + if (view == null) { + view = themedInflater.inflate(R.layout.plugins_list_item, parent, false); + } + Context context = view.getContext(); + + boolean active = false; + int logoContDescId = R.string.shared_string_disable; + String name = ""; + + ImageButton pluginLogo = view.findViewById(R.id.plugin_logo); + ImageView pluginOptions = view.findViewById(R.id.plugin_options); + TextView pluginDescription = view.findViewById(R.id.plugin_description); + + Object item = getItem(position); + if (item instanceof ConnectedApp) { + final ConnectedApp app = (ConnectedApp) item; + active = app.isEnabled(); + if (!active) { + logoContDescId = R.string.shared_string_enable; + } + name = app.getName(); + pluginDescription.setText(R.string.third_party_application); + pluginLogo.setImageDrawable(app.getIcon()); + pluginLogo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchEnabled(app); + } + }); + pluginOptions.setVisibility(View.GONE); + pluginOptions.setOnClickListener(null); + view.setTag(app); + } else if (item instanceof OsmandPlugin) { + final OsmandPlugin plugin = (OsmandPlugin) item; + active = plugin.isActive(); + if (!active) { + logoContDescId = plugin.needsInstallation() + ? R.string.access_shared_string_not_installed : R.string.shared_string_enable; + } + name = plugin.getName(); + pluginDescription.setText(plugin.getDescription()); + + int linkTextColorId = nightMode ? R.color.ctx_menu_bottom_view_url_color_dark : R.color.ctx_menu_bottom_view_url_color_light; + int linkTextColor = ContextCompat.getColor(context, linkTextColorId); + + pluginDescription.setLinkTextColor(linkTextColor); + pluginDescription.setMovementMethod(LinkMovementMethod.getInstance()); + AndroidUtils.removeLinkUnderline(pluginDescription); + + int color = AndroidUtils.getColorFromAttr(context, R.attr.list_background_color); + pluginLogo.setImageDrawable(UiUtilities.tintDrawable(plugin.getLogoResource(), color)); + pluginLogo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (plugin.isActive() || !plugin.needsInstallation()) { + enableDisablePlugin(plugin, !plugin.isActive()); + } + } + }); + pluginOptions.setVisibility(View.VISIBLE); + pluginOptions.setImageDrawable(app.getUIUtilities().getThemedIcon(R.drawable.ic_overflow_menu_white)); + pluginOptions.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showOptionsMenu(v, plugin); + } + }); + view.setTag(plugin); + } + + pluginLogo.setContentDescription(getString(logoContDescId)); + if (active) { + pluginLogo.setBackgroundResource(nightMode ? R.drawable.bg_plugin_logo_enabled_dark : R.drawable.bg_plugin_logo_enabled_light); + } else { + TypedArray attributes = context.getTheme().obtainStyledAttributes(new int[] {R.attr.bg_plugin_logo_disabled}); + pluginLogo.setBackgroundDrawable(attributes.getDrawable(0)); + attributes.recycle(); + } + + TextView pluginName = view.findViewById(R.id.plugin_name); + pluginName.setText(name); + pluginName.setContentDescription(name + " " + getString(active + ? R.string.item_checked + : R.string.item_unchecked)); + + return view; + } + } + + private void showOptionsMenu(View view, final OsmandPlugin plugin) { + final PopupMenu optionsMenu = new PopupMenu(view.getContext(), view); + if (plugin.isActive() || !plugin.needsInstallation()) { + MenuItem enableDisableItem = optionsMenu.getMenu().add( + plugin.isActive() ? R.string.shared_string_disable + : R.string.shared_string_enable); + enableDisableItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + enableDisablePlugin(plugin, !plugin.isActive()); + optionsMenu.dismiss(); + return true; + } + }); + } + + final SettingsScreenType settingsScreenType = plugin.getSettingsScreenType(); + if (settingsScreenType != null && plugin.isActive()) { + MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_settings); + settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + FragmentActivity activity = getActivity(); + if (activity != null) { + BaseSettingsFragment.showInstance(activity, settingsScreenType); + } + optionsMenu.dismiss(); + return true; + } + }); + } + + if (plugin instanceof CustomOsmandPlugin) { + MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_delete); + settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + showDeletePluginDialog((CustomOsmandPlugin) plugin); + optionsMenu.dismiss(); + return true; + } + }); + } + + optionsMenu.show(); + } + + private void showDeletePluginDialog(final CustomOsmandPlugin plugin) { + Context context = getContext(); + if (context != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(getString(R.string.delete_confirmation_msg, plugin.getName())); + builder.setMessage(R.string.are_you_sure); + builder.setNegativeButton(R.string.shared_string_cancel, null); + builder.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + OsmandPlugin.removeCustomPlugin(app, plugin); + adapter.remove(plugin); + } + }); + builder.show(); + } + } + + public void dismissImmediate() { + FragmentActivity activity = getActivity(); + if (activity != null) { + try { + activity.getSupportFragmentManager().popBackStackImmediate(TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); + } catch (Exception e) { + log.error(e); + } + } + } + + public static boolean showInstance(FragmentManager fragmentManager) { + try { + PluginsFragment fragment = new PluginsFragment(); + fragmentManager.beginTransaction() + .add(R.id.fragmentContainer, fragment, TAG) + .addToBackStack(TAG) + .commitAllowingStateLoss(); + return true; + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java b/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java index 17559f0e9d..3509f99d65 100644 --- a/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java @@ -1,11 +1,9 @@ package net.osmand.plus.activities; -import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; @@ -59,33 +57,24 @@ public class SettingsActivity extends SettingsBaseActivity { } PreferenceCategory plugins = (PreferenceCategory) screen.findPreference("plugin_settings"); for(OsmandPlugin op : OsmandPlugin.getEnabledPlugins()) { - final Class sa = op.getSettingsActivity(); - if(sa != null) { - Preference preference = new Preference(this); - preference.setTitle(op.getName()); - preference.setKey(op.getId()); - preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - - @Override - public boolean onPreferenceClick(Preference preference) { - startActivity(new Intent(SettingsActivity.this, sa)); - return false; - } - }); - plugins.addPreference(preference); - } +// final Class sa = op.getSettingsActivity(); +// if(sa != null) { +// Preference preference = new Preference(this); +// preference.setTitle(op.getName()); +// preference.setKey(op.getId()); +// preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { +// +// @Override +// public boolean onPreferenceClick(Preference preference) { +// startActivity(new Intent(SettingsActivity.this, sa)); +// return false; +// } +// }); +// plugins.addPreference(preference); +// } } } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if ((requestCode == PLUGINS_SELECTION_REQUEST) && (resultCode == PluginsActivity.ACTIVE_PLUGINS_LIST_MODIFIED)) { - finish(); - startActivity(getIntent()); - } - } - @Override public boolean onPreferenceClick(Preference preference) { if (preference == general) { diff --git a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java index f2f31e3a26..a0594ffc66 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java @@ -96,13 +96,11 @@ public class AudioVideoNoteMenuController extends MenuController { @Override public Drawable getRightIcon() { - if (mRecording.isPhoto()) { - return getIcon(R.drawable.ic_action_photo_dark, R.color.audio_video_icon_color); - } else if (mRecording.isAudio()) { - return getIcon(R.drawable.ic_action_micro_dark, R.color.audio_video_icon_color); - } else { - return getIcon(R.drawable.ic_action_video_dark, R.color.audio_video_icon_color); + int iconId = AudioVideoNotesPlugin.getIconIdForRecordingFile(mRecording.getFile()); + if (iconId == -1) { + iconId = R.drawable.ic_action_photo_dark; } + return getIcon(iconId, R.color.audio_video_icon_color); } @NonNull diff --git a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java index a68a8bc749..c28b110c85 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java @@ -47,14 +47,11 @@ import net.osmand.PlatformUtil; import net.osmand.data.DataTileManager; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; -import net.osmand.plus.settings.backend.ApplicationMode; 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.settings.backend.CommonPreference; -import net.osmand.plus.settings.backend.OsmandPreference; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; @@ -66,12 +63,15 @@ import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.quickaction.QuickActionType; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; -import net.osmand.plus.views.layers.MapInfoLayer; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.CommonPreference; +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; -import net.osmand.plus.views.mapwidgets.widgetstates.WidgetState; +import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget; +import net.osmand.plus.views.mapwidgets.widgetstates.WidgetState; import net.osmand.util.Algorithms; import net.osmand.util.GeoPointParserUtil.GeoParsedPoint; import net.osmand.util.MapUtils; @@ -517,7 +517,18 @@ public class AudioVideoNotesPlugin extends OsmandPlugin { } return additional.toString(); } + } + public static int getIconIdForRecordingFile(@NonNull File file) { + String fileName = file.getName(); + if (fileName.endsWith(IMG_EXTENSION)) { + return R.drawable.ic_action_photo_dark; + } else if (fileName.endsWith(MPEG4_EXTENSION)) { + return R.drawable.ic_action_video_dark; + } else if (fileName.endsWith(THREEGP_EXTENSION)) { + return R.drawable.ic_action_micro_dark; + } + return -1; } // private static void initializeRemoteControlRegistrationMethods() { @@ -893,7 +904,7 @@ public class AudioVideoNotesPlugin extends OsmandPlugin { } @Override - public void mapActivityResume(MapActivity activity) { + public void mapActivityResumeOnTop(MapActivity activity) { this.mapActivity = activity; // ((AudioManager) activity.getSystemService(Context.AUDIO_SERVICE)).registerMediaButtonEventReceiver( // new ComponentName(activity, MediaRemoteControlReceiver.class)); @@ -1800,13 +1811,8 @@ public class AudioVideoNotesPlugin extends OsmandPlugin { } @Override - public Class getSettingsActivity() { - return SettingsAudioVideoActivity.class; - } - - @Override - public Class getSettingsFragment() { - return MultimediaNotesFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.MULTIMEDIA_NOTES; } @Override diff --git a/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java b/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java index 6996f7ee5b..4c1178ef61 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java @@ -13,6 +13,8 @@ import android.os.Build; import android.os.Bundle; import android.os.StatFs; import android.text.SpannableString; +import android.view.LayoutInflater; +import android.view.View; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; @@ -22,17 +24,18 @@ import androidx.preference.PreferenceViewHolder; import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet.CopyAppModePrefsListener; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet.ResetAppModePrefsListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.preferences.ListPreferenceEx; import net.osmand.plus.settings.preferences.SwitchPreferenceEx; import net.osmand.plus.widgets.style.CustomTypefaceSpan; @@ -43,6 +46,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import static net.osmand.plus.activities.PluginInfoFragment.PLUGIN_INFO; import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AUDIO_BITRATE_DEFAULT; import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_AUTO; import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_CONTINUOUS; @@ -66,6 +70,35 @@ public class MultimediaNotesFragment extends BaseSettingsFragment implements Cop private static final String RESET_TO_DEFAULT = "reset_to_default"; private static final String OPEN_NOTES = "open_notes"; + boolean showSwitchProfile = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args != null) { + showSwitchProfile = args.getBoolean(PLUGIN_INFO, false); + } + } + + @Override + protected void createToolbar(LayoutInflater inflater, View view) { + super.createToolbar(inflater, view); + + View switchProfile = view.findViewById(R.id.profile_button); + if (switchProfile != null) { + AndroidUiHelper.updateVisibility(switchProfile, showSwitchProfile); + } + } + + @Override + public Bundle buildArguments() { + Bundle args = super.buildArguments(); + args.putBoolean(PLUGIN_INFO, showSwitchProfile); + return args; + } + @Override protected void setupPreferences() { AudioVideoNotesPlugin plugin = OsmandPlugin.getPlugin(AudioVideoNotesPlugin.class); diff --git a/OsmAnd/src/net/osmand/plus/audionotes/SettingsAudioVideoActivity.java b/OsmAnd/src/net/osmand/plus/audionotes/SettingsAudioVideoActivity.java deleted file mode 100644 index 815b77108c..0000000000 --- a/OsmAnd/src/net/osmand/plus/audionotes/SettingsAudioVideoActivity.java +++ /dev/null @@ -1,334 +0,0 @@ -package net.osmand.plus.audionotes; - -import android.hardware.Camera; -import android.hardware.Camera.Parameters; -import android.media.CamcorderProfile; -import android.media.MediaRecorder; -import android.os.Build; -import android.os.Bundle; -import android.os.StatFs; -import android.preference.ListPreference; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; - -import net.osmand.AndroidUtils; -import net.osmand.PlatformUtil; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.activities.SettingsBaseActivity; - -import org.apache.commons.logging.Log; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AUDIO_BITRATE_DEFAULT; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_AUTO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_CONTINUOUS; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_EDOF; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_HIPERFOCAL; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_INFINITY; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_MACRO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_AUDIO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_CHOOSE; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_TAKEPICTURE; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_VIDEO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.cameraPictureSizeDefault; - -// camera picture size: -// support camera focus select: -//// - -public class SettingsAudioVideoActivity extends SettingsBaseActivity { - - private static final Log log = PlatformUtil.getLog(AudioVideoNotesPlugin.class); - - - @Override - public void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - getToolbar().setTitle(R.string.av_settings); - PreferenceScreen grp = getPreferenceScreen(); - AudioVideoNotesPlugin p = OsmandPlugin.getEnabledPlugin(AudioVideoNotesPlugin.class); - if (p != null) { - String[] entries; - Integer[] intValues; - - entries = new String[]{getString(R.string.av_def_action_choose), getString(R.string.av_def_action_audio), - getString(R.string.av_def_action_video), getString(R.string.av_def_action_picture)}; - intValues = new Integer[]{AV_DEFAULT_ACTION_CHOOSE, AV_DEFAULT_ACTION_AUDIO, AV_DEFAULT_ACTION_VIDEO, - AV_DEFAULT_ACTION_TAKEPICTURE}; - ListPreference defAct = createListPreference(p.AV_DEFAULT_ACTION, entries, intValues, R.string.av_widget_action, - R.string.av_widget_action_descr); - grp.addPreference(defAct); - - PreferenceCategory photo = new PreferenceCategory(this); - photo.setTitle(R.string.shared_string_photo); - grp.addPreference(photo); - - final Camera cam = openCamera(); - if (cam != null) { - // camera type settings - photo.addPreference(createCheckBoxPreference(p.AV_EXTERNAL_PHOTO_CAM, R.string.av_use_external_camera, - R.string.av_use_external_camera_descr)); - - Parameters parameters = cam.getParameters(); - createCameraPictureSizesPref(p, photo, parameters); - createCameraFocusModesPref(p, photo, parameters); - - // play sound on success photo - photo.addPreference(createCheckBoxPreference(p.AV_PHOTO_PLAY_SOUND, R.string.av_photo_play_sound, - R.string.av_photo_play_sound_descr)); - - cam.release(); - } - - // video settings - PreferenceCategory video = new PreferenceCategory(this); - video.setTitle(R.string.shared_string_video); - grp.addPreference(video); - - video.addPreference(createCheckBoxPreference(p.AV_EXTERNAL_RECORDER, R.string.av_use_external_recorder, - R.string.av_use_external_recorder_descr)); - -// entries = new String[] { "3GP", "MP4" }; -// intValues = new Integer[] { VIDEO_OUTPUT_3GP, VIDEO_OUTPUT_MP4 }; -// ListPreference lp = createListPreference(p.AV_VIDEO_FORMAT, entries, intValues, R.string.av_video_format, -// R.string.av_video_format_descr); -// video.addPreference(lp); - - List qNames = new ArrayList<>(); - List qValues = new ArrayList<>(); - if (Build.VERSION.SDK_INT < 11 || CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_LOW)) { - qNames.add(getString(R.string.av_video_quality_low)); - qValues.add(CamcorderProfile.QUALITY_LOW); - } - if (Build.VERSION.SDK_INT >= 11 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { - qNames.add("720 x 480 (480p)"); - qValues.add(CamcorderProfile.QUALITY_480P); - } - if (Build.VERSION.SDK_INT >= 11 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { - qNames.add("1280 x 720 (720p)"); - qValues.add(CamcorderProfile.QUALITY_720P); - } - if (Build.VERSION.SDK_INT >= 11 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { - qNames.add("1920 x 1080 (1080p)"); - qValues.add(CamcorderProfile.QUALITY_1080P); - } - if (Build.VERSION.SDK_INT >= 21 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) { - qNames.add("3840x2160 (2160p)"); - qValues.add(CamcorderProfile.QUALITY_2160P); - } - if (Build.VERSION.SDK_INT < 11 || CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)) { - qNames.add(getString(R.string.av_video_quality_high)); - qValues.add(CamcorderProfile.QUALITY_HIGH); - } - - ListPreference lp = createListPreference(p.AV_VIDEO_QUALITY, - qNames.toArray(new String[qNames.size()]), - qValues.toArray(new Integer[qValues.size()]), - R.string.av_video_quality, - R.string.av_video_quality_descr); - video.addPreference(lp); - - // Recorder Split settings - PreferenceCategory recSplit = new PreferenceCategory(this); - recSplit.setTitle(R.string.rec_split); - grp.addPreference(recSplit); - - recSplit.addPreference(createCheckBoxPreference(p.AV_RECORDER_SPLIT, R.string.rec_split_title, - R.string.rec_split_desc)); - - intValues = new Integer[]{1, 2, 3, 4, 5, 7, 10, 15, 20, 25, 30}; - entries = new String[intValues.length]; - int i = 0; - String minStr = getString(R.string.int_min); - for (int v : intValues) { - entries[i++] = String.valueOf(v) + " " + minStr; - } - lp = createListPreference(p.AV_RS_CLIP_LENGTH, entries, intValues, - R.string.rec_split_clip_length, - R.string.rec_split_clip_length_desc); - recSplit.addPreference(lp); - - File dir = getMyApplication().getAppPath("").getParentFile(); - long size = 0; - if (dir.canRead()) { - StatFs fs = new StatFs(dir.getAbsolutePath()); - size = ((long) fs.getBlockSize() * (long) fs.getBlockCount()) / (1 << 30); - } - if (size > 0) { - int value = 1; - ArrayList gbList = new ArrayList<>(); - while (value < size) { - gbList.add(value); - if (value < 5) { - value++; - } else { - value += 5; - } - } - if (value != size) { - gbList.add((int) size); - } - entries = new String[gbList.size()]; - intValues = new Integer[gbList.size()]; - i = 0; - for (int v : gbList) { - intValues[i] = v; - entries[i] = AndroidUtils.formatSize(this, v * (1l << 30)); - i++; - } - - lp = createListPreference(p.AV_RS_STORAGE_SIZE, entries, intValues, - R.string.rec_split_storage_size, - R.string.rec_split_storage_size_desc); - recSplit.addPreference(lp); - } - - // audio settings - PreferenceCategory audio = new PreferenceCategory(this); - audio.setTitle(R.string.shared_string_audio); - grp.addPreference(audio); - - entries = new String[]{"Default", "AAC"}; - intValues = new Integer[]{MediaRecorder.AudioEncoder.DEFAULT, MediaRecorder.AudioEncoder.AAC}; - lp = createListPreference(p.AV_AUDIO_FORMAT, entries, intValues, - R.string.av_audio_format, - R.string.av_audio_format_descr); - audio.addPreference(lp); - - entries = new String[]{"Default", "16 kbps", "32 kbps", "48 kbps", "64 kbps", "96 kbps", "128 kbps"}; - intValues = new Integer[]{AUDIO_BITRATE_DEFAULT, 16 * 1024, 32 * 1024, 48 * 1024, 64 * 1024, 96 * 1024, 128 * 1024}; - lp = createListPreference(p.AV_AUDIO_BITRATE, entries, intValues, - R.string.av_audio_bitrate, - R.string.av_audio_bitrate_descr); - audio.addPreference(lp); - } - } - - private void createCameraPictureSizesPref(AudioVideoNotesPlugin p, PreferenceCategory photo, Parameters parameters) { - String[] entries; - Integer[] intValues; - // Photo picture size - // get supported sizes - List psps = parameters.getSupportedPictureSizes(); - if (psps == null) { - return; - } - // list of megapixels of each resolution - List mpix = new ArrayList(); - // list of index each resolution in list, returned by getSupportedPictureSizes() - List picSizesValues = new ArrayList(); - // fill lists for sort - for (int index = 0; index < psps.size(); index++) { - mpix.add((psps.get(index)).width * (psps.get(index)).height); - picSizesValues.add(index); - } - // sort list for max resolution in begining of list - for (int i = 0; i < mpix.size(); i++) { - for (int j = 0; j < mpix.size() - i - 1; j++) { - if (mpix.get(j) < mpix.get(j + 1)) { - // change elements - int tmp = mpix.get(j + 1); - mpix.set(j + 1, mpix.get(j)); - mpix.set(j, tmp); - - tmp = picSizesValues.get(j + 1); - picSizesValues.set(j + 1, picSizesValues.get(j)); - picSizesValues.set(j, tmp); - } - } - } - // set default photo size to max resolution (set index of element with max resolution in List, returned by getSupportedPictureSizes() ) - cameraPictureSizeDefault = picSizesValues.get(0); - log.debug("onCreate() set cameraPictureSizeDefault=" + cameraPictureSizeDefault); - - List itemsPicSizes = new ArrayList(); - String prefix; - for (int index = 0; index < psps.size(); index++) { - float px = (float) ((psps.get(picSizesValues.get(index))).width * (psps.get(picSizesValues.get(index))).height); - if (px > 102400) // 100 K - { - px = px / 1048576; - prefix = "Mpx"; - } else { - px = px / 1024; - prefix = "Kpx"; - } - - itemsPicSizes.add((psps.get(picSizesValues.get(index))).width + - "x" + - (psps.get(picSizesValues.get(index))).height + - " ( " + - String.format("%.2f", px) + - " " + - prefix + - " )"); - } - log.debug("onCreate() set default size: width=" + psps.get(cameraPictureSizeDefault).width + " height=" - + psps.get(cameraPictureSizeDefault).height + " index in ps=" + cameraPictureSizeDefault); - - entries = itemsPicSizes.toArray(new String[itemsPicSizes.size()]); - intValues = picSizesValues.toArray(new Integer[picSizesValues.size()]); - if (entries.length > 0) { - ListPreference camSizes = createListPreference(p.AV_CAMERA_PICTURE_SIZE, entries, intValues, R.string.av_camera_pic_size, - R.string.av_camera_pic_size_descr); - photo.addPreference(camSizes); - } - } - - private void createCameraFocusModesPref(AudioVideoNotesPlugin p, PreferenceCategory photo, Parameters parameters) { - String[] entries; - Integer[] intValues; - // focus mode settings - // show in menu only suppoted modes - List sfm = parameters.getSupportedFocusModes(); - if (sfm == null) { - return; - } - List items = new ArrayList(); - List itemsValues = new ArrayList(); - // filtering known types for translate and set index - for (int index = 0; index < sfm.size(); index++) { - if (sfm.get(index).equals("auto")) { - items.add(getString(R.string.av_camera_focus_auto)); - itemsValues.add(AV_CAMERA_FOCUS_AUTO); - } else if (sfm.get(index).equals("fixed")) { - items.add(getString(R.string.av_camera_focus_hiperfocal)); - itemsValues.add(AV_CAMERA_FOCUS_HIPERFOCAL); - } else if (sfm.get(index).equals("edof")) { - items.add(getString(R.string.av_camera_focus_edof)); - itemsValues.add(AV_CAMERA_FOCUS_EDOF); - } else if (sfm.get(index).equals("infinity")) { - items.add(getString(R.string.av_camera_focus_infinity)); - itemsValues.add(AV_CAMERA_FOCUS_INFINITY); - } else if (sfm.get(index).equals("macro")) { - items.add(getString(R.string.av_camera_focus_macro)); - itemsValues.add(AV_CAMERA_FOCUS_MACRO); - } else if (sfm.get(index).equals("continuous-picture")) { - items.add(getString(R.string.av_camera_focus_continuous)); - itemsValues.add(AV_CAMERA_FOCUS_CONTINUOUS); - } - } - entries = items.toArray(new String[items.size()]); - intValues = itemsValues.toArray(new Integer[itemsValues.size()]); - if (entries.length > 0) { - ListPreference camFocus = createListPreference(p.AV_CAMERA_FOCUS_TYPE, entries, intValues, R.string.av_camera_focus, - R.string.av_camera_focus_descr); - photo.addPreference(camFocus); - } - } - - protected Camera openCamera() { - try { - return Camera.open(); - } catch (Exception e) { - log.error("Error open camera", e); - return null; - } - } -} diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java b/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java index 20affaa27a..15d222a980 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java @@ -14,11 +14,12 @@ import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.activities.PluginActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; import net.osmand.plus.dashboard.tools.DashFragmentData; import net.osmand.plus.development.OsmandDevelopmentPlugin; @@ -68,9 +69,10 @@ public class DashPluginsFragment extends DashBaseFragment { return new View.OnClickListener() { @Override public void onClick(View view) { - Intent intent = new Intent(getActivity(), PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, plugin.getId()); - startActivity(intent); + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginsFragment.showInstance(activity.getSupportFragmentManager()); + } closeDashboard(); } }; @@ -84,7 +86,10 @@ public class DashPluginsFragment extends DashBaseFragment { view.findViewById(R.id.show_all).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - startActivity(new Intent(getActivity(), getMyApplication().getAppCustomization().getPluginsActivity())); + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginsFragment.showInstance(activity.getSupportFragmentManager()); + } closeDashboard(); } }); diff --git a/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java b/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java index 6786e78d70..d7798a646b 100644 --- a/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java +++ b/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java @@ -14,7 +14,7 @@ import net.osmand.plus.Version; import net.osmand.plus.activities.ContributionVersionActivity; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.dashboard.tools.DashFragmentData; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; @@ -122,13 +122,8 @@ public class OsmandDevelopmentPlugin extends OsmandPlugin { } @Override - public Class getSettingsActivity() { - return SettingsDevelopmentActivity.class; - } - - @Override - public Class getSettingsFragment() { - return DevelopmentSettingsFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.DEVELOPMENT_SETTINGS; } @Override diff --git a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java deleted file mode 100644 index c500a28254..0000000000 --- a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java +++ /dev/null @@ -1,212 +0,0 @@ -package net.osmand.plus.development; - - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.os.Bundle; -import android.os.Debug; -import android.os.Debug.MemoryInfo; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; - -import net.osmand.plus.OsmAndLocationSimulation; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.Version; -import net.osmand.plus.activities.SettingsBaseActivity; -import net.osmand.plus.render.NativeOsmandLibrary; -import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.util.SunriseSunset; - -import java.text.SimpleDateFormat; - -//import net.osmand.plus.development.OsmandDevelopmentPlugin; - -public class SettingsDevelopmentActivity extends SettingsBaseActivity { - - @SuppressLint("SimpleDateFormat") - @Override - public void onCreate(Bundle savedInstanceState) { - OsmandApplication app = getMyApplication(); - app.applyTheme(this); - super.onCreate(savedInstanceState); - getToolbar().setTitle(R.string.debugging_and_development); - PreferenceScreen category = getPreferenceScreen(); - Preference pref; - - if (Version.isOpenGlAvailable(app)) { - category.addPreference(createCheckBoxPreference(settings.USE_OPENGL_RENDER, - R.string.use_opengl_render, R.string.use_opengl_render_descr)); - } - - if (!Version.isBlackberry(app)) { - CheckBoxPreference nativeCheckbox = createCheckBoxPreference(settings.SAFE_MODE, R.string.safe_mode, R.string.safe_mode_description); - // disable the checkbox if the library cannot be used - if ((NativeOsmandLibrary.isLoaded() && !NativeOsmandLibrary.isSupported()) || settings.NATIVE_RENDERING_FAILED.get()) { - nativeCheckbox.setEnabled(false); - nativeCheckbox.setChecked(true); - } - category.addPreference(nativeCheckbox); - } - - PreferenceCategory navigation = new PreferenceCategory(this); - navigation.setTitle(R.string.routing_settings); - category.addPreference(navigation); - pref = new Preference(this); - final Preference simulate = pref; - final OsmAndLocationSimulation sim = getMyApplication().getLocationProvider().getLocationSimulation(); - final Runnable updateTitle = new Runnable(){ - - @Override - public void run() { - simulate.setSummary(sim.isRouteAnimating() ? - R.string.simulate_your_location_stop_descr : R.string.simulate_your_location_gpx_descr); - } - }; - pref.setTitle(R.string.simulate_your_location); - updateTitle.run(); - pref.setKey("simulate_your_location"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - updateTitle.run(); - sim.startStopRouteAnimation(SettingsDevelopmentActivity.this, true, updateTitle); - return true; - } - }); - navigation.addPreference(pref); - - PreferenceCategory debug = new PreferenceCategory(this); - debug.setTitle(R.string.debugging_and_development); - category.addPreference(debug); - - CheckBoxPreference dbg = createCheckBoxPreference(settings.DEBUG_RENDERING_INFO, - R.string.trace_rendering, R.string.trace_rendering_descr); - debug.addPreference(dbg); - - - final Preference firstRunPreference = new Preference(this); - firstRunPreference.setTitle(R.string.simulate_initial_startup); - firstRunPreference.setSummary(R.string.simulate_initial_startup_descr); - firstRunPreference.setSelectable(true); - firstRunPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - getMyApplication().getAppInitializer().resetFirstTimeRun(); - OsmandSettings settings = getMyApplication().getSettings(); - settings.FIRST_MAP_IS_DOWNLOADED.set(false); - settings.MAPILLARY_FIRST_DIALOG_SHOWN.set(false); - settings.WEBGL_SUPPORTED.set(true); - settings.WIKI_ARTICLE_SHOW_IMAGES_ASKED.set(false); - - getMyApplication().showToastMessage(R.string.shared_string_ok); - return true; - } - }); - debug.addPreference(firstRunPreference); - - debug.addPreference(createCheckBoxPreference(settings.SHOULD_SHOW_FREE_VERSION_BANNER, - R.string.show_free_version_banner, - R.string.show_free_version_banner_description)); - - pref = new Preference(this); - pref.setTitle(R.string.test_voice_prompts); - pref.setSummary(R.string.play_commands_of_currently_selected_voice); - pref.setKey("test_voice_commands"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startActivity(new Intent(SettingsDevelopmentActivity.this, TestVoiceActivity.class)); - return true; - } - }); - category.addPreference(pref); - - pref = new Preference(this); - pref.setTitle(R.string.logcat_buffer); - pref.setSummary(R.string.logcat_buffer_descr); - pref.setKey("logcat_buffer"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startActivity(new Intent(SettingsDevelopmentActivity.this, LogcatActivity.class)); - return true; - } - }); - category.addPreference(pref); - - PreferenceCategory info = new PreferenceCategory(this); - info.setTitle(R.string.info_button); - category.addPreference(info); - - pref = new Preference(this); - pref.setTitle(R.string.global_app_allocated_memory); - - long javaAvailMem = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/ (1024*1024l); - long javaTotal = Runtime.getRuntime().totalMemory() / (1024*1024l); - long dalvikSize = android.os.Debug.getNativeHeapAllocatedSize() / (1024*1024l); - pref.setSummary(getString(R.string.global_app_allocated_memory_descr, javaAvailMem, javaTotal, dalvikSize)); - pref.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - info.addPreference(pref); - -// ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); -// ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); -// activityManager.getMemoryInfo(memoryInfo); -// long totalSize = memoryInfo.availMem / (1024*1024l); - MemoryInfo mem = new Debug.MemoryInfo(); - Debug.getMemoryInfo(mem); - pref = new Preference(this); - pref.setTitle(R.string.native_app_allocated_memory); - pref.setSummary(getString(R.string.native_app_allocated_memory_descr - , mem.nativePrivateDirty / 1024, mem.dalvikPrivateDirty / 1024 , mem.otherPrivateDirty / 1024 - , mem.nativePss / 1024, mem.dalvikPss / 1024 , mem.otherPss / 1024)); - pref.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - info.addPreference(pref); - - final Preference agpspref = new Preference(this); - agpspref.setTitle(R.string.agps_info); - if (settings.AGPS_DATA_LAST_TIME_DOWNLOADED.get() != 0L) { - SimpleDateFormat prt = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - agpspref.setSummary(getString(R.string.agps_data_last_downloaded, prt.format(settings.AGPS_DATA_LAST_TIME_DOWNLOADED.get()))); - } else { - agpspref.setSummary(getString(R.string.agps_data_last_downloaded, "--")); - } - agpspref.setSelectable(true); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - agpspref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - if(getMyApplication().getSettings().isInternetConnectionAvailable(true)) { - getMyApplication().getLocationProvider().redownloadAGPS(); - SimpleDateFormat prt = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - agpspref.setSummary(getString(R.string.agps_data_last_downloaded, prt.format(settings.AGPS_DATA_LAST_TIME_DOWNLOADED.get()))); - } - return true; - } - }); - info.addPreference(agpspref); - - SunriseSunset sunriseSunset = getMyApplication().getDaynightHelper().getSunriseSunset(); - pref = new Preference(this); - pref.setTitle(R.string.day_night_info); - if (sunriseSunset != null && sunriseSunset.getSunrise() != null && sunriseSunset.getSunset() != null) { - SimpleDateFormat prt = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - pref.setSummary(getString(R.string.day_night_info_description, prt.format(sunriseSunset.getSunrise()), - prt.format(sunriseSunset.getSunset()))); - } else { - pref.setSummary(getString(R.string.day_night_info_description, "null", "null")); - } - pref.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - info.addPreference(pref); - } -} diff --git a/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java b/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java index 9234239040..29a64bcee4 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java @@ -1,7 +1,6 @@ package net.osmand.plus.dialogs; import android.content.DialogInterface; -import android.content.Intent; import android.view.View; import android.widget.ArrayAdapter; import android.widget.CompoundButton; @@ -20,7 +19,7 @@ import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivityLayers; -import net.osmand.plus.activities.PluginActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.poi.PoiFiltersHelper; import net.osmand.plus.poi.PoiUIFilter; @@ -149,9 +148,7 @@ final class MapLayerMenuListener extends OnRowItemClick { settings.SHOW_MAP_MARKERS.set(isChecked); } else if (itemId == R.string.layer_map) { if (OsmandPlugin.getEnabledPlugin(OsmandRasterMapsPlugin.class) == null) { - Intent intent = new Intent(mapActivity, PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, OsmandRasterMapsPlugin.ID); - mapActivity.startActivity(intent); + PluginsFragment.showInstance(mapActivity.getSupportFragmentManager()); } else { ContextMenuItem it = adapter.getItem(pos); mapActivity.getMapLayers().selectMapLayer(mapActivity.getMapView(), it, adapter); diff --git a/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java b/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java index ec1c70fd41..e4cdc5ce94 100644 --- a/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java +++ b/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.AsyncTask; +import android.os.Bundle; import android.util.TypedValue; import android.view.MenuItem; import android.view.View; @@ -25,6 +26,8 @@ import net.osmand.plus.R; import net.osmand.plus.Version; import net.osmand.plus.activities.LocalIndexHelper.LocalIndexType; import net.osmand.plus.activities.LocalIndexInfo; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; import net.osmand.plus.download.CityItem; import net.osmand.plus.download.CustomIndexItem; @@ -343,8 +346,7 @@ public class ItemViewHolder { ChoosePlanDialogFragment.showSeaDepthMapsInstance(context.getSupportFragmentManager()); break; case ASK_FOR_SEAMARKS_PLUGIN: - context.startActivity(new Intent(context, context.getMyApplication().getAppCustomization() - .getPluginsActivity())); + showPluginsScreen(); Toast.makeText(context.getApplicationContext(), context.getString(R.string.activate_seamarks_plugin), Toast.LENGTH_SHORT).show(); break; @@ -352,8 +354,7 @@ public class ItemViewHolder { ChoosePlanDialogFragment.showHillshadeSrtmPluginInstance(context.getSupportFragmentManager()); break; case ASK_FOR_SRTM_PLUGIN_ENABLE: - context.startActivity(new Intent(context, context.getMyApplication().getAppCustomization() - .getPluginsActivity())); + showPluginsScreen(); Toast.makeText(context, context.getString(R.string.activate_srtm_plugin), Toast.LENGTH_SHORT).show(); break; @@ -361,6 +362,13 @@ public class ItemViewHolder { break; } } + + private void showPluginsScreen() { + Bundle params = new Bundle(); + params.putBoolean(PluginsFragment.OPEN_PLUGINS, true); + Intent intent = context.getIntent(); + MapActivity.launchMapActivityMoveToTop(context, intent != null ? intent.getExtras() : null, null, params); + } }; } else { final boolean isDownloading = context.getDownloadThread().isDownloading(item); diff --git a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java index 47650dc2ec..dd7e804b4c 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java @@ -13,6 +13,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.text.SpannableString; import android.text.style.StyleSpan; import android.view.ContextThemeWrapper; @@ -80,6 +81,7 @@ import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.helpers.enums.MetricsConstants; import net.osmand.plus.helpers.enums.SpeedConstants; import net.osmand.plus.settings.backend.CommonPreference; @@ -90,7 +92,6 @@ import net.osmand.plus.Version; import net.osmand.plus.activities.ActivityResultListener; import net.osmand.plus.activities.ActivityResultListener.OnActivityResultListener; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.activities.PluginActivity; import net.osmand.plus.activities.SettingsActivity; import net.osmand.plus.dialogs.ConfigureMapMenu; import net.osmand.plus.dialogs.GpxAppearanceAdapter; @@ -649,9 +650,9 @@ public class GpxUiHelper { confirm.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(activity, PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, OsmandMonitoringPlugin.ID); - activity.startActivity(intent); + Bundle params = new Bundle(); + params.putBoolean(PluginsFragment.OPEN_PLUGINS, true); + MapActivity.launchMapActivityMoveToTop(activity, null, null, params); } }); confirm.setNegativeButton(R.string.shared_string_cancel, null); diff --git a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java index d58271a27c..19748b100c 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java @@ -11,16 +11,18 @@ import net.osmand.PlatformUtil; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.map.TileSourceManager; -import net.osmand.plus.mapsource.EditMapSourceDialogFragment; -import net.osmand.plus.search.QuickSearchDialogFragment; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.MapMarkersHelper; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.dashboard.DashboardOnMap.DashboardType; import net.osmand.plus.mapmarkers.MapMarkersDialogFragment; +import net.osmand.plus.mapsource.EditMapSourceDialogFragment; +import net.osmand.plus.osmedit.OsmEditingFragment; +import net.osmand.plus.search.QuickSearchDialogFragment; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.util.Algorithms; @@ -58,6 +60,9 @@ public class IntentHelper { if (!applied) { applied = parseSendIntent(); } + if (!applied) { + applied = parseOAuthIntent(); + } return applied; } @@ -207,10 +212,22 @@ public class IntentHelper { mapActivity.setIntent(null); } if (intent.hasExtra(BaseSettingsFragment.OPEN_SETTINGS)) { - String settingsType = intent.getStringExtra(BaseSettingsFragment.OPEN_SETTINGS); String appMode = intent.getStringExtra(BaseSettingsFragment.APP_MODE_KEY); - if (BaseSettingsFragment.OPEN_CONFIG_PROFILE.equals(settingsType)) { - BaseSettingsFragment.showInstance(mapActivity, SettingsScreenType.CONFIGURE_PROFILE, ApplicationMode.valueOfStringKey(appMode, null)); + String settingsTypeName = intent.getStringExtra(BaseSettingsFragment.OPEN_SETTINGS); + if (!Algorithms.isEmpty(settingsTypeName)) { + try { + SettingsScreenType screenType = SettingsScreenType.valueOf(settingsTypeName); + BaseSettingsFragment.showInstance(mapActivity, screenType, ApplicationMode.valueOfStringKey(appMode, null)); + } catch (IllegalArgumentException e) { + LOG.error("error", e); + } + } + mapActivity.setIntent(null); + } + if (intent.hasExtra(PluginsFragment.OPEN_PLUGINS)) { + boolean openPlugins = intent.getBooleanExtra(PluginsFragment.OPEN_PLUGINS, false); + if (openPlugins) { + PluginsFragment.showInstance(mapActivity.getSupportFragmentManager()); } mapActivity.setIntent(null); } @@ -271,6 +288,23 @@ public class IntentHelper { return false; } + private boolean parseOAuthIntent() { + Intent intent = mapActivity.getIntent(); + if (intent != null && intent.getData() != null) { + Uri uri = intent.getData(); + if (uri.toString().startsWith("osmand-oauth")) { + OsmEditingFragment fragment = mapActivity.getOsmEditingFragment(); + if (fragment != null) { + String oauthVerifier = uri.getQueryParameter("oauth_verifier"); + fragment.authorize(oauthVerifier); + mapActivity.setIntent(null); + return true; + } + } + } + return false; + } + private boolean handleSendText(Intent intent) { String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); if (!Algorithms.isEmpty(sharedText)) { diff --git a/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java b/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java index 9047baeb59..beb3534a46 100644 --- a/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java +++ b/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java @@ -34,6 +34,7 @@ class FavoritesImportTask extends BaseImportAsyncTask { protected GPXFile doInBackground(Void... nothing) { List favourites = asFavourites(app, gpxFile.getPoints(), fileName, forceImportFavourites); FavouritesDbHelper favoritesHelper = app.getFavorites(); + checkDuplicateNames(favourites); for (FavouritePoint favourite : favourites) { favoritesHelper.deleteFavourite(favourite, false); favoritesHelper.addFavourite(favourite, false); @@ -43,6 +44,27 @@ class FavoritesImportTask extends BaseImportAsyncTask { return null; } + public void checkDuplicateNames(List favourites) { + for (FavouritePoint fp : favourites) { + int number = 1; + String index; + String name = fp.getName(); + boolean duplicatesFound = false; + for (FavouritePoint fp2 : favourites) { + if (name.equals(fp2.getName()) && fp.getCategory().equals(fp2.getCategory()) && !fp.equals(fp2)) { + if (!duplicatesFound) { + index = " (" + number + ")"; + fp.setName(name + index); + } + duplicatesFound = true; + number++; + index = " (" + number + ")"; + fp2.setName(fp2.getName() + index); + } + } + } + } + @Override protected void onPostExecute(GPXFile result) { hideProgress(); diff --git a/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java b/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java index 1059a46bb0..1416e29e31 100644 --- a/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java +++ b/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java @@ -34,8 +34,8 @@ import net.osmand.plus.dialogs.ImportGpxBottomSheetDialogFragment; import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.helpers.GpxUiHelper.GPXInfo; import net.osmand.plus.measurementtool.MeasurementToolFragment; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java b/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java index 55135832e5..d2d598d927 100644 --- a/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java +++ b/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java @@ -13,12 +13,12 @@ import net.osmand.FileUtils; import net.osmand.IndexConstants; import net.osmand.plus.CustomOsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper.CheckDuplicatesListener; -import net.osmand.plus.settings.backend.SettingsHelper.PluginSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ProfileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsCollectListener; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsImportListener; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.CheckDuplicatesListener; +import net.osmand.plus.settings.backend.backup.PluginSettingsItem; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsCollectListener; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsImportListener; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.settings.fragments.ImportSettingsFragment; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java index 272975b987..ac6b8287b4 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java @@ -22,6 +22,7 @@ import net.osmand.plus.activities.LocalIndexHelper; import net.osmand.plus.activities.LocalIndexHelper.LocalIndexType; import net.osmand.plus.activities.LocalIndexInfo; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.download.DownloadActivityType; import net.osmand.plus.download.DownloadIndexesThread; import net.osmand.plus.download.DownloadValidationManager; @@ -110,8 +111,7 @@ public class MapDataMenuController extends MenuController { activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_LONG).show(); } } else { - activity.startActivity(new Intent(activity, activity.getMyApplication().getAppCustomization() - .getPluginsActivity())); + PluginsFragment.showInstance(activity.getSupportFragmentManager()); Toast.makeText(activity, activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_SHORT).show(); } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java index 7500a17d77..c487b7acc6 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java @@ -618,12 +618,12 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { } } HorizontalSelectionAdapter horizontalSelectionAdapter = new HorizontalSelectionAdapter(app, nightMode); - horizontalSelectionAdapter.setItems(new ArrayList<>(iconCategories.keySet())); - horizontalSelectionAdapter.setSelectedItem(selectedIconCategory); + horizontalSelectionAdapter.setTitledItems(new ArrayList<>(iconCategories.keySet())); + horizontalSelectionAdapter.setSelectedItemByTitle(selectedIconCategory); horizontalSelectionAdapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedIconCategory = item; + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + selectedIconCategory = item.getTitle(); createIconForCategory(); updateIconSelector(selectedIcon, PointEditorFragmentNew.this.view); } @@ -632,7 +632,7 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { iconCategoriesRecyclerView.setAdapter(horizontalSelectionAdapter); iconCategoriesRecyclerView.setLayoutManager(new LinearLayoutManager(app, RecyclerView.HORIZONTAL, false)); horizontalSelectionAdapter.notifyDataSetChanged(); - iconCategoriesRecyclerView.smoothScrollToPosition(horizontalSelectionAdapter.getItemPosition(selectedIconCategory)); + iconCategoriesRecyclerView.smoothScrollToPosition(horizontalSelectionAdapter.getItemPositionByTitle(selectedIconCategory)); for (String name : iconNameList) { selectIcon.addView(createIconItemView(name, selectIcon), new FlowLayout.LayoutParams(0, 0)); } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java index bf7844a0f4..06783d6f50 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java @@ -17,6 +17,7 @@ import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; +import java.util.ArrayList; import java.util.List; import static net.osmand.util.Algorithms.capitalizeFirstLetter; @@ -24,19 +25,28 @@ import static net.osmand.util.Algorithms.capitalizeFirstLetter; public class HorizontalSelectionAdapter extends RecyclerView.Adapter { - private List items; + public static int INVALID_ID = -1; + + private List items; private OsmandApplication app; private boolean nightMode; private HorizontalSelectionAdapterListener listener; - - private String selectedItem = ""; + private HorizontalSelectionItem selectedItem = null; public HorizontalSelectionAdapter(OsmandApplication app, boolean nightMode) { this.app = app; this.nightMode = nightMode; } - public void setItems(List items) { + public void setTitledItems(List titles) { + List items = new ArrayList<>(); + for (String title : titles) { + items.add(new HorizontalSelectionItem(title)); + } + setItems(items); + } + + public void setItems(List items) { this.items = items; } @@ -44,23 +54,30 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter graphTypes = new ArrayList<>(); + + private View commonGraphContainer; + private View customGraphContainer; + private View messageContainer; + private LineChart commonGraphChart; + private HorizontalBarChart customGraphChart; + 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); + + CommonGraphType(int titleId, boolean canBeCalculated) { + this.titleId = titleId; + this.canBeCalculated = canBeCalculated; + } + + final int titleId; + final boolean canBeCalculated; + } + + public GraphsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) { + super(mapActivity); + this.fragment = fragment; + } + + @Override + protected void updateContent() { + if (mapActivity == null || fragment == null) return; + editingCtx = fragment.getEditingCtx(); + + 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(); + + graphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); + graphTypesMenu.setLayoutManager( + new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); + + refreshGraphTypesSelectionMenu(); + updateDataView(); + } + + @Override + public int getCardLayoutId() { + return R.layout.fragment_measurement_tool_graph; + } + + @Override + public void onUpdateAdditionalInfo() { + if (!isRouteCalculating()) { + updateGraphData(); + refreshGraphTypesSelectionMenu(); + } + updateDataView(); + } + + private void refreshGraphTypesSelectionMenu() { + graphTypesMenu.removeAllViews(); + 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); + final ArrayList items = new ArrayList<>(); + for (GraphType type : graphTypes) { + HorizontalSelectionItem item = new HorizontalSelectionItem(type.getTitle(), type); + if (type.isCustom()) { + item.setTitleColorId(activeColorId); + } + if (type.isAvailable()) { + items.add(item); + } + } + adapter.setItems(items); + String selectedItemKey = visibleGraphType.getTitle(); + adapter.setSelectedItemByTitle(selectedItemKey); + adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { + @Override + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + adapter.setItems(items); + adapter.setSelectedItem(item); + GraphType chosenGraphType = (GraphType) item.getObject(); + if (!isCurrentVisibleType(chosenGraphType)) { + setupVisibleGraphType(chosenGraphType); + } + } + }); + graphTypesMenu.setAdapter(adapter); + adapter.notifyDataSetChanged(); + } + + private void setupVisibleGraphType(GraphType type) { + visibleGraphType = type; + updateDataView(); + } + + private boolean isCurrentVisibleType(GraphType type) { + if (visibleGraphType != null && type != null) { + return Algorithms.objectEquals(visibleGraphType.getTitle(), type.getTitle()); + } + return false; + } + + private GraphType getFirstAvailableGraphType() { + for (GraphType type : graphTypes) { + if (type.isAvailable()) { + return type; + } + } + return null; + } + + private void updateDataView() { + if (isRouteCalculating()) { + showProgressMessage(); + } else if (visibleGraphType.hasData()) { + showGraph(); + } else if (visibleGraphType.canBeCalculated()) { + showMessage(); + } + } + + private void showProgressMessage() { + 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.VISIBLE); + icon.setVisibility(View.GONE); + tvMessage.setText(R.string.message_graph_will_be_available_after_recalculation); + } + + private void showGraph() { + if (visibleGraphType.isCustom()) { + customGraphChart.clear(); + commonGraphContainer.setVisibility(View.GONE); + customGraphContainer.setVisibility(View.VISIBLE); + messageContainer.setVisibility(View.GONE); + prepareCustomGraphView((BarData) visibleGraphType.getGraphData()); + } else { + commonGraphChart.clear(); + commonGraphContainer.setVisibility(View.VISIBLE); + customGraphContainer.setVisibility(View.GONE); + messageContainer.setVisibility(View.GONE); + prepareCommonGraphView((LineData) visibleGraphType.getGraphData()); + } + } + + 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() { + graphTypes.clear(); + OsmandApplication app = getMyApplication(); + GPXTrackAnalysis analysis = createGpxTrackAnalysis(); + + // 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)); + } + + // update custom graph data + List routeSegments = editingCtx.getAllRouteSegments(); + List routeStatistics = calculateRouteStatistics(routeSegments); + if (analysis != null && routeStatistics != null) { + for (RouteStatistics statistics : routeStatistics) { + String title = SettingsBaseActivity.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)); + } + } + + // update current visible graph type + if (visibleGraphType == null) { + visibleGraphType = getFirstAvailableGraphType(); + } else { + for (GraphType type : graphTypes) { + if (isCurrentVisibleType(type)) { + visibleGraphType = type.isAvailable() ? type : getFirstAvailableGraphType(); + 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(); + } else { + gpx = editingCtx.exportRouteAsGpx(GRAPH_DATA_GPX_FILE_NAME); + } + return gpx != null ? gpx.getAnalysis(0) : null; + } + + private List calculateRouteStatistics(List route) { + OsmandApplication app = getMyApplication(); + if (route == null || app == null) return null; + return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode); + } + + private boolean isRouteCalculating() { + return fragment.isProgressBarVisible(); + } + + private static class GraphType { + private String title; + private boolean isCustom; + private boolean canBeCalculated; + private ChartData graphData; + + public GraphType(String title, boolean isCustom, boolean canBeCalculated, ChartData graphData) { + this.title = title; + this.isCustom = isCustom; + this.canBeCalculated = canBeCalculated; + this.graphData = graphData; + } + + public String getTitle() { + return title; + } + + public boolean isCustom() { + return isCustom; + } + + public boolean isAvailable() { + return hasData() || canBeCalculated(); + } + + public boolean canBeCalculated() { + return canBeCalculated; + } + + public boolean hasData() { + return getGraphData() != null; + } + + public ChartData getGraphData() { + return graphData; + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java index 8661d42f39..91a1d6e163 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java @@ -398,6 +398,20 @@ public class MeasurementEditingContext { return before.points.size(); } + public List getAllRouteSegments() { + List allSegments = new ArrayList<>(); + for (Pair key : getOrderedRoadSegmentDataKeys()) { + RoadSegmentData data = roadSegmentData.get(key); + if (data != null) { + List segments = data.getSegments(); + if (segments != null) { + allSegments.addAll(segments); + } + } + } + return allSegments.size() > 0 ? allSegments : null; + } + void splitSegments(int position) { List points = new ArrayList<>(); points.addAll(before.points); @@ -623,6 +637,16 @@ public class MeasurementEditingContext { return res; } + private List> getOrderedRoadSegmentDataKeys() { + List> keys = new ArrayList<>(); + for (List points : Arrays.asList(before.points, after.points)) { + for (int i = 0; i < points.size() - 1; i++) { + keys.add(new Pair<>(points.get(i), points.get(i + 1))); + } + } + return keys; + } + private void recreateSegments(List segments, List segmentsForSnap, List points, boolean calculateIfNeeded) { List roadSegmentIndexes = new ArrayList<>(); TrkSegment s = new TrkSegment(); diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index b22b276bb0..3212ef60b8 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -5,7 +5,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.util.TypedValue; import android.view.LayoutInflater; @@ -14,6 +13,7 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -24,10 +24,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.core.widget.TextViewCompat; -import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; @@ -59,7 +57,6 @@ import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragme import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragment.RouteBetweenPointsFragmentListener; import net.osmand.plus.measurementtool.SaveGpxRouteAsyncTask.SaveGpxRouteListener; import net.osmand.plus.measurementtool.SelectedPointBottomSheetDialogFragment.SelectedPointFragmentListener; -import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter; import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter.MeasurementAdapterListener; import net.osmand.plus.measurementtool.command.AddPointCommand; import net.osmand.plus.measurementtool.command.ApplyGpxApproximationCommand; @@ -70,9 +67,9 @@ import net.osmand.plus.measurementtool.command.MovePointCommand; import net.osmand.plus.measurementtool.command.RemovePointCommand; import net.osmand.plus.measurementtool.command.ReorderPointCommand; import net.osmand.plus.measurementtool.command.ReversePointsCommand; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback; import net.osmand.plus.views.layers.MapControlsLayer; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType; @@ -90,6 +87,8 @@ import java.util.Locale; import static net.osmand.IndexConstants.GPX_FILE_EXT; import static net.osmand.IndexConstants.GPX_INDEX_DIR; +import static net.osmand.plus.UiUtilities.CustomRadioButtonType.END; +import static net.osmand.plus.UiUtilities.CustomRadioButtonType.START; import static net.osmand.plus.measurementtool.MeasurementEditingContext.CalculationMode; import static net.osmand.plus.measurementtool.MeasurementEditingContext.SnapToRoadProgressListener; import static net.osmand.plus.measurementtool.SaveAsNewTrackBottomSheetDialogFragment.SaveAsNewTrackFragmentListener; @@ -107,18 +106,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route public static final String TAG = MeasurementToolFragment.class.getSimpleName(); public static final String TAPS_DISABLED_KEY = "taps_disabled_key"; - private RecyclerView pointsRv; private String previousToolBarTitle = ""; private MeasurementToolBarController toolBarController; - private MeasurementToolAdapter adapter; private TextView distanceTv; private TextView pointsTv; private TextView distanceToCenterTv; private String pointsSt; - private Drawable upIcon; - private Drawable downIcon; - private View pointsListContainer; - private View upDownRow; + private View additionalInfoContainer; + private ViewGroup additionalInfoCardsContainer; + private BaseCard visibleAdditionalInfoCard; + private LinearLayout customRadioButton; private View mainView; private ImageView upDownBtn; private ImageView undoBtn; @@ -127,9 +124,11 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private Snackbar snackbar; private String fileName; + private AdditionalInfoType currentAdditionalInfoType; + private boolean wasCollapseButtonVisible; private boolean progressBarVisible; - private boolean pointsListOpened; + private boolean additionalInfoExpanded; private static final int PLAN_ROUTE_MODE = 0x1; private static final int DIRECTION_MODE = 0x2; @@ -156,8 +155,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route SHOW_IS_SAVED_FRAGMENT } - protected MeasurementEditingContext getEditingCtx() { - return editingCtx; + private enum AdditionalInfoType { + POINTS, + GRAPH } private void setEditingCtx(MeasurementEditingContext editingCtx) { @@ -219,6 +219,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route @Override public void showProgressBar() { MeasurementToolFragment.this.showProgressBar(); + updateAdditionalInfoView(); } @Override @@ -230,6 +231,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route public void hideProgressBar() { ((ProgressBar) mainView.findViewById(R.id.snap_to_road_progress_bar)).setVisibility(View.GONE); progressBarVisible = false; + updateAdditionalInfoView(); } @Override @@ -241,17 +243,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route measurementLayer.setEditingCtx(editingCtx); - // If rotate the screen from landscape to portrait when the list of points is displayed then - // the RecyclerViewFragment will exist without view. This is necessary to remove it. - if (!portrait) { - hidePointsListFragment(); - } - nightMode = mapActivity.getMyApplication().getDaynightHelper().isNightModeForMapControls(); portrait = AndroidUiHelper.isOrientationPortrait(mapActivity); - upIcon = getContentIcon(R.drawable.ic_action_arrow_up); - downIcon = getContentIcon(R.drawable.ic_action_arrow_down); pointsSt = getString(R.string.shared_string_gpx_points).toLowerCase(); View view = UiUtilities.getInflater(getContext(), nightMode) @@ -259,12 +253,30 @@ 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); - pointsListContainer = view.findViewById(R.id.points_list_container); - if (portrait && pointsListContainer != null) { - final int backgroundColor = ContextCompat.getColor(mapActivity, nightMode - ? R.color.activity_background_color_dark - : R.color.activity_background_color_light); - pointsListContainer.setBackgroundColor(backgroundColor); + additionalInfoContainer = mainView.findViewById(R.id.additional_info_container); + additionalInfoCardsContainer = mainView.findViewById(R.id.cards_container); + if (portrait) { + customRadioButton = mainView.findViewById(R.id.custom_radio_buttons); + + View pointListBtn = customRadioButton.findViewById(R.id.left_button_container); + TextView tvPointListBtn = customRadioButton.findViewById(R.id.left_button); + tvPointListBtn.setText(R.string.shared_string_gpx_points); + pointListBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + changeAdditionalInfoType(AdditionalInfoType.POINTS); + } + }); + + View graphBtn = customRadioButton.findViewById(R.id.right_button_container); + TextView tvGraphBtn = customRadioButton.findViewById(R.id.right_button); + tvGraphBtn.setText(R.string.shared_string_graph); + graphBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + changeAdditionalInfoType(AdditionalInfoType.GRAPH); + } + }); } if (progressBarVisible) { @@ -277,7 +289,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route mainIcon = (ImageView) mainView.findViewById(R.id.main_icon); upDownBtn = (ImageView) mainView.findViewById(R.id.up_down_button); - upDownBtn.setImageDrawable(upIcon); + updateUpDownBtn(); mainView.findViewById(R.id.cancel_move_point_button).setOnClickListener(new OnClickListener() { @Override @@ -293,14 +305,14 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } }); - upDownRow = mainView.findViewById(R.id.up_down_row); + View upDownRow = mainView.findViewById(R.id.up_down_row); upDownRow.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - if (!pointsListOpened && editingCtx.getPointsCount() > 0 && editingCtx.getSelectedPointPosition() == -1) { - showPointsList(); + if (!additionalInfoExpanded && editingCtx.getPointsCount() > 0 && editingCtx.getSelectedPointPosition() == -1) { + expandAdditionalInfoView(); } else { - hidePointsList(); + collapseAdditionalInfoView(); } } }); @@ -393,8 +405,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route @Override public void onSelectPoint(int selectedPointPos) { - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } if (selectedPointPos != -1) { openSelectedPointMenu(mapActivity); @@ -417,8 +429,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route measurementLayer.setOnEnterMovePointModeListener(new MeasurementToolLayer.OnEnterMovePointModeListener() { @Override public void onEnterMovePointMode() { - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } switchMovePointMode(true); } @@ -471,17 +483,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route updateToolbar(); final GpxData gpxData = editingCtx.getGpxData(); - adapter = new MeasurementToolAdapter(getMapActivity(), editingCtx.getPoints()); - if (portrait) { - pointsRv = mainView.findViewById(R.id.measure_points_recycler_view); - } else { - pointsRv = new RecyclerView(getActivity()); - } - ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter)); - touchHelper.attachToRecyclerView(pointsRv); - adapter.setAdapterListener(createMeasurementAdapterListener(touchHelper)); - pointsRv.setLayoutManager(new LinearLayoutManager(getContext())); - pointsRv.setAdapter(adapter); ImageButton snapToRoadBtn = (ImageButton) mapActivity.findViewById(R.id.snap_to_road_image_button); snapToRoadBtn.setBackgroundResource(nightMode ? R.drawable.btn_circle_night : R.drawable.btn_circle); @@ -508,6 +509,36 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route return view; } + private void changeAdditionalInfoType(@NonNull AdditionalInfoType type) { + 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); + UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START); + } else if (AdditionalInfoType.GRAPH == type) { + additionalInfoCard = new GraphsCard(ma, this); + UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END); + } + if (additionalInfoCard != null) { + visibleAdditionalInfoCard = additionalInfoCard; + additionalInfoCardsContainer.removeAllViews(); + additionalInfoCardsContainer.addView(additionalInfoCard.build(ma)); + additionalInfoExpanded = true; + } + } + } + + private void updateAdditionalInfoView() { + if (visibleAdditionalInfoCard instanceof OnUpdateAdditionalInfoListener) { + ((OnUpdateAdditionalInfoListener) visibleAdditionalInfoCard).onUpdateAdditionalInfo(); + } + } + public boolean isInEditMode() { return !isPlanRouteMode() && !editingCtx.isNewData() && !isDirectionMode() && !isFollowTrackMode(); } @@ -516,12 +547,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route this.fileName = fileName; } + public MeasurementEditingContext getEditingCtx() { + return editingCtx; + } + private void updateUndoRedoCommonStuff() { - hidePointsListIfNoPoints(); + collapseAdditionalInfoIfNoPointsEnough(); if (editingCtx.getPointsCount() > 0) { enable(upDownBtn); } - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateDistancePointsText(); updateSnapToRoadControls(); } @@ -576,9 +611,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route super.onDestroyView(); cancelModes(); exitMeasurementMode(); - adapter.setAdapterListener(null); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } MeasurementToolLayer layer = getMeasurementLayer(); if (layer != null) { @@ -626,6 +660,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route progressBarVisible = true; } + public boolean isProgressBarVisible() { + return progressBarVisible; + } + private void updateMainIcon() { GpxData gpxData = editingCtx.getGpxData(); mainIcon.setImageDrawable(getActiveIcon(gpxData != null ? R.drawable.ic_action_polygom_dark : R.drawable.ic_action_ruler)); @@ -826,8 +864,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route MeasurementToolLayer measurementLayer = getMeasurementLayer(); editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, ALL)); editingCtx.cancelSnapToRoad(); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } updateUndoRedoButton(false, redoBtn); disable(upDownBtn); @@ -842,8 +880,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (points.size() > 1) { MeasurementToolLayer measurementLayer = getMeasurementLayer(); editingCtx.getCommandManager().execute(new ReversePointsCommand(measurementLayer)); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } updateUndoRedoButton(false, redoBtn); updateUndoRedoButton(true, undoBtn); @@ -911,8 +949,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private void trimRoute(ClearCommandMode before) { MeasurementToolLayer measurementLayer = getMeasurementLayer(); editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, before)); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } editingCtx.setSelectedPointPosition(-1); editingCtx.splitSegments(editingCtx.getBeforePoints().size() + editingCtx.getAfterPoints().size()); @@ -1065,11 +1103,11 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private void removePoint(MeasurementToolLayer measurementLayer, int position) { if (measurementLayer != null) { editingCtx.getCommandManager().execute(new RemovePointCommand(measurementLayer, position)); - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateUndoRedoButton(true, undoBtn); updateUndoRedoButton(false, redoBtn); updateDistancePointsText(); - hidePointsListIfNoPoints(); + collapseAdditionalInfoIfNoPointsEnough(); } } @@ -1078,7 +1116,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route saveNewGpx(folderName, fileName, showOnMap, simplified, FinalSaveAction.SHOW_IS_SAVED_FRAGMENT); } - private MeasurementAdapterListener createMeasurementAdapterListener(final ItemTouchHelper touchHelper) { + MeasurementAdapterListener createMeasurementAdapterListener(final ItemTouchHelper touchHelper) { return new MeasurementAdapterListener() { final MapActivity mapActivity = getMapActivity(); @@ -1094,8 +1132,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route @Override public void onItemClick(int position) { if (mapActivity != null && measurementLayer != null) { - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } if (portrait) { setMapPosition(OsmandSettings.MIDDLE_TOP_CONSTANT); @@ -1117,7 +1155,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route toPosition = holder.getAdapterPosition(); if (toPosition >= 0 && fromPosition >= 0 && toPosition != fromPosition) { editingCtx.getCommandManager().execute(new ReorderPointCommand(measurementLayer, fromPosition, toPosition)); - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateUndoRedoButton(false, redoBtn); updateDistancePointsText(); mapActivity.refreshMap(); @@ -1179,7 +1217,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (!isUndoMode()) { editingCtx.addPoints(); } - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateDistancePointsText(); } } @@ -1375,80 +1413,48 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route updateUndoRedoButton(true, undoBtn); updateUndoRedoButton(false, redoBtn); updateDistancePointsText(); - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); } - private void showPointsList() { - pointsListOpened = true; - upDownBtn.setImageDrawable(downIcon); - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - if (portrait && pointsListContainer != null) { - pointsListContainer.setVisibility(View.VISIBLE); - } else { - showPointsListFragment(); + private void expandAdditionalInfoView() { + if (portrait) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + additionalInfoContainer.setVisibility(View.VISIBLE); + AdditionalInfoType typeToShow = currentAdditionalInfoType == null + ? AdditionalInfoType.POINTS : currentAdditionalInfoType; + changeAdditionalInfoType(typeToShow); + setMapPosition(portrait + ? OsmandSettings.MIDDLE_TOP_CONSTANT + : OsmandSettings.LANDSCAPE_MIDDLE_RIGHT_CONSTANT); } - setMapPosition(portrait - ? OsmandSettings.MIDDLE_TOP_CONSTANT - : OsmandSettings.LANDSCAPE_MIDDLE_RIGHT_CONSTANT); } } - private void hidePointsList() { - pointsListOpened = false; - upDownBtn.setImageDrawable(upIcon); - if (portrait && pointsListContainer != null) { - pointsListContainer.setVisibility(View.GONE); - } else { - hidePointsListFragment(); + private void collapseAdditionalInfoView() { + if (portrait) { + additionalInfoExpanded = false; + updateUpDownBtn(); + additionalInfoContainer.setVisibility(View.GONE); + setDefaultMapPosition(); } - setDefaultMapPosition(); } - private void hidePointsListIfNoPoints() { + private void collapseAdditionalInfoIfNoPointsEnough() { MeasurementToolLayer measurementLayer = getMeasurementLayer(); if (measurementLayer != null) { - if (editingCtx.getPointsCount() < 1) { + int pointsCount = editingCtx.getPointsCount(); + if (isCurrentAdditionalInfoType(AdditionalInfoType.GRAPH) && pointsCount < 2) { + collapseAdditionalInfoView(); + } else if (pointsCount < 1) { disable(upDownBtn); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } } } } - private void showPointsListFragment() { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - boolean transparentStatusBar = Build.VERSION.SDK_INT >= 21; - int statusBarHeight = transparentStatusBar ? 0 : AndroidUtils.getStatusBarHeight(mapActivity); - int screenHeight = AndroidUtils.getScreenHeight(mapActivity) - statusBarHeight; - RecyclerViewFragment fragment = new RecyclerViewFragment(); - fragment.setRecyclerView(pointsRv); - fragment.setWidth(upDownRow.getWidth()); - fragment.setHeight(screenHeight - upDownRow.getHeight()); - fragment.setTransparentStatusBar(transparentStatusBar); - mapActivity.getSupportFragmentManager().beginTransaction() - .add(R.id.fragmentContainer, fragment, RecyclerViewFragment.TAG) - .commitAllowingStateLoss(); - } - } - - private void hidePointsListFragment() { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - try { - FragmentManager manager = mapActivity.getSupportFragmentManager(); - Fragment fragment = manager.findFragmentByTag(RecyclerViewFragment.TAG); - if (fragment != null) { - manager.beginTransaction().remove(fragment).commitAllowingStateLoss(); - } - } catch (Exception e) { - // ignore - } - } - } - private void setDefaultMapPosition() { setMapPosition(OsmandSettings.CENTER_CONSTANT); } @@ -1472,6 +1478,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } } + private void updateUpDownBtn() { + Drawable icon = getContentIcon(additionalInfoExpanded + ? R.drawable.ic_action_arrow_down : R.drawable.ic_action_arrow_up); + upDownBtn.setImageDrawable(icon); + } + + private boolean isCurrentAdditionalInfoType(@NonNull AdditionalInfoType type) { + return type.equals(currentAdditionalInfoType); + } + private String getSuggestedFileName() { GpxData gpxData = editingCtx.getGpxData(); String displayedName = null; @@ -1750,8 +1766,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route final MapActivity mapActivity = getMapActivity(); MeasurementToolLayer measurementLayer = getMeasurementLayer(); if (mapActivity != null && measurementLayer != null) { - if (pointsListOpened && hidePointsListFirst) { - hidePointsList(); + if (additionalInfoExpanded && hidePointsListFirst) { + collapseAdditionalInfoView(); return; } if (!editingCtx.hasChanges()) { @@ -1771,8 +1787,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (clearContext) { editingCtx.clearSegments(); } - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } resetAppMode(); hideSnapToRoadIcon(); @@ -1910,8 +1926,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (!approximationMode || !editingCtx.getCommandManager().update(command)) { editingCtx.getCommandManager().execute(command); } - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } updateSnapToRoadControls(); } @@ -1984,4 +2000,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route public boolean isNightModeForMapControls() { return nightMode; } + + public interface OnUpdateAdditionalInfoListener { + void onUpdateAdditionalInfo(); + } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java b/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java new file mode 100644 index 0000000000..4b0117bd42 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java @@ -0,0 +1,48 @@ +package net.osmand.plus.measurementtool; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.measurementtool.MeasurementToolFragment.OnUpdateAdditionalInfoListener; +import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback; + +public class PointsCard extends BaseCard implements OnUpdateAdditionalInfoListener { + + private MeasurementToolAdapter adapter; + private MeasurementToolFragment fragment; + + public PointsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) { + super(mapActivity); + this.fragment = fragment; + } + + @Override + public void onUpdateAdditionalInfo() { + adapter.notifyDataSetChanged(); + } + + @Override + public int getCardLayoutId() { + return R.layout.fragment_measurement_tool_points_list; + } + + @Override + protected void updateContent() { + MeasurementEditingContext editingCtx = fragment.getEditingCtx(); + final GpxData gpxData = editingCtx.getGpxData(); + adapter = new MeasurementToolAdapter(mapActivity, editingCtx.getPoints(), + gpxData != null ? gpxData.getActionType() : null); + RecyclerView pointsRv = view.findViewById(R.id.measure_points_recycler_view); + ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter)); + touchHelper.attachToRecyclerView(pointsRv); + adapter.setAdapterListener(fragment.createMeasurementAdapterListener(touchHelper)); + pointsRv.setLayoutManager(new LinearLayoutManager(app)); + pointsRv.setAdapter(adapter); + } +} diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java b/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java index c155ec493c..b89cbda954 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java @@ -28,6 +28,7 @@ import net.osmand.plus.helpers.GpxUiHelper.GPXInfo; import net.osmand.plus.helpers.enums.TracksSortByMode; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener; +import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; import java.io.File; import java.util.ArrayList; @@ -128,7 +129,7 @@ public class SelectFileBottomSheet extends BottomSheetBehaviourDialogFragment { sortButton.setImageResource(mode.getIconId()); updateDescription(descriptionView); sortFolderList(); - folderAdapter.setItems(getFolderNames()); + folderAdapter.setTitledItems(getFolderNames()); folderAdapter.notifyDataSetChanged(); sortFileList(); adapter.notifyDataSetChanged(); @@ -191,13 +192,13 @@ public class SelectFileBottomSheet extends BottomSheetBehaviourDialogFragment { folders = new ArrayList<>(); collectDirs(gpxDir, folders); sortFolderList(); - folderAdapter.setItems(getFolderNames()); - folderAdapter.setSelectedItem(selectedFolder); + folderAdapter.setTitledItems(getFolderNames()); + folderAdapter.setSelectedItemByTitle(selectedFolder); foldersRecyclerView.setAdapter(folderAdapter); folderAdapter.setListener(new HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedFolder = item; + public void onItemSelected(HorizontalSelectionItem item) { + selectedFolder = item.getTitle(); updateFileList(folderAdapter); } }); diff --git a/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java b/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java index 901348a6d3..ae35fd767c 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java @@ -6,10 +6,13 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.SpannableString; import android.text.SpannableStringBuilder; +import android.view.LayoutInflater; +import android.view.View; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; +import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.OsmandPlugin; @@ -32,6 +35,7 @@ import net.osmand.plus.widgets.style.CustomTypefaceSpan; import java.util.HashMap; import java.util.LinkedHashMap; +import static net.osmand.plus.activities.PluginInfoFragment.PLUGIN_INFO; import static net.osmand.plus.settings.backend.OsmandSettings.MONTHLY_DIRECTORY; import static net.osmand.plus.settings.backend.OsmandSettings.REC_DIRECTORY; import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.MINUTES; @@ -46,6 +50,35 @@ public class MonitoringSettingsFragment extends BaseSettingsFragment private static final String OPEN_TRACKS = "open_tracks"; private static final String SAVE_GLOBAL_TRACK_INTERVAL = "save_global_track_interval"; + boolean showSwitchProfile = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args != null) { + showSwitchProfile = args.getBoolean(PLUGIN_INFO, false); + } + } + + @Override + protected void createToolbar(LayoutInflater inflater, View view) { + super.createToolbar(inflater, view); + + View switchProfile = view.findViewById(R.id.profile_button); + if (switchProfile != null) { + AndroidUiHelper.updateVisibility(switchProfile, showSwitchProfile); + } + } + + @Override + public Bundle buildArguments() { + Bundle args = super.buildArguments(); + args.putBoolean(PLUGIN_INFO, showSwitchProfile); + return args; + } + @Override protected void setupPreferences() { setupSaveTrackToGpxPref(); diff --git a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java index 94987d1c8a..ec255b1bbc 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java @@ -42,7 +42,7 @@ import net.osmand.plus.activities.SavingTrackHelper.SaveGpxResult; import net.osmand.plus.dashboard.tools.DashFragmentData; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.layers.MapInfoLayer; @@ -166,15 +166,9 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public static final int[] MINUTES = new int[] {2, 3, 5}; public static final int[] MAX_INTERVAL_TO_SEND_MINUTES = new int[] {1, 2, 5, 10, 15, 20, 30, 60, 90, 2 * 60, 3 * 60, 4 * 60, 6 * 60, 12 * 60, 24 * 60}; - @Override - public Class getSettingsActivity() { - return SettingsMonitoringActivity.class; - } - - @Override - public Class getSettingsFragment() { - return MonitoringSettingsFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.MONITORING_SETTINGS; } @Override diff --git a/OsmAnd/src/net/osmand/plus/monitoring/SettingsMonitoringActivity.java b/OsmAnd/src/net/osmand/plus/monitoring/SettingsMonitoringActivity.java deleted file mode 100644 index 7ace6377ad..0000000000 --- a/OsmAnd/src/net/osmand/plus/monitoring/SettingsMonitoringActivity.java +++ /dev/null @@ -1,325 +0,0 @@ -package net.osmand.plus.monitoring; - - -import android.content.BroadcastReceiver; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; -import android.text.SpannableString; -import android.text.style.StyleSpan; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; - -import net.osmand.plus.OsmAndFormatter; -import net.osmand.plus.OsmAndTaskManager.OsmAndTaskRunnable; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.activities.SavingTrackHelper; -import net.osmand.plus.activities.SettingsBaseActivity; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.util.Algorithms; - -import java.util.Map; - -import static net.osmand.plus.settings.backend.OsmandSettings.MONTHLY_DIRECTORY; -import static net.osmand.plus.settings.backend.OsmandSettings.REC_DIRECTORY; - -public class SettingsMonitoringActivity extends SettingsBaseActivity { - - public static final String PROFILE_STRING_KEY = "string_key"; - - private CheckBoxPreference routeServiceEnabled; - private BroadcastReceiver broadcastReceiver; - - public static final int[] BG_SECONDS = new int[]{0, 30, 60, 90}; - public static final int[] BG_MINUTES = new int[]{2, 3, 5, 10, 15, 30, 60, 90}; - private static final int[] SECONDS = OsmandMonitoringPlugin.SECONDS; - private static final int[] MINUTES = OsmandMonitoringPlugin.MINUTES; - private static final int[] MAX_INTERVAL_TO_SEND_MINUTES = OsmandMonitoringPlugin.MAX_INTERVAL_TO_SEND_MINUTES; - - public SettingsMonitoringActivity() { - super(true); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - requestWindowFeature(Window.FEATURE_PROGRESS); - super.onCreate(savedInstanceState); - setProgressVisibility(false); - getToolbar().setTitle(R.string.monitoring_settings); - PreferenceScreen grp = getPreferenceScreen(); - - createLoggingSection(grp); - createLiveSection(grp); - createNotificationSection(grp); - - Intent intent = getIntent(); - if (intent != null && intent.hasExtra(PROFILE_STRING_KEY)) { - String modeName = intent.getStringExtra(PROFILE_STRING_KEY); - selectedAppMode = ApplicationMode.valueOfStringKey(modeName, ApplicationMode.CAR); - } else { - selectAppModeDialog().show(); - } - } - - - private void createLoggingSection(PreferenceScreen grp) { - PreferenceCategory cat = new PreferenceCategory(this); - cat.setTitle(R.string.save_track_to_gpx_globally); - grp.addPreference(cat); - - Preference globalrecord = new Preference(this); - globalrecord.setTitle(R.string.save_track_to_gpx_globally_headline); - globalrecord.setSummary(R.string.save_track_to_gpx_globally_descr); - globalrecord.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //globalrecord.setEnabled(false); - cat.addPreference(globalrecord); - - if(settings.SAVE_GLOBAL_TRACK_REMEMBER.get()) { - cat.addPreference(createTimeListPreference(settings.SAVE_GLOBAL_TRACK_INTERVAL, SECONDS, - MINUTES, 1000, settings.SAVE_GLOBAL_TRACK_REMEMBER, R.string.save_global_track_interval, R.string.save_global_track_interval_descr)); - } - - Preference pref = new Preference(this); - pref.setTitle(R.string.save_current_track); - pref.setSummary(getMyApplication().getString(R.string.save_current_track_descr) - + " (" + OsmAndFormatter.getFormattedDistance(getMyApplication().getSavingTrackHelper().getDistance(), getMyApplication()) + ")"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - SavingTrackHelper helper = getMyApplication().getSavingTrackHelper(); - if (helper.hasDataToSave()) { - saveCurrentTracks(helper); - } else { - helper.close(); - } - return true; - } - }); - cat.addPreference(pref); - - cat.addPreference(createCheckBoxPreference(settings.SAVE_TRACK_TO_GPX, R.string.save_track_to_gpx, - R.string.save_track_to_gpx_descrp)); - cat.addPreference(createTimeListPreference(settings.SAVE_TRACK_INTERVAL, SECONDS, - MINUTES, 1000, R.string.save_track_interval, R.string.save_track_interval_descr)); - String[] names; - Float[] floatValues; - floatValues = new Float[] {0.f, 2.0f, 5.0f, 10.0f, 20.0f, 30.0f, 50.0f}; - names = new String[floatValues.length]; - names[0] = getString(R.string.shared_string_not_selected); - for(int i = 1; i < floatValues.length; i++) { - names[i] = floatValues[i].intValue() + " " + getString(R.string.m); - } - cat.addPreference(createListPreference(settings.SAVE_TRACK_MIN_DISTANCE, names, floatValues, - R.string.save_track_min_distance, R.string.save_track_min_distance_descr)); - floatValues = new Float[] {0.f, 1.0f, 2.0f, 5.0f, 10.0f, 15.0f, 20.0f, 50.0f, 100.0f}; - names = new String[floatValues.length]; - names[0] = getString(R.string.shared_string_not_selected); - for(int i = 1; i < floatValues.length; i++) { - names[i] = floatValues[i].intValue() + " " + getString(R.string.m) + " (" + Math.round(floatValues[i]/0.3048f) + " " + getString(R.string.foot) + ")"; - } - cat.addPreference(createListPreference(settings.SAVE_TRACK_PRECISION, names, floatValues, - R.string.save_track_precision, R.string.save_track_precision_descr)); - floatValues = new Float[] {0.f, 0.000001f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; - names = new String[floatValues.length]; - names[0] = getString(R.string.shared_string_not_selected); - names[1] = "> 0"; // This option for the GPS chipset motion detection - for(int i = 2; i < floatValues.length; i++) { - names[i] = floatValues[i].intValue() + " " + getString(R.string.km_h); - floatValues[i] = floatValues[i] / 3.6f; - } - cat.addPreference(createListPreference(settings.SAVE_TRACK_MIN_SPEED, names, floatValues, - R.string.save_track_min_speed, R.string.save_track_min_speed_descr)); - cat.addPreference(createCheckBoxPreference(settings.AUTO_SPLIT_RECORDING, R.string.auto_split_recording_title, - R.string.auto_split_recording_descr)); - cat.addPreference(createCheckBoxPreference(settings.DISABLE_RECORDING_ONCE_APP_KILLED, R.string.disable_recording_once_app_killed, - R.string.disable_recording_once_app_killed_descrp)); - cat.addPreference(createCheckBoxPreference(settings.SAVE_HEADING_TO_GPX, R.string.save_heading, - R.string.save_heading_descr)); - - Integer[] intValues = new Integer[]{REC_DIRECTORY, MONTHLY_DIRECTORY}; - names = new String[intValues.length]; - names[0] = getString(R.string.store_tracks_in_rec_directory); - names[1] = getString(R.string.store_tracks_in_monthly_directories); -// names[2] = getString(R.string.store_tracks_in_daily_directories); - cat.addPreference(createListPreference(settings.TRACK_STORAGE_DIRECTORY, names, intValues, - R.string.track_storage_directory, R.string.track_storage_directory_descrp)); - } - - - private void createLiveSection(PreferenceScreen grp) { - PreferenceCategory cat; - cat = new PreferenceCategory(this); - cat.setTitle(R.string.live_monitoring_m); - grp.addPreference(cat); - - EditTextPreference urlPreference = createEditTextPreference(settings.LIVE_MONITORING_URL, R.string.live_monitoring_url, - R.string.live_monitoring_url_descr); - urlPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (Algorithms.isValidMessageFormat((String) newValue)) { - return SettingsMonitoringActivity.super.onPreferenceChange(preference, newValue); - } else { - Toast.makeText(SettingsMonitoringActivity.this, R.string.wrong_format, Toast.LENGTH_SHORT).show(); - return false; - } - } - }); - cat.addPreference(urlPreference); - final CheckBoxPreference liveMonitoring = createCheckBoxPreference(settings.LIVE_MONITORING, R.string.live_monitoring_m, - R.string.live_monitoring_m_descr); - cat.addPreference(liveMonitoring); - cat.addPreference(createTimeListPreference(settings.LIVE_MONITORING_INTERVAL, SECONDS, - MINUTES, 1000, R.string.live_monitoring_interval, R.string.live_monitoring_interval_descr)); - cat.addPreference(createTimeListPreference(settings.LIVE_MONITORING_MAX_INTERVAL_TO_SEND, null, - MAX_INTERVAL_TO_SEND_MINUTES, 1000, R.string.live_monitoring_max_interval_to_send, R.string.live_monitoring_max_interval_to_send_desrc)); - } - - private void createNotificationSection(PreferenceScreen grp) { - PreferenceCategory cat; - cat = new PreferenceCategory(this); - cat.setTitle(R.string.shared_string_notifications); - grp.addPreference(cat); - - final CheckBoxPreference tripRecording = createCheckBoxPreference(settings.SHOW_TRIP_REC_NOTIFICATION, R.string.trip_rec_notification_settings, - R.string.trip_rec_notification_settings_desc); - cat.addPreference(tripRecording); - } - - public void updateAllSettings() { - super.updateAllSettings(); - - if(routeServiceEnabled != null) { - routeServiceEnabled.setChecked(getMyApplication().getNavigationService() != null); - } - } - - private void saveCurrentTracks(final SavingTrackHelper helper) { - setProgressVisibility(true); - getMyApplication().getTaskManager().runInBackground(new OsmAndTaskRunnable() { - - @Override - protected Void doInBackground(Void... params) { - SavingTrackHelper helper = getMyApplication().getSavingTrackHelper(); - helper.saveDataToGpx(getMyApplication().getAppCustomization().getTracksDir()); - helper.close(); - return null; - } - @Override - protected void onPostExecute(Void result) { - setProgressVisibility(false); - } - - }, (Void) null); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if(broadcastReceiver != null) { - unregisterReceiver(broadcastReceiver); - broadcastReceiver = null; - } - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - String prefId = preference.getKey(); - if (preference instanceof ListPreference) { - int ind = ((ListPreference) preference).findIndexOfValue((String) newValue); - CharSequence entry = ((ListPreference) preference).getEntries()[ind]; - Map map = getListPrefValues().get(prefId); - if (map != null) { - newValue = map.get(entry); - } - } - showConfirmDialog(prefId, newValue); - return false; - } - - protected void showConfirmDialog(final String prefId, final Object newValue) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - - String appModeName = selectedAppMode.toHumanString(); - String currentModeText = getString(R.string.apply_to_current_profile, appModeName); - int start = currentModeText.indexOf(appModeName); - - SpannableString[] strings = new SpannableString[2]; - strings[0] = new SpannableString(getString(R.string.apply_to_all_profiles)); - strings[1] = new SpannableString(currentModeText); - strings[1].setSpan(new StyleSpan(Typeface.BOLD), start, start + appModeName.length(), 0); - - final int[] icons = new int[2]; - icons[0] = R.drawable.ic_action_copy; - icons[1] = selectedAppMode.getIconRes(); - - final boolean nightMode = !settings.isLightContent(); - final OsmandApplication app = getMyApplication(); - final LayoutInflater themedInflater = UiUtilities.getInflater(this, nightMode); - - //set up dialog title - View dialogTitle = themedInflater.inflate(R.layout.bottom_sheet_item_simple, null); - dialogTitle.findViewById(R.id.icon).setVisibility(View.GONE); - TextView tvTitle = dialogTitle.findViewById(R.id.title); - tvTitle.setText(R.string.change_default_settings); - int textSize = (int) app.getResources().getDimension(R.dimen.dialog_header_text_size); - tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - builder.setCustomTitle(dialogTitle); - - final ArrayAdapter singleChoiceAdapter = new ArrayAdapter(this, R.layout.bottom_sheet_item_simple, R.id.title, strings) { - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View v = convertView; - if (v == null) { - v = themedInflater.inflate(R.layout.bottom_sheet_item_simple, parent, false); - } - int activeColor = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; - Drawable icon = app.getUIUtilities().getIcon(icons[position], activeColor); - ((TextView) v.findViewById(R.id.title)).setText(getItem(position)); - ((ImageView) v.findViewById(R.id.icon)).setImageDrawable(icon); - return v; - } - }; - - builder.setAdapter(singleChoiceAdapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - settings.setPreferenceForAllModes(prefId, newValue); - } else { - settings.setPreference(prefId, newValue); - } - updateAllSettings(); - } - }); - - builder.setNegativeButton(R.string.discard_changes, null); - AlertDialog dialog = builder.create(); - dialog.getListView().setDividerHeight(0); - dialog.show(); - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java index 393dd7d965..b90a22c0b9 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java @@ -18,6 +18,7 @@ import net.osmand.osm.edit.Entity.EntityType; import net.osmand.osm.edit.EntityInfo; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; +import net.osmand.osm.io.Base64; import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.OsmBaseStorage; import net.osmand.plus.OsmandApplication; @@ -30,10 +31,8 @@ import org.apache.commons.logging.Log; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; +import java.io.*; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.HashMap; @@ -108,14 +107,16 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { boolean doAuthenticate) { log.info("Sending request " + url); //$NON-NLS-1$ try { - if (doAuthenticate){ - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequest(url,requestMethod,requestBody); - return response.getBody(); - } - else { - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequestWithoutAuth(url,requestMethod,requestBody); + OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); + if (doAuthenticate) { + if (client.isValidToken()) { + Response response = client.performRequest(url, requestMethod, requestBody); + return response.getBody(); + } else { + return performBasicAuthRequest(url, requestMethod, requestBody, userOperation); + } + } else { + Response response = client.performRequestWithoutAuth(url, requestMethod, requestBody); return response.getBody(); } } catch (NullPointerException e) { @@ -135,7 +136,7 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); - } catch (ExecutionException e) { + } catch (Exception e) { log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); @@ -144,6 +145,55 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { return null; } + private String performBasicAuthRequest(String url, String requestMethod, String requestBody, String userOperation) throws IOException { + HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); + connection.setConnectTimeout(15000); + connection.setRequestMethod(requestMethod); + connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); //$NON-NLS-1$ + StringBuilder responseBody = new StringBuilder(); + String token = settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(); //$NON-NLS-1$ + connection.addRequestProperty("Authorization", "Basic " + Base64.encode(token.getBytes("UTF-8"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoInput(true); + if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoOutput(true); + connection.setRequestProperty("Content-type", "text/xml"); //$NON-NLS-1$ //$NON-NLS-2$ + OutputStream out = connection.getOutputStream(); + if (requestBody != null) { + BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"), 1024); //$NON-NLS-1$ + bwr.write(requestBody); + bwr.flush(); + } + out.close(); + } + connection.connect(); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + String msg = userOperation + + " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage(); //$NON-NLS-1$//$NON-NLS-2$ + log.error(msg); + showWarning(msg); + } else { + log.info("Response : " + connection.getResponseMessage()); //$NON-NLS-1$ + // populate return fields. + responseBody.setLength(0); + InputStream i = connection.getInputStream(); + if (i != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); //$NON-NLS-1$ + String s; + boolean f = true; + while ((s = in.readLine()) != null) { + if (!f) { + responseBody.append("\n"); //$NON-NLS-1$ + } else { + f = false; + } + responseBody.append(s); + } + } + return responseBody.toString(); + } + return null; + } + public long openChangeSet(String comment) { long id = -1; StringWriter writer = new StringWriter(256); diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java index 4c5ed73b07..daaeb6d034 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java @@ -1,5 +1,6 @@ package net.osmand.plus.osmedit; +import android.content.Context; import android.content.Intent; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -7,31 +8,50 @@ import android.os.Bundle; import android.text.SpannableString; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.TextView; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; -import net.osmand.plus.settings.backend.OsmAndAppCustomization; +import net.osmand.PlatformUtil; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.FontCache; +import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter; +import net.osmand.plus.settings.backend.OsmAndAppCustomization; +import net.osmand.plus.settings.bottomsheets.OsmLoginDataBottomSheet; import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.fragments.OnPreferenceChanged; -import net.osmand.plus.settings.bottomsheets.OsmLoginDataBottomSheet; import net.osmand.plus.settings.preferences.SwitchPreferenceEx; import net.osmand.plus.widgets.style.CustomTypefaceSpan; +import org.apache.commons.logging.Log; + import static net.osmand.plus.myplaces.FavoritesActivity.TAB_ID; import static net.osmand.plus.osmedit.OsmEditingPlugin.OSM_EDIT_TAB; public class OsmEditingFragment extends BaseSettingsFragment implements OnPreferenceChanged { + private static final Log log = PlatformUtil.getLog(OsmEditingFragment.class); + private static final String OSM_EDITING_INFO = "osm_editing_info"; private static final String OPEN_OSM_EDITS = "open_osm_edits"; private static final String OSM_LOGIN_DATA = "osm_login_data"; + private static final String OSM_OAUTH_SUCCESS = "osm_oauth_success"; + private static final String OSM_OAUTH_CLEAR = "osm_oauth_clear"; + private static final String OSM_OAUTH_LOGIN = "osm_oauth_login"; + + private OsmOAuthAuthorizationAdapter client; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + client = new OsmOAuthAuthorizationAdapter(app); + } @Override protected void setupPreferences() { @@ -42,6 +62,7 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer setupOfflineEditingPref(); setupOsmEditsDescrPref(); setupOsmEditsPref(); + setupOAuthPrefs(); } @Override @@ -73,7 +94,7 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer Drawable enabled = getActiveIcon(R.drawable.ic_world_globe_dark); Drawable icon = getPersistentPrefIcon(enabled, disabled); - SwitchPreferenceEx offlineEditingPref = (SwitchPreferenceEx) findPreference(settings.OFFLINE_EDITION.getId()); + SwitchPreferenceEx offlineEditingPref = findPreference(settings.OFFLINE_EDITION.getId()); offlineEditingPref.setDescription(getString(R.string.offline_edition_descr)); offlineEditingPref.setIcon(icon); } @@ -101,9 +122,37 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer createProfile.setIcon(getActiveIcon(R.drawable.ic_action_folder)); } + private void setupOAuthPrefs() { + Context ctx = getContext(); + if (ctx != null) { + PreferenceScreen screen = getPreferenceScreen(); + if (client.isValidToken()) { + Preference prefOAuth = new Preference(ctx); + prefOAuth.setTitle(R.string.osm_authorization_success); + prefOAuth.setSummary(R.string.osm_authorization_success); + prefOAuth.setKey(OSM_OAUTH_SUCCESS); + + Preference prefClearToken = new Preference(ctx); + prefClearToken.setTitle(R.string.shared_string_logoff); + prefClearToken.setSummary(R.string.clear_osm_token); + prefClearToken.setKey(OSM_OAUTH_CLEAR); + + screen.addPreference(prefOAuth); + screen.addPreference(prefClearToken); + } else { + Preference prefOAuth = new Preference(ctx); + prefOAuth.setTitle(R.string.perform_oauth_authorization); + prefOAuth.setSummary(R.string.perform_oauth_authorization_description); + prefOAuth.setKey(OSM_OAUTH_LOGIN); + screen.addPreference(prefOAuth); + } + } + } + @Override public boolean onPreferenceClick(Preference preference) { - if (OPEN_OSM_EDITS.equals(preference.getKey())) { + String prefId = preference.getKey(); + if (OPEN_OSM_EDITS.equals(prefId)) { Bundle bundle = new Bundle(); bundle.putInt(TAB_ID, OSM_EDIT_TAB); @@ -113,12 +162,29 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer favorites.putExtra(MapActivity.INTENT_PARAMS, bundle); startActivity(favorites); return true; - } else if (OSM_LOGIN_DATA.equals(preference.getKey())) { + } else if (OSM_LOGIN_DATA.equals(prefId)) { FragmentManager fragmentManager = getFragmentManager(); if (fragmentManager != null) { OsmLoginDataBottomSheet.showInstance(fragmentManager, OSM_LOGIN_DATA, this, false, getSelectedAppMode()); return true; } + } else if (OSM_OAUTH_CLEAR.equals(prefId)) { + settings.USER_ACCESS_TOKEN.set(""); + settings.USER_ACCESS_TOKEN_SECRET.set(""); + + client.resetToken(); + client = new OsmOAuthAuthorizationAdapter(app); + + app.showShortToastMessage(R.string.osm_edit_logout_success); + updateAllSettings(); + return true; + } else if (OSM_OAUTH_LOGIN.equals(prefId)) { + View view = getView(); + if (view != null) { + ViewGroup appBarLayout = view.findViewById(R.id.appbar); + client.startOAuth(appBarLayout); + } + return true; } return super.onPreferenceClick(preference); } @@ -130,4 +196,11 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer nameAndPasswordPref.setSummary(settings.USER_NAME.get()); } } + + public void authorize(String oauthVerifier) { + if (client != null) { + client.authorize(oauthVerifier); + } + updateAllSettings(); + } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java index 5030530f88..ea779a93b1 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java @@ -13,9 +13,11 @@ import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; + import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; import net.osmand.data.Amenity; @@ -23,13 +25,11 @@ import net.osmand.data.MapObject; import net.osmand.data.TransportStop; import net.osmand.osm.PoiType; import net.osmand.osm.edit.Entity; -import net.osmand.plus.*; +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.settings.backend.OsmandPreference; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.activities.EnumAdapter; import net.osmand.plus.activities.EnumAdapter.IEnumWithResource; @@ -42,16 +42,21 @@ import net.osmand.plus.myplaces.AvailableGPXFragment.GpxInfo; import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.osmedit.OsmPoint.Action; import net.osmand.plus.quickaction.QuickActionType; +import net.osmand.plus.settings.backend.OsmandPreference; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.Algorithms; + import org.apache.commons.logging.Log; import java.util.ArrayList; import java.util.List; -import static net.osmand.aidlapi.OsmAndCustomizationConstants.*; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_CREATE_POI; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_OPEN_OSM_NOTE; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.OSM_EDITS; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.OSM_NOTES; import static net.osmand.plus.ContextMenuAdapter.makeDeleteAction; @@ -193,13 +198,8 @@ public class OsmEditingPlugin extends OsmandPlugin { } @Override - public Class getSettingsActivity() { - return SettingsOsmEditingActivity.class; - } - - @Override - public Class getSettingsFragment() { - return OsmEditingFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.OPEN_STREET_MAP_EDITING; } @Override diff --git a/OsmAnd/src/net/osmand/plus/osmedit/SettingsOsmEditingActivity.java b/OsmAnd/src/net/osmand/plus/osmedit/SettingsOsmEditingActivity.java deleted file mode 100644 index 1be623825b..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/SettingsOsmEditingActivity.java +++ /dev/null @@ -1,189 +0,0 @@ -package net.osmand.plus.osmedit; - - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.StrictMode; -import android.preference.CheckBoxPreference; -import android.preference.DialogPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceScreen; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.Toast; -import com.github.scribejava.core.model.OAuthAsyncRequestCallback; -import com.github.scribejava.core.model.Response; -import net.osmand.PlatformUtil; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.activities.SettingsBaseActivity; -import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter; -import net.osmand.plus.settings.backend.OsmAndAppCustomization; -import org.apache.commons.logging.Log; - -import java.io.IOException; - -public class SettingsOsmEditingActivity extends SettingsBaseActivity { - private OsmOAuthAuthorizationAdapter client; - private static final Log log = PlatformUtil.getLog(SettingsOsmEditingActivity.class); - - @Override - public void onCreate(Bundle savedInstanceState) { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .detectNetwork() - .penaltyLog() - .build()); - - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - - client = new OsmOAuthAuthorizationAdapter(getMyApplication()); - - getToolbar().setTitle(R.string.osm_settings); - @SuppressWarnings("deprecation") - PreferenceScreen grp = getPreferenceScreen(); - - DialogPreference loginDialogPreference = new OsmLoginDataDialogPreference(this, null); - grp.addPreference(loginDialogPreference); - - CheckBoxPreference poiEdit = createCheckBoxPreference(settings.OFFLINE_EDITION, - R.string.offline_edition, R.string.offline_edition_descr); - grp.addPreference(poiEdit); - - final Preference pref = new Preference(this); - pref.setTitle(R.string.local_openstreetmap_settings); - pref.setSummary(R.string.local_openstreetmap_settings_descr); - pref.setKey("local_openstreetmap_points"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - OsmAndAppCustomization appCustomization = getMyApplication().getAppCustomization(); - final Intent favorites = new Intent(SettingsOsmEditingActivity.this, - appCustomization.getFavoritesActivity()); - favorites.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - getMyApplication().getSettings().FAVORITES_TAB.set(R.string.osm_edits); - startActivity(favorites); - return true; - } - }); - grp.addPreference(pref); - - final Preference prefOAuth = new Preference(this); - if (client.isValidToken()){ - prefOAuth.setTitle(R.string.osm_authorization_success); - prefOAuth.setSummary(R.string.osm_authorization_success); - prefOAuth.setKey("local_openstreetmap_oauth_success"); - final Preference prefClearToken = new Preference(this); - prefClearToken.setTitle(R.string.shared_string_logoff); - prefClearToken.setSummary(R.string.clear_osm_token); - prefClearToken.setKey("local_openstreetmap_oauth_clear"); - prefClearToken.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - settings.USER_ACCESS_TOKEN.set(""); - settings.USER_ACCESS_TOKEN_SECRET.set(""); - client.resetToken(); - Toast.makeText(SettingsOsmEditingActivity.this, R.string.osm_edit_logout_success, Toast.LENGTH_SHORT).show(); - finish(); - startActivity(getIntent()); - return true; - } - }); - grp.addPreference(prefClearToken); - } - else { - prefOAuth.setTitle(R.string.perform_oauth_authorization); - prefOAuth.setSummary(R.string.perform_oauth_authorization_description); - prefOAuth.setKey("local_openstreetmap_oauth_login"); - prefOAuth.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - ViewGroup preferenceView = (ViewGroup)getListView().getChildAt(preference.getOrder()); - client.startOAuth(preferenceView); - return true; - } - }); - } - grp.addPreference(prefOAuth); - } - - public class OsmLoginDataDialogPreference extends DialogPreference { - private TextView userNameEditText; - private TextView passwordEditText; - - public OsmLoginDataDialogPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - setDialogLayoutResource(R.layout.osm_user_login_details); - setPositiveButtonText(android.R.string.ok); - setNegativeButtonText(android.R.string.cancel); - setDialogTitle(R.string.open_street_map_login_and_pass); - - setTitle(R.string.open_street_map_login_and_pass); - setSummary(R.string.open_street_map_login_descr); - - setDialogIcon(null); - } - - @Override - protected void onBindDialogView(View view) { - userNameEditText = (TextView) view.findViewById(R.id.user_name_field); - userNameEditText.setText(settings.USER_NAME.get()); - passwordEditText = (TextView) view.findViewById(R.id.password_field); - passwordEditText.setText(settings.USER_PASSWORD.get()); - super.onBindDialogView(view); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - if (positiveResult) { - settings.USER_NAME.set(userNameEditText.getText().toString()); - settings.USER_PASSWORD.set(passwordEditText.getText().toString()); - new ValidateOsmLoginDetailsTask(SettingsOsmEditingActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - } - - public static class ValidateOsmLoginDetailsTask extends AsyncTask { - private final Context context; - - public ValidateOsmLoginDetailsTask(Context context) { - this.context = context; - } - - @Override - protected OsmBugsUtil.OsmBugResult doInBackground(Void... params) { - OsmEditingPlugin plugin = OsmandPlugin.getPlugin(OsmEditingPlugin.class); - assert plugin != null; - OsmBugsRemoteUtil remoteUtil = plugin.getOsmNotesRemoteUtil(); - return remoteUtil.validateLoginDetails(); - } - - @Override - protected void onPostExecute(OsmBugsUtil.OsmBugResult osmBugResult) { - String text = osmBugResult.warning != null ? osmBugResult.warning : context.getString(R.string.osm_authorization_success); - Toast.makeText(context, text, Toast.LENGTH_LONG).show(); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - Uri uri = intent.getData(); - if (uri != null && uri.toString().startsWith("osmand-oauth")) { - String oauthVerifier = uri.getQueryParameter("oauth_verifier"); - client.authorize(oauthVerifier); - finish(); - startActivity(getIntent()); - } - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/ValidateOsmLoginDetailsTask.java b/OsmAnd/src/net/osmand/plus/osmedit/ValidateOsmLoginDetailsTask.java new file mode 100644 index 0000000000..722f104c51 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/ValidateOsmLoginDetailsTask.java @@ -0,0 +1,34 @@ +package net.osmand.plus.osmedit; + +import android.os.AsyncTask; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.osmedit.OsmBugsUtil.OsmBugResult; + +public class ValidateOsmLoginDetailsTask extends AsyncTask { + + private OsmandApplication app; + + public ValidateOsmLoginDetailsTask(OsmandApplication app) { + this.app = app; + } + + @Override + protected OsmBugResult doInBackground(Void... params) { + OsmEditingPlugin plugin = OsmandPlugin.getPlugin(OsmEditingPlugin.class); + assert plugin != null; + OsmBugsRemoteUtil remoteUtil = plugin.getOsmNotesRemoteUtil(); + return remoteUtil.validateLoginDetails(); + } + + @Override + protected void onPostExecute(OsmBugResult osmBugResult) { + if (osmBugResult.warning != null) { + app.showToastMessage(osmBugResult.warning); + } else { + app.showToastMessage(R.string.osm_authorization_success); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index 3f8ed81ba7..1b070e38a1 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -46,6 +46,7 @@ public class RendererRegistry { public final static String LIGHTRS_RENDER = "LightRS"; //$NON-NLS-1$ public final static String UNIRS_RENDER = "UniRS"; //$NON-NLS-1$ public final static String DESERT_RENDER = "Desert"; //$NON-NLS-1$ + public final static String SNOWMOBILE_RENDER = "Snowmobile"; //$NON-NLS-1$ private RenderingRulesStorage defaultRender = null; private RenderingRulesStorage currentSelectedRender = null; @@ -75,6 +76,7 @@ public class RendererRegistry { internalRenderers.put(WINTER_SKI_RENDER, "skimap" + ".render.xml"); internalRenderers.put(OFFROAD_RENDER, "offroad" + ".render.xml"); internalRenderers.put(DESERT_RENDER, "desert" + ".render.xml"); + internalRenderers.put(SNOWMOBILE_RENDER, "snowmobile" + ".render.xml"); } public RenderingRulesStorage defaultRender() { @@ -323,6 +325,8 @@ public class RendererRegistry { return ctx.getString(R.string.off_road_render_descr); case DESERT_RENDER: return ctx.getString(R.string.desert_render_descr); + case SNOWMOBILE_RENDER: + return ctx.getString(R.string.snowmobile_render_descr); } return ""; } diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java index fcab4d6578..1f29dfd1b7 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java @@ -345,18 +345,9 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT List route = app.getRoutingHelper().getRoute().getOriginalRoute(); if (route != null) { - RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer(); - RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender(); - - MapRenderRepositories maps = app.getResourceManager().getRenderer(); - RenderingRuleSearchRequest currentSearchRequest = maps.getSearchRequestWithAppliedCustomRules(currentRenderer, isNightMode()); - RenderingRuleSearchRequest defaultSearchRequest = maps.getSearchRequestWithAppliedCustomRules(defaultRender, isNightMode()); - - List routeStatistics = RouteStatisticsHelper.calculateRouteStatistic(route, - currentRenderer, defaultRender, currentSearchRequest, defaultSearchRequest); + List routeStatistics = calculateRouteStatistics(app, route, isNightMode()); GPXTrackAnalysis analysis = gpx.getAnalysis(0); - for (RouteStatistics statistic : routeStatistics) { RouteInfoCard routeClassCard = new RouteInfoCard(mapActivity, statistic, analysis); addRouteCard(cardsContainer, routeClassCard); @@ -375,6 +366,20 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT } } + public static List calculateRouteStatistics(OsmandApplication app, + List route, + boolean nightMode) { + RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer(); + RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender(); + MapRenderRepositories maps = app.getResourceManager().getRenderer(); + RenderingRuleSearchRequest currentSearchRequest = + maps.getSearchRequestWithAppliedCustomRules(currentRenderer, nightMode); + RenderingRuleSearchRequest defaultSearchRequest = + maps.getSearchRequestWithAppliedCustomRules(defaultRender, nightMode); + return RouteStatisticsHelper.calculateRouteStatistic(route, currentRenderer, + defaultRender, currentSearchRequest, defaultSearchRequest); + } + @Override protected void calculateLayout(View view, boolean initLayout) { super.calculateLayout(view, initLayout); diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java index 94770e0135..c21b508116 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java @@ -85,13 +85,13 @@ public class TracksToFollowCard extends BaseCard { private void setupCategoriesRow() { final HorizontalSelectionAdapter selectionAdapter = new HorizontalSelectionAdapter(app, nightMode); - selectionAdapter.setItems(new ArrayList<>(gpxInfoCategories.keySet())); - selectionAdapter.setSelectedItem(selectedCategory); + selectionAdapter.setTitledItems(new ArrayList<>(gpxInfoCategories.keySet())); + selectionAdapter.setSelectedItemByTitle(selectedCategory); selectionAdapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedCategory = item; - List items = gpxInfoCategories.get(item); + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + selectedCategory = item.getTitle(); + List items = gpxInfoCategories.get(selectedCategory); tracksAdapter.setShowFolderName(showFoldersName()); tracksAdapter.setGpxInfoList(items != null ? items : new ArrayList()); tracksAdapter.notifyDataSetChanged(); diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java index bda48d389f..bd7e8e9f4e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java @@ -7,5 +7,7 @@ public enum ExportSettingsType { MAP_SOURCES, CUSTOM_RENDER_STYLE, CUSTOM_ROUTING, - AVOID_ROADS + AVOID_ROADS, + TRACKS, + MULTIMEDIA_NOTES } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java index c8025bc2d5..7795f943ba 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java @@ -27,12 +27,11 @@ import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.Version; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.activities.PluginsActivity; import net.osmand.plus.activities.SettingsActivity; import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.download.DownloadActivity; -import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.helpers.WaypointHelper; +import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.routing.RouteCalculationResult; import net.osmand.plus.views.OsmandMapTileView; @@ -81,10 +80,16 @@ public class OsmAndAppCustomization { private Set featuresDisabledIds = new HashSet<>(); private Set featuresEnabledPatterns = new HashSet<>(); private Set featuresDisabledPatterns = new HashSet<>(); + private Set marginAppModeUsage = new HashSet<>(); private Map> widgetsVisibilityMap = new LinkedHashMap<>(); private Map> widgetsAvailabilityMap = new LinkedHashMap<>(); private CustomOsmandSettings customOsmandSettings; + private int marginLeft; + private int marginTop; + private int marginRight; + private int marginBottom; + private boolean featuresCustomized; private boolean widgetsCustomized; @@ -151,6 +156,10 @@ public class OsmAndAppCustomization { featuresCustomized = false; widgetsCustomized = false; customOsmandSettings = null; + marginLeft = 0; + marginTop = 0; + marginRight = 0; + marginBottom = 0; restoreOsmandSettings(); featuresEnabledIds.clear(); @@ -159,6 +168,7 @@ public class OsmAndAppCustomization { featuresDisabledPatterns.clear(); widgetsVisibilityMap.clear(); widgetsAvailabilityMap.clear(); + marginAppModeUsage.clear(); return true; } @@ -184,10 +194,6 @@ public class OsmAndAppCustomization { return DownloadActivity.class; } - public Class getPluginsActivity() { - return PluginsActivity.class; - } - public Class getDownloadActivity() { return DownloadActivity.class; } @@ -368,6 +374,26 @@ public class OsmAndAppCustomization { return set; } + public void setMapMargins(int left, int top, int right, int bottom, List appModeKeys) { + marginLeft = left; + marginTop = top; + marginRight = right; + marginBottom = bottom; + marginAppModeUsage.addAll(getAppModesSet(appModeKeys)); + } + + public void updateMapMargins(MapActivity mapActivity) { + if (isMapMarginAvailable()) { + mapActivity.setMargins(marginLeft, marginTop, marginRight, marginBottom); + } else { + mapActivity.setMargins(0, 0, 0, 0); + } + } + + boolean isMapMarginAvailable() { + return marginAppModeUsage.contains(app.getSettings().getApplicationMode()); + } + public boolean isWidgetVisible(@NonNull String key, ApplicationMode appMode) { Set set = widgetsVisibilityMap.get(key); if (set == null) { diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/SettingsHelper.java deleted file mode 100644 index a708f92a46..0000000000 --- a/OsmAnd/src/net/osmand/plus/settings/backend/SettingsHelper.java +++ /dev/null @@ -1,3139 +0,0 @@ -package net.osmand.plus.settings.backend; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.AsyncTask; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - -import net.osmand.AndroidUtils; -import net.osmand.IndexConstants; -import net.osmand.PlatformUtil; -import net.osmand.data.LatLon; -import net.osmand.map.ITileSource; -import net.osmand.map.TileSourceManager; -import net.osmand.map.TileSourceManager.TileSourceTemplate; -import net.osmand.map.WorldRegion; -import net.osmand.osm.MapPoiTypes; -import net.osmand.osm.PoiCategory; -import net.osmand.plus.CustomOsmandPlugin; -import net.osmand.plus.CustomOsmandPlugin.SuggestedDownloadItem; -import net.osmand.plus.CustomRegion; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.SQLiteTileSource; -import net.osmand.plus.helpers.AvoidSpecificRoads; -import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; -import net.osmand.plus.poi.PoiUIFilter; -import net.osmand.plus.quickaction.QuickAction; -import net.osmand.plus.quickaction.QuickActionRegistry; -import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; -import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBuilder; -import net.osmand.router.GeneralRouter; -import net.osmand.util.Algorithms; - -import org.apache.commons.logging.Log; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; - -/* - Usage: - - SettingsHelper helper = app.getSettingsHelper(); - File file = new File(app.getAppPath(null), "settings.zip"); - - List items = new ArrayList<>(); - items.add(new GlobalSettingsItem(app.getSettings())); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.DEFAULT)); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.CAR)); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.PEDESTRIAN)); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.BICYCLE)); - items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 2.gpx"))); - items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 3.gpx"))); - items.add(new FileSettingsItem(app, new File(app.getAppPath(RENDERERS_DIR), "default.render.xml"))); - items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '1'}, "data1")); - items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '2'}, "data2")); - - helper.exportSettings(file, items); - - helper.importSettings(file); - */ - -public class SettingsHelper { - - public static final int VERSION = 1; - - public static final String SETTINGS_TYPE_LIST_KEY = "settings_type_list_key"; - public static final String REPLACE_KEY = "replace"; - public static final String SETTINGS_LATEST_CHANGES_KEY = "settings_latest_changes"; - public static final String SETTINGS_VERSION_KEY = "settings_version"; - - private static final Log LOG = PlatformUtil.getLog(SettingsHelper.class); - private static final int BUFFER = 1024; - - private OsmandApplication app; - - private ImportAsyncTask importTask; - private Map exportAsyncTasks = new HashMap<>(); - - public interface SettingsImportListener { - void onSettingsImportFinished(boolean succeed, @NonNull List items); - } - - public interface SettingsCollectListener { - void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items); - } - - public interface CheckDuplicatesListener { - void onDuplicatesChecked(@NonNull List duplicates, List items); - } - - public interface SettingsExportListener { - void onSettingsExportFinished(@NonNull File file, boolean succeed); - } - - public SettingsHelper(@NonNull OsmandApplication app) { - this.app = app; - } - - public enum SettingsItemType { - GLOBAL, - PROFILE, - PLUGIN, - DATA, - FILE, - RESOURCES, - QUICK_ACTIONS, - POI_UI_FILTERS, - MAP_SOURCES, - AVOID_ROADS, - SUGGESTED_DOWNLOADS, - DOWNLOADS - } - - public abstract static class SettingsItem { - - protected OsmandApplication app; - - protected String pluginId; - protected String fileName; - - boolean shouldReplace = false; - - protected List warnings; - - SettingsItem(@NonNull OsmandApplication app) { - this.app = app; - init(); - } - - SettingsItem(@NonNull OsmandApplication app, @Nullable SettingsItem baseItem) { - this.app = app; - if (baseItem != null) { - this.pluginId = baseItem.pluginId; - this.fileName = baseItem.fileName; - } - init(); - } - - SettingsItem(OsmandApplication app, @NonNull JSONObject json) throws JSONException { - this.app = app; - init(); - readFromJson(json); - } - - protected void init() { - warnings = new ArrayList<>(); - } - - public List getWarnings() { - return warnings; - } - - @NonNull - public abstract SettingsItemType getType(); - - @NonNull - public abstract String getName(); - - @NonNull - public abstract String getPublicName(@NonNull Context ctx); - - @NonNull - public String getDefaultFileName() { - return getName() + getDefaultFileExtension(); - } - - @NonNull - public String getDefaultFileExtension() { - return ".json"; - } - - public String getPluginId() { - return pluginId; - } - - @Nullable - public String getFileName() { - return fileName; - } - - public boolean applyFileName(@NonNull String fileName) { - String n = getFileName(); - return n != null && n.endsWith(fileName); - } - - public boolean shouldReadOnCollecting() { - return false; - } - - public void setShouldReplace(boolean shouldReplace) { - this.shouldReplace = shouldReplace; - } - - static SettingsItemType parseItemType(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { - String type = json.has("type") ? json.getString("type") : null; - if (type == null) { - throw new IllegalArgumentException("No type field"); - } - if (type.equals("QUICK_ACTION")) { - type = "QUICK_ACTIONS"; - } - return SettingsItemType.valueOf(type); - } - - public boolean exists() { - return false; - } - - public void apply() { - // non implemented - } - - void readFromJson(@NonNull JSONObject json) throws JSONException { - pluginId = json.has("pluginId") ? json.getString("pluginId") : null; - if (json.has("name")) { - fileName = json.getString("name") + getDefaultFileExtension(); - } - if (json.has("file")) { - fileName = json.getString("file"); - } - readItemsFromJson(json); - } - - void writeToJson(@NonNull JSONObject json) throws JSONException { - json.put("type", getType().name()); - String pluginId = getPluginId(); - if (!Algorithms.isEmpty(pluginId)) { - json.put("pluginId", pluginId); - } - if (getWriter() != null) { - String fileName = getFileName(); - if (Algorithms.isEmpty(fileName)) { - fileName = getDefaultFileName(); - } - json.put("file", fileName); - } - writeItemsToJson(json); - } - - String toJson() throws JSONException { - JSONObject json = new JSONObject(); - writeToJson(json); - return json.toString(); - } - - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - // override - } - - void writeItemsToJson(@NonNull JSONObject json) { - // override - } - - @Nullable - abstract SettingsItemReader getReader(); - - @Nullable - abstract SettingsItemWriter getWriter(); - - @NonNull - SettingsItemReader getJsonReader() { - return new SettingsItemReader(this) { - @Override - public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { - StringBuilder buf = new StringBuilder(); - try { - BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); - String str; - while ((str = in.readLine()) != null) { - buf.append(str); - } - } catch (IOException e) { - throw new IOException("Cannot read json body", e); - } - String json = buf.toString(); - if (json.length() == 0) { - throw new IllegalArgumentException("Json body is empty"); - } - try { - readItemsFromJson(new JSONObject(json)); - } catch (JSONException e) { - throw new IllegalArgumentException("Json parsing error", e); - } - } - }; - } - - @NonNull - SettingsItemWriter getJsonWriter() { - return new SettingsItemWriter(this) { - @Override - public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { - JSONObject json = new JSONObject(); - writeItemsToJson(json); - if (json.length() > 0) { - try { - String s = json.toString(2); - outputStream.write(s.getBytes("UTF-8")); - } catch (JSONException e) { - LOG.error("Failed to write json to stream", e); - } - return true; - } - return false; - } - }; - } - - @Override - public int hashCode() { - return (getType().name() + getName()).hashCode(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof SettingsItem)) { - return false; - } - - SettingsItem item = (SettingsItem) other; - return item.getType() == getType() - && item.getName().equals(getName()) - && Algorithms.stringsEqual(item.getFileName(), getFileName()); - } - } - - public static class PluginSettingsItem extends SettingsItem { - - private CustomOsmandPlugin plugin; - private List pluginDependentItems; - - PluginSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - pluginDependentItems = new ArrayList<>(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.PLUGIN; - } - - @NonNull - @Override - public String getName() { - return plugin.getId(); - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return plugin.getName(); - } - - @NonNull - @Override - public String getDefaultFileName() { - return getName(); - } - - public CustomOsmandPlugin getPlugin() { - return plugin; - } - - public List getPluginDependentItems() { - return pluginDependentItems; - } - - @Override - public boolean exists() { - return OsmandPlugin.getPlugin(getPluginId()) != null; - } - - @Override - public void apply() { - if (shouldReplace || !exists()) { - for (SettingsHelper.SettingsItem item : pluginDependentItems) { - if (item instanceof SettingsHelper.FileSettingsItem) { - FileSettingsItem fileItem = (FileSettingsItem) item; - if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { - plugin.addRenderer(fileItem.getName()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { - plugin.addRouter(fileItem.getName()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.OTHER) { - plugin.setResourceDirName(item.getFileName()); - } - } else if (item instanceof SuggestedDownloadsItem) { - plugin.updateSuggestedDownloads(((SuggestedDownloadsItem) item).getItems()); - } else if (item instanceof DownloadsItem) { - plugin.updateDownloadItems(((DownloadsItem) item).getItems()); - } - } - OsmandPlugin.addCustomPlugin(app, plugin); - } - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - plugin = new CustomOsmandPlugin(app, json); - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - plugin.writeAdditionalDataToJson(json); - } - - @Nullable - @Override - SettingsItemReader getReader() { - return null; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class SuggestedDownloadsItem extends SettingsItem { - - private List items; - - SuggestedDownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - items = new ArrayList<>(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.SUGGESTED_DOWNLOADS; - - } - - @NonNull - @Override - public String getName() { - return "suggested_downloads"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "suggested_downloads"; - } - - public List getItems() { - return items; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.getJSONObject(i); - String scopeId = object.optString("scope-id"); - String searchType = object.optString("search-type"); - int limit = object.optInt("limit", -1); - - List names = new ArrayList<>(); - if (object.has("names")) { - JSONArray namesArray = object.getJSONArray("names"); - for (int j = 0; j < namesArray.length(); j++) { - names.add(namesArray.getString(j)); - } - } - SuggestedDownloadItem suggestedDownload = new SuggestedDownloadItem(scopeId, searchType, names, limit); - items.add(suggestedDownload); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (SuggestedDownloadItem downloadItem : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("scope-id", downloadItem.getScopeId()); - if (downloadItem.getLimit() != -1) { - jsonObject.put("limit", downloadItem.getLimit()); - } - if (!Algorithms.isEmpty(downloadItem.getSearchType())) { - jsonObject.put("search-type", downloadItem.getSearchType()); - } - if (!Algorithms.isEmpty(downloadItem.getNames())) { - JSONArray namesArray = new JSONArray(); - for (String downloadName : downloadItem.getNames()) { - namesArray.put(downloadName); - } - jsonObject.put("names", namesArray); - } - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return null; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class DownloadsItem extends SettingsItem { - - private List items; - - DownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - items = new ArrayList<>(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.DOWNLOADS; - - } - - @NonNull - @Override - public String getName() { - return "downloads"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "downloads"; - } - - public List getItems() { - return items; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - items.addAll(CustomOsmandPlugin.collectRegionsFromJson(app, jsonArray)); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (WorldRegion region : items) { - if (region instanceof CustomRegion) { - JSONObject regionJson = ((CustomRegion) region).toJson(); - jsonArray.put(regionJson); - } - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return null; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public abstract static class CollectionSettingsItem extends SettingsItem { - - protected List items; - protected List appliedItems; - protected List duplicateItems; - protected List existingItems; - - @Override - protected void init() { - super.init(); - items = new ArrayList<>(); - appliedItems = new ArrayList<>(); - duplicateItems = new ArrayList<>(); - } - - CollectionSettingsItem(@NonNull OsmandApplication app, @Nullable CollectionSettingsItem baseItem, @NonNull List items) { - super(app, baseItem); - this.items = items; - } - - CollectionSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @NonNull - public List getItems() { - return items; - } - - @NonNull - public List getAppliedItems() { - return appliedItems; - } - - @NonNull - public List getDuplicateItems() { - return duplicateItems; - } - - @NonNull - public List processDuplicateItems() { - if (!items.isEmpty()) { - for (T item : items) { - if (isDuplicate(item)) { - duplicateItems.add(item); - } - } - } - return duplicateItems; - } - - public List getNewItems() { - List res = new ArrayList<>(items); - res.removeAll(duplicateItems); - return res; - } - - public abstract boolean isDuplicate(@NonNull T item); - - @NonNull - public abstract T renameItem(@NonNull T item); - } - - public abstract static class SettingsItemReader { - - private T item; - - public SettingsItemReader(@NonNull T item) { - this.item = item; - } - - public abstract void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException; - } - - public abstract static class SettingsItemWriter { - - private T item; - - public SettingsItemWriter(T item) { - this.item = item; - } - - public T getItem() { - return item; - } - - public abstract boolean writeToStream(@NonNull OutputStream outputStream) throws IOException; - } - - public abstract static class OsmandSettingsItem extends SettingsItem { - - private OsmandSettings settings; - - protected OsmandSettingsItem(@NonNull OsmandSettings settings) { - super(settings.getContext()); - this.settings = settings; - } - - protected OsmandSettingsItem(@NonNull OsmandSettings settings, @Nullable OsmandSettingsItem baseItem) { - super(settings.getContext(), baseItem); - this.settings = settings; - } - - protected OsmandSettingsItem(@NonNull SettingsItemType type, @NonNull OsmandSettings settings, @NonNull JSONObject json) throws JSONException { - super(settings.getContext(), json); - this.settings = settings; - } - - public OsmandSettings getSettings() { - return settings; - } - } - - public abstract static class OsmandSettingsItemReader extends SettingsItemReader { - - private OsmandSettings settings; - - public OsmandSettingsItemReader(@NonNull T item, @NonNull OsmandSettings settings) { - super(item); - this.settings = settings; - } - - protected abstract void readPreferenceFromJson(@NonNull OsmandPreference preference, - @NonNull JSONObject json) throws JSONException; - - @Override - public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { - StringBuilder buf = new StringBuilder(); - try { - BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); - String str; - while ((str = in.readLine()) != null) { - buf.append(str); - } - } catch (IOException e) { - throw new IOException("Cannot read json body", e); - } - String jsonStr = buf.toString(); - if (Algorithms.isEmpty(jsonStr)) { - throw new IllegalArgumentException("Cannot find json body"); - } - final JSONObject json; - try { - json = new JSONObject(jsonStr); - } catch (JSONException e) { - throw new IllegalArgumentException("Json parse error", e); - } - readPreferencesFromJson(json); - } - - void readPreferencesFromJson(final JSONObject json) { - settings.getContext().runInUIThread(new Runnable() { - @Override - public void run() { - Map> prefs = settings.getRegisteredPreferences(); - Iterator iter = json.keys(); - while (iter.hasNext()) { - String key = iter.next(); - OsmandPreference p = prefs.get(key); - if (p != null) { - try { - readPreferenceFromJson(p, json); - } catch (Exception e) { - LOG.error("Failed to read preference: " + key, e); - } - } else { - LOG.warn("No preference while importing settings: " + key); - } - } - } - }); - } - } - - public abstract static class OsmandSettingsItemWriter extends SettingsItemWriter { - - private OsmandSettings settings; - - public OsmandSettingsItemWriter(@NonNull T item, @NonNull OsmandSettings settings) { - super(item); - this.settings = settings; - } - - protected abstract void writePreferenceToJson(@NonNull OsmandPreference preference, - @NonNull JSONObject json) throws JSONException; - - @Override - public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { - JSONObject json = new JSONObject(); - Map> prefs = settings.getRegisteredPreferences(); - for (OsmandPreference pref : prefs.values()) { - try { - writePreferenceToJson(pref, json); - } catch (JSONException e) { - LOG.error("Failed to write preference: " + pref.getId(), e); - } - } - if (json.length() > 0) { - try { - String s = json.toString(2); - outputStream.write(s.getBytes("UTF-8")); - } catch (JSONException e) { - LOG.error("Failed to write json to stream", e); - } - return true; - } - return false; - } - } - - public static class GlobalSettingsItem extends OsmandSettingsItem { - - public GlobalSettingsItem(@NonNull OsmandSettings settings) { - super(settings); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.GLOBAL; - } - - @NonNull - @Override - public String getName() { - return "general_settings"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return ctx.getString(R.string.general_settings_2); - } - - @Override - public boolean exists() { - return true; - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new OsmandSettingsItemReader(this, getSettings()) { - @Override - protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - preference.readFromJson(json, null); - } - }; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return new OsmandSettingsItemWriter(this, getSettings()) { - @Override - protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - preference.writeToJson(json, null); - } - }; - } - } - - public static class ProfileSettingsItem extends OsmandSettingsItem { - - private ApplicationMode appMode; - private ApplicationModeBuilder builder; - private ApplicationModeBean modeBean; - - private JSONObject additionalPrefsJson; - private Set appModeBeanPrefsIds; - - public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull ApplicationMode appMode) { - super(app.getSettings()); - this.appMode = appMode; - } - - public ProfileSettingsItem(@NonNull OsmandApplication app, @Nullable ProfileSettingsItem baseItem, @NonNull ApplicationModeBean modeBean) { - super(app.getSettings(), baseItem); - this.modeBean = modeBean; - builder = ApplicationMode.fromModeBean(app, modeBean); - appMode = builder.getApplicationMode(); - } - - public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(SettingsItemType.PROFILE, app.getSettings(), json); - } - - @Override - protected void init() { - super.init(); - appModeBeanPrefsIds = new HashSet<>(Arrays.asList(getAppModeBeanPrefsIds())); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.PROFILE; - } - - public ApplicationMode getAppMode() { - return appMode; - } - - public ApplicationModeBean getModeBean() { - return modeBean; - } - - @NonNull - @Override - public String getName() { - return appMode.getStringKey(); - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - if (appMode.isCustomProfile()) { - return modeBean.userProfileName; - } else if (appMode.getNameKeyResource() != -1) { - return ctx.getString(appMode.getNameKeyResource()); - } else { - return getName(); - } - } - - @NonNull - @Override - public String getDefaultFileName() { - return "profile_" + getName() + getDefaultFileExtension(); - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - String appModeJson = json.getString("appMode"); - modeBean = ApplicationMode.fromJson(appModeJson); - builder = ApplicationMode.fromModeBean(app, modeBean); - ApplicationMode appMode = builder.getApplicationMode(); - if (!appMode.isCustomProfile()) { - appMode = ApplicationMode.valueOfStringKey(appMode.getStringKey(), appMode); - } - this.appMode = appMode; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - additionalPrefsJson = json.optJSONObject("prefs"); - } - - @Override - public boolean exists() { - return builder != null && ApplicationMode.valueOfStringKey(getName(), null) != null; - } - - private void renameProfile() { - List values = ApplicationMode.allPossibleValues(); - if (Algorithms.isEmpty(modeBean.userProfileName)) { - ApplicationMode appMode = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); - if (appMode != null) { - modeBean.userProfileName = app.getString(appMode.getNameKeyResource()); - } - } - int number = 0; - while (true) { - number++; - String key = modeBean.stringKey + "_" + number; - String name = modeBean.userProfileName + '_' + number; - if (ApplicationMode.valueOfStringKey(key, null) == null && isNameUnique(values, name)) { - modeBean.userProfileName = name; - modeBean.stringKey = key; - break; - } - } - } - - private boolean isNameUnique(List values, String name) { - for (ApplicationMode mode : values) { - if (mode.getUserProfileName().equals(name)) { - return false; - } - } - return true; - } - - @Override - public void apply() { - if (!appMode.isCustomProfile() && !shouldReplace) { - ApplicationMode parent = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); - renameProfile(); - ApplicationMode.ApplicationModeBuilder builder = ApplicationMode - .createCustomMode(parent, modeBean.stringKey, app) - .setIconResName(modeBean.iconName) - .setUserProfileName(modeBean.userProfileName) - .setRoutingProfile(modeBean.routingProfile) - .setRouteService(modeBean.routeService) - .setIconColor(modeBean.iconColor) - .setLocationIcon(modeBean.locIcon) - .setNavigationIcon(modeBean.navIcon); - app.getSettings().copyPreferencesFromProfile(parent, builder.getApplicationMode()); - appMode = ApplicationMode.saveProfile(builder, app); - } else if (!shouldReplace && exists()) { - renameProfile(); - builder = ApplicationMode.fromModeBean(app, modeBean); - appMode = ApplicationMode.saveProfile(builder, app); - } else { - builder = ApplicationMode.fromModeBean(app, modeBean); - appMode = ApplicationMode.saveProfile(builder, app); - } - ApplicationMode.changeProfileAvailability(appMode, true, app); - } - - public void applyAdditionalPrefs() { - if (additionalPrefsJson != null) { - updatePluginResPrefs(); - - SettingsItemReader reader = getReader(); - if (reader instanceof OsmandSettingsItemReader) { - ((OsmandSettingsItemReader) reader).readPreferencesFromJson(additionalPrefsJson); - } - } - } - - private void updatePluginResPrefs() { - String pluginId = getPluginId(); - if (Algorithms.isEmpty(pluginId)) { - return; - } - OsmandPlugin plugin = OsmandPlugin.getPlugin(pluginId); - if (plugin instanceof CustomOsmandPlugin) { - CustomOsmandPlugin customPlugin = (CustomOsmandPlugin) plugin; - String resDirPath = IndexConstants.PLUGINS_DIR + pluginId + "/" + customPlugin.getResourceDirName(); - - for (Iterator it = additionalPrefsJson.keys(); it.hasNext(); ) { - try { - String prefId = it.next(); - Object value = additionalPrefsJson.get(prefId); - if (value instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) value; - for (Iterator iterator = jsonObject.keys(); iterator.hasNext(); ) { - String key = iterator.next(); - Object val = jsonObject.get(key); - if (val instanceof String) { - val = checkPluginResPath((String) val, resDirPath); - } - jsonObject.put(key, val); - } - } else if (value instanceof String) { - value = checkPluginResPath((String) value, resDirPath); - additionalPrefsJson.put(prefId, value); - } - } catch (JSONException e) { - LOG.error(e); - } - } - } - } - - private String checkPluginResPath(String path, String resDirPath) { - if (path.startsWith("@")) { - return resDirPath + "/" + path.substring(1); - } - return path; - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - json.put("appMode", new JSONObject(appMode.toJson())); - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new OsmandSettingsItemReader(this, getSettings()) { - @Override - protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - if (!appModeBeanPrefsIds.contains(preference.getId())) { - preference.readFromJson(json, appMode); - } - } - - @Override - void readPreferencesFromJson(final JSONObject json) { - getSettings().getContext().runInUIThread(new Runnable() { - @Override - public void run() { - OsmandSettings settings = getSettings(); - Map> prefs = settings.getRegisteredPreferences(); - Iterator iter = json.keys(); - while (iter.hasNext()) { - String key = iter.next(); - OsmandPreference p = prefs.get(key); - if (p == null) { - if (OsmandSettings.isRoutingPreference(key)) { - p = settings.registerStringPreference(key, ""); - } - } - if (p != null) { - try { - readPreferenceFromJson(p, json); - if (OsmandSettings.isRoutingPreference(p.getId())) { - if (p.getId().endsWith(GeneralRouter.USE_SHORTEST_WAY)) { - settings.FAST_ROUTE_MODE.setModeValue(appMode, - !settings.getCustomRoutingBooleanProperty(GeneralRouter.USE_SHORTEST_WAY, false).getModeValue(appMode)); - } - } - } catch (Exception e) { - LOG.error("Failed to read preference: " + key, e); - } - } else { - LOG.warn("No preference while importing settings: " + key); - } - } - } - }); - } - }; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return new OsmandSettingsItemWriter(this, getSettings()) { - @Override - protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - if (!appModeBeanPrefsIds.contains(preference.getId())) { - preference.writeToJson(json, appMode); - } - } - }; - } - - private String[] getAppModeBeanPrefsIds() { - OsmandSettings settings = app.getSettings(); - return new String[] { - settings.ICON_COLOR.getId(), - settings.ICON_RES_NAME.getId(), - settings.PARENT_APP_MODE.getId(), - settings.ROUTING_PROFILE.getId(), - settings.ROUTE_SERVICE.getId(), - settings.USER_PROFILE_NAME.getId(), - settings.LOCATION_ICON.getId(), - settings.NAVIGATION_ICON.getId(), - settings.APP_MODE_ORDER.getId() - }; - } - } - - public abstract static class StreamSettingsItemReader extends SettingsItemReader { - - public StreamSettingsItemReader(@NonNull StreamSettingsItem item) { - super(item); - } - } - - public static class StreamSettingsItemWriter extends SettingsItemWriter { - - public StreamSettingsItemWriter(StreamSettingsItem item) { - super(item); - } - - @Override - public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { - boolean hasData = false; - InputStream is = getItem().inputStream; - if (is != null) { - byte[] data = new byte[BUFFER]; - int count; - while ((count = is.read(data, 0, BUFFER)) != -1) { - outputStream.write(data, 0, count); - if (!hasData) { - hasData = true; - } - } - Algorithms.closeStream(is); - } - return hasData; - } - } - - public abstract static class StreamSettingsItem extends SettingsItem { - - @Nullable - private InputStream inputStream; - protected String name; - - public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { - super(app); - this.name = name; - this.fileName = name; - } - - StreamSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull InputStream inputStream, @NonNull String name) { - super(app); - this.inputStream = inputStream; - this.name = name; - this.fileName = name; - } - - @Nullable - public InputStream getInputStream() { - return inputStream; - } - - protected void setInputStream(@Nullable InputStream inputStream) { - this.inputStream = inputStream; - } - - @NonNull - @Override - public String getName() { - return name; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return getName(); - } - - @NonNull - @Override - public String getDefaultFileExtension() { - return ""; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - name = json.has("name") ? json.getString("name") : null; - } - - @Nullable - @Override - public SettingsItemWriter getWriter() { - return new StreamSettingsItemWriter(this); - } - } - - public static class DataSettingsItem extends StreamSettingsItem { - - @Nullable - private byte[] data; - - public DataSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { - super(app, name); - } - - DataSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - public DataSettingsItem(@NonNull OsmandApplication app, @NonNull byte[] data, @NonNull String name) { - super(app, name); - this.data = data; - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.DATA; - } - - @NonNull - @Override - public String getDefaultFileExtension() { - return ".dat"; - } - - @Nullable - public byte[] getData() { - return data; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - String fileName = getFileName(); - if (!Algorithms.isEmpty(fileName)) { - name = Algorithms.getFileNameWithoutExtension(new File(fileName)); - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new StreamSettingsItemReader(this) { - @Override - public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[BUFFER]; - while ((nRead = inputStream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - - buffer.flush(); - DataSettingsItem.this.data = buffer.toByteArray(); - } - }; - } - - @Nullable - @Override - public SettingsItemWriter getWriter() { - setInputStream(new ByteArrayInputStream(data)); - return super.getWriter(); - } - } - - public static 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), - 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); - - private String subtypeName; - private String subtypeFolder; - - FileSubtype(String subtypeName, String subtypeFolder) { - this.subtypeName = subtypeName; - this.subtypeFolder = subtypeFolder; - } - - public String getSubtypeName() { - return subtypeName; - } - - public String getSubtypeFolder() { - return subtypeFolder; - } - - public static FileSubtype getSubtypeByName(@NonNull String name) { - for (FileSubtype subtype : FileSubtype.values()) { - if (name.equals(subtype.subtypeName)) { - return subtype; - } - } - return null; - } - - public static FileSubtype getSubtypeByFileName(@NonNull String fileName) { - String name = fileName; - if (fileName.startsWith(File.separator)) { - name = fileName.substring(1); - } - for (FileSubtype subtype : FileSubtype.values()) { - switch (subtype) { - case UNKNOWN: - case OTHER: - break; - case OBF_MAP: - if (name.endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) { - return subtype; - } - break; - default: - if (name.startsWith(subtype.subtypeFolder)) { - return subtype; - } - break; - } - } - return UNKNOWN; - } - - @NonNull - @Override - public String toString() { - return subtypeName; - } - } - - protected File file; - private File appPath; - protected FileSubtype subtype; - - public FileSettingsItem(@NonNull OsmandApplication app, @NonNull File file) throws IllegalArgumentException { - super(app, file.getPath().replace(app.getAppPath(null).getPath(), "")); - this.file = file; - this.appPath = app.getAppPath(null); - String fileName = getFileName(); - if (fileName != null) { - this.subtype = FileSubtype.getSubtypeByFileName(fileName); - } - if (subtype == FileSubtype.UNKNOWN || subtype == null) { - throw new IllegalArgumentException("Unknown file subtype: " + fileName); - } - } - - FileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - this.appPath = app.getAppPath(null); - if (subtype == FileSubtype.OTHER) { - this.file = new File(appPath, name); - } else if (subtype == FileSubtype.UNKNOWN || subtype == null) { - throw new IllegalArgumentException("Unknown file subtype: " + getFileName()); - } else { - this.file = new File(app.getAppPath(subtype.subtypeFolder), name); - } - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.FILE; - } - - public File getPluginPath() { - String pluginId = getPluginId(); - if (!Algorithms.isEmpty(pluginId)) { - return new File(appPath, IndexConstants.PLUGINS_DIR + pluginId); - } - return appPath; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - String fileName = getFileName(); - if (subtype == null) { - String subtypeStr = json.has("subtype") ? json.getString("subtype") : null; - if (!Algorithms.isEmpty(subtypeStr)) { - subtype = FileSubtype.getSubtypeByName(subtypeStr); - } else if (!Algorithms.isEmpty(fileName)) { - subtype = FileSubtype.getSubtypeByFileName(fileName); - } else { - subtype = FileSubtype.UNKNOWN; - } - } - if (!Algorithms.isEmpty(fileName)) { - if (subtype == FileSubtype.OTHER) { - name = fileName; - } else if (subtype != null && subtype != FileSubtype.UNKNOWN) { - name = Algorithms.getFileWithoutDirs(fileName); - } - } - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - if (subtype != null) { - json.put("subtype", subtype.getSubtypeName()); - } - } - - @NonNull - public File getFile() { - return file; - } - - @NonNull - public FileSubtype getSubtype() { - return subtype; - } - - @Override - public boolean exists() { - return file.exists(); - } - - private File renameFile(File file) { - 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; - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new StreamSettingsItemReader(this) { - @Override - public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { - OutputStream output; - File dest = FileSettingsItem.this.file; - if (dest.exists() && !shouldReplace) { - dest = renameFile(dest); - } - if (dest.getParentFile() != null && !dest.getParentFile().exists()) { - dest.getParentFile().mkdirs(); - } - output = new FileOutputStream(dest); - byte[] buffer = new byte[BUFFER]; - int count; - try { - while ((count = inputStream.read(buffer)) != -1) { - output.write(buffer, 0, count); - } - output.flush(); - } finally { - Algorithms.closeStream(output); - } - } - }; - } - - @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())); - LOG.error("Failed to set input stream from file: " + file.getName(), e); - } - return super.getWriter(); - } - } - - public static class ResourcesSettingsItem extends FileSettingsItem { - - ResourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - shouldReplace = true; - String fileName = getFileName(); - if (!Algorithms.isEmpty(fileName) && !fileName.endsWith(File.separator)) { - this.fileName = fileName + File.separator; - } - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.RESOURCES; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - subtype = FileSubtype.OTHER; - super.readFromJson(json); - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - String fileName = getFileName(); - if (!Algorithms.isEmpty(fileName)) { - if (fileName.endsWith(File.separator)) { - fileName = fileName.substring(0, fileName.length() - 1); - } - json.put("file", fileName); - } - } - - @Override - public boolean applyFileName(@NonNull String fileName) { - if (fileName.endsWith(File.separator)) { - return false; - } - String itemFileName = getFileName(); - if (itemFileName != null && itemFileName.endsWith(File.separator)) { - if (fileName.startsWith(itemFileName)) { - this.file = new File(getPluginPath(), fileName); - return true; - } else { - return false; - } - } else { - return super.applyFileName(fileName); - } - } - - @Nullable - @Override - public SettingsItemWriter getWriter() { - return null; - } - } - - public static class QuickActionsSettingsItem extends CollectionSettingsItem { - - private QuickActionRegistry actionRegistry; - - public QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public QuickActionsSettingsItem(@NonNull OsmandApplication app, @Nullable QuickActionsSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - actionRegistry = app.getQuickActionRegistry(); - existingItems = actionRegistry.getQuickActions(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.QUICK_ACTIONS; - } - - @Override - public boolean isDuplicate(@NonNull QuickAction item) { - return !actionRegistry.isNameUnique(item, app); - } - - @NonNull - @Override - public QuickAction renameItem(@NonNull QuickAction item) { - return actionRegistry.generateUniqueName(item, app); - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - List newActions = new ArrayList<>(existingItems); - if (!duplicateItems.isEmpty()) { - if (shouldReplace) { - for (QuickAction duplicateItem : duplicateItems) { - for (QuickAction savedAction : existingItems) { - if (duplicateItem.getName(app).equals(savedAction.getName(app))) { - newActions.remove(savedAction); - } - } - } - } else { - for (QuickAction duplicateItem : duplicateItems) { - renameItem(duplicateItem); - } - } - appliedItems.addAll(duplicateItems); - } - newActions.addAll(appliedItems); - actionRegistry.updateQuickActions(newActions); - } - } - - @Override - public boolean shouldReadOnCollecting() { - return true; - } - - @NonNull - @Override - public String getName() { - return "quick_actions"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "quick_actions"; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); - QuickActionRegistry quickActionRegistry = app.getQuickActionRegistry(); - JSONArray itemsJson = json.getJSONArray("items"); - for (int i = 0; i < itemsJson.length(); i++) { - JSONObject object = itemsJson.getJSONObject(i); - String name = object.getString("name"); - QuickAction quickAction = null; - if (object.has("actionType")) { - quickAction = quickActionRegistry.newActionByStringType(object.getString("actionType")); - } else if (object.has("type")) { - quickAction = quickActionRegistry.newActionByType(object.getInt("type")); - } - if (quickAction != null) { - String paramsString = object.getString("params"); - HashMap params = gson.fromJson(paramsString, type); - - if (!name.isEmpty()) { - quickAction.setName(name); - } - quickAction.setParams(params); - items.add(quickAction); - } else { - warnings.add(app.getString(R.string.settings_item_read_error, name)); - } - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); - if (!items.isEmpty()) { - try { - for (QuickAction action : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", action.hasCustomName(app) - ? action.getName(app) : ""); - jsonObject.put("actionType", action.getActionType().getStringId()); - jsonObject.put("params", gson.toJson(action.getParams(), type)); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class PoiUiFiltersSettingsItem extends CollectionSettingsItem { - - public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @Nullable PoiUiFiltersSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - existingItems = app.getPoiFilters().getUserDefinedPoiFilters(false); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.POI_UI_FILTERS; - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - - for (PoiUIFilter duplicate : duplicateItems) { - appliedItems.add(shouldReplace ? duplicate : renameItem(duplicate)); - } - for (PoiUIFilter filter : appliedItems) { - app.getPoiFilters().createPoiFilter(filter, false); - } - app.getSearchUICore().refreshCustomPoiFilters(); - } - } - - @Override - public boolean isDuplicate(@NonNull PoiUIFilter item) { - String savedName = item.getName(); - for (PoiUIFilter filter : existingItems) { - if (filter.getName().equals(savedName)) { - return true; - } - } - return false; - } - - @NonNull - @Override - public PoiUIFilter renameItem(@NonNull PoiUIFilter item) { - int number = 0; - while (true) { - number++; - PoiUIFilter renamedItem = new PoiUIFilter(item, - item.getName() + "_" + number, - item.getFilterId() + "_" + number); - if (!isDuplicate(renamedItem)) { - return renamedItem; - } - } - } - - @NonNull - @Override - public String getName() { - return "poi_ui_filters"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "poi_ui_filters"; - } - - @Override - public boolean shouldReadOnCollecting() { - return true; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - Gson gson = new Gson(); - Type type = new TypeToken>>() { - }.getType(); - MapPoiTypes poiTypes = app.getPoiTypes(); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.getJSONObject(i); - String name = object.getString("name"); - String filterId = object.getString("filterId"); - String acceptedTypesString = object.getString("acceptedTypes"); - HashMap> acceptedTypes = gson.fromJson(acceptedTypesString, type); - Map> acceptedTypesDone = new HashMap<>(); - for (Map.Entry> mapItem : acceptedTypes.entrySet()) { - final PoiCategory a = poiTypes.getPoiCategoryByName(mapItem.getKey()); - acceptedTypesDone.put(a, mapItem.getValue()); - } - PoiUIFilter filter = new PoiUIFilter(name, filterId, acceptedTypesDone, app); - items.add(filter); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - Gson gson = new Gson(); - Type type = new TypeToken>>() { - }.getType(); - if (!items.isEmpty()) { - try { - for (PoiUIFilter filter : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", filter.getName()); - jsonObject.put("filterId", filter.getFilterId()); - jsonObject.put("acceptedTypes", gson.toJson(filter.getAcceptedTypes(), type)); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class MapSourcesSettingsItem extends CollectionSettingsItem { - - private List existingItemsNames; - - public MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public MapSourcesSettingsItem(@NonNull OsmandApplication app, @Nullable MapSourcesSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - existingItemsNames = new ArrayList<>(app.getSettings().getTileSourceEntries().values()); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.MAP_SOURCES; - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - if (shouldReplace) { - for (ITileSource tileSource : duplicateItems) { - if (tileSource instanceof SQLiteTileSource) { - File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName() + IndexConstants.SQLITE_EXT); - if (f != null && f.exists() && Algorithms.removeAllFiles(f)) { - appliedItems.add(tileSource); - } - } else if (tileSource instanceof TileSourceManager.TileSourceTemplate) { - File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName()); - if (f != null && f.exists() && f.isDirectory() && Algorithms.removeAllFiles(f)) { - appliedItems.add(tileSource); - } - } - } - } else { - for (ITileSource tileSource : duplicateItems) { - appliedItems.add(renameItem(tileSource)); - } - } - for (ITileSource tileSource : appliedItems) { - if (tileSource instanceof TileSourceManager.TileSourceTemplate) { - app.getSettings().installTileSource((TileSourceManager.TileSourceTemplate) tileSource); - } else if (tileSource instanceof SQLiteTileSource) { - ((SQLiteTileSource) tileSource).createDataBase(); - } - } - } - } - - @NonNull - @Override - public ITileSource renameItem(@NonNull ITileSource item) { - int number = 0; - while (true) { - number++; - if (item instanceof SQLiteTileSource) { - SQLiteTileSource oldItem = (SQLiteTileSource) item; - SQLiteTileSource renamedItem = new SQLiteTileSource( - oldItem, - oldItem.getName() + "_" + number, - app); - if (!isDuplicate(renamedItem)) { - return renamedItem; - } - } else if (item instanceof TileSourceManager.TileSourceTemplate) { - TileSourceManager.TileSourceTemplate oldItem = (TileSourceManager.TileSourceTemplate) item; - oldItem.setName(oldItem.getName() + "_" + number); - if (!isDuplicate(oldItem)) { - return oldItem; - } - } - } - } - - @Override - public boolean isDuplicate(@NonNull ITileSource item) { - for (String name : existingItemsNames) { - if (name.equals(item.getName())) { - return true; - } - } - return false; - } - - @NonNull - @Override - public String getName() { - return "map_sources"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "map_sources"; - } - - @Override - public boolean shouldReadOnCollecting() { - return true; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.getJSONObject(i); - boolean sql = object.optBoolean("sql"); - String name = object.optString("name"); - int minZoom = object.optInt("minZoom"); - int maxZoom = object.optInt("maxZoom"); - String url = object.optString("url"); - String randoms = object.optString("randoms"); - boolean ellipsoid = object.optBoolean("ellipsoid", false); - boolean invertedY = object.optBoolean("inverted_y", false); - String referer = object.optString("referer"); - String userAgent = object.optString("userAgent"); - boolean timeSupported = object.optBoolean("timesupported", false); - long expire = object.optLong("expire", -1); - boolean inversiveZoom = object.optBoolean("inversiveZoom", false); - String ext = object.optString("ext"); - int tileSize = object.optInt("tileSize"); - int bitDensity = object.optInt("bitDensity"); - int avgSize = object.optInt("avgSize"); - String rule = object.optString("rule"); - - if (expire > 0 && expire < 3600000) { - expire = expire * 60 * 1000L; - } - - ITileSource template; - if (!sql) { - TileSourceTemplate tileSourceTemplate = new TileSourceTemplate(name, url, ext, maxZoom, minZoom, tileSize, bitDensity, avgSize); - tileSourceTemplate.setRule(rule); - tileSourceTemplate.setRandoms(randoms); - tileSourceTemplate.setReferer(referer); - tileSourceTemplate.setUserAgent(userAgent); - tileSourceTemplate.setEllipticYTile(ellipsoid); - tileSourceTemplate.setInvertedYTile(invertedY); - tileSourceTemplate.setExpirationTimeMillis(timeSupported ? expire : -1); - - template = tileSourceTemplate; - } else { - template = new SQLiteTileSource(app, name, minZoom, maxZoom, url, randoms, ellipsoid, invertedY, referer, userAgent, timeSupported, expire, inversiveZoom, rule); - } - items.add(template); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (ITileSource template : items) { - JSONObject jsonObject = new JSONObject(); - boolean sql = template instanceof SQLiteTileSource; - jsonObject.put("sql", sql); - jsonObject.put("name", template.getName()); - jsonObject.put("minZoom", template.getMinimumZoomSupported()); - jsonObject.put("maxZoom", template.getMaximumZoomSupported()); - jsonObject.put("url", template.getUrlTemplate()); - jsonObject.put("randoms", template.getRandoms()); - jsonObject.put("ellipsoid", template.isEllipticYTile()); - jsonObject.put("inverted_y", template.isInvertedYTile()); - jsonObject.put("referer", template.getReferer()); - jsonObject.put("userAgent", template.getUserAgent()); - jsonObject.put("timesupported", template.isTimeSupported()); - jsonObject.put("expire", template.getExpirationTimeMinutes()); - jsonObject.put("inversiveZoom", template.getInversiveZoom()); - jsonObject.put("ext", template.getTileFormat()); - jsonObject.put("tileSize", template.getTileSize()); - jsonObject.put("bitDensity", template.getBitDensity()); - jsonObject.put("avgSize", template.getAvgSize()); - jsonObject.put("rule", template.getRule()); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class AvoidRoadsSettingsItem extends CollectionSettingsItem { - - private OsmandSettings settings; - private AvoidSpecificRoads specificRoads; - - public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @Nullable AvoidRoadsSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - settings = app.getSettings(); - specificRoads = app.getAvoidSpecificRoads(); - existingItems = new ArrayList<>(specificRoads.getImpassableRoads().values()); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.AVOID_ROADS; - } - - @NonNull - @Override - public String getName() { - return "avoid_roads"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "avoid_roads"; - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - for (AvoidRoadInfo duplicate : duplicateItems) { - if (shouldReplace) { - LatLon latLon = new LatLon(duplicate.latitude, duplicate.longitude); - if (settings.removeImpassableRoad(latLon)) { - settings.addImpassableRoad(duplicate); - } - } else { - settings.addImpassableRoad(renameItem(duplicate)); - } - } - for (AvoidRoadInfo avoidRoad : appliedItems) { - settings.addImpassableRoad(avoidRoad); - } - specificRoads.loadImpassableRoads(); - specificRoads.initRouteObjects(true); - } - } - - @Override - public boolean isDuplicate(@NonNull AvoidRoadInfo item) { - return existingItems.contains(item); - } - - @Override - public boolean shouldReadOnCollecting() { - return true; - } - - @NonNull - @Override - public AvoidRoadInfo renameItem(@NonNull AvoidRoadInfo item) { - int number = 0; - while (true) { - number++; - AvoidRoadInfo renamedItem = new AvoidRoadInfo(); - renamedItem.name = item.name + "_" + number; - if (!isDuplicate(renamedItem)) { - renamedItem.id = item.id; - renamedItem.latitude = item.latitude; - renamedItem.longitude = item.longitude; - renamedItem.appModeKey = item.appModeKey; - return renamedItem; - } - } - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.getJSONObject(i); - double latitude = object.optDouble("latitude"); - double longitude = object.optDouble("longitude"); - String name = object.optString("name"); - String appModeKey = object.optString("appModeKey"); - AvoidRoadInfo roadInfo = new AvoidRoadInfo(); - roadInfo.id = 0; - roadInfo.latitude = latitude; - roadInfo.longitude = longitude; - roadInfo.name = name; - if (ApplicationMode.valueOfStringKey(appModeKey, null) != null) { - roadInfo.appModeKey = appModeKey; - } else { - roadInfo.appModeKey = app.getRoutingHelper().getAppMode().getStringKey(); - } - items.add(roadInfo); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (AvoidRoadInfo avoidRoad : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("latitude", avoidRoad.latitude); - jsonObject.put("longitude", avoidRoad.longitude); - jsonObject.put("name", avoidRoad.name); - jsonObject.put("appModeKey", avoidRoad.appModeKey); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - private static class SettingsItemsFactory { - - private OsmandApplication app; - private List items = new ArrayList<>(); - - SettingsItemsFactory(@NonNull OsmandApplication app, String jsonStr) throws IllegalArgumentException, JSONException { - this.app = app; - collectItems(new JSONObject(jsonStr)); - } - - private void collectItems(JSONObject json) throws IllegalArgumentException, JSONException { - JSONArray itemsJson = json.getJSONArray("items"); - int version = json.has("version") ? json.getInt("version") : 1; - if (version > VERSION) { - throw new IllegalArgumentException("Unsupported osf version: " + version); - } - Map> pluginItems = new HashMap<>(); - for (int i = 0; i < itemsJson.length(); i++) { - JSONObject itemJson = itemsJson.getJSONObject(i); - SettingsItem item; - try { - item = createItem(itemJson); - items.add(item); - String pluginId = item.getPluginId(); - if (pluginId != null && item.getType() != SettingsItemType.PLUGIN) { - List items = pluginItems.get(pluginId); - if (items != null) { - items.add(item); - } else { - items = new ArrayList<>(); - items.add(item); - pluginItems.put(pluginId, items); - } - } - } catch (IllegalArgumentException e) { - LOG.error("Error creating item from json: " + itemJson, e); - } - } - if (items.size() == 0) { - throw new IllegalArgumentException("No items"); - } - for (SettingsItem item : items) { - if (item instanceof PluginSettingsItem) { - PluginSettingsItem pluginSettingsItem = ((PluginSettingsItem) item); - List pluginDependentItems = pluginItems.get(pluginSettingsItem.getName()); - if (!Algorithms.isEmpty(pluginDependentItems)) { - pluginSettingsItem.getPluginDependentItems().addAll(pluginDependentItems); - } - } - } - } - - @NonNull - public List getItems() { - return items; - } - - @Nullable - public SettingsItem getItemByFileName(@NonNull String fileName) { - for (SettingsItem item : items) { - if (Algorithms.stringsEqual(item.getFileName(), fileName)) { - return item; - } - } - return null; - } - - @NonNull - private SettingsItem createItem(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { - SettingsItem item = null; - SettingsItemType type = SettingsItem.parseItemType(json); - OsmandSettings settings = app.getSettings(); - switch (type) { - case GLOBAL: - item = new GlobalSettingsItem(settings); - break; - case PROFILE: - item = new ProfileSettingsItem(app, json); - break; - case PLUGIN: - item = new PluginSettingsItem(app, json); - break; - case DATA: - item = new DataSettingsItem(app, json); - break; - case FILE: - item = new FileSettingsItem(app, json); - break; - case RESOURCES: - item = new ResourcesSettingsItem(app, json); - break; - case QUICK_ACTIONS: - item = new QuickActionsSettingsItem(app, json); - break; - case POI_UI_FILTERS: - item = new PoiUiFiltersSettingsItem(app, json); - break; - case MAP_SOURCES: - item = new MapSourcesSettingsItem(app, json); - break; - case AVOID_ROADS: - item = new AvoidRoadsSettingsItem(app, json); - break; - case SUGGESTED_DOWNLOADS: - item = new SuggestedDownloadsItem(app, json); - break; - case DOWNLOADS: - item = new DownloadsItem(app, json); - break; - } - return item; - } - } - - private static class SettingsExporter { - - private Map items; - private Map additionalParams; - private boolean exportItemsFiles; - - SettingsExporter(boolean exportItemsFiles) { - this.exportItemsFiles = exportItemsFiles; - items = new LinkedHashMap<>(); - additionalParams = new LinkedHashMap<>(); - } - - void addSettingsItem(SettingsItem item) throws IllegalArgumentException { - if (items.containsKey(item.getName())) { - throw new IllegalArgumentException("Already has such item: " + item.getName()); - } - items.put(item.getName(), item); - } - - 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), BUFFER); - ZipOutputStream zos = new ZipOutputStream(os); - try { - ZipEntry entry = new ZipEntry("items.json"); - zos.putNextEntry(entry); - zos.write(json.toString(2).getBytes("UTF-8")); - zos.closeEntry(); - if (exportItemsFiles) { - writeItemFiles(zos); - } - zos.flush(); - zos.finish(); - } finally { - Algorithms.closeStream(zos); - Algorithms.closeStream(os); - } - } - - private void writeItemFiles(ZipOutputStream zos) throws IOException { - for (SettingsItem item : items.values()) { - SettingsItemWriter writer = item.getWriter(); - if (writer != null) { - String fileName = item.getFileName(); - if (Algorithms.isEmpty(fileName)) { - fileName = item.getDefaultFileName(); - } - ZipEntry entry = new ZipEntry(fileName); - zos.putNextEntry(entry); - writer.writeToStream(zos); - zos.closeEntry(); - } - } - } - - private JSONObject createItemsJson() throws JSONException { - JSONObject json = new JSONObject(); - json.put("version", VERSION); - for (Map.Entry param : additionalParams.entrySet()) { - json.put(param.getKey(), param.getValue()); - } - JSONArray itemsJson = new JSONArray(); - for (SettingsItem item : items.values()) { - itemsJson.put(new JSONObject(item.toJson())); - } - json.put("items", itemsJson); - return json; - } - } - - private static class SettingsImporter { - - private OsmandApplication app; - - SettingsImporter(@NonNull OsmandApplication app) { - this.app = app; - } - - List collectItems(@NonNull File file) throws IllegalArgumentException, IOException { - return processItems(file, null); - } - - void importItems(@NonNull File file, @NonNull List items) throws IllegalArgumentException, IOException { - processItems(file, items); - } - - private List getItemsFromJson(@NonNull File file) throws IOException { - List items = new ArrayList<>(); - ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); - InputStream ois = new BufferedInputStream(zis); - try { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - String fileName = checkEntryName(entry.getName()); - if (fileName.equals("items.json")) { - String itemsJson = null; - try { - itemsJson = Algorithms.readFromInputStream(ois, false).toString(); - } catch (IOException e) { - LOG.error("Error reading items.json: " + itemsJson, e); - throw new IllegalArgumentException("No items"); - } finally { - zis.closeEntry(); - } - try { - SettingsItemsFactory itemsFactory = new SettingsItemsFactory(app, itemsJson); - items.addAll(itemsFactory.getItems()); - } catch (IllegalArgumentException e) { - LOG.error("Error parsing items: " + itemsJson, e); - throw new IllegalArgumentException("No items"); - } catch (JSONException e) { - LOG.error("Error parsing items: " + itemsJson, e); - throw new IllegalArgumentException("No items"); - } - break; - } - } - } catch (IOException ex) { - LOG.error("Failed to read next entry", ex); - } finally { - Algorithms.closeStream(ois); - Algorithms.closeStream(zis); - } - return items; - } - - private List processItems(@NonNull File file, @Nullable List items) throws IllegalArgumentException, IOException { - boolean collecting = items == null; - if (collecting) { - items = getItemsFromJson(file); - } else { - if (items.size() == 0) { - throw new IllegalArgumentException("No items"); - } - } - ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); - InputStream ois = new BufferedInputStream(zis); - try { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - String fileName = checkEntryName(entry.getName()); - SettingsItem item = null; - for (SettingsItem settingsItem : items) { - if (settingsItem != null && settingsItem.applyFileName(fileName)) { - item = settingsItem; - break; - } - } - if (item != null && collecting && item.shouldReadOnCollecting() - || item != null && !collecting && !item.shouldReadOnCollecting()) { - try { - SettingsItemReader reader = item.getReader(); - if (reader != null) { - reader.readFromStream(ois); - } - } catch (IllegalArgumentException e) { - item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); - LOG.error("Error reading item data: " + item.getName(), e); - } catch (IOException e) { - item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); - LOG.error("Error reading item data: " + item.getName(), e); - } finally { - zis.closeEntry(); - } - } - } - } catch (IOException ex) { - LOG.error("Failed to read next entry", ex); - } finally { - Algorithms.closeStream(ois); - Algorithms.closeStream(zis); - } - return items; - } - - private String checkEntryName(String entryName) { - String fileExt = OSMAND_SETTINGS_FILE_EXT + "/"; - int index = entryName.indexOf(fileExt); - if (index != -1) { - entryName = entryName.substring(index + fileExt.length()); - } - return entryName; - } - } - - public static Map> getSettingsToOperate(List settingsItems, boolean importComplete) { - Map> settingsToOperate = new HashMap<>(); - List profiles = new ArrayList<>(); - List quickActions = new ArrayList<>(); - List poiUIFilters = new ArrayList<>(); - List tileSourceTemplates = new ArrayList<>(); - List routingFilesList = new ArrayList<>(); - List renderFilesList = new ArrayList<>(); - List avoidRoads = new ArrayList<>(); - for (SettingsItem item : settingsItems) { - switch (item.getType()) { - case PROFILE: - profiles.add(((ProfileSettingsItem) item).getModeBean()); - break; - case FILE: - FileSettingsItem fileItem = (FileSettingsItem) item; - if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { - renderFilesList.add(fileItem.getFile()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { - routingFilesList.add(fileItem.getFile()); - } - break; - case QUICK_ACTIONS: - QuickActionsSettingsItem quickActionsItem = (QuickActionsSettingsItem) item; - if (importComplete) { - quickActions.addAll(quickActionsItem.getAppliedItems()); - } else { - quickActions.addAll(quickActionsItem.getItems()); - } - break; - case POI_UI_FILTERS: - PoiUiFiltersSettingsItem poiUiFilterItem = (PoiUiFiltersSettingsItem) item; - if (importComplete) { - poiUIFilters.addAll(poiUiFilterItem.getAppliedItems()); - } else { - poiUIFilters.addAll(poiUiFilterItem.getItems()); - } - break; - case MAP_SOURCES: - MapSourcesSettingsItem mapSourcesItem = (MapSourcesSettingsItem) item; - if (importComplete) { - tileSourceTemplates.addAll(mapSourcesItem.getAppliedItems()); - } else { - tileSourceTemplates.addAll(mapSourcesItem.getItems()); - } - break; - case AVOID_ROADS: - AvoidRoadsSettingsItem avoidRoadsItem = (AvoidRoadsSettingsItem) item; - if (importComplete) { - avoidRoads.addAll(avoidRoadsItem.getAppliedItems()); - } else { - avoidRoads.addAll(avoidRoadsItem.getItems()); - } - break; - default: - break; - } - } - - if (!profiles.isEmpty()) { - settingsToOperate.put(ExportSettingsType.PROFILE, profiles); - } - if (!quickActions.isEmpty()) { - settingsToOperate.put(ExportSettingsType.QUICK_ACTIONS, quickActions); - } - if (!poiUIFilters.isEmpty()) { - settingsToOperate.put(ExportSettingsType.POI_TYPES, poiUIFilters); - } - if (!tileSourceTemplates.isEmpty()) { - settingsToOperate.put(ExportSettingsType.MAP_SOURCES, tileSourceTemplates); - } - if (!renderFilesList.isEmpty()) { - settingsToOperate.put(ExportSettingsType.CUSTOM_RENDER_STYLE, renderFilesList); - } - if (!routingFilesList.isEmpty()) { - settingsToOperate.put(ExportSettingsType.CUSTOM_ROUTING, routingFilesList); - } - if (!avoidRoads.isEmpty()) { - settingsToOperate.put(ExportSettingsType.AVOID_ROADS, avoidRoads); - } - return settingsToOperate; - } - - @SuppressLint("StaticFieldLeak") - public class ImportAsyncTask extends AsyncTask> { - - private File file; - private String latestChanges; - private int version; - - private SettingsImportListener importListener; - private SettingsCollectListener collectListener; - private CheckDuplicatesListener duplicatesListener; - private SettingsImporter importer; - - private List items = new ArrayList<>(); - private List selectedItems = new ArrayList<>(); - private List duplicates; - - private ImportType importType; - private boolean importDone; - - ImportAsyncTask(@NonNull File file, String latestChanges, int version, @Nullable SettingsCollectListener collectListener) { - this.file = file; - this.collectListener = collectListener; - this.latestChanges = latestChanges; - this.version = version; - importer = new SettingsImporter(app); - importType = ImportType.COLLECT; - } - - ImportAsyncTask(@NonNull File file, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener importListener) { - this.file = file; - this.importListener = importListener; - this.items = items; - this.latestChanges = latestChanges; - this.version = version; - importer = new SettingsImporter(app); - importType = ImportType.IMPORT; - } - - ImportAsyncTask(@NonNull File file, @NonNull List items, @NonNull List selectedItems, @Nullable CheckDuplicatesListener duplicatesListener) { - this.file = file; - this.items = items; - this.duplicatesListener = duplicatesListener; - this.selectedItems = selectedItems; - importer = new SettingsImporter(app); - importType = ImportType.CHECK_DUPLICATES; - } - - @Override - protected void onPreExecute() { - ImportAsyncTask importTask = SettingsHelper.this.importTask; - if (importTask != null && !importTask.importDone) { - finishImport(importListener, false, items); - } - SettingsHelper.this.importTask = this; - } - - @Override - protected List doInBackground(Void... voids) { - switch (importType) { - case COLLECT: - try { - return importer.collectItems(file); - } catch (IllegalArgumentException e) { - LOG.error("Failed to collect items from: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to collect items from: " + file.getName(), e); - } - break; - case CHECK_DUPLICATES: - this.duplicates = getDuplicatesData(selectedItems); - return selectedItems; - case IMPORT: - return items; - } - return null; - } - - @Override - protected void onPostExecute(@Nullable List items) { - if (items != null && importType != ImportType.CHECK_DUPLICATES) { - this.items = items; - } else { - selectedItems = items; - } - switch (importType) { - case COLLECT: - importDone = true; - collectListener.onSettingsCollectFinished(true, false, this.items); - break; - case CHECK_DUPLICATES: - importDone = true; - if (duplicatesListener != null) { - duplicatesListener.onDuplicatesChecked(duplicates, selectedItems); - } - break; - case IMPORT: - if (items != null && items.size() > 0) { - for (SettingsItem item : items) { - item.apply(); - } - new ImportItemsAsyncTask(file, importListener, items).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - break; - } - } - - public List getItems() { - return items; - } - - public File getFile() { - return file; - } - - public void setImportListener(SettingsImportListener importListener) { - this.importListener = importListener; - } - - public void setDuplicatesListener(CheckDuplicatesListener duplicatesListener) { - this.duplicatesListener = duplicatesListener; - } - - ImportType getImportType() { - return importType; - } - - boolean isImportDone() { - return importDone; - } - - public List getDuplicates() { - return duplicates; - } - - public List getSelectedItems() { - return selectedItems; - } - - private List getDuplicatesData(List items) { - List duplicateItems = new ArrayList<>(); - for (SettingsItem item : items) { - if (item instanceof ProfileSettingsItem) { - if (item.exists()) { - duplicateItems.add(((ProfileSettingsItem) item).getModeBean()); - } - } else if (item instanceof CollectionSettingsItem) { - List duplicates = ((CollectionSettingsItem) item).processDuplicateItems(); - if (!duplicates.isEmpty()) { - duplicateItems.addAll(duplicates); - } - } else if (item instanceof FileSettingsItem) { - if (item.exists()) { - duplicateItems.add(((FileSettingsItem) item).getFile()); - } - } - } - return duplicateItems; - } - } - - @Nullable - public ImportAsyncTask getImportTask() { - return importTask; - } - - @Nullable - public ImportType getImportTaskType() { - ImportAsyncTask importTask = this.importTask; - return importTask != null ? importTask.getImportType() : null; - } - - public boolean isImportDone() { - ImportAsyncTask importTask = this.importTask; - return importTask == null || importTask.isImportDone(); - } - - public boolean isFileExporting(File file) { - return exportAsyncTasks.containsKey(file); - } - - public void updateExportListener(File file, SettingsExportListener listener) { - ExportAsyncTask exportAsyncTask = exportAsyncTasks.get(file); - if (exportAsyncTask != null) { - exportAsyncTask.listener = listener; - } - } - - @SuppressLint("StaticFieldLeak") - private class ImportItemsAsyncTask extends AsyncTask { - - private SettingsImporter importer; - private File file; - private SettingsImportListener listener; - private List items; - - ImportItemsAsyncTask(@NonNull File file, - @Nullable SettingsImportListener listener, - @NonNull List items) { - importer = new SettingsImporter(app); - this.file = file; - this.listener = listener; - this.items = items; - } - - @Override - protected Boolean doInBackground(Void... voids) { - try { - importer.importItems(file, items); - return true; - } catch (IllegalArgumentException e) { - LOG.error("Failed to import items from: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to import items from: " + file.getName(), e); - } - return false; - } - - @Override - protected void onPostExecute(Boolean success) { - finishImport(listener, success, items); - } - } - - private void finishImport(@Nullable SettingsImportListener listener, boolean success, @NonNull List items) { - importTask = null; - List warnings = new ArrayList<>(); - for (SettingsItem item : items) { - warnings.addAll(item.getWarnings()); - } - if (!warnings.isEmpty()) { - app.showToastMessage(AndroidUtils.formatWarnings(warnings).toString()); - } - if (listener != null) { - listener.onSettingsImportFinished(success, items); - } - } - - @SuppressLint("StaticFieldLeak") - private class ExportAsyncTask extends AsyncTask { - - private SettingsExporter exporter; - private File file; - private SettingsExportListener listener; - - ExportAsyncTask(@NonNull File settingsFile, - @Nullable SettingsExportListener listener, - @NonNull List items, boolean exportItemsFiles) { - this.file = settingsFile; - this.listener = listener; - this.exporter = new SettingsExporter(exportItemsFiles); - for (SettingsItem item : items) { - exporter.addSettingsItem(item); - } - } - - @Override - protected Boolean doInBackground(Void... voids) { - try { - exporter.exportSettings(file); - return true; - } catch (JSONException e) { - LOG.error("Failed to export items to: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to export items to: " + file.getName(), e); - } - return false; - } - - @Override - protected void onPostExecute(Boolean success) { - exportAsyncTasks.remove(file); - if (listener != null) { - listener.onSettingsExportFinished(file, success); - } - } - } - - public void collectSettings(@NonNull File settingsFile, String latestChanges, int version, - @Nullable SettingsCollectListener listener) { - new ImportAsyncTask(settingsFile, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void checkDuplicates(@NonNull File file, @NonNull List items, @NonNull List selectedItems, CheckDuplicatesListener listener) { - new ImportAsyncTask(file, items, selectedItems, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void importSettings(@NonNull File settingsFile, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener listener) { - new ImportAsyncTask(settingsFile, items, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void exportSettings(@NonNull File fileDir, @NonNull String fileName, @Nullable SettingsExportListener listener, @NonNull List items, boolean exportItemsFiles) { - File file = new File(fileDir, fileName + OSMAND_SETTINGS_FILE_EXT); - ExportAsyncTask exportAsyncTask = new ExportAsyncTask(file, listener, items, exportItemsFiles); - exportAsyncTasks.put(file, exportAsyncTask); - exportAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void exportSettings(@NonNull File fileDir, @NonNull String fileName, @Nullable SettingsExportListener listener, - boolean exportItemsFiles, @NonNull SettingsItem... items) { - exportSettings(fileDir, fileName, listener, new ArrayList<>(Arrays.asList(items)), exportItemsFiles); - } - - public enum ImportType { - COLLECT, - CHECK_DUPLICATES, - IMPORT - } - - public List getFilteredSettingsItems(Map> additionalData, - List settingsTypes) { - List settingsItems = new ArrayList<>(); - for (ExportSettingsType settingsType : settingsTypes) { - List settingsDataObjects = additionalData.get(settingsType); - if (settingsDataObjects != null) { - for (Object object : settingsDataObjects) { - if (object instanceof ApplicationModeBean) { - settingsItems.add(new ProfileSettingsItem(app, null, (ApplicationModeBean) object)); - } - } - settingsItems.addAll(prepareAdditionalSettingsItems(new ArrayList<>(settingsDataObjects))); - } - } - return settingsItems; - } - - public Map> getAdditionalData() { - Map> dataList = new HashMap<>(); - - QuickActionRegistry registry = app.getQuickActionRegistry(); - List actionsList = registry.getQuickActions(); - if (!actionsList.isEmpty()) { - dataList.put(ExportSettingsType.QUICK_ACTIONS, actionsList); - } - - List poiList = app.getPoiFilters().getUserDefinedPoiFilters(false); - if (!poiList.isEmpty()) { - dataList.put(ExportSettingsType.POI_TYPES, poiList); - } - - List iTileSources = new ArrayList<>(); - Set tileSourceNames = app.getSettings().getTileSourceEntries(true).keySet(); - for (String name : tileSourceNames) { - File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + name); - if (f != null) { - ITileSource template; - if (f.getName().endsWith(SQLiteTileSource.EXT)) { - template = new SQLiteTileSource(app, f, TileSourceManager.getKnownSourceTemplates()); - } else { - template = TileSourceManager.createTileSourceTemplate(f); - } - if (template.getUrlTemplate() != null) { - iTileSources.add(template); - } - } - } - if (!iTileSources.isEmpty()) { - dataList.put(ExportSettingsType.MAP_SOURCES, iTileSources); - } - - Map externalRenderers = app.getRendererRegistry().getExternalRenderers(); - if (!externalRenderers.isEmpty()) { - dataList.put(ExportSettingsType.CUSTOM_RENDER_STYLE, new ArrayList<>(externalRenderers.values())); - } - - File routingProfilesFolder = app.getAppPath(IndexConstants.ROUTING_PROFILES_DIR); - if (routingProfilesFolder.exists() && routingProfilesFolder.isDirectory()) { - File[] fl = routingProfilesFolder.listFiles(); - if (fl != null && fl.length > 0) { - dataList.put(ExportSettingsType.CUSTOM_ROUTING, Arrays.asList(fl)); - } - } - - Map impassableRoads = app.getAvoidSpecificRoads().getImpassableRoads(); - if (!impassableRoads.isEmpty()) { - dataList.put(ExportSettingsType.AVOID_ROADS, new ArrayList<>(impassableRoads.values())); - } - return dataList; - } - - public List prepareAdditionalSettingsItems(List data) { - List settingsItems = new ArrayList<>(); - List quickActions = new ArrayList<>(); - List poiUIFilters = new ArrayList<>(); - List tileSourceTemplates = new ArrayList<>(); - List avoidRoads = new ArrayList<>(); - for (Object object : data) { - if (object instanceof QuickAction) { - quickActions.add((QuickAction) object); - } else if (object instanceof PoiUIFilter) { - poiUIFilters.add((PoiUIFilter) object); - } else if (object instanceof TileSourceTemplate || object instanceof SQLiteTileSource) { - tileSourceTemplates.add((ITileSource) object); - } else if (object instanceof File) { - try { - settingsItems.add(new FileSettingsItem(app, (File) object)); - } catch (IllegalArgumentException e) { - LOG.warn("Trying to export unsuported file type", e); - } - } else if (object instanceof AvoidRoadInfo) { - avoidRoads.add((AvoidRoadInfo) object); - } - } - if (!quickActions.isEmpty()) { - settingsItems.add(new QuickActionsSettingsItem(app, quickActions)); - } - if (!poiUIFilters.isEmpty()) { - settingsItems.add(new PoiUiFiltersSettingsItem(app, poiUIFilters)); - } - if (!tileSourceTemplates.isEmpty()) { - settingsItems.add(new MapSourcesSettingsItem(app, tileSourceTemplates)); - } - if (!avoidRoads.isEmpty()) { - settingsItems.add(new AvoidRoadsSettingsItem(app, avoidRoads)); - } - return settingsItems; - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java new file mode 100644 index 0000000000..3088d9e86f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java @@ -0,0 +1,179 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.helpers.AvoidSpecificRoads; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class AvoidRoadsSettingsItem extends CollectionSettingsItem { + + private OsmandSettings settings; + private AvoidSpecificRoads specificRoads; + + public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @Nullable AvoidRoadsSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + settings = app.getSettings(); + specificRoads = app.getAvoidSpecificRoads(); + existingItems = new ArrayList<>(specificRoads.getImpassableRoads().values()); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.AVOID_ROADS; + } + + @NonNull + @Override + public String getName() { + return "avoid_roads"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "avoid_roads"; + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + for (AvoidSpecificRoads.AvoidRoadInfo duplicate : duplicateItems) { + if (shouldReplace) { + LatLon latLon = new LatLon(duplicate.latitude, duplicate.longitude); + if (settings.removeImpassableRoad(latLon)) { + settings.addImpassableRoad(duplicate); + } + } else { + settings.addImpassableRoad(renameItem(duplicate)); + } + } + for (AvoidSpecificRoads.AvoidRoadInfo avoidRoad : appliedItems) { + settings.addImpassableRoad(avoidRoad); + } + specificRoads.loadImpassableRoads(); + specificRoads.initRouteObjects(true); + } + } + + @Override + public boolean isDuplicate(@NonNull AvoidSpecificRoads.AvoidRoadInfo item) { + return existingItems.contains(item); + } + + @Override + public boolean shouldReadOnCollecting() { + return true; + } + + @NonNull + @Override + public AvoidSpecificRoads.AvoidRoadInfo renameItem(@NonNull AvoidSpecificRoads.AvoidRoadInfo item) { + int number = 0; + while (true) { + number++; + AvoidSpecificRoads.AvoidRoadInfo renamedItem = new AvoidSpecificRoads.AvoidRoadInfo(); + renamedItem.name = item.name + "_" + number; + if (!isDuplicate(renamedItem)) { + renamedItem.id = item.id; + renamedItem.latitude = item.latitude; + renamedItem.longitude = item.longitude; + renamedItem.appModeKey = item.appModeKey; + return renamedItem; + } + } + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.getJSONObject(i); + double latitude = object.optDouble("latitude"); + double longitude = object.optDouble("longitude"); + String name = object.optString("name"); + String appModeKey = object.optString("appModeKey"); + AvoidSpecificRoads.AvoidRoadInfo roadInfo = new AvoidSpecificRoads.AvoidRoadInfo(); + roadInfo.id = 0; + roadInfo.latitude = latitude; + roadInfo.longitude = longitude; + roadInfo.name = name; + if (ApplicationMode.valueOfStringKey(appModeKey, null) != null) { + roadInfo.appModeKey = appModeKey; + } else { + roadInfo.appModeKey = app.getRoutingHelper().getAppMode().getStringKey(); + } + items.add(roadInfo); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (AvoidSpecificRoads.AvoidRoadInfo avoidRoad : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("latitude", avoidRoad.latitude); + jsonObject.put("longitude", avoidRoad.longitude); + jsonObject.put("name", avoidRoad.name); + jsonObject.put("appModeKey", avoidRoad.appModeKey); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/CollectionSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/CollectionSettingsItem.java new file mode 100644 index 0000000000..dea9d51bc8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/CollectionSettingsItem.java @@ -0,0 +1,75 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public abstract class CollectionSettingsItem extends SettingsItem { + + protected List items; + protected List appliedItems; + protected List duplicateItems; + protected List existingItems; + + @Override + protected void init() { + super.init(); + items = new ArrayList<>(); + appliedItems = new ArrayList<>(); + duplicateItems = new ArrayList<>(); + } + + CollectionSettingsItem(@NonNull OsmandApplication app, @Nullable CollectionSettingsItem baseItem, @NonNull List items) { + super(app, baseItem); + this.items = items; + } + + CollectionSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @NonNull + public List getItems() { + return items; + } + + @NonNull + public List getAppliedItems() { + return appliedItems; + } + + @NonNull + public List getDuplicateItems() { + return duplicateItems; + } + + @NonNull + public List processDuplicateItems() { + if (!items.isEmpty()) { + for (T item : items) { + if (isDuplicate(item)) { + duplicateItems.add(item); + } + } + } + return duplicateItems; + } + + public List getNewItems() { + List res = new ArrayList<>(items); + res.removeAll(duplicateItems); + return res; + } + + public abstract boolean isDuplicate(@NonNull T item); + + @NonNull + public abstract T renameItem(@NonNull T item); +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java new file mode 100644 index 0000000000..d68325fe5f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java @@ -0,0 +1,87 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class DataSettingsItem extends StreamSettingsItem { + + @Nullable + private byte[] data; + + public DataSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { + super(app, name); + } + + DataSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + public DataSettingsItem(@NonNull OsmandApplication app, @NonNull byte[] data, @NonNull String name) { + super(app, name); + this.data = data; + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.DATA; + } + + @NonNull + @Override + public String getDefaultFileExtension() { + return ".dat"; + } + + @Nullable + public byte[] getData() { + return data; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + String fileName = getFileName(); + if (!Algorithms.isEmpty(fileName)) { + name = Algorithms.getFileNameWithoutExtension(new File(fileName)); + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new StreamSettingsItemReader(this) { + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[SettingsHelper.BUFFER]; + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + buffer.flush(); + DataSettingsItem.this.data = buffer.toByteArray(); + } + }; + } + + @Nullable + @Override + public SettingsItemWriter getWriter() { + setInputStream(new ByteArrayInputStream(data)); + return super.getWriter(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/DownloadsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DownloadsItem.java new file mode 100644 index 0000000000..67b9d5812e --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DownloadsItem.java @@ -0,0 +1,102 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.map.WorldRegion; +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.CustomRegion; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class DownloadsItem extends SettingsItem { + + private List items; + + DownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + items = new ArrayList<>(); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.DOWNLOADS; + + } + + @NonNull + @Override + public String getName() { + return "downloads"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "downloads"; + } + + public List getItems() { + return items; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + items.addAll(CustomOsmandPlugin.collectRegionsFromJson(app, jsonArray)); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (WorldRegion region : items) { + if (region instanceof CustomRegion) { + JSONObject regionJson = ((CustomRegion) region).toJson(); + jsonArray.put(regionJson); + } + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return null; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java new file mode 100644 index 0000000000..357c0aadbf --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java @@ -0,0 +1,242 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.IndexConstants; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +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), + 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); + + private String subtypeName; + private String subtypeFolder; + + FileSubtype(String subtypeName, String subtypeFolder) { + this.subtypeName = subtypeName; + this.subtypeFolder = subtypeFolder; + } + + public String getSubtypeName() { + return subtypeName; + } + + public String getSubtypeFolder() { + return subtypeFolder; + } + + public static FileSubtype getSubtypeByName(@NonNull String name) { + for (FileSubtype subtype : FileSubtype.values()) { + if (name.equals(subtype.subtypeName)) { + return subtype; + } + } + return null; + } + + public static FileSubtype getSubtypeByFileName(@NonNull String fileName) { + String name = fileName; + if (fileName.startsWith(File.separator)) { + name = fileName.substring(1); + } + for (FileSubtype subtype : FileSubtype.values()) { + switch (subtype) { + case UNKNOWN: + case OTHER: + break; + case OBF_MAP: + if (name.endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) { + return subtype; + } + break; + default: + if (name.startsWith(subtype.subtypeFolder)) { + return subtype; + } + break; + } + } + return UNKNOWN; + } + + @NonNull + @Override + public String toString() { + return subtypeName; + } + } + + protected File file; + private File appPath; + protected FileSubtype subtype; + + public FileSettingsItem(@NonNull OsmandApplication app, @NonNull File file) throws IllegalArgumentException { + super(app, file.getPath().replace(app.getAppPath(null).getPath(), "")); + this.file = file; + this.appPath = app.getAppPath(null); + String fileName = getFileName(); + if (fileName != null) { + this.subtype = FileSubtype.getSubtypeByFileName(fileName); + } + if (subtype == FileSubtype.UNKNOWN || subtype == null) { + throw new IllegalArgumentException("Unknown file subtype: " + fileName); + } + } + + FileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + this.appPath = app.getAppPath(null); + if (subtype == FileSubtype.OTHER) { + this.file = new File(appPath, name); + } else if (subtype == FileSubtype.UNKNOWN || subtype == null) { + throw new IllegalArgumentException("Unknown file subtype: " + getFileName()); + } else { + String subtypeFolder = subtype.subtypeFolder; + int nameIndex = fileName.indexOf(name); + int folderIndex = fileName.indexOf(subtype.subtypeFolder); + if (nameIndex != -1 && folderIndex != -1) { + String subfolderPath = fileName.substring(folderIndex + subtype.subtypeFolder.length(), nameIndex); + subtypeFolder = subtypeFolder + subfolderPath; + } + this.file = new File(app.getAppPath(subtypeFolder), name); + } + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.FILE; + } + + public File getPluginPath() { + String pluginId = getPluginId(); + if (!Algorithms.isEmpty(pluginId)) { + return new File(appPath, IndexConstants.PLUGINS_DIR + pluginId); + } + return appPath; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + String fileName = getFileName(); + if (subtype == null) { + String subtypeStr = json.has("subtype") ? json.getString("subtype") : null; + if (!Algorithms.isEmpty(subtypeStr)) { + subtype = FileSubtype.getSubtypeByName(subtypeStr); + } else if (!Algorithms.isEmpty(fileName)) { + subtype = FileSubtype.getSubtypeByFileName(fileName); + } else { + subtype = FileSubtype.UNKNOWN; + } + } + if (!Algorithms.isEmpty(fileName)) { + if (subtype == FileSubtype.OTHER) { + name = fileName; + } else if (subtype != null && subtype != FileSubtype.UNKNOWN) { + name = Algorithms.getFileWithoutDirs(fileName); + } + } + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + if (subtype != null) { + json.put("subtype", subtype.getSubtypeName()); + } + } + + @NonNull + public File getFile() { + return file; + } + + @NonNull + public FileSubtype getSubtype() { + return subtype; + } + + @Override + public boolean exists() { + return file.exists(); + } + + private File renameFile(File file) { + 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; + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new StreamSettingsItemReader(this) { + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + OutputStream output; + File dest = FileSettingsItem.this.file; + if (dest.exists() && !shouldReplace) { + dest = renameFile(dest); + } + if (dest.getParentFile() != null && !dest.getParentFile().exists()) { + dest.getParentFile().mkdirs(); + } + output = new FileOutputStream(dest); + byte[] buffer = new byte[SettingsHelper.BUFFER]; + int count; + try { + while ((count = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, count); + } + output.flush(); + } finally { + Algorithms.closeStream(output); + } + } + }; + } + + @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); + } + return super.getWriter(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/GlobalSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/GlobalSettingsItem.java new file mode 100644 index 0000000000..ca7de253fb --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/GlobalSettingsItem.java @@ -0,0 +1,65 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.R; +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONException; +import org.json.JSONObject; + +public class GlobalSettingsItem extends OsmandSettingsItem { + + public GlobalSettingsItem(@NonNull OsmandSettings settings) { + super(settings); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.GLOBAL; + } + + @NonNull + @Override + public String getName() { + return "general_settings"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return ctx.getString(R.string.general_settings_2); + } + + @Override + public boolean exists() { + return true; + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new OsmandSettingsItemReader(this, getSettings()) { + @Override + protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + preference.readFromJson(json, null); + } + }; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return new OsmandSettingsItemWriter(this, getSettings()) { + @Override + protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + preference.writeToJson(json, null); + } + }; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/MapSourcesSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/MapSourcesSettingsItem.java new file mode 100644 index 0000000000..82cf840b94 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/MapSourcesSettingsItem.java @@ -0,0 +1,241 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.IndexConstants; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.SQLiteTileSource; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class MapSourcesSettingsItem extends CollectionSettingsItem { + + private List existingItemsNames; + + public MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public MapSourcesSettingsItem(@NonNull OsmandApplication app, @Nullable MapSourcesSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + existingItemsNames = new ArrayList<>(app.getSettings().getTileSourceEntries().values()); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.MAP_SOURCES; + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + if (shouldReplace) { + for (ITileSource tileSource : duplicateItems) { + if (tileSource instanceof SQLiteTileSource) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName() + IndexConstants.SQLITE_EXT); + if (f != null && f.exists() && Algorithms.removeAllFiles(f)) { + appliedItems.add(tileSource); + } + } else if (tileSource instanceof TileSourceManager.TileSourceTemplate) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName()); + if (f != null && f.exists() && f.isDirectory() && Algorithms.removeAllFiles(f)) { + appliedItems.add(tileSource); + } + } + } + } else { + for (ITileSource tileSource : duplicateItems) { + appliedItems.add(renameItem(tileSource)); + } + } + for (ITileSource tileSource : appliedItems) { + if (tileSource instanceof TileSourceManager.TileSourceTemplate) { + app.getSettings().installTileSource((TileSourceManager.TileSourceTemplate) tileSource); + } else if (tileSource instanceof SQLiteTileSource) { + ((SQLiteTileSource) tileSource).createDataBase(); + } + } + } + } + + @NonNull + @Override + public ITileSource renameItem(@NonNull ITileSource item) { + int number = 0; + while (true) { + number++; + if (item instanceof SQLiteTileSource) { + SQLiteTileSource oldItem = (SQLiteTileSource) item; + SQLiteTileSource renamedItem = new SQLiteTileSource( + oldItem, + oldItem.getName() + "_" + number, + app); + if (!isDuplicate(renamedItem)) { + return renamedItem; + } + } else if (item instanceof TileSourceManager.TileSourceTemplate) { + TileSourceManager.TileSourceTemplate oldItem = (TileSourceManager.TileSourceTemplate) item; + oldItem.setName(oldItem.getName() + "_" + number); + if (!isDuplicate(oldItem)) { + return oldItem; + } + } + } + } + + @Override + public boolean isDuplicate(@NonNull ITileSource item) { + for (String name : existingItemsNames) { + if (name.equals(item.getName())) { + return true; + } + } + return false; + } + + @NonNull + @Override + public String getName() { + return "map_sources"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "map_sources"; + } + + @Override + public boolean shouldReadOnCollecting() { + return true; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.getJSONObject(i); + boolean sql = object.optBoolean("sql"); + String name = object.optString("name"); + int minZoom = object.optInt("minZoom"); + int maxZoom = object.optInt("maxZoom"); + String url = object.optString("url"); + String randoms = object.optString("randoms"); + boolean ellipsoid = object.optBoolean("ellipsoid", false); + boolean invertedY = object.optBoolean("inverted_y", false); + String referer = object.optString("referer"); + String userAgent = object.optString("userAgent"); + boolean timeSupported = object.optBoolean("timesupported", false); + long expire = object.optLong("expire", -1); + boolean inversiveZoom = object.optBoolean("inversiveZoom", false); + String ext = object.optString("ext"); + int tileSize = object.optInt("tileSize"); + int bitDensity = object.optInt("bitDensity"); + int avgSize = object.optInt("avgSize"); + String rule = object.optString("rule"); + + if (expire > 0 && expire < 3600000) { + expire = expire * 60 * 1000L; + } + + ITileSource template; + if (!sql) { + TileSourceManager.TileSourceTemplate tileSourceTemplate = new TileSourceManager.TileSourceTemplate(name, url, ext, maxZoom, minZoom, tileSize, bitDensity, avgSize); + tileSourceTemplate.setRule(rule); + tileSourceTemplate.setRandoms(randoms); + tileSourceTemplate.setReferer(referer); + tileSourceTemplate.setUserAgent(userAgent); + tileSourceTemplate.setEllipticYTile(ellipsoid); + tileSourceTemplate.setInvertedYTile(invertedY); + tileSourceTemplate.setExpirationTimeMillis(timeSupported ? expire : -1); + + template = tileSourceTemplate; + } else { + template = new SQLiteTileSource(app, name, minZoom, maxZoom, url, randoms, ellipsoid, invertedY, referer, userAgent, timeSupported, expire, inversiveZoom, rule); + } + items.add(template); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (ITileSource template : items) { + JSONObject jsonObject = new JSONObject(); + boolean sql = template instanceof SQLiteTileSource; + jsonObject.put("sql", sql); + jsonObject.put("name", template.getName()); + jsonObject.put("minZoom", template.getMinimumZoomSupported()); + jsonObject.put("maxZoom", template.getMaximumZoomSupported()); + jsonObject.put("url", template.getUrlTemplate()); + jsonObject.put("randoms", template.getRandoms()); + jsonObject.put("ellipsoid", template.isEllipticYTile()); + jsonObject.put("inverted_y", template.isInvertedYTile()); + jsonObject.put("referer", template.getReferer()); + jsonObject.put("userAgent", template.getUserAgent()); + jsonObject.put("timesupported", template.isTimeSupported()); + jsonObject.put("expire", template.getExpirationTimeMinutes()); + jsonObject.put("inversiveZoom", template.getInversiveZoom()); + jsonObject.put("ext", template.getTileFormat()); + jsonObject.put("tileSize", template.getTileSize()); + jsonObject.put("bitDensity", template.getBitDensity()); + jsonObject.put("avgSize", template.getAvgSize()); + jsonObject.put("rule", template.getRule()); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java new file mode 100644 index 0000000000..033b856e05 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java @@ -0,0 +1,33 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONException; +import org.json.JSONObject; + +public abstract class OsmandSettingsItem extends SettingsItem { + + private OsmandSettings settings; + + protected OsmandSettingsItem(@NonNull OsmandSettings settings) { + super(settings.getContext()); + this.settings = settings; + } + + protected OsmandSettingsItem(@NonNull OsmandSettings settings, @Nullable OsmandSettingsItem baseItem) { + super(settings.getContext(), baseItem); + this.settings = settings; + } + + protected OsmandSettingsItem(@NonNull SettingsItemType type, @NonNull OsmandSettings settings, @NonNull JSONObject json) throws JSONException { + super(settings.getContext(), json); + this.settings = settings; + } + + public OsmandSettings getSettings() { + return settings; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java new file mode 100644 index 0000000000..0267e69471 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java @@ -0,0 +1,78 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.Map; + +public abstract class OsmandSettingsItemReader extends SettingsItemReader { + + private OsmandSettings settings; + + public OsmandSettingsItemReader(@NonNull T item, @NonNull OsmandSettings settings) { + super(item); + this.settings = settings; + } + + protected abstract void readPreferenceFromJson(@NonNull OsmandPreference preference, + @NonNull JSONObject json) throws JSONException; + + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + StringBuilder buf = new StringBuilder(); + try { + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + String str; + while ((str = in.readLine()) != null) { + buf.append(str); + } + } catch (IOException e) { + throw new IOException("Cannot read json body", e); + } + String jsonStr = buf.toString(); + if (Algorithms.isEmpty(jsonStr)) { + throw new IllegalArgumentException("Cannot find json body"); + } + final JSONObject json; + try { + json = new JSONObject(jsonStr); + } catch (JSONException e) { + throw new IllegalArgumentException("Json parse error", e); + } + readPreferencesFromJson(json); + } + + void readPreferencesFromJson(final JSONObject json) { + settings.getContext().runInUIThread(new Runnable() { + @Override + public void run() { + Map> prefs = settings.getRegisteredPreferences(); + Iterator iter = json.keys(); + while (iter.hasNext()) { + String key = iter.next(); + OsmandPreference p = prefs.get(key); + if (p != null) { + try { + readPreferenceFromJson(p, json); + } catch (Exception e) { + SettingsHelper.LOG.error("Failed to read preference: " + key, e); + } + } else { + SettingsHelper.LOG.warn("No preference while importing settings: " + key); + } + } + } + }); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemWriter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemWriter.java new file mode 100644 index 0000000000..810ae90f31 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemWriter.java @@ -0,0 +1,49 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; + +public abstract class OsmandSettingsItemWriter extends SettingsItemWriter { + + private OsmandSettings settings; + + public OsmandSettingsItemWriter(@NonNull T item, @NonNull OsmandSettings settings) { + super(item); + this.settings = settings; + } + + protected abstract void writePreferenceToJson(@NonNull OsmandPreference preference, + @NonNull JSONObject json) throws JSONException; + + @Override + public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { + JSONObject json = new JSONObject(); + Map> prefs = settings.getRegisteredPreferences(); + for (OsmandPreference pref : prefs.values()) { + try { + writePreferenceToJson(pref, json); + } catch (JSONException e) { + SettingsHelper.LOG.error("Failed to write preference: " + pref.getId(), e); + } + } + if (json.length() > 0) { + try { + String s = json.toString(2); + outputStream.write(s.getBytes("UTF-8")); + } catch (JSONException e) { + SettingsHelper.LOG.error("Failed to write json to stream", e); + } + return true; + } + return false; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java new file mode 100644 index 0000000000..32d6a04e22 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java @@ -0,0 +1,116 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class PluginSettingsItem extends SettingsItem { + + private CustomOsmandPlugin plugin; + private List pluginDependentItems; + + PluginSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + pluginDependentItems = new ArrayList<>(); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.PLUGIN; + } + + @NonNull + @Override + public String getName() { + return plugin.getId(); + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return plugin.getName(); + } + + @NonNull + @Override + public String getDefaultFileName() { + return getName(); + } + + public CustomOsmandPlugin getPlugin() { + return plugin; + } + + public List getPluginDependentItems() { + return pluginDependentItems; + } + + @Override + public boolean exists() { + return OsmandPlugin.getPlugin(getPluginId()) != null; + } + + @Override + public void apply() { + if (shouldReplace || !exists()) { + for (SettingsItem item : pluginDependentItems) { + if (item instanceof FileSettingsItem) { + FileSettingsItem fileItem = (FileSettingsItem) item; + if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { + plugin.addRenderer(fileItem.getName()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { + plugin.addRouter(fileItem.getName()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.OTHER) { + plugin.setResourceDirName(item.getFileName()); + } + } else if (item instanceof SuggestedDownloadsItem) { + plugin.updateSuggestedDownloads(((SuggestedDownloadsItem) item).getItems()); + } else if (item instanceof DownloadsItem) { + plugin.updateDownloadItems(((DownloadsItem) item).getItems()); + } + } + OsmandPlugin.addCustomPlugin(app, plugin); + } + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + plugin = new CustomOsmandPlugin(app, json); + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + plugin.writeAdditionalDataToJson(json); + } + + @Nullable + @Override + SettingsItemReader getReader() { + return null; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java new file mode 100644 index 0000000000..8d1c464761 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java @@ -0,0 +1,178 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import net.osmand.osm.MapPoiTypes; +import net.osmand.osm.PoiCategory; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.poi.PoiUIFilter; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +public class PoiUiFiltersSettingsItem extends CollectionSettingsItem { + + public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @Nullable PoiUiFiltersSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + existingItems = app.getPoiFilters().getUserDefinedPoiFilters(false); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.POI_UI_FILTERS; + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + + for (PoiUIFilter duplicate : duplicateItems) { + appliedItems.add(shouldReplace ? duplicate : renameItem(duplicate)); + } + for (PoiUIFilter filter : appliedItems) { + app.getPoiFilters().createPoiFilter(filter, false); + } + app.getSearchUICore().refreshCustomPoiFilters(); + } + } + + @Override + public boolean isDuplicate(@NonNull PoiUIFilter item) { + String savedName = item.getName(); + for (PoiUIFilter filter : existingItems) { + if (filter.getName().equals(savedName)) { + return true; + } + } + return false; + } + + @NonNull + @Override + public PoiUIFilter renameItem(@NonNull PoiUIFilter item) { + int number = 0; + while (true) { + number++; + PoiUIFilter renamedItem = new PoiUIFilter(item, + item.getName() + "_" + number, + item.getFilterId() + "_" + number); + if (!isDuplicate(renamedItem)) { + return renamedItem; + } + } + } + + @NonNull + @Override + public String getName() { + return "poi_ui_filters"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "poi_ui_filters"; + } + + @Override + public boolean shouldReadOnCollecting() { + return true; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + Gson gson = new Gson(); + Type type = new TypeToken>>() { + }.getType(); + MapPoiTypes poiTypes = app.getPoiTypes(); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.getJSONObject(i); + String name = object.getString("name"); + String filterId = object.getString("filterId"); + String acceptedTypesString = object.getString("acceptedTypes"); + HashMap> acceptedTypes = gson.fromJson(acceptedTypesString, type); + Map> acceptedTypesDone = new HashMap<>(); + for (Map.Entry> mapItem : acceptedTypes.entrySet()) { + final PoiCategory a = poiTypes.getPoiCategoryByName(mapItem.getKey()); + acceptedTypesDone.put(a, mapItem.getValue()); + } + PoiUIFilter filter = new PoiUIFilter(name, filterId, acceptedTypesDone, app); + items.add(filter); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>>() { + }.getType(); + if (!items.isEmpty()) { + try { + for (PoiUIFilter filter : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", filter.getName()); + jsonObject.put("filterId", filter.getFilterId()); + jsonObject.put("acceptedTypes", gson.toJson(filter.getAcceptedTypes(), type)); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/ProfileSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ProfileSettingsItem.java new file mode 100644 index 0000000000..309fe4407c --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ProfileSettingsItem.java @@ -0,0 +1,312 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.IndexConstants; +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.router.GeneralRouter; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ProfileSettingsItem extends OsmandSettingsItem { + + private ApplicationMode appMode; + private ApplicationMode.ApplicationModeBuilder builder; + private ApplicationMode.ApplicationModeBean modeBean; + + private JSONObject additionalPrefsJson; + private Set appModeBeanPrefsIds; + + public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull ApplicationMode appMode) { + super(app.getSettings()); + this.appMode = appMode; + } + + public ProfileSettingsItem(@NonNull OsmandApplication app, @Nullable ProfileSettingsItem baseItem, @NonNull ApplicationMode.ApplicationModeBean modeBean) { + super(app.getSettings(), baseItem); + this.modeBean = modeBean; + builder = ApplicationMode.fromModeBean(app, modeBean); + appMode = builder.getApplicationMode(); + } + + public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(SettingsItemType.PROFILE, app.getSettings(), json); + } + + @Override + protected void init() { + super.init(); + appModeBeanPrefsIds = new HashSet<>(Arrays.asList(getAppModeBeanPrefsIds())); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.PROFILE; + } + + public ApplicationMode getAppMode() { + return appMode; + } + + public ApplicationMode.ApplicationModeBean getModeBean() { + return modeBean; + } + + @NonNull + @Override + public String getName() { + return appMode.getStringKey(); + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + if (appMode.isCustomProfile()) { + return modeBean.userProfileName; + } else if (appMode.getNameKeyResource() != -1) { + return ctx.getString(appMode.getNameKeyResource()); + } else { + return getName(); + } + } + + @NonNull + @Override + public String getDefaultFileName() { + return "profile_" + getName() + getDefaultFileExtension(); + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + String appModeJson = json.getString("appMode"); + modeBean = ApplicationMode.fromJson(appModeJson); + builder = ApplicationMode.fromModeBean(app, modeBean); + ApplicationMode appMode = builder.getApplicationMode(); + if (!appMode.isCustomProfile()) { + appMode = ApplicationMode.valueOfStringKey(appMode.getStringKey(), appMode); + } + this.appMode = appMode; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + additionalPrefsJson = json.optJSONObject("prefs"); + } + + @Override + public boolean exists() { + return builder != null && ApplicationMode.valueOfStringKey(getName(), null) != null; + } + + private void renameProfile() { + List values = ApplicationMode.allPossibleValues(); + if (Algorithms.isEmpty(modeBean.userProfileName)) { + ApplicationMode appMode = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); + if (appMode != null) { + modeBean.userProfileName = app.getString(appMode.getNameKeyResource()); + } + } + int number = 0; + while (true) { + number++; + String key = modeBean.stringKey + "_" + number; + String name = modeBean.userProfileName + '_' + number; + if (ApplicationMode.valueOfStringKey(key, null) == null && isNameUnique(values, name)) { + modeBean.userProfileName = name; + modeBean.stringKey = key; + break; + } + } + } + + private boolean isNameUnique(List values, String name) { + for (ApplicationMode mode : values) { + if (mode.getUserProfileName().equals(name)) { + return false; + } + } + return true; + } + + @Override + public void apply() { + if (!appMode.isCustomProfile() && !shouldReplace) { + ApplicationMode parent = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); + renameProfile(); + ApplicationMode.ApplicationModeBuilder builder = ApplicationMode + .createCustomMode(parent, modeBean.stringKey, app) + .setIconResName(modeBean.iconName) + .setUserProfileName(modeBean.userProfileName) + .setRoutingProfile(modeBean.routingProfile) + .setRouteService(modeBean.routeService) + .setIconColor(modeBean.iconColor) + .setLocationIcon(modeBean.locIcon) + .setNavigationIcon(modeBean.navIcon); + app.getSettings().copyPreferencesFromProfile(parent, builder.getApplicationMode()); + appMode = ApplicationMode.saveProfile(builder, app); + } else if (!shouldReplace && exists()) { + renameProfile(); + builder = ApplicationMode.fromModeBean(app, modeBean); + appMode = ApplicationMode.saveProfile(builder, app); + } else { + builder = ApplicationMode.fromModeBean(app, modeBean); + appMode = ApplicationMode.saveProfile(builder, app); + } + ApplicationMode.changeProfileAvailability(appMode, true, app); + } + + public void applyAdditionalPrefs() { + if (additionalPrefsJson != null) { + updatePluginResPrefs(); + + SettingsItemReader reader = getReader(); + if (reader instanceof OsmandSettingsItemReader) { + ((OsmandSettingsItemReader) reader).readPreferencesFromJson(additionalPrefsJson); + } + } + } + + private void updatePluginResPrefs() { + String pluginId = getPluginId(); + if (Algorithms.isEmpty(pluginId)) { + return; + } + OsmandPlugin plugin = OsmandPlugin.getPlugin(pluginId); + if (plugin instanceof CustomOsmandPlugin) { + CustomOsmandPlugin customPlugin = (CustomOsmandPlugin) plugin; + String resDirPath = IndexConstants.PLUGINS_DIR + pluginId + "/" + customPlugin.getResourceDirName(); + + for (Iterator it = additionalPrefsJson.keys(); it.hasNext(); ) { + try { + String prefId = it.next(); + Object value = additionalPrefsJson.get(prefId); + if (value instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) value; + for (Iterator iterator = jsonObject.keys(); iterator.hasNext(); ) { + String key = iterator.next(); + Object val = jsonObject.get(key); + if (val instanceof String) { + val = checkPluginResPath((String) val, resDirPath); + } + jsonObject.put(key, val); + } + } else if (value instanceof String) { + value = checkPluginResPath((String) value, resDirPath); + additionalPrefsJson.put(prefId, value); + } + } catch (JSONException e) { + SettingsHelper.LOG.error(e); + } + } + } + } + + private String checkPluginResPath(String path, String resDirPath) { + if (path.startsWith("@")) { + return resDirPath + "/" + path.substring(1); + } + return path; + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + json.put("appMode", new JSONObject(appMode.toJson())); + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new OsmandSettingsItemReader(this, getSettings()) { + @Override + protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + if (!appModeBeanPrefsIds.contains(preference.getId())) { + preference.readFromJson(json, appMode); + } + } + + @Override + void readPreferencesFromJson(final JSONObject json) { + getSettings().getContext().runInUIThread(new Runnable() { + @Override + public void run() { + OsmandSettings settings = getSettings(); + Map> prefs = settings.getRegisteredPreferences(); + Iterator iter = json.keys(); + while (iter.hasNext()) { + String key = iter.next(); + OsmandPreference p = prefs.get(key); + if (p == null) { + if (OsmandSettings.isRoutingPreference(key)) { + p = settings.registerStringPreference(key, ""); + } + } + if (p != null) { + try { + readPreferenceFromJson(p, json); + if (OsmandSettings.isRoutingPreference(p.getId())) { + if (p.getId().endsWith(GeneralRouter.USE_SHORTEST_WAY)) { + settings.FAST_ROUTE_MODE.setModeValue(appMode, + !settings.getCustomRoutingBooleanProperty(GeneralRouter.USE_SHORTEST_WAY, false).getModeValue(appMode)); + } + } + } catch (Exception e) { + SettingsHelper.LOG.error("Failed to read preference: " + key, e); + } + } else { + SettingsHelper.LOG.warn("No preference while importing settings: " + key); + } + } + } + }); + } + }; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return new OsmandSettingsItemWriter(this, getSettings()) { + @Override + protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + if (!appModeBeanPrefsIds.contains(preference.getId())) { + preference.writeToJson(json, appMode); + } + } + }; + } + + private String[] getAppModeBeanPrefsIds() { + OsmandSettings settings = app.getSettings(); + return new String[] { + settings.ICON_COLOR.getId(), + settings.ICON_RES_NAME.getId(), + settings.PARENT_APP_MODE.getId(), + settings.ROUTING_PROFILE.getId(), + settings.ROUTE_SERVICE.getId(), + settings.USER_PROFILE_NAME.getId(), + settings.LOCATION_ICON.getId(), + settings.NAVIGATION_ICON.getId(), + settings.APP_MODE_ORDER.getId() + }; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/QuickActionsSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/QuickActionsSettingsItem.java new file mode 100644 index 0000000000..b2e597ab60 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/QuickActionsSettingsItem.java @@ -0,0 +1,183 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.quickaction.QuickAction; +import net.osmand.plus.quickaction.QuickActionRegistry; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class QuickActionsSettingsItem extends CollectionSettingsItem { + + private QuickActionRegistry actionRegistry; + + public QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public QuickActionsSettingsItem(@NonNull OsmandApplication app, @Nullable QuickActionsSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + actionRegistry = app.getQuickActionRegistry(); + existingItems = actionRegistry.getQuickActions(); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.QUICK_ACTIONS; + } + + @Override + public boolean isDuplicate(@NonNull QuickAction item) { + return !actionRegistry.isNameUnique(item, app); + } + + @NonNull + @Override + public QuickAction renameItem(@NonNull QuickAction item) { + return actionRegistry.generateUniqueName(item, app); + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + List newActions = new ArrayList<>(existingItems); + if (!duplicateItems.isEmpty()) { + if (shouldReplace) { + for (QuickAction duplicateItem : duplicateItems) { + for (QuickAction savedAction : existingItems) { + if (duplicateItem.getName(app).equals(savedAction.getName(app))) { + newActions.remove(savedAction); + } + } + } + } else { + for (QuickAction duplicateItem : duplicateItems) { + renameItem(duplicateItem); + } + } + appliedItems.addAll(duplicateItems); + } + newActions.addAll(appliedItems); + actionRegistry.updateQuickActions(newActions); + } + } + + @Override + public boolean shouldReadOnCollecting() { + return true; + } + + @NonNull + @Override + public String getName() { + return "quick_actions"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "quick_actions"; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + QuickActionRegistry quickActionRegistry = app.getQuickActionRegistry(); + JSONArray itemsJson = json.getJSONArray("items"); + for (int i = 0; i < itemsJson.length(); i++) { + JSONObject object = itemsJson.getJSONObject(i); + String name = object.getString("name"); + QuickAction quickAction = null; + if (object.has("actionType")) { + quickAction = quickActionRegistry.newActionByStringType(object.getString("actionType")); + } else if (object.has("type")) { + quickAction = quickActionRegistry.newActionByType(object.getInt("type")); + } + if (quickAction != null) { + String paramsString = object.getString("params"); + HashMap params = gson.fromJson(paramsString, type); + + if (!name.isEmpty()) { + quickAction.setName(name); + } + quickAction.setParams(params); + items.add(quickAction); + } else { + warnings.add(app.getString(R.string.settings_item_read_error, name)); + } + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + if (!items.isEmpty()) { + try { + for (QuickAction action : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", action.hasCustomName(app) + ? action.getName(app) : ""); + jsonObject.put("actionType", action.getActionType().getStringId()); + jsonObject.put("params", gson.toJson(action.getParams(), type)); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/ResourcesSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ResourcesSettingsItem.java new file mode 100644 index 0000000000..194125e7b9 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ResourcesSettingsItem.java @@ -0,0 +1,72 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; + +public class ResourcesSettingsItem extends FileSettingsItem { + + ResourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + shouldReplace = true; + String fileName = getFileName(); + if (!Algorithms.isEmpty(fileName) && !fileName.endsWith(File.separator)) { + this.fileName = fileName + File.separator; + } + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.RESOURCES; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + subtype = FileSubtype.OTHER; + super.readFromJson(json); + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + String fileName = getFileName(); + if (!Algorithms.isEmpty(fileName)) { + if (fileName.endsWith(File.separator)) { + fileName = fileName.substring(0, fileName.length() - 1); + } + json.put("file", fileName); + } + } + + @Override + public boolean applyFileName(@NonNull String fileName) { + if (fileName.endsWith(File.separator)) { + return false; + } + String itemFileName = getFileName(); + if (itemFileName != null && itemFileName.endsWith(File.separator)) { + if (fileName.startsWith(itemFileName)) { + this.file = new File(getPluginPath(), fileName); + return true; + } else { + return false; + } + } else { + return super.applyFileName(fileName); + } + } + + @Nullable + @Override + public SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java new file mode 100644 index 0000000000..ccc23b5416 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java @@ -0,0 +1,91 @@ +package net.osmand.plus.settings.backend.backup; + +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +class SettingsExporter { + + private Map items; + private Map additionalParams; + private boolean exportItemsFiles; + + SettingsExporter(boolean exportItemsFiles) { + this.exportItemsFiles = exportItemsFiles; + items = new LinkedHashMap<>(); + additionalParams = new LinkedHashMap<>(); + } + + void addSettingsItem(SettingsItem item) throws IllegalArgumentException { + if (items.containsKey(item.getName())) { + throw new IllegalArgumentException("Already has such item: " + item.getName()); + } + items.put(item.getName(), item); + } + + 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); + ZipOutputStream zos = new ZipOutputStream(os); + try { + ZipEntry entry = new ZipEntry("items.json"); + zos.putNextEntry(entry); + zos.write(json.toString(2).getBytes("UTF-8")); + zos.closeEntry(); + if (exportItemsFiles) { + writeItemFiles(zos); + } + zos.flush(); + zos.finish(); + } finally { + Algorithms.closeStream(zos); + Algorithms.closeStream(os); + } + } + + private void writeItemFiles(ZipOutputStream zos) throws IOException { + for (SettingsItem item : items.values()) { + SettingsItemWriter writer = item.getWriter(); + if (writer != null) { + String fileName = item.getFileName(); + if (Algorithms.isEmpty(fileName)) { + fileName = item.getDefaultFileName(); + } + ZipEntry entry = new ZipEntry(fileName); + zos.putNextEntry(entry); + writer.writeToStream(zos); + zos.closeEntry(); + } + } + } + + private JSONObject createItemsJson() throws JSONException { + JSONObject json = new JSONObject(); + json.put("version", SettingsHelper.VERSION); + for (Map.Entry param : additionalParams.entrySet()) { + json.put(param.getKey(), param.getValue()); + } + JSONArray itemsJson = new JSONArray(); + for (SettingsItem item : items.values()) { + itemsJson.put(new JSONObject(item.toJson())); + } + json.put("items", itemsJson); + return json; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java new file mode 100644 index 0000000000..530147b84f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java @@ -0,0 +1,643 @@ +package net.osmand.plus.settings.backend.backup; + +import android.annotation.SuppressLint; +import android.os.AsyncTask; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.AndroidUtils; +import net.osmand.IndexConstants; +import net.osmand.PlatformUtil; +import net.osmand.data.LatLon; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; +import net.osmand.map.TileSourceManager.TileSourceTemplate; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.SQLiteTileSource; +import net.osmand.plus.audionotes.AudioVideoNotesPlugin; +import net.osmand.plus.audionotes.AudioVideoNotesPlugin.Recording; +import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; +import net.osmand.plus.helpers.GpxUiHelper; +import net.osmand.plus.helpers.GpxUiHelper.GPXInfo; +import net.osmand.plus.poi.PoiUIFilter; +import net.osmand.plus.quickaction.QuickAction; +import net.osmand.plus.quickaction.QuickActionRegistry; +import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; +import net.osmand.plus.settings.backend.ExportSettingsType; + +import org.apache.commons.logging.Log; +import org.json.JSONException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; + +/* + Usage: + + SettingsHelper helper = app.getSettingsHelper(); + File file = new File(app.getAppPath(null), "settings.zip"); + + List items = new ArrayList<>(); + items.add(new GlobalSettingsItem(app.getSettings())); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.DEFAULT)); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.CAR)); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.PEDESTRIAN)); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.BICYCLE)); + items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 2.gpx"))); + items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 3.gpx"))); + items.add(new FileSettingsItem(app, new File(app.getAppPath(RENDERERS_DIR), "default.render.xml"))); + items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '1'}, "data1")); + items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '2'}, "data2")); + + helper.exportSettings(file, items); + + helper.importSettings(file); + */ + +public class SettingsHelper { + + public static final int VERSION = 1; + + public static final String SETTINGS_TYPE_LIST_KEY = "settings_type_list_key"; + public static final String REPLACE_KEY = "replace"; + public static final String SETTINGS_LATEST_CHANGES_KEY = "settings_latest_changes"; + public static final String SETTINGS_VERSION_KEY = "settings_version"; + + public static final int BUFFER = 1024; + + protected static final Log LOG = PlatformUtil.getLog(SettingsHelper.class); + + private OsmandApplication app; + + private ImportAsyncTask importTask; + private Map exportAsyncTasks = new HashMap<>(); + + public interface SettingsImportListener { + void onSettingsImportFinished(boolean succeed, @NonNull List items); + } + + public interface SettingsCollectListener { + void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items); + } + + public interface CheckDuplicatesListener { + void onDuplicatesChecked(@NonNull List duplicates, List items); + } + + public interface SettingsExportListener { + void onSettingsExportFinished(@NonNull File file, boolean succeed); + } + + public enum ImportType { + COLLECT, + CHECK_DUPLICATES, + IMPORT + } + + public SettingsHelper(@NonNull OsmandApplication app) { + this.app = app; + } + + @Nullable + public ImportAsyncTask getImportTask() { + return importTask; + } + + @Nullable + public ImportType getImportTaskType() { + ImportAsyncTask importTask = this.importTask; + return importTask != null ? importTask.getImportType() : null; + } + + public boolean isImportDone() { + ImportAsyncTask importTask = this.importTask; + return importTask == null || importTask.isImportDone(); + } + + public boolean isFileExporting(File file) { + return exportAsyncTasks.containsKey(file); + } + + public void updateExportListener(File file, SettingsExportListener listener) { + ExportAsyncTask exportAsyncTask = exportAsyncTasks.get(file); + if (exportAsyncTask != null) { + exportAsyncTask.listener = listener; + } + } + + private void finishImport(@Nullable SettingsImportListener listener, boolean success, @NonNull List items) { + importTask = null; + List warnings = new ArrayList<>(); + for (SettingsItem item : items) { + warnings.addAll(item.getWarnings()); + } + if (!warnings.isEmpty()) { + app.showToastMessage(AndroidUtils.formatWarnings(warnings).toString()); + } + if (listener != null) { + listener.onSettingsImportFinished(success, items); + } + } + + @SuppressLint("StaticFieldLeak") + private class ImportItemsAsyncTask extends AsyncTask { + + private SettingsImporter importer; + private File file; + private SettingsImportListener listener; + private List items; + + ImportItemsAsyncTask(@NonNull File file, + @Nullable SettingsImportListener listener, + @NonNull List items) { + importer = new SettingsImporter(app); + this.file = file; + this.listener = listener; + this.items = items; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + importer.importItems(file, items); + return true; + } catch (IllegalArgumentException e) { + LOG.error("Failed to import items from: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to import items from: " + file.getName(), e); + } + return false; + } + + @Override + protected void onPostExecute(Boolean success) { + finishImport(listener, success, items); + } + } + + @SuppressLint("StaticFieldLeak") + private class ExportAsyncTask extends AsyncTask { + + private SettingsExporter exporter; + private File file; + private SettingsExportListener listener; + + ExportAsyncTask(@NonNull File settingsFile, + @Nullable SettingsExportListener listener, + @NonNull List items, boolean exportItemsFiles) { + this.file = settingsFile; + this.listener = listener; + this.exporter = new SettingsExporter(exportItemsFiles); + for (SettingsItem item : items) { + exporter.addSettingsItem(item); + } + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + exporter.exportSettings(file); + return true; + } catch (JSONException e) { + LOG.error("Failed to export items to: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to export items to: " + file.getName(), e); + } + return false; + } + + @Override + protected void onPostExecute(Boolean success) { + exportAsyncTasks.remove(file); + if (listener != null) { + listener.onSettingsExportFinished(file, success); + } + } + } + + @SuppressLint("StaticFieldLeak") + public class ImportAsyncTask extends AsyncTask> { + + private File file; + private String latestChanges; + private int version; + + private SettingsImportListener importListener; + private SettingsCollectListener collectListener; + private CheckDuplicatesListener duplicatesListener; + private SettingsImporter importer; + + private List items = new ArrayList<>(); + private List selectedItems = new ArrayList<>(); + private List duplicates; + + private ImportType importType; + private boolean importDone; + + ImportAsyncTask(@NonNull File file, String latestChanges, int version, @Nullable SettingsCollectListener collectListener) { + this.file = file; + this.collectListener = collectListener; + this.latestChanges = latestChanges; + this.version = version; + importer = new SettingsImporter(app); + importType = ImportType.COLLECT; + } + + ImportAsyncTask(@NonNull File file, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener importListener) { + this.file = file; + this.importListener = importListener; + this.items = items; + this.latestChanges = latestChanges; + this.version = version; + importer = new SettingsImporter(app); + importType = ImportType.IMPORT; + } + + ImportAsyncTask(@NonNull File file, @NonNull List items, @NonNull List selectedItems, @Nullable CheckDuplicatesListener duplicatesListener) { + this.file = file; + this.items = items; + this.duplicatesListener = duplicatesListener; + this.selectedItems = selectedItems; + importer = new SettingsImporter(app); + importType = ImportType.CHECK_DUPLICATES; + } + + @Override + protected void onPreExecute() { + ImportAsyncTask importTask = SettingsHelper.this.importTask; + if (importTask != null && !importTask.importDone) { + finishImport(importListener, false, items); + } + SettingsHelper.this.importTask = this; + } + + @Override + protected List doInBackground(Void... voids) { + switch (importType) { + case COLLECT: + try { + return importer.collectItems(file); + } catch (IllegalArgumentException e) { + LOG.error("Failed to collect items from: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to collect items from: " + file.getName(), e); + } + break; + case CHECK_DUPLICATES: + this.duplicates = getDuplicatesData(selectedItems); + return selectedItems; + case IMPORT: + return items; + } + return null; + } + + @Override + protected void onPostExecute(@Nullable List items) { + if (items != null && importType != ImportType.CHECK_DUPLICATES) { + this.items = items; + } else { + selectedItems = items; + } + switch (importType) { + case COLLECT: + importDone = true; + collectListener.onSettingsCollectFinished(true, false, this.items); + break; + case CHECK_DUPLICATES: + importDone = true; + if (duplicatesListener != null) { + duplicatesListener.onDuplicatesChecked(duplicates, selectedItems); + } + break; + case IMPORT: + if (items != null && items.size() > 0) { + for (SettingsItem item : items) { + item.apply(); + } + new ImportItemsAsyncTask(file, importListener, items).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + break; + } + } + + public List getItems() { + return items; + } + + public File getFile() { + return file; + } + + public void setImportListener(SettingsImportListener importListener) { + this.importListener = importListener; + } + + public void setDuplicatesListener(CheckDuplicatesListener duplicatesListener) { + this.duplicatesListener = duplicatesListener; + } + + ImportType getImportType() { + return importType; + } + + boolean isImportDone() { + return importDone; + } + + public List getDuplicates() { + return duplicates; + } + + public List getSelectedItems() { + return selectedItems; + } + + private List getDuplicatesData(List items) { + List duplicateItems = new ArrayList<>(); + for (SettingsItem item : items) { + if (item instanceof ProfileSettingsItem) { + if (item.exists()) { + duplicateItems.add(((ProfileSettingsItem) item).getModeBean()); + } + } else if (item instanceof CollectionSettingsItem) { + List duplicates = ((CollectionSettingsItem) item).processDuplicateItems(); + if (!duplicates.isEmpty()) { + duplicateItems.addAll(duplicates); + } + } else if (item instanceof FileSettingsItem) { + if (item.exists()) { + duplicateItems.add(((FileSettingsItem) item).getFile()); + } + } + } + return duplicateItems; + } + } + + public void collectSettings(@NonNull File settingsFile, String latestChanges, int version, + @Nullable SettingsCollectListener listener) { + new ImportAsyncTask(settingsFile, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void checkDuplicates(@NonNull File file, @NonNull List items, @NonNull List selectedItems, CheckDuplicatesListener listener) { + new ImportAsyncTask(file, items, selectedItems, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void importSettings(@NonNull File settingsFile, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener listener) { + new ImportAsyncTask(settingsFile, items, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void exportSettings(@NonNull File fileDir, @NonNull String fileName, @Nullable SettingsExportListener listener, @NonNull List items, boolean exportItemsFiles) { + File file = new File(fileDir, fileName + OSMAND_SETTINGS_FILE_EXT); + ExportAsyncTask exportAsyncTask = new ExportAsyncTask(file, listener, items, exportItemsFiles); + exportAsyncTasks.put(file, exportAsyncTask); + exportAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void exportSettings(@NonNull File fileDir, @NonNull String fileName, @Nullable SettingsExportListener listener, + boolean exportItemsFiles, @NonNull SettingsItem... items) { + exportSettings(fileDir, fileName, listener, new ArrayList<>(Arrays.asList(items)), exportItemsFiles); + } + + public List getFilteredSettingsItems(Map> additionalData, + List settingsTypes) { + List settingsItems = new ArrayList<>(); + for (ExportSettingsType settingsType : settingsTypes) { + List settingsDataObjects = additionalData.get(settingsType); + if (settingsDataObjects != null) { + for (Object object : settingsDataObjects) { + if (object instanceof ApplicationModeBean) { + settingsItems.add(new ProfileSettingsItem(app, null, (ApplicationModeBean) object)); + } + } + settingsItems.addAll(prepareAdditionalSettingsItems(new ArrayList<>(settingsDataObjects))); + } + } + return settingsItems; + } + + public Map> getAdditionalData() { + Map> dataList = new HashMap<>(); + + QuickActionRegistry registry = app.getQuickActionRegistry(); + List actionsList = registry.getQuickActions(); + if (!actionsList.isEmpty()) { + dataList.put(ExportSettingsType.QUICK_ACTIONS, actionsList); + } + + List poiList = app.getPoiFilters().getUserDefinedPoiFilters(false); + if (!poiList.isEmpty()) { + dataList.put(ExportSettingsType.POI_TYPES, poiList); + } + + List iTileSources = new ArrayList<>(); + Set tileSourceNames = app.getSettings().getTileSourceEntries(true).keySet(); + for (String name : tileSourceNames) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + name); + if (f != null) { + ITileSource template; + if (f.getName().endsWith(SQLiteTileSource.EXT)) { + template = new SQLiteTileSource(app, f, TileSourceManager.getKnownSourceTemplates()); + } else { + template = TileSourceManager.createTileSourceTemplate(f); + } + if (template.getUrlTemplate() != null) { + iTileSources.add(template); + } + } + } + if (!iTileSources.isEmpty()) { + dataList.put(ExportSettingsType.MAP_SOURCES, iTileSources); + } + + Map externalRenderers = app.getRendererRegistry().getExternalRenderers(); + if (!externalRenderers.isEmpty()) { + dataList.put(ExportSettingsType.CUSTOM_RENDER_STYLE, new ArrayList<>(externalRenderers.values())); + } + + File routingProfilesFolder = app.getAppPath(IndexConstants.ROUTING_PROFILES_DIR); + if (routingProfilesFolder.exists() && routingProfilesFolder.isDirectory()) { + File[] fl = routingProfilesFolder.listFiles(); + if (fl != null && fl.length > 0) { + dataList.put(ExportSettingsType.CUSTOM_ROUTING, Arrays.asList(fl)); + } + } + + Map impassableRoads = app.getAvoidSpecificRoads().getImpassableRoads(); + if (!impassableRoads.isEmpty()) { + dataList.put(ExportSettingsType.AVOID_ROADS, new ArrayList<>(impassableRoads.values())); + } + AudioVideoNotesPlugin plugin = OsmandPlugin.getPlugin(AudioVideoNotesPlugin.class); + if (plugin != null) { + List files = new ArrayList<>(); + for (Recording rec : plugin.getAllRecordings()) { + File file = rec.getFile(); + if (file != null && file.exists()) { + files.add(file); + } + } + if (!files.isEmpty()) { + dataList.put(ExportSettingsType.MULTIMEDIA_NOTES, files); + } + } + File gpxDir = app.getAppPath(IndexConstants.GPX_INDEX_DIR); + List gpxInfoList = GpxUiHelper.getSortedGPXFilesInfo(gpxDir, null, true); + if (!gpxInfoList.isEmpty()) { + List files = new ArrayList<>(); + for (GPXInfo gpxInfo : gpxInfoList) { + File file = new File(gpxInfo.getFileName()); + if (file.exists()) { + files.add(file); + } + } + if (!files.isEmpty()) { + dataList.put(ExportSettingsType.TRACKS, files); + } + } + return dataList; + } + + public List prepareAdditionalSettingsItems(List data) { + List settingsItems = new ArrayList<>(); + List quickActions = new ArrayList<>(); + List poiUIFilters = new ArrayList<>(); + List tileSourceTemplates = new ArrayList<>(); + List avoidRoads = new ArrayList<>(); + for (Object object : data) { + if (object instanceof QuickAction) { + quickActions.add((QuickAction) object); + } else if (object instanceof PoiUIFilter) { + poiUIFilters.add((PoiUIFilter) object); + } else if (object instanceof TileSourceTemplate || object instanceof SQLiteTileSource) { + tileSourceTemplates.add((ITileSource) object); + } else if (object instanceof File) { + try { + settingsItems.add(new FileSettingsItem(app, (File) object)); + } catch (IllegalArgumentException e) { + LOG.warn("Trying to export unsuported file type", e); + } + } else if (object instanceof AvoidRoadInfo) { + avoidRoads.add((AvoidRoadInfo) object); + } + } + if (!quickActions.isEmpty()) { + settingsItems.add(new QuickActionsSettingsItem(app, quickActions)); + } + if (!poiUIFilters.isEmpty()) { + settingsItems.add(new PoiUiFiltersSettingsItem(app, poiUIFilters)); + } + if (!tileSourceTemplates.isEmpty()) { + settingsItems.add(new MapSourcesSettingsItem(app, tileSourceTemplates)); + } + if (!avoidRoads.isEmpty()) { + settingsItems.add(new AvoidRoadsSettingsItem(app, avoidRoads)); + } + return settingsItems; + } + + public static Map> getSettingsToOperate(List settingsItems, boolean importComplete) { + Map> settingsToOperate = new HashMap<>(); + List profiles = new ArrayList<>(); + List quickActions = new ArrayList<>(); + List poiUIFilters = new ArrayList<>(); + List tileSourceTemplates = new ArrayList<>(); + List routingFilesList = new ArrayList<>(); + List renderFilesList = new ArrayList<>(); + List multimediaFilesList = new ArrayList<>(); + List tracksFilesList = new ArrayList<>(); + List avoidRoads = new ArrayList<>(); + for (SettingsItem item : settingsItems) { + switch (item.getType()) { + case PROFILE: + profiles.add(((ProfileSettingsItem) item).getModeBean()); + break; + case FILE: + FileSettingsItem fileItem = (FileSettingsItem) item; + if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { + renderFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { + routingFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.MULTIMEDIA_NOTES) { + multimediaFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.GPX) { + tracksFilesList.add(fileItem.getFile()); + } + break; + case QUICK_ACTIONS: + QuickActionsSettingsItem quickActionsItem = (QuickActionsSettingsItem) item; + if (importComplete) { + quickActions.addAll(quickActionsItem.getAppliedItems()); + } else { + quickActions.addAll(quickActionsItem.getItems()); + } + break; + case POI_UI_FILTERS: + PoiUiFiltersSettingsItem poiUiFilterItem = (PoiUiFiltersSettingsItem) item; + if (importComplete) { + poiUIFilters.addAll(poiUiFilterItem.getAppliedItems()); + } else { + poiUIFilters.addAll(poiUiFilterItem.getItems()); + } + break; + case MAP_SOURCES: + MapSourcesSettingsItem mapSourcesItem = (MapSourcesSettingsItem) item; + if (importComplete) { + tileSourceTemplates.addAll(mapSourcesItem.getAppliedItems()); + } else { + tileSourceTemplates.addAll(mapSourcesItem.getItems()); + } + break; + case AVOID_ROADS: + AvoidRoadsSettingsItem avoidRoadsItem = (AvoidRoadsSettingsItem) item; + if (importComplete) { + avoidRoads.addAll(avoidRoadsItem.getAppliedItems()); + } else { + avoidRoads.addAll(avoidRoadsItem.getItems()); + } + break; + default: + break; + } + } + + if (!profiles.isEmpty()) { + settingsToOperate.put(ExportSettingsType.PROFILE, profiles); + } + if (!quickActions.isEmpty()) { + settingsToOperate.put(ExportSettingsType.QUICK_ACTIONS, quickActions); + } + if (!poiUIFilters.isEmpty()) { + settingsToOperate.put(ExportSettingsType.POI_TYPES, poiUIFilters); + } + if (!tileSourceTemplates.isEmpty()) { + settingsToOperate.put(ExportSettingsType.MAP_SOURCES, tileSourceTemplates); + } + if (!renderFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.CUSTOM_RENDER_STYLE, renderFilesList); + } + if (!routingFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.CUSTOM_ROUTING, routingFilesList); + } + if (!avoidRoads.isEmpty()) { + settingsToOperate.put(ExportSettingsType.AVOID_ROADS, avoidRoads); + } + if (!multimediaFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.MULTIMEDIA_NOTES, multimediaFilesList); + } + if (!tracksFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.TRACKS, tracksFilesList); + } + return settingsToOperate; + } +} \ 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 new file mode 100644 index 0000000000..a06b8e8749 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java @@ -0,0 +1,137 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.util.Algorithms; + +import org.json.JSONException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; + +class SettingsImporter { + + private OsmandApplication app; + + SettingsImporter(@NonNull OsmandApplication app) { + this.app = app; + } + + List collectItems(@NonNull File file) throws IllegalArgumentException, IOException { + return processItems(file, null); + } + + void importItems(@NonNull File file, @NonNull List items) throws IllegalArgumentException, IOException { + processItems(file, items); + } + + private List getItemsFromJson(@NonNull File file) throws IOException { + List items = new ArrayList<>(); + ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); + InputStream ois = new BufferedInputStream(zis); + try { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + String fileName = checkEntryName(entry.getName()); + if (fileName.equals("items.json")) { + String itemsJson = null; + try { + itemsJson = Algorithms.readFromInputStream(ois, false).toString(); + } catch (IOException e) { + SettingsHelper.LOG.error("Error reading items.json: " + itemsJson, e); + throw new IllegalArgumentException("No items"); + } finally { + zis.closeEntry(); + } + try { + SettingsItemsFactory itemsFactory = new SettingsItemsFactory(app, itemsJson); + items.addAll(itemsFactory.getItems()); + } catch (IllegalArgumentException e) { + SettingsHelper.LOG.error("Error parsing items: " + itemsJson, e); + throw new IllegalArgumentException("No items"); + } catch (JSONException e) { + SettingsHelper.LOG.error("Error parsing items: " + itemsJson, e); + throw new IllegalArgumentException("No items"); + } + break; + } + } + } catch (IOException ex) { + SettingsHelper.LOG.error("Failed to read next entry", ex); + } finally { + Algorithms.closeStream(ois); + Algorithms.closeStream(zis); + } + return items; + } + + private List processItems(@NonNull File file, @Nullable List items) throws IllegalArgumentException, IOException { + boolean collecting = items == null; + if (collecting) { + items = getItemsFromJson(file); + } else { + if (items.size() == 0) { + throw new IllegalArgumentException("No items"); + } + } + ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); + InputStream ois = new BufferedInputStream(zis); + try { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + String fileName = checkEntryName(entry.getName()); + SettingsItem item = null; + for (SettingsItem settingsItem : items) { + if (settingsItem != null && settingsItem.applyFileName(fileName)) { + item = settingsItem; + break; + } + } + if (item != null && collecting && item.shouldReadOnCollecting() + || item != null && !collecting && !item.shouldReadOnCollecting()) { + try { + SettingsItemReader reader = item.getReader(); + if (reader != null) { + reader.readFromStream(ois); + } + } catch (IllegalArgumentException e) { + item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); + SettingsHelper.LOG.error("Error reading item data: " + item.getName(), e); + } catch (IOException e) { + item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); + SettingsHelper.LOG.error("Error reading item data: " + item.getName(), e); + } finally { + zis.closeEntry(); + } + } + } + } catch (IOException ex) { + SettingsHelper.LOG.error("Failed to read next entry", ex); + } finally { + Algorithms.closeStream(ois); + Algorithms.closeStream(zis); + } + return items; + } + + private String checkEntryName(String entryName) { + String fileExt = OSMAND_SETTINGS_FILE_EXT + "/"; + int index = entryName.indexOf(fileExt); + if (index != -1) { + entryName = entryName.substring(index + fileExt.length()); + } + return entryName; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java new file mode 100644 index 0000000000..caf2d57301 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java @@ -0,0 +1,236 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public abstract class SettingsItem { + + protected OsmandApplication app; + + protected String pluginId; + protected String fileName; + + boolean shouldReplace = false; + + protected List warnings; + + SettingsItem(@NonNull OsmandApplication app) { + this.app = app; + init(); + } + + SettingsItem(@NonNull OsmandApplication app, @Nullable SettingsItem baseItem) { + this.app = app; + if (baseItem != null) { + this.pluginId = baseItem.pluginId; + this.fileName = baseItem.fileName; + } + init(); + } + + SettingsItem(OsmandApplication app, @NonNull JSONObject json) throws JSONException { + this.app = app; + init(); + readFromJson(json); + } + + protected void init() { + warnings = new ArrayList<>(); + } + + public List getWarnings() { + return warnings; + } + + @NonNull + public abstract SettingsItemType getType(); + + @NonNull + public abstract String getName(); + + @NonNull + public abstract String getPublicName(@NonNull Context ctx); + + @NonNull + public String getDefaultFileName() { + return getName() + getDefaultFileExtension(); + } + + @NonNull + public String getDefaultFileExtension() { + return ".json"; + } + + public String getPluginId() { + return pluginId; + } + + @Nullable + public String getFileName() { + return fileName; + } + + public boolean applyFileName(@NonNull String fileName) { + String n = getFileName(); + return n != null && n.endsWith(fileName); + } + + public boolean shouldReadOnCollecting() { + return false; + } + + public void setShouldReplace(boolean shouldReplace) { + this.shouldReplace = shouldReplace; + } + + static SettingsItemType parseItemType(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { + String type = json.has("type") ? json.getString("type") : null; + if (type == null) { + throw new IllegalArgumentException("No type field"); + } + if (type.equals("QUICK_ACTION")) { + type = "QUICK_ACTIONS"; + } + return SettingsItemType.valueOf(type); + } + + public boolean exists() { + return false; + } + + public void apply() { + // non implemented + } + + void readFromJson(@NonNull JSONObject json) throws JSONException { + pluginId = json.has("pluginId") ? json.getString("pluginId") : null; + if (json.has("name")) { + fileName = json.getString("name") + getDefaultFileExtension(); + } + if (json.has("file")) { + fileName = json.getString("file"); + } + readItemsFromJson(json); + } + + void writeToJson(@NonNull JSONObject json) throws JSONException { + json.put("type", getType().name()); + String pluginId = getPluginId(); + if (!Algorithms.isEmpty(pluginId)) { + json.put("pluginId", pluginId); + } + if (getWriter() != null) { + String fileName = getFileName(); + if (Algorithms.isEmpty(fileName)) { + fileName = getDefaultFileName(); + } + json.put("file", fileName); + } + writeItemsToJson(json); + } + + String toJson() throws JSONException { + JSONObject json = new JSONObject(); + writeToJson(json); + return json.toString(); + } + + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + // override + } + + void writeItemsToJson(@NonNull JSONObject json) { + // override + } + + @Nullable + abstract SettingsItemReader getReader(); + + @Nullable + abstract SettingsItemWriter getWriter(); + + @NonNull + SettingsItemReader getJsonReader() { + return new SettingsItemReader(this) { + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + StringBuilder buf = new StringBuilder(); + try { + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + String str; + while ((str = in.readLine()) != null) { + buf.append(str); + } + } catch (IOException e) { + throw new IOException("Cannot read json body", e); + } + String json = buf.toString(); + if (json.length() == 0) { + throw new IllegalArgumentException("Json body is empty"); + } + try { + readItemsFromJson(new JSONObject(json)); + } catch (JSONException e) { + throw new IllegalArgumentException("Json parsing error", e); + } + } + }; + } + + @NonNull + SettingsItemWriter getJsonWriter() { + return new SettingsItemWriter(this) { + @Override + public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { + JSONObject json = new JSONObject(); + writeItemsToJson(json); + if (json.length() > 0) { + try { + String s = json.toString(2); + outputStream.write(s.getBytes("UTF-8")); + } catch (JSONException e) { + SettingsHelper.LOG.error("Failed to write json to stream", e); + } + return true; + } + return false; + } + }; + } + + @Override + public int hashCode() { + return (getType().name() + getName()).hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof SettingsItem)) { + return false; + } + + SettingsItem item = (SettingsItem) other; + return item.getType() == getType() + && item.getName().equals(getName()) + && Algorithms.stringsEqual(item.getFileName(), getFileName()); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java new file mode 100644 index 0000000000..daa66f979a --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java @@ -0,0 +1,17 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStream; + +public abstract class SettingsItemReader { + + private T item; + + public SettingsItemReader(@NonNull T item) { + this.item = item; + } + + public abstract void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException; +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java new file mode 100644 index 0000000000..1a25e24817 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java @@ -0,0 +1,16 @@ +package net.osmand.plus.settings.backend.backup; + +public enum SettingsItemType { + GLOBAL, + PROFILE, + PLUGIN, + DATA, + FILE, + RESOURCES, + QUICK_ACTIONS, + POI_UI_FILTERS, + MAP_SOURCES, + AVOID_ROADS, + SUGGESTED_DOWNLOADS, + DOWNLOADS +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java new file mode 100644 index 0000000000..9e3cf61377 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java @@ -0,0 +1,21 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +public abstract class SettingsItemWriter { + + private T item; + + public SettingsItemWriter(T item) { + this.item = item; + } + + public T getItem() { + return item; + } + + public abstract boolean writeToStream(@NonNull OutputStream outputStream) throws IOException; +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java new file mode 100644 index 0000000000..d4a639da4d --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java @@ -0,0 +1,131 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class SettingsItemsFactory { + + private OsmandApplication app; + private List items = new ArrayList<>(); + + SettingsItemsFactory(@NonNull OsmandApplication app, String jsonStr) throws IllegalArgumentException, JSONException { + this.app = app; + collectItems(new JSONObject(jsonStr)); + } + + private void collectItems(JSONObject json) throws IllegalArgumentException, JSONException { + JSONArray itemsJson = json.getJSONArray("items"); + int version = json.has("version") ? json.getInt("version") : 1; + if (version > SettingsHelper.VERSION) { + throw new IllegalArgumentException("Unsupported osf version: " + version); + } + Map> pluginItems = new HashMap<>(); + for (int i = 0; i < itemsJson.length(); i++) { + JSONObject itemJson = itemsJson.getJSONObject(i); + SettingsItem item; + try { + item = createItem(itemJson); + items.add(item); + String pluginId = item.getPluginId(); + if (pluginId != null && item.getType() != SettingsItemType.PLUGIN) { + List items = pluginItems.get(pluginId); + if (items != null) { + items.add(item); + } else { + items = new ArrayList<>(); + items.add(item); + pluginItems.put(pluginId, items); + } + } + } catch (IllegalArgumentException e) { + SettingsHelper.LOG.error("Error creating item from json: " + itemJson, e); + } + } + if (items.size() == 0) { + throw new IllegalArgumentException("No items"); + } + for (SettingsItem item : items) { + if (item instanceof PluginSettingsItem) { + PluginSettingsItem pluginSettingsItem = ((PluginSettingsItem) item); + List pluginDependentItems = pluginItems.get(pluginSettingsItem.getName()); + if (!Algorithms.isEmpty(pluginDependentItems)) { + pluginSettingsItem.getPluginDependentItems().addAll(pluginDependentItems); + } + } + } + } + + @NonNull + public List getItems() { + return items; + } + + @Nullable + public SettingsItem getItemByFileName(@NonNull String fileName) { + for (SettingsItem item : items) { + if (Algorithms.stringsEqual(item.getFileName(), fileName)) { + return item; + } + } + return null; + } + + @NonNull + private SettingsItem createItem(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { + SettingsItem item = null; + SettingsItemType type = SettingsItem.parseItemType(json); + OsmandSettings settings = app.getSettings(); + switch (type) { + case GLOBAL: + item = new GlobalSettingsItem(settings); + break; + case PROFILE: + item = new ProfileSettingsItem(app, json); + break; + case PLUGIN: + item = new PluginSettingsItem(app, json); + break; + case DATA: + item = new DataSettingsItem(app, json); + break; + case FILE: + item = new FileSettingsItem(app, json); + break; + case RESOURCES: + item = new ResourcesSettingsItem(app, json); + break; + case QUICK_ACTIONS: + item = new QuickActionsSettingsItem(app, json); + break; + case POI_UI_FILTERS: + item = new PoiUiFiltersSettingsItem(app, json); + break; + case MAP_SOURCES: + item = new MapSourcesSettingsItem(app, json); + break; + case AVOID_ROADS: + item = new AvoidRoadsSettingsItem(app, json); + break; + case SUGGESTED_DOWNLOADS: + item = new SuggestedDownloadsItem(app, json); + break; + case DOWNLOADS: + item = new DownloadsItem(app, json); + break; + } + return item; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItem.java new file mode 100644 index 0000000000..ae683f49b8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItem.java @@ -0,0 +1,76 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.InputStream; + +public abstract class StreamSettingsItem extends SettingsItem { + + @Nullable + private InputStream inputStream; + protected String name; + + public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { + super(app); + this.name = name; + this.fileName = name; + } + + StreamSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull InputStream inputStream, @NonNull String name) { + super(app); + this.inputStream = inputStream; + this.name = name; + this.fileName = name; + } + + @Nullable + public InputStream getInputStream() { + return inputStream; + } + + protected void setInputStream(@Nullable InputStream inputStream) { + this.inputStream = inputStream; + } + + @NonNull + @Override + public String getName() { + return name; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return getName(); + } + + @NonNull + @Override + public String getDefaultFileExtension() { + return ""; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + name = json.has("name") ? json.getString("name") : null; + } + + @Nullable + @Override + public SettingsItemWriter getWriter() { + return new StreamSettingsItemWriter(this); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemReader.java new file mode 100644 index 0000000000..b231f1f8ae --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemReader.java @@ -0,0 +1,10 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +public abstract class StreamSettingsItemReader extends SettingsItemReader { + + public StreamSettingsItemReader(@NonNull StreamSettingsItem item) { + super(item); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemWriter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemWriter.java new file mode 100644 index 0000000000..0029568a93 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemWriter.java @@ -0,0 +1,34 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import net.osmand.util.Algorithms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class StreamSettingsItemWriter extends SettingsItemWriter { + + public StreamSettingsItemWriter(StreamSettingsItem item) { + super(item); + } + + @Override + public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { + boolean hasData = false; + InputStream is = getItem().getInputStream(); + if (is != null) { + byte[] data = new byte[SettingsHelper.BUFFER]; + int count; + while ((count = is.read(data, 0, SettingsHelper.BUFFER)) != -1) { + outputStream.write(data, 0, count); + if (!hasData) { + hasData = true; + } + } + Algorithms.closeStream(is); + } + return hasData; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java new file mode 100644 index 0000000000..d2dd9a8554 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java @@ -0,0 +1,128 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class SuggestedDownloadsItem extends SettingsItem { + + private List items; + + SuggestedDownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + items = new ArrayList<>(); + } + + @NonNull + @Override + public SettingsItemType getType() { + return SettingsItemType.SUGGESTED_DOWNLOADS; + + } + + @NonNull + @Override + public String getName() { + return "suggested_downloads"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "suggested_downloads"; + } + + public List getItems() { + return items; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.getJSONObject(i); + String scopeId = object.optString("scope-id"); + String searchType = object.optString("search-type"); + int limit = object.optInt("limit", -1); + + List names = new ArrayList<>(); + if (object.has("names")) { + JSONArray namesArray = object.getJSONArray("names"); + for (int j = 0; j < namesArray.length(); j++) { + names.add(namesArray.getString(j)); + } + } + CustomOsmandPlugin.SuggestedDownloadItem suggestedDownload = new CustomOsmandPlugin.SuggestedDownloadItem(scopeId, searchType, names, limit); + items.add(suggestedDownload); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (CustomOsmandPlugin.SuggestedDownloadItem downloadItem : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("scope-id", downloadItem.getScopeId()); + if (downloadItem.getLimit() != -1) { + jsonObject.put("limit", downloadItem.getLimit()); + } + if (!Algorithms.isEmpty(downloadItem.getSearchType())) { + jsonObject.put("search-type", downloadItem.getSearchType()); + } + if (!Algorithms.isEmpty(downloadItem.getNames())) { + JSONArray namesArray = new JSONArray(); + for (String downloadName : downloadItem.getNames()) { + namesArray.put(downloadName); + } + jsonObject.put("names", namesArray); + } + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return null; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java index 37e993cf43..2b7494f0a4 100644 --- a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java @@ -12,13 +12,13 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; +import net.osmand.plus.osmedit.ValidateOsmLoginDetailsTask; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem; import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; -import net.osmand.plus.osmedit.SettingsOsmEditingActivity; import net.osmand.plus.settings.fragments.OnPreferenceChanged; public class OsmLoginDataBottomSheet extends BasePreferenceBottomSheet { @@ -84,7 +84,7 @@ public class OsmLoginDataBottomSheet extends BasePreferenceBottomSheet { app.getSettings().USER_NAME.set(userNameEditText.getText().toString()); app.getSettings().USER_PASSWORD.set(passwordEditText.getText().toString()); - new SettingsOsmEditingActivity.ValidateOsmLoginDetailsTask(app).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new ValidateOsmLoginDetailsTask(app).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); Fragment target = getTargetFragment(); Preference preference = getPreference(); diff --git a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java index 5ac7aea8e8..b3f160f910 100644 --- a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java @@ -121,19 +121,19 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet { currentValue = 0.0f; } selectedItem = preference.getEntryFromValue(String.valueOf(currentValue)); - adapter.setSelectedItem(selectedItem); - int itemPosition = adapter.getItemPosition(selectedItem); + adapter.setSelectedItemByTitle(selectedItem); + int itemPosition = adapter.getItemPositionByTitle(selectedItem); if (itemPosition >= 0) { recyclerView.smoothScrollToPosition(itemPosition); } } }); - adapter.setItems(Arrays.asList(preference.getEntries())); + adapter.setTitledItems(Arrays.asList(preference.getEntries())); adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedItem = item; + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + selectedItem = item.getTitle(); currentValue = preference.getValueFromEntries(selectedItem); String currentValueStr = currentValue == 0.0f ? "" : df.format(currentValue + 0.01f); @@ -145,7 +145,7 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet { } }); recyclerView.setAdapter(adapter); - adapter.setSelectedItem(selectedItem); + adapter.setSelectedItemByTitle(selectedItem); return new BaseBottomSheetItem.Builder() .setCustomView(mainView) .create(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java index 86940e7d7a..db12e7d781 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java @@ -53,11 +53,7 @@ import com.google.android.material.snackbar.Snackbar; import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; import net.osmand.access.AccessibilitySettingsFragment; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.settings.backend.OsmandPreference; -import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; @@ -69,6 +65,10 @@ import net.osmand.plus.monitoring.MonitoringSettingsFragment; import net.osmand.plus.osmedit.OsmEditingFragment; import net.osmand.plus.profiles.SelectAppModesBottomSheetDialogFragment; import net.osmand.plus.profiles.SelectAppModesBottomSheetDialogFragment.AppModeChangedListener; +import net.osmand.plus.settings.backend.ApplicationMode; +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.settings.bottomsheets.BooleanPreferenceBottomSheet; import net.osmand.plus.settings.bottomsheets.ChangeGeneralProfilesPrefBottomSheet; import net.osmand.plus.settings.bottomsheets.EditTextPreferenceBottomSheet; @@ -112,7 +112,7 @@ public abstract class BaseSettingsFragment extends PreferenceFragmentCompat impl public enum SettingsScreenType { - MAIN_SETTINGS(MainSettingsFragment.TAG, false, null, R.xml.settings_main_screen, R.layout.global_preference_toolbar), + MAIN_SETTINGS(MainSettingsFragment.class.getName(), false, null, R.xml.settings_main_screen, R.layout.global_preference_toolbar), GLOBAL_SETTINGS(GlobalSettingsFragment.class.getName(), false, null, R.xml.global_settings, R.layout.global_preference_toolbar), CONFIGURE_PROFILE(ConfigureProfileFragment.class.getName(), true, null, R.xml.configure_profile, R.layout.profile_preference_toolbar_with_switch), PROXY_SETTINGS(ProxySettingsFragment.class.getName(), false, null, R.xml.proxy_preferences, R.layout.global_preferences_toolbar_with_switch), @@ -899,9 +899,13 @@ public abstract class BaseSettingsFragment extends PreferenceFragmentCompat impl } public static boolean showInstance(FragmentActivity activity, SettingsScreenType screenType, @Nullable ApplicationMode appMode) { + return showInstance(activity, screenType, null, new Bundle()); + } + + public static boolean showInstance(FragmentActivity activity, SettingsScreenType screenType, + @Nullable ApplicationMode appMode, @NonNull Bundle args) { try { Fragment fragment = Fragment.instantiate(activity, screenType.fragmentName); - Bundle args = new Bundle(); if (appMode != null) { args.putString(APP_MODE_KEY, appMode.getStringKey()); } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java index 330f1c0a18..beb1711f31 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java @@ -1,7 +1,6 @@ package net.osmand.plus.settings.fragments; import android.app.Activity; -import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -22,13 +21,13 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.ContextMenuItem; import net.osmand.plus.OsmandApplication; @@ -36,14 +35,14 @@ import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivityActions; -import net.osmand.plus.activities.PluginsActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.base.BaseOsmAndFragment; import net.osmand.plus.dialogs.ConfigureMapMenu; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.mapcontextmenu.MapContextMenu; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.widgets.style.CustomTypefaceSpan; - import org.apache.commons.logging.Log; import java.util.ArrayList; @@ -229,8 +228,7 @@ public class ConfigureMenuRootFragment extends BaseOsmAndFragment { if (holder instanceof DescriptionHolder) { DescriptionHolder descriptionHolder = (DescriptionHolder) holder; String plugins = getString(R.string.prefs_plugins); - setupClickableText( - descriptionHolder.description, (String) currentItem, plugins, new Intent(app, PluginsActivity.class)); + setupClickableText(descriptionHolder.description, (String) currentItem, plugins); descriptionHolder.image.setVisibility(View.GONE); } else { final ScreenType item = (ScreenType) currentItem; @@ -253,12 +251,15 @@ public class ConfigureMenuRootFragment extends BaseOsmAndFragment { return items.size(); } - private void setupClickableText(TextView textView, String text, String clickableText, final Intent intent) { + private void setupClickableText(TextView textView, String text, String clickableText) { SpannableString spannableString = new SpannableString(text); ClickableSpan clickableSpan = new ClickableSpan() { @Override public void onClick(@NonNull View view) { - startActivity(intent); + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginsFragment.showInstance(activity.getSupportFragmentManager()); + } } }; try { diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java index e65a351041..08aa65fd50 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java @@ -35,8 +35,8 @@ import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsCollectListener; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsCollectListener; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; @@ -44,6 +44,7 @@ import net.osmand.plus.helpers.FontCache; import net.osmand.plus.openseamapsplugin.NauticalMapsPlugin; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet.CopyAppModePrefsListener; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet.ResetAppModePrefsListener; import net.osmand.plus.skimapsplugin.SkiMapsPlugin; @@ -186,9 +187,9 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co private void restoreCustomModeFromFile(final File file) { app.getSettingsHelper().collectSettings(file, "", 1, new SettingsCollectListener() { @Override - public void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items) { + public void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items) { if (succeed) { - for (SettingsHelper.SettingsItem item : items) { + for (SettingsItem item : items) { item.setShouldReplace(true); } importBackupSettingsItems(file, items); @@ -197,10 +198,10 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co }); } - private void importBackupSettingsItems(File file, List items) { + private void importBackupSettingsItems(File file, List items) { app.getSettingsHelper().importSettings(file, items, "", 1, new SettingsHelper.SettingsImportListener() { @Override - public void onSettingsImportFinished(boolean succeed, @NonNull List items) { + public void onSettingsImportFinished(boolean succeed, @NonNull List items) { app.showToastMessage(R.string.profile_prefs_reset_successful); updateCopiedOrResetPrefs(); } @@ -368,7 +369,7 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co } List plugins = OsmandPlugin.getVisiblePlugins(); for (OsmandPlugin plugin : plugins) { - if (plugin instanceof SkiMapsPlugin || plugin instanceof NauticalMapsPlugin || plugin.getSettingsFragment() == null) { + if (plugin instanceof SkiMapsPlugin || plugin instanceof NauticalMapsPlugin || plugin.getSettingsScreenType() == null) { continue; } Preference preference = new Preference(ctx); @@ -378,7 +379,7 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co preference.setSummary(plugin.getPrefsDescription()); preference.setIcon(getContentIcon(plugin.getLogoResourceId())); preference.setLayoutResource(R.layout.preference_with_descr); - preference.setFragment(plugin.getSettingsFragment().getName()); + preference.setFragment(plugin.getSettingsScreenType().fragmentName); preferenceCategory.addPreference(preference); } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java index dc78f5755c..8160e81e6e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java @@ -13,6 +13,8 @@ import net.osmand.AndroidUtils; import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.map.ITileSource; +import net.osmand.plus.audionotes.AudioVideoNotesPlugin; +import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; import net.osmand.plus.OsmandApplication; @@ -131,6 +133,15 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter tileSources = new ArrayList<>(); List renderFilesList = new ArrayList<>(); List routingFilesList = new ArrayList<>(); + List multimediaFilesList = new ArrayList<>(); + List trackFilesList = new ArrayList<>(); List avoidRoads = new ArrayList<>(); for (Object object : duplicatesList) { @@ -184,10 +190,14 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View tileSources.add((ITileSource) object); } else if (object instanceof File) { File file = (File) object; - if (file.getAbsolutePath().contains("files/rendering")) { + if (file.getAbsolutePath().contains(RENDERERS_DIR)) { renderFilesList.add(file); - } else if (file.getAbsolutePath().contains("files/routing")) { + } else if (file.getAbsolutePath().contains(ROUTING_PROFILES_DIR)) { routingFilesList.add(file); + } else if (file.getAbsolutePath().contains(AV_INDEX_DIR)) { + multimediaFilesList.add(file); + } else if (file.getAbsolutePath().contains(GPX_INDEX_DIR)) { + trackFilesList.add(file); } } else if (object instanceof AvoidRoadInfo) { avoidRoads.add((AvoidRoadInfo) object); @@ -217,6 +227,14 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View duplicates.add(getString(R.string.shared_string_rendering_style)); duplicates.addAll(renderFilesList); } + if (!multimediaFilesList.isEmpty()) { + duplicates.add(getString(R.string.audionotes_plugin_name)); + duplicates.addAll(multimediaFilesList); + } + if (!trackFilesList.isEmpty()) { + duplicates.add(getString(R.string.shared_string_tracks)); + duplicates.addAll(trackFilesList); + } if (!avoidRoads.isEmpty()) { duplicates.add(getString(R.string.avoid_road)); duplicates.addAll(avoidRoads); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java index dd02f3f716..6002c8a58e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java @@ -42,17 +42,17 @@ import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.quickaction.QuickAction; import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; import net.osmand.plus.settings.backend.ExportSettingsType; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.AvoidRoadsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.FileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ImportAsyncTask; -import net.osmand.plus.settings.backend.SettingsHelper.ImportType; -import net.osmand.plus.settings.backend.SettingsHelper.MapSourcesSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.PoiUiFiltersSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ProfileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.QuickActionsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItemType; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.AvoidRoadsSettingsItem; +import net.osmand.plus.settings.backend.backup.FileSettingsItem; +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.MapSourcesSettingsItem; +import net.osmand.plus.settings.backend.backup.PoiUiFiltersSettingsItem; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.QuickActionsSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsItemType; import net.osmand.plus.widgets.TextViewEx; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java index e663e6f189..57198c2f04 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java @@ -106,6 +106,14 @@ public class ImportedSettingsItemsAdapter extends holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_alert, activeColorRes)); holder.title.setText(R.string.avoid_road); break; + case MULTIMEDIA_NOTES: + holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_photo_dark, activeColorRes)); + holder.title.setText(R.string.audionotes_plugin_name); + break; + case TRACKS: + holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_route_distance, activeColorRes)); + holder.title.setText(R.string.shared_string_tracks); + break; } } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java index c3381520a7..51a6f43531 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java @@ -18,8 +18,8 @@ import net.osmand.CallbackWithObject; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItemType; +import net.osmand.plus.settings.backend.backup.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsItemType; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java index 6fcf8a17bf..1bdbc38b19 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java @@ -42,7 +42,8 @@ import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.UiUtilities; import net.osmand.plus.UiUtilities.DialogButtonType; import net.osmand.plus.profiles.LocationIcon; @@ -828,7 +829,7 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment { tempDir.mkdirs(); } app.getSettingsHelper().exportSettings(tempDir, mode.getStringKey(), - getSettingsExportListener(), true, new SettingsHelper.ProfileSettingsItem(app, mode)); + getSettingsExportListener(), true, new ProfileSettingsItem(app, mode)); } } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java index a17c265645..0e3698644d 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java @@ -162,6 +162,7 @@ public class RouteParametersFragment extends BaseSettingsFragment implements OnP useOsmLiveForPublicTransport.setDescription(getString(R.string.use_osm_live_public_transport_description)); useOsmLiveForPublicTransport.setSummaryOn(R.string.shared_string_enabled); useOsmLiveForPublicTransport.setSummaryOff(R.string.shared_string_disabled); + useOsmLiveForPublicTransport.setIcon(getContentIcon(R.drawable.ic_action_osm_live)); useOsmLiveForPublicTransport.setIconSpaceReserved(true); getPreferenceScreen().addPreference(useOsmLiveForPublicTransport); } @@ -184,13 +185,14 @@ public class RouteParametersFragment extends BaseSettingsFragment implements OnP useOsmLiveForRouting.setDescription(getString(R.string.use_osm_live_routing_description)); useOsmLiveForRouting.setSummaryOn(R.string.shared_string_enabled); useOsmLiveForRouting.setSummaryOff(R.string.shared_string_disabled); + useOsmLiveForRouting.setIcon(getContentIcon(R.drawable.ic_action_osm_live)); useOsmLiveForRouting.setIconSpaceReserved(true); getPreferenceScreen().addPreference(useOsmLiveForRouting); } private void setupDisableComplexRoutingPref() { SwitchPreferenceEx disableComplexRouting = createSwitchPreferenceEx(settings.DISABLE_COMPLEX_ROUTING.getId(), - R.string.use_complex_routing, R.layout.preference_with_descr_dialog_and_switch); + R.string.use_two_phase_routing, R.layout.preference_with_descr_dialog_and_switch); disableComplexRouting.setDescription(getString(R.string.complex_routing_descr)); disableComplexRouting.setSummaryOn(R.string.shared_string_enabled); disableComplexRouting.setSummaryOff(R.string.shared_string_disabled); @@ -204,6 +206,7 @@ public class RouteParametersFragment extends BaseSettingsFragment implements OnP useFastRecalculation.setDescription(getString(R.string.use_fast_recalculation_desc)); useFastRecalculation.setSummaryOn(R.string.shared_string_enabled); useFastRecalculation.setSummaryOff(R.string.shared_string_disabled); + useFastRecalculation.setIcon(getContentIcon(R.drawable.ic_action_route_part)); useFastRecalculation.setIconSpaceReserved(true); getPreferenceScreen().addPreference(useFastRecalculation); }