diff --git a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java index bee9fa2b19..ede3b6f8cf 100644 --- a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java +++ b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java @@ -1822,6 +1822,25 @@ public class GPXUtilities { } serializer.endTag(null, "metadata"); + for (WptPt l : file.points) { + serializer.startTag(null, "wpt"); //$NON-NLS-1$ + writeWpt(format, serializer, l); + serializer.endTag(null, "wpt"); //$NON-NLS-1$ + } + + for (Route track : file.routes) { + serializer.startTag(null, "rte"); //$NON-NLS-1$ + writeNotNullText(serializer, "name", track.name); + writeNotNullText(serializer, "desc", track.desc); + + for (WptPt p : track.points) { + serializer.startTag(null, "rtept"); //$NON-NLS-1$ + writeWpt(format, serializer, p); + serializer.endTag(null, "rtept"); //$NON-NLS-1$ + } + writeExtensions(serializer, track); + serializer.endTag(null, "rte"); //$NON-NLS-1$ + } for (Track track : file.tracks) { if (!track.generalTrack) { @@ -1844,26 +1863,6 @@ public class GPXUtilities { } } - for (Route track : file.routes) { - serializer.startTag(null, "rte"); //$NON-NLS-1$ - writeNotNullText(serializer, "name", track.name); - writeNotNullText(serializer, "desc", track.desc); - - for (WptPt p : track.points) { - serializer.startTag(null, "rtept"); //$NON-NLS-1$ - writeWpt(format, serializer, p); - serializer.endTag(null, "rtept"); //$NON-NLS-1$ - } - writeExtensions(serializer, track); - serializer.endTag(null, "rte"); //$NON-NLS-1$ - } - - for (WptPt l : file.points) { - serializer.startTag(null, "wpt"); //$NON-NLS-1$ - writeWpt(format, serializer, l); - serializer.endTag(null, "wpt"); //$NON-NLS-1$ - } - writeExtensions(serializer, file); serializer.endTag(null, "gpx"); //$NON-NLS-1$ diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java b/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java index 2f607aef3a..d3c151c12a 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java @@ -20,7 +20,7 @@ public abstract class StringBundleWriter { public void writeBundle() { for (Entry> entry : bundle.getMap().entrySet()) { - writeItem(entry.getKey(), entry.getValue()); + writeItem("osmand:" + entry.getKey(), entry.getValue()); } } } diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java index 3ef6a54652..14d0abab79 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java @@ -41,6 +41,7 @@ public class Amenity extends MapObject { public static final String OSM_DELETE_TAG = "osmand_change"; public static final String IMAGE_TITLE = "image_title"; public static final String IS_PART = "is_part"; + public static final String IS_PARENT_OF = "is_parent_of"; public static final String IS_AGGR_PART = "is_aggr_part"; public static final String CONTENT_JSON = "content_json"; public static final String ROUTE_ID = "route_id"; diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Building.java b/OsmAnd-java/src/main/java/net/osmand/data/Building.java index 0adacd411a..5e16f755fc 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/Building.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/Building.java @@ -182,10 +182,10 @@ public class Building extends MapObject { @Override public String toString() { - if(interpolationInterval !=0){ - return name+"-"+name2 +" (+"+interpolationInterval+") "; - } else if(interpolationType != null) { - return name+"-"+name2 +" ("+interpolationType+") "; + if (interpolationInterval != 0) { + return name + "-" + name2 + " (+" + interpolationInterval + ") "; + } else if (interpolationType != null) { + return name + "-" + name2 + " (" + interpolationType + ") "; } return name; } diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java index 4df69c3adc..44c48f69ec 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java @@ -1189,6 +1189,7 @@ public class RouteResultPreparation { RouteSegmentResult last = rr; RouteSegmentResult firstRoundabout = rr; RouteSegmentResult lastRoundabout = rr; + for (int j = i; j < result.size(); j++) { RouteSegmentResult rnext = result.get(j); last = rnext; @@ -1215,12 +1216,12 @@ public class RouteResultPreparation { TurnType t = TurnType.getExitTurn(exit, 0, leftSide); // usually covers more than expected float turnAngleBasedOnOutRoads = (float) MapUtils.degreesDiff(last.getBearingBegin(), prev.getBearingEnd()); - // usually covers less than expected float turnAngleBasedOnCircle = (float) -MapUtils.degreesDiff(firstRoundabout.getBearingBegin(), lastRoundabout.getBearingEnd() + 180); - if(Math.abs(turnAngleBasedOnOutRoads - turnAngleBasedOnCircle) > 180) { - t.setTurnAngle(turnAngleBasedOnCircle ) ; + if (Math.abs(turnAngleBasedOnOutRoads) > 120) { + // correctly identify if angle is +- 180, so we approach from left or right side + t.setTurnAngle(turnAngleBasedOnCircle) ; } else { - t.setTurnAngle((turnAngleBasedOnCircle + turnAngleBasedOnOutRoads) / 2) ; + t.setTurnAngle(turnAngleBasedOnOutRoads) ; } return t; } diff --git a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java index df8c2bd5be..0e542891d8 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java @@ -954,17 +954,15 @@ public class SearchUICore { break; } case COMPARE_AMENITY_TYPE_ADDITIONAL: { - if(o1.object instanceof AbstractPoiType && o2.object instanceof AbstractPoiType ) { - boolean additional1 = ((AbstractPoiType) o1.object).isAdditional(); - boolean additional2 = ((AbstractPoiType) o2.object).isAdditional(); - if (additional1 != additional2) { - // -1 - means 1st is less than 2nd - return additional1 ? 1 : -1; - } + boolean additional1 = o1.object instanceof AbstractPoiType && ((AbstractPoiType) o1.object).isAdditional(); + boolean additional2 = o2.object instanceof AbstractPoiType && ((AbstractPoiType) o2.object).isAdditional(); + if (additional1 != additional2) { + // -1 - means 1st is less than 2nd + return additional1 ? 1 : -1; } break; } - case COMPARE_DISTANCE_TO_PARENT_SEARCH_RESULT: + case COMPARE_DISTANCE_TO_PARENT_SEARCH_RESULT: double ps1 = o1.parentSearchResult == null ? 0 : o1.parentSearchResult.getSearchDistance(c.loc); double ps2 = o2.parentSearchResult == null ? 0 : o2.parentSearchResult.getSearchDistance(c.loc); if (ps1 != ps2) { @@ -1004,17 +1002,12 @@ public class SearchUICore { String subType2 = a2.getSubType() == null ? "" : a2.getSubType(); int cmp = 0; - - if (FILTER_DUPLICATE_POI_SUBTYPE.contains(subType1)) { - cmp = 1; - } else if (FILTER_DUPLICATE_POI_SUBTYPE.contains(subType2)) { - cmp = -1; - } - - if (cmp != 0) { - return cmp; + boolean subtypeFilter1 = FILTER_DUPLICATE_POI_SUBTYPE.contains(subType1); + boolean subtypeFilter2 = FILTER_DUPLICATE_POI_SUBTYPE.contains(subType2); + if (subtypeFilter1 != subtypeFilter2) { + // to filter second + return subtypeFilter1 ? 1 : -1; } - cmp = c.collator.compare(type1, type2); if (cmp != 0) { return cmp; @@ -1047,12 +1040,17 @@ public class SearchUICore { @Override public int compare(SearchResult o1, SearchResult o2) { - for(ResultCompareStep step : ResultCompareStep.values()) { + List steps = new ArrayList<>(); + for (ResultCompareStep step : ResultCompareStep.values()) { int r = step.compare(o1, o2, this); - if(r != 0) { + steps.add(step); + if (r != 0) { + // debug crashes and identify non-transitive comparision + // LOG.debug(String.format("%d: %s o1='%s' o2='%s'", r, steps, o1, o2)); return r; } } + // LOG.debug(String.format("EQUAL: o1='%s' o2='%s'", o1, o2)); return 0; } diff --git a/OsmAnd/AndroidManifest-free.xml b/OsmAnd/AndroidManifest-gplayFree.xml similarity index 100% rename from OsmAnd/AndroidManifest-free.xml rename to OsmAnd/AndroidManifest-gplayFree.xml diff --git a/OsmAnd/AndroidManifest-freehuawei.xml b/OsmAnd/AndroidManifest-huawei.xml similarity index 100% rename from OsmAnd/AndroidManifest-freehuawei.xml rename to OsmAnd/AndroidManifest-huawei.xml diff --git a/OsmAnd/AndroidManifest-freedev.xml b/OsmAnd/AndroidManifest-nightlyFree.xml similarity index 93% rename from OsmAnd/AndroidManifest-freedev.xml rename to OsmAnd/AndroidManifest-nightlyFree.xml index 09ecf8a880..6ec6d5a12e 100644 --- a/OsmAnd/AndroidManifest-freedev.xml +++ b/OsmAnd/AndroidManifest-nightlyFree.xml @@ -10,7 +10,7 @@ android:value="fb792288460976727"/> [^<]*<", ">" + "" + "<") - } - return line; -} - task updateNoTranslate(type: Copy) { from('.') { include 'no_translate.xml' @@ -130,7 +127,7 @@ task validateTranslate { line.contains("1\$ ") || line.contains("2\$ ") || line.contains("3\$ ") || line.contains("%1s") || line.contains(" 1\$s") || (line.contains("% \$") || line.contains("% 1") || line.contains("% 2") || - line.contains("% 3") || line.contains("% s"))) { + line.contains("% 3") || line.contains("% s"))) { throw new GradleException("Incorrect translation " + it.getAbsolutePath() + " " + line); } } @@ -151,7 +148,7 @@ task collectVoiceAssets(type: Sync) { } task cleanNoTranslate(type: Delete) { - delete('res/values/no_translate.xml') + delete('res/values/no_translate.xml') } task collectFonts(type: Copy) { @@ -180,7 +177,7 @@ task collectHelpContentsAssets(type: Copy) { include "*.html" } from("../../help/website/blog_articles") { - include "osmand-3-8-released.html" + include "osmand-3-9-released.html" } into "assets/feature_articles" } @@ -277,8 +274,6 @@ task collectExternalResources { // Legacy core build import org.apache.tools.ant.taskdefs.condition.Os -import java.util.regex.Pattern - task buildOsmAndCore(type: Exec) { Gradle gradle = getGradle() String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() @@ -297,7 +292,7 @@ task buildOsmAndCore(type: Exec) { flavour = flavour.length() == 0 ? "X86_ONLY" : "" } } - + description "Build Legacy OsmAndCore" if (!Os.isFamily(Os.FAMILY_WINDOWS)) { @@ -325,38 +320,11 @@ task cleanupDuplicatesInCore() { } } -afterEvaluate { - android.libraryVariants.all { variant -> - variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) - } -} - task appStart(type: Exec) { - // linux + // linux commandLine 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' // windows - // commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' -} - -project.afterEvaluate { - publishing { - repositories { - ivy { - url = System.getenv("OSMAND_BINARIES_IVY_ROOT") ?: "./" - version = "0.1-SNAPSHOT" - } - } - publications { - aar(IvyPublication) { - artifact bundleLegacyFatDebugAar { - classifier 'debug' - } - artifact bundleLegacyFatReleaseAar { - classifier 'release' - } - } - } - } + // commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' } dependencies { @@ -369,7 +337,7 @@ dependencies { implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.preference:preference:1.1.0' - implementation fileTree(include: ['gnu-trove-osmand.jar', 'icu4j-49_1_patched.jar'], dir: 'libs') + implementation fileTree(include: ['gnu-trove-osmand.jar', 'icu4j-49_1_patched.jar'], dir: 'libs') implementation group: 'commons-logging', name: 'commons-logging', version: '1.2' implementation 'commons-codec:commons-codec:1.11' @@ -387,28 +355,21 @@ dependencies { // JS core implementation group: 'org.mozilla', name: 'rhino', version: '1.7.9' // size restrictions -// implementation 'com.ibm.icu:icu4j:50.1' +// implementation 'com.ibm.icu:icu4j:50.1' // implementation 'net.sf.trove4j:trove4j:3.0.3' -// qtcoreImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') -// qtcoredebugImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') - legacyImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@jar" -// qtcoredebugImplementation "net.osmand:OsmAndCore_androidNativeDebug:0.1-SNAPSHOT@aar" -// qtcoredebugImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" -// qtcoreImplementation "net.osmand:OsmAndCore_androidNativeRelease:0.1-SNAPSHOT@aar" -// qtcoreImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" - implementation ("com.getkeepsafe.taptargetview:taptargetview:1.12.0"){ + + implementation("com.getkeepsafe.taptargetview:taptargetview:1.12.0") { exclude group: 'com.android.support' } implementation 'com.github.PhilJay:MPAndroidChart:v3.0.1' - implementation ("com.github.HITGIF:TextFieldBoxes:1.4.5"){ + implementation("com.github.HITGIF:TextFieldBoxes:1.4.5") { exclude group: 'com.android.support' } - implementation('com.github.scribejava:scribejava-apis:7.1.1'){ + implementation('com.github.scribejava:scribejava-apis:7.1.1') { exclude group: "com.fasterxml.jackson.core" } implementation 'com.jaredrummler:colorpicker:1.1.0' - implementation 'org.bouncycastle:bcpkix-jdk15on:1.56' -} +} \ No newline at end of file diff --git a/OsmAnd/build-library.gradle b/OsmAnd/build-library.gradle new file mode 100644 index 0000000000..b8cf0a21a5 --- /dev/null +++ b/OsmAnd/build-library.gradle @@ -0,0 +1,64 @@ +apply plugin: 'com.android.library' +apply plugin: 'ivy-publish' +apply from: 'build-common.gradle' + +android { + + defaultConfig { + minSdkVersion 15 + } + + lintOptions { + tasks.lint.enabled = false + } + + sourceSets { + main { + manifest.srcFile "AndroidManifest-library.xml" + java.srcDirs = ["src", "src-gms", "src-google"] + } + } + + productFlavors { + // CoreVersion + // Build that doesn't include 3D OpenGL + legacy { + dimension "coreversion" + } + } +} + +def replaceNoTranslate(line) { + return line; +} + +afterEvaluate { + android.libraryVariants.all { variant -> + variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) + } +} + +project.afterEvaluate { + publishing { + repositories { + ivy { + url = System.getenv("OSMAND_BINARIES_IVY_ROOT") ?: "./" + version = "0.1-SNAPSHOT" + } + } + publications { + aar(IvyPublication) { + artifact bundleLegacyFatDebugAar { + classifier 'debug' + } + artifact bundleLegacyFatReleaseAar { + classifier 'release' + } + } + } + } +} + +dependencies { + implementation 'com.google.android.gms:play-services-location:17.1.0' +} diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index a3f80f4c46..436aa652dc 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.application' +apply from: 'build-common.gradle' // Global Parameters accepted // TARGET_APP_NAME - app name // APK_NUMBER_VERSION - version number of apk // APK_VERSION_SUFFIX - build number like #99999Z, appended (for dev builds) to Manifest's versionName as X.X.X#99999Z -// Z means flavor: M=-master, D=-main-default, B=-Blackberry, Des=-design, MQA=-main-qt-arm, MQDA=-main-qt-default-arm, S=-sherpafy +// Z means flavor: M=-master, D=-main-default, Des=-design, MQA=-main-qt-arm, MQDA=-main-qt-default-arm, S=-sherpafy // APP_EDITION - date stamp of builds -// APP_FEATURES - features +play_market +gps_status -parking_plugin -blackberry -free_version -amazon // 1. To be done Filter fonts // @@ -17,15 +17,7 @@ apply plugin: 'com.android.application' // // Less important -task printc { - configurations.each { if(it.isCanBeResolved()) println it.name } -} - android { - compileSdkVersion 29 - buildToolsVersion "29.0.3" - // compileNdkVersion "android-ndk-r17b" - signingConfigs { development { storeFile file("../keystores/debug.keystore") @@ -44,21 +36,15 @@ android { defaultConfig { minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 - targetSdkVersion 29 versionCode 390 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode - multiDexEnabled true versionName "3.9.0" versionName System.getenv("APK_VERSION")? System.getenv("APK_VERSION").toString(): versionName versionName System.getenv("APK_VERSION_SUFFIX")? versionName + System.getenv("APK_VERSION_SUFFIX").toString(): versionName - // Stops the Gradle plugin’s automatic rasterization of vectors - // vectorDrawables.generatedDensities = ['hdpi'] - vectorDrawables.useSupportLibrary = true } lintOptions { lintConfig file("lint.xml") - abortOnError false warningsAsErrors false } @@ -79,48 +65,33 @@ android { // exclude '/META-INF/NOTICE.md' //} - // This is from OsmAndCore_android.aar - for some reason it's not inherited - aaptOptions { - // Don't compress any embedded resources - noCompress "qz" - cruncherEnabled = false - // Flag notifies aapt to keep the attribute IDs around - // additionalParameters "--no-version-vectors" - } - - dexOptions { - javaMaxHeapSize "4g" - } - sourceSets { - main { - manifest.srcFile "AndroidManifest.xml" - jni.srcDirs = [] - jniLibs.srcDirs = ["libs"] - aidl.srcDirs = ["src"] - java.srcDirs = ["src"] - resources.srcDirs = ["src"] - renderscript.srcDirs = ["src"] - res.srcDirs = ["res"] - assets.srcDirs = ["assets"] - } debug { manifest.srcFile "AndroidManifest-debug.xml" } - full { - java.srcDirs = ["src-google"] + androidFull { + java.srcDirs = ["src-nogms", "src-google"] } - free { - java.srcDirs = ["src-google"] - manifest.srcFile "AndroidManifest-free.xml" + gplayFull { + java.srcDirs = ["src-gms", "src-google"] } - freedev { - java.srcDirs = ["src-google"] - manifest.srcFile "AndroidManifest-freedev.xml" + gplayFree { + java.srcDirs = ["src-gms", "src-google"] + manifest.srcFile "AndroidManifest-gplayFree.xml" } - freehuawei { - java.srcDirs = ["src-huawei"] - manifest.srcFile "AndroidManifest-freehuawei.xml" + nightlyFree { + java.srcDirs = ["src-nogms", "src-google"] + manifest.srcFile "AndroidManifest-nightlyFree.xml" + } + amazonFree { + java.srcDirs = ["src-nogms", "src-google"] + } + amazonFull { + java.srcDirs = ["src-nogms", "src-google"] + } + huawei { + java.srcDirs = ["src-nogms", "src-google"] + manifest.srcFile "AndroidManifest-huawei.xml" } legacy { @@ -130,67 +101,43 @@ android { flavorDimensions "version", "coreversion", "abi" productFlavors { - // ABI - armv7 { - dimension "abi" - ndk { - abiFilter 'armeabi-v7a' - } - } - arm64 { - dimension "abi" - ndk { - abiFilter 'arm64-v8a' - } - } - x86 { - dimension "abi" - ndk { - abiFilters 'x86', 'x86_64' - } - } - armonly { - dimension "abi" - ndk { - abiFilters 'arm64-v8a', 'armeabi-v7a' - } - } - fat { - dimension "abi" - ndk { - abiFilters 'arm64-v8a', 'x86', 'x86_64', 'armeabi-v7a' - } - } - // Version - freedev { + nightlyFree { dimension "version" applicationId "net.osmand.dev" // resConfig "en" } - free { - dimension "version" - applicationId "net.osmand" - } - full { + androidFull { dimension "version" applicationId "net.osmand.plus" } - freehuawei { + gplayFree { + dimension "version" + applicationId "net.osmand" + } + gplayFull { + dimension "version" + applicationId "net.osmand.plus" + } + amazonFree { + dimension "version" + applicationId "net.osmand" + } + amazonFull { + dimension "version" + applicationId "net.osmand.plus" + } + huawei { dimension "version" applicationId "net.osmand.huawei" } - // CoreVersion - // Build that doesn't include 3D OpenGL - legacy { - dimension "coreversion" - } + // Build that includes 3D OpenGL release - qtcore { + opengl { dimension "coreversion" } // Build that includes 3D OpenGL debug - qtcoredebug { + opengldebug { dimension "coreversion" } } @@ -216,226 +163,9 @@ def replaceNoTranslate(line) { if (line.contains("\"app_edition\"") && System.getenv("APP_EDITION")) { return line.replaceAll(">[^<]*<", ">" + System.getenv("APP_EDITION") + "<") } - if (line.contains("\"versionFeatures\"") && System.getenv("APP_FEATURES")) { - return line.replaceAll(">[^<]*<", ">" + System.getenv("APP_FEATURES") + "<") - } return line; } -task updateNoTranslate(type: Copy) { - from('.') { - include 'no_translate.xml' - filter { - line -> replaceNoTranslate(line); - } - } - into 'res/values/' -} - -task validateTranslate { - println "Validating translations" - - file("res").eachFileRecurse groovy.io.FileType.FILES, { - if (it.name == "strings.xml" || it.name == "phrases.xml") { - it.eachLine { line -> - if (line.contains("\$ s") || line.contains("\$ d") || line.contains("\$ f") || - line.contains(" \$s") || line.contains(" \$d") || line.contains(" \$f") || - line.contains("1\$ ") || line.contains("2\$ ") || line.contains("3\$ ") || - line.contains("%1s") || line.contains(" 1\$s") || - (line.contains("% \$") || line.contains("% 1") || line.contains("% 2") || - line.contains("% 3") || line.contains("% s"))) { - throw new GradleException("Incorrect translation " + it.getAbsolutePath() + " " + line); - } - } - } - } -} - -task downloadWorldMiniBasemap { - doLast { - ant.get(src: 'http://builder.osmand.net/basemap/World_basemap_mini_2.obf', dest: 'assets/World_basemap_mini.obf', skipexisting: 'true') - } -} - -task collectVoiceAssets(type: Sync) { - from "../../resources/voice" - into "assets/voice" - include "**/*.js" -} - -task cleanNoTranslate(type: Delete) { - delete('res/values/no_translate.xml') -} - -task collectFonts(type: Copy) { - from "../../resources/fonts" - from "../../resources/rendering_styles/fonts" -// from "../../resources/rendering_styles/fonts/OpenSans" - into "assets/fonts" - include "*.ttf" -} - -task collectHelpContentsStyle(type: Copy) { - from("../../help/website/help/") { - include "style.css" - } - into "assets" -} - -task collectHelpContentsAssets(type: Copy) { - from("../../help/website/help") { - include "about.html" - include "changes.html" - include "faq.html" - include "map-legend.html" - } - from("../../help/website/feature_articles") { - include "*.html" - } - from("../../help/website/blog_articles") { - include "osmand-3-9-released.html" - } - into "assets/feature_articles" -} - -task copyPoiCategories(type: Copy) { - from("../../resources/poi") { - include "poi_categories.json" - } - into "assets" -} - -task copyMapShaderIcons(type: Sync) { - // from "../../resources/rendering_styles/style-icons/map-shaders-png" - // into "res/" - from "../../resources/rendering_styles/style-icons/map-shaders-vector" - into "res/drawable" - include "**/*.png", "**/*.xml" - preserve { - include '**/*' - exclude "**/h_*" - } -} - -task copyMapPOIIcons(type: Sync) { - from "../../resources/rendering_styles/style-icons/map-icons-vector" - into "res/drawable/" - // from "../../resources/rendering_styles/style-icons/map-icons-png" - // into "res/" - - include "**/*.png", "**/*.xml" - preserve { - include '**/*' - exclude "**/mm_*" - } -} - -task copyLargePOIIcons(type: Sync) { - from "../../resources/rendering_styles/style-icons/poi-icons-vector" - into "res/drawable/" - include "**/*.png", "**/*.xml" - preserve { - include '**/*' - exclude "**/mx_*" - } -} - -task copyWidgetIconsXhdpi(type: Sync) { - from "res/drawable-xxhdpi/" - into "res/drawable-large-xhdpi/" - include "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" - preserve { - include '*' - exclude "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" - } -} - -task copyWidgetIconsHdpi(type: Sync) { - from "res/drawable-xhdpi/" - into "res/drawable-large-hdpi/" - include "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" - preserve { - include '*' - exclude "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" - } -} - -task copyWidgetIcons(type: Sync) { - from "res/drawable-hdpi/" - into "res/drawable-large/" - include "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" - preserve { - include '*' - exclude "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" - } -} - -task collectExternalResources { - dependsOn collectVoiceAssets, - collectFonts, - collectHelpContentsAssets, - collectHelpContentsStyle, - copyMapShaderIcons, - copyMapPOIIcons, - copyLargePOIIcons, - updateNoTranslate, - validateTranslate, - copyWidgetIcons, - copyWidgetIconsHdpi, - copyWidgetIconsXhdpi, - copyPoiCategories, - downloadWorldMiniBasemap -} - -// Legacy core build -import org.apache.tools.ant.taskdefs.condition.Os - -task buildOsmAndCore(type: Exec) { - Gradle gradle = getGradle() - String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() - String flavour = ""; - if(!tskReqStr.contains("fat")) { - if(tskReqStr.contains("arm64")) { - flavour = flavour.length() == 0 ? "ARM64_ONLY" : "" - } - if(tskReqStr.contains("armv7")) { - flavour = flavour.length() == 0 ? "ARMV7_ONLY" : "" - } - if(tskReqStr.contains("armonly")) { - flavour = flavour.length() == 0 ? "ARM_ONLY" : "" - } - if(tskReqStr.contains("x86")) { - flavour = flavour.length() == 0 ? "X86_ONLY" : "" - } - } - - description "Build Legacy OsmAndCore" - - if (!Os.isFamily(Os.FAMILY_WINDOWS)) { - if(flavour.length() > 0) { - environment "$flavour", "1" - } - commandLine "bash", file("./old-ndk-build.sh").getAbsolutePath() - } else { - commandLine "cmd", "/c", "echo", "Not supported" - } -} - -task cleanupDuplicatesInCore() { - dependsOn buildOsmAndCore - // doesn't work for legacy debug builds - doLast { - file("libc++/armeabi-v7a").mkdirs() - file("libs/armeabi-v7a/libc++_shared.so").renameTo(file("libc++/armeabi-v7a/libc++_shared.so")) - file("libc++/arm64-v8a").mkdirs() - file("libs/arm64-v8a/libc++_shared.so").renameTo(file("libc++/arm64-v8a/libc++_shared.so")) - file("libc++/x86").mkdirs() - file("libs/x86/libc++_shared.so").renameTo(file("libc++/x86/libc++_shared.so")) - file("libc++/x86_64").mkdirs() - file("libs/x86_64/libc++_shared.so").renameTo(file("libc++/x86_64/libc++_shared.so")) - } -} - afterEvaluate { android.applicationVariants.all { variant -> variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) @@ -447,52 +177,14 @@ afterEvaluate { } } -task appStart(type: Exec) { - // linux - commandLine 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' - // windows - // commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' -} - dependencies { - implementation project(path: ':OsmAnd-java', configuration: 'android') - implementation project(':OsmAnd-api') - implementation 'androidx.multidex:multidex:2.0.1' - implementation 'androidx.gridlayout:gridlayout:1.0.0' - implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.2.1' - implementation 'androidx.browser:browser:1.0.0' - implementation 'androidx.preference:preference:1.1.0' - implementation fileTree(include: ['gnu-trove-osmand.jar', 'icu4j-49_1_patched.jar'], dir: 'libs') + openglImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') + opengldebugImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') - implementation group: 'commons-logging', name: 'commons-logging', version: '1.2' - implementation 'commons-codec:commons-codec:1.11' - implementation 'it.unibo.alice.tuprolog:tuprolog:3.2.1' - implementation 'org.apache.commons:commons-compress:1.17' - implementation 'com.moparisthebest:junidecode:0.1.1' - implementation 'org.immutables:gson:2.5.0' - implementation 'com.vividsolutions:jts-core:1.14.0' - implementation 'com.google.openlocationcode:openlocationcode:1.0.4' - implementation 'com.android.billingclient:billing:2.0.3' - // turn off for now - //implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0' - implementation 'com.squareup.picasso:picasso:2.71828' - implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2' - // JS core - implementation group: 'org.mozilla', name: 'rhino', version: '1.7.9' -// size restrictions -// implementation 'com.ibm.icu:icu4j:50.1' -// implementation 'net.sf.trove4j:trove4j:3.0.3' - - qtcoreImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') - qtcoredebugImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') - - legacyImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@jar" - qtcoredebugImplementation "net.osmand:OsmAndCore_androidNativeDebug:0.1-SNAPSHOT@aar" - qtcoredebugImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" - qtcoreImplementation "net.osmand:OsmAndCore_androidNativeRelease:0.1-SNAPSHOT@aar" - qtcoreImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" + opengldebugImplementation "net.osmand:OsmAndCore_androidNativeDebug:0.1-SNAPSHOT@aar" + opengldebugImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" + openglImplementation "net.osmand:OsmAndCore_androidNativeRelease:0.1-SNAPSHOT@aar" + openglImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" implementation ("com.getkeepsafe.taptargetview:taptargetview:1.12.0"){ exclude group: 'com.android.support' } @@ -504,8 +196,10 @@ dependencies { exclude group: "com.fasterxml.jackson.core" } implementation 'com.jaredrummler:colorpicker:1.1.0' - - freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' - implementation "org.bouncycastle:bcpkix-jdk15on:1.56" + + huaweiImplementation 'com.huawei.hms:iap:5.0.2.300' + + gplayFreeImplementation 'com.google.android.gms:play-services-location:17.1.0' + gplayFullImplementation 'com.google.android.gms:play-services-location:17.1.0' } diff --git a/OsmAnd/no_translate.xml b/OsmAnd/no_translate.xml index 20d7a424ca..d8e4108d4a 100644 --- a/OsmAnd/no_translate.xml +++ b/OsmAnd/no_translate.xml @@ -12,7 +12,6 @@ UA-28342846-2 10 true - +play_market -amazon -blackberry © OpenStreetMap support@osmand.net diff --git a/OsmAnd/res/drawable/ic_action_arrow_down_16.xml b/OsmAnd/res/drawable/ic_action_arrow_down_16.xml new file mode 100644 index 0000000000..ce235325ff --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_arrow_down_16.xml @@ -0,0 +1,9 @@ + + + diff --git a/OsmAnd/res/drawable/ic_action_arrow_up_16.xml b/OsmAnd/res/drawable/ic_action_arrow_up_16.xml new file mode 100644 index 0000000000..b0e16b0095 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_arrow_up_16.xml @@ -0,0 +1,9 @@ + + + diff --git a/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml b/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml index 2ff6a1bf58..95091e1086 100644 --- a/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml +++ b/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml @@ -27,6 +27,8 @@ android:layout_weight="1" android:layout_marginLeft="@dimen/content_padding" android:layout_marginStart="@dimen/content_padding" + android:paddingTop="@dimen/content_padding_small" + android:paddingBottom="@dimen/content_padding_small" android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/online_routing_preference_segment.xml b/OsmAnd/res/layout/online_routing_preference_segment.xml index 5f095e126c..437ba875e5 100644 --- a/OsmAnd/res/layout/online_routing_preference_segment.xml +++ b/OsmAnd/res/layout/online_routing_preference_segment.xml @@ -129,6 +129,19 @@ tools:visibility="visible" android:visibility="gone" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/values-ar/phrases.xml b/OsmAnd/res/values-ar/phrases.xml index 398583adbf..86450cf7d9 100644 --- a/OsmAnd/res/values-ar/phrases.xml +++ b/OsmAnd/res/values-ar/phrases.xml @@ -3669,4 +3669,10 @@ محطة نقل نفايات ميزان المركبات مكتب الحارس + خدمات المواطن + تأشيرات الهجرة + تأشيرات لغير المهاجرين + اتصال + قنصلية + سفارة \ No newline at end of file diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index bd3397afc2..8929bd6f4d 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -2826,7 +2826,7 @@ أسلوب الملاحة مع التباين العالي والحد الأعلى من التفاصيل. يتضمن كل خيارات النمط الافتراضي أوسماند، مع عرض أكبر قدر ممكن من التفاصيل ، ولا سيما الطرق والمسارات وطرق السفر الأخرى. التمييز الواضح بين \"جولة الأطلس\" بين أنواع الطرق. مناسبة للاستخدام النهاري والليلي وفي الهواء الطلق. أسلوب الغرض العام. تقديم نظافة مبسطة في المدن المكتظة بالسكان. الملامح الرئيسية: خطوط الكنتور ، والطرق ، وجودة السطح ، والقيود المفروضة على الوصول ، ودروع الطريق ، والمسارات التي تظهر وفقاً لمقياس SAC ، وميزات رياضة الماء الأبيض. قم بتنزيل أدلة السفر هذه من ويكي الرحلات لعرض مقالات حول الأماكن في العالم بدون إنترنت. - دليل السفر حاليا على أساس Wikivoyage. اختبار كافة الميزات أثناء اختبار بيتا المفتوحة مجانا.بعد ذلك، وأدلة السفر ستكون متاحة للمشتركين في أوسماند غير المحدود و اصحاب +أوسماند. + دليل السفر حاليا على أساس Wikivoyage. اختبار كافة الميزات أثناء اختبار بيتا المفتوحة مجانا. ملف GPX مع الإحداثيات والبيانات من الملاحظات المحددة. ملف GPX مع الإحداثيات والبيانات من كافة الملاحظات. + The name is already exists + Server error: %1$s + MTB + Racing bike + Scooter + Truck + Small truck + HGV + Regular cycling + Road cycling + Mountain cycling + Electric cycling + Walking + Hiking + Wheelchair + Show track on map + Start recording Announcement time Announcement time of different voice prompts depends on prompt type, current navigation speed and default navigation speed. Time and distance intervals @@ -35,7 +52,6 @@ Online routing engines Online routing engine Copy address - Error, recheck parameters Car Bike Foot @@ -1172,7 +1188,7 @@ Update available Download file The free worldwide travel guide anyone can edit. - Travel guides are currently based on Wikivoyage. Test all features during open beta testing for free. Afterwards, travel guides will be available to subscribers of OsmAnd Unlimited and owners of OsmAnd+. + Travel guides are currently based on Wikivoyage. Test all features during open beta testing for free. You can and should edit any article on Wikivoyage. Share knowledge, experience, talent, and your attention. Start editing Get unlimited access diff --git a/OsmAnd/res/values/styles.xml b/OsmAnd/res/values/styles.xml index 5945485c19..a7cbb98fda 100644 --- a/OsmAnd/res/values/styles.xml +++ b/OsmAnd/res/values/styles.xml @@ -54,7 +54,7 @@ @drawable/first_splash_screen_free - diff --git a/OsmAnd/src-gms/net/osmand/plus/LocationServiceHelperImpl.java b/OsmAnd/src-gms/net/osmand/plus/LocationServiceHelperImpl.java new file mode 100644 index 0000000000..41c74012a3 --- /dev/null +++ b/OsmAnd/src-gms/net/osmand/plus/LocationServiceHelperImpl.java @@ -0,0 +1,159 @@ +package net.osmand.plus; + +import android.location.Location; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationAvailability; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationResult; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; + +import net.osmand.PlatformUtil; +import net.osmand.plus.helpers.DayNightHelper; +import net.osmand.plus.helpers.LocationServiceHelper; + +import org.apache.commons.logging.Log; + +import java.util.Collections; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class LocationServiceHelperImpl extends LocationServiceHelper { + + private static final Log LOG = PlatformUtil.getLog(DayNightHelper.class); + + private final OsmandApplication app; + + // FusedLocationProviderClient - Main class for receiving location updates. + private final FusedLocationProviderClient fusedLocationProviderClient; + + // LocationRequest - Requirements for the location updates, i.e., how often you should receive + // updates, the priority, etc. + private final LocationRequest fusedLocationRequest; + + // LocationCallback - Called when FusedLocationProviderClient has a new Location. + private final com.google.android.gms.location.LocationCallback fusedLocationCallback; + + private LocationCallback locationCallback; + + public LocationServiceHelperImpl(@NonNull OsmandApplication app) { + this.app = app; + + fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(app); + + fusedLocationRequest = new LocationRequest() + // Sets the desired interval for active location updates. This interval is inexact. You + // may not receive updates at all if no location sources are available, or you may + // receive them less frequently than requested. You may also receive updates more + // frequently than requested if other applications are requesting location at a more + // frequent interval. + // + // IMPORTANT NOTE: Apps running on Android 8.0 and higher devices (regardless of + // targetSdkVersion) may receive updates less frequently than this interval when the app + // is no longer in the foreground. + .setInterval(100) + + // Sets the fastest rate for active location updates. This interval is exact, and your + // application will never receive updates more frequently than this value. + .setFastestInterval(50) + + // Sets the maximum time when batched location updates are delivered. Updates may be + // delivered sooner than this interval. + .setMaxWaitTime(200) + + .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + + fusedLocationCallback = new com.google.android.gms.location.LocationCallback() { + @Override + public void onLocationResult(LocationResult locationResult) { + LocationCallback locationCallback = LocationServiceHelperImpl.this.locationCallback; + if (locationCallback != null) { + Location location = locationResult != null ? locationResult.getLastLocation() : null; + net.osmand.Location l = convertLocation(location); + locationCallback.onLocationResult(l == null + ? Collections.emptyList() : Collections.singletonList(l)); + } + + } + + @Override + public void onLocationAvailability(LocationAvailability locationAvailability) { + LocationCallback locationCallback = LocationServiceHelperImpl.this.locationCallback; + if (locationAvailability != null && locationCallback != null) { + locationCallback.onLocationAvailability(locationAvailability.isLocationAvailable()); + } + } + }; + } + + @Override + public void requestLocationUpdates(@NonNull LocationCallback locationCallback) { + this.locationCallback = locationCallback; + // request location updates + try { + fusedLocationProviderClient.requestLocationUpdates( + fusedLocationRequest, fusedLocationCallback, Looper.myLooper()); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted"); + throw e; + } catch (IllegalArgumentException e) { + LOG.debug("GPS location provider not available"); + throw e; + } + } + + @Override + public boolean isNetworkLocationUpdatesSupported() { + return false; + } + + @Override + public void requestNetworkLocationUpdates(@NonNull LocationCallback locationCallback) { + } + + @Override + public void removeLocationUpdates() { + // remove location updates + try { + fusedLocationProviderClient.removeLocationUpdates(fusedLocationCallback); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted", e); + throw e; + } + } + + @Nullable + public net.osmand.Location getFirstTimeRunDefaultLocation(@Nullable final LocationCallback locationCallback) { + if (locationCallback == null) { + return null; + } + try { + Task lastLocation = fusedLocationProviderClient.getLastLocation(); + lastLocation.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Location loc) { + locationCallback.onLocationResult(loc != null + ? Collections.singletonList(convertLocation(loc)) : Collections.emptyList() ); + } + }); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted"); + } catch (IllegalArgumentException e) { + LOG.debug("GPS location provider not available"); + } + return null; + } + + @Nullable + private net.osmand.Location convertLocation(@Nullable Location location) { + return location == null ? null : OsmAndLocationProvider.convertLocation(location, app); + } +} diff --git a/OsmAnd/src-nogms/net/osmand/plus/LocationServiceHelperImpl.java b/OsmAnd/src-nogms/net/osmand/plus/LocationServiceHelperImpl.java new file mode 100644 index 0000000000..9a2caa6a5f --- /dev/null +++ b/OsmAnd/src-nogms/net/osmand/plus/LocationServiceHelperImpl.java @@ -0,0 +1,188 @@ +package net.osmand.plus; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.PlatformUtil; +import net.osmand.plus.helpers.DayNightHelper; +import net.osmand.plus.helpers.LocationServiceHelper; + +import org.apache.commons.logging.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import static android.content.Context.LOCATION_SERVICE; + +public class LocationServiceHelperImpl extends LocationServiceHelper implements LocationListener { + + private static final Log LOG = PlatformUtil.getLog(DayNightHelper.class); + + private final OsmandApplication app; + + private LocationCallback locationCallback; + private LocationCallback networkLocationCallback; + private final LinkedList networkListeners = new LinkedList<>(); + + // Working with location checkListeners + private class NetworkListener implements LocationListener { + + @Override + public void onLocationChanged(Location location) { + LocationCallback locationCallback = LocationServiceHelperImpl.this.networkLocationCallback; + if (locationCallback != null) { + net.osmand.Location l = convertLocation(location); + locationCallback.onLocationResult(l == null + ? Collections.emptyList() : Collections.singletonList(l)); + } + } + + @Override + public void onProviderDisabled(String provider) { + } + + @Override + public void onProviderEnabled(String provider) { + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + } + + public LocationServiceHelperImpl(@NonNull OsmandApplication app) { + this.app = app; + } + + @Override + public void requestLocationUpdates(@NonNull LocationCallback locationCallback) { + this.locationCallback = locationCallback; + // request location updates + LocationManager locationManager = (LocationManager) app.getSystemService(LOCATION_SERVICE); + try { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted"); + throw e; + } catch (IllegalArgumentException e) { + LOG.debug("GPS location provider not available"); + throw e; + } + } + + @Override + public boolean isNetworkLocationUpdatesSupported() { + return true; + } + + @Override + public void requestNetworkLocationUpdates(@NonNull LocationCallback locationCallback) { + this.networkLocationCallback = locationCallback; + // request location updates + LocationManager locationManager = (LocationManager) app.getSystemService(LOCATION_SERVICE); + List providers = locationManager.getProviders(true); + for (String provider : providers) { + if (provider == null || provider.equals(LocationManager.GPS_PROVIDER)) { + continue; + } + try { + NetworkListener networkListener = new NetworkListener(); + locationManager.requestLocationUpdates(provider, 0, 0, networkListener); + networkListeners.add(networkListener); + } catch (SecurityException e) { + LOG.debug(provider + " location service permission not granted"); + } catch (IllegalArgumentException e) { + LOG.debug(provider + " location provider not available"); + } + } + } + + @Override + public void removeLocationUpdates() { + // remove location updates + LocationManager locationManager = (LocationManager) app.getSystemService(LOCATION_SERVICE); + try { + locationManager.removeUpdates(this); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted", e); + throw e; + } finally { + while (!networkListeners.isEmpty()) { + LocationListener listener = networkListeners.poll(); + if (listener != null) { + locationManager.removeUpdates(listener); + } + } + } + } + + @Nullable + public net.osmand.Location getFirstTimeRunDefaultLocation(@Nullable LocationCallback locationCallback) { + LocationManager locationManager = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); + List providers = new ArrayList<>(locationManager.getProviders(true)); + // note, passive provider is from API_LEVEL 8 but it is a constant, we can check for it. + // constant should not be changed in future + int passiveFirst = providers.indexOf(LocationManager.PASSIVE_PROVIDER); + // put passive provider to first place + if (passiveFirst > -1) { + providers.add(0, providers.remove(passiveFirst)); + } + // find location + for (String provider : providers) { + try { + net.osmand.Location location = convertLocation(locationManager.getLastKnownLocation(provider)); + if (location != null) { + return location; + } + } catch (SecurityException e) { + // location service permission not granted + } catch (IllegalArgumentException e) { + // location provider not available + } + } + return null; + } + + @Nullable + private net.osmand.Location convertLocation(@Nullable Location location) { + return location == null ? null : OsmAndLocationProvider.convertLocation(location, app); + } + + @Override + public void onLocationChanged(Location location) { + LocationCallback locationCallback = this.locationCallback; + if (locationCallback != null) { + net.osmand.Location l = convertLocation(location); + locationCallback.onLocationResult(l == null + ? Collections.emptyList() : Collections.singletonList(l)); + } + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + @Override + public void onProviderEnabled(String provider) { + LocationCallback locationCallback = this.locationCallback; + if (locationCallback != null) { + locationCallback.onLocationAvailability(true); + } + } + + @Override + public void onProviderDisabled(String provider) { + LocationCallback locationCallback = this.locationCallback; + if (locationCallback != null) { + locationCallback.onLocationAvailability(false); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/NavigationService.java b/OsmAnd/src/net/osmand/plus/NavigationService.java index 3a824f937e..16f1afddcc 100644 --- a/OsmAnd/src/net/osmand/plus/NavigationService.java +++ b/OsmAnd/src/net/osmand/plus/NavigationService.java @@ -4,20 +4,24 @@ import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.location.Location; -import android.location.LocationListener; import android.location.LocationManager; import android.os.Binder; -import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.widget.Toast; +import androidx.annotation.NonNull; + +import net.osmand.Location; import net.osmand.PlatformUtil; +import net.osmand.plus.helpers.LocationServiceHelper; +import net.osmand.plus.helpers.LocationServiceHelper.LocationCallback; import net.osmand.plus.notifications.OsmandNotification; import net.osmand.plus.settings.backend.OsmandSettings; -public class NavigationService extends Service implements LocationListener { +import java.util.List; + +public class NavigationService extends Service { public static class NavigationServiceBinder extends Binder { } @@ -29,11 +33,11 @@ public class NavigationService extends Service implements LocationListener { private final NavigationServiceBinder binder = new NavigationServiceBinder(); - private String serviceOffProvider; private OsmandSettings settings; protected int usedBy = 0; private OsmAndLocationProvider locationProvider; + private LocationServiceHelper locationServiceHelper; @Override public IBinder onBind(Intent intent) { @@ -72,21 +76,37 @@ public class NavigationService extends Service implements LocationListener { settings = app.getSettings(); usedBy = intent.getIntExtra(USAGE_INTENT, 0); - // use only gps provider - serviceOffProvider = LocationManager.GPS_PROVIDER; locationProvider = app.getLocationProvider(); + locationServiceHelper = app.createLocationServiceHelper(); app.setNavigationService(this); // request location updates - LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); try { - locationManager.requestLocationUpdates(serviceOffProvider, 0, 0, NavigationService.this); + locationServiceHelper.requestLocationUpdates(new LocationCallback() { + @Override + public void onLocationResult(@NonNull List locations) { + if (!locations.isEmpty()) { + Location location = locations.get(locations.size() - 1); + if (!settings.MAP_ACTIVITY_ENABLED.get()) { + locationProvider.setLocationFromService(location); + } + } + } + + @Override + public void onLocationAvailability(boolean locationAvailable) { + if (!locationAvailable) { + OsmandApplication app = (OsmandApplication) getApplication(); + if (app != null) { + app.showToastMessage(getString(R.string.off_router_service_no_gps_available)); + } + } + } + }); } catch (SecurityException e) { Toast.makeText(this, R.string.no_location_permission, Toast.LENGTH_LONG).show(); - Log.d(PlatformUtil.TAG, "Location service permission not granted"); //$NON-NLS-1$ } catch (IllegalArgumentException e) { Toast.makeText(this, R.string.gps_not_available, Toast.LENGTH_LONG).show(); - Log.d(PlatformUtil.TAG, "GPS location provider not available"); //$NON-NLS-1$ } // registering icon at top level @@ -117,11 +137,10 @@ public class NavigationService extends Service implements LocationListener { app.setNavigationService(null); usedBy = 0; // remove updates - LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); try { - locationManager.removeUpdates(this); + locationServiceHelper.removeLocationUpdates(); } catch (SecurityException e) { - Log.d(PlatformUtil.TAG, "Location service permission not granted"); //$NON-NLS-1$ + // Location service permission not granted } // remove notification stopForeground(Boolean.TRUE); @@ -134,29 +153,6 @@ public class NavigationService extends Service implements LocationListener { }, 500); } - @Override - public void onLocationChanged(Location l) { - if (l != null && !settings.MAP_ACTIVITY_ENABLED.get()) { - net.osmand.Location location = OsmAndLocationProvider.convertLocation(l, (OsmandApplication) getApplication()); - locationProvider.setLocationFromService(location); - } - } - - @Override - public void onProviderDisabled(String provider) { - Toast.makeText(this, getString(R.string.off_router_service_no_gps_available), Toast.LENGTH_LONG).show(); - } - - - @Override - public void onProviderEnabled(String provider) { - } - - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - @Override public void onTaskRemoved(Intent rootIntent) { OsmandApplication app = ((OsmandApplication) getApplication()); diff --git a/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java b/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java index 9f043a78c3..35f63b96bd 100644 --- a/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java +++ b/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java @@ -1,6 +1,7 @@ package net.osmand.plus; import android.Manifest; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; @@ -16,7 +17,6 @@ import android.location.GpsSatellite; import android.location.GpsStatus; import android.location.GpsStatus.Listener; import android.location.Location; -import android.location.LocationListener; import android.location.LocationManager; import android.os.Build; import android.os.Build.VERSION; @@ -25,6 +25,7 @@ import android.os.Bundle; import android.provider.Settings; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; @@ -38,8 +39,9 @@ import net.osmand.binary.RouteDataObject; import net.osmand.data.LatLon; import net.osmand.data.QuadPoint; import net.osmand.plus.TargetPointsHelper.TargetPoint; -import net.osmand.plus.routing.RoutingHelper; +import net.osmand.plus.helpers.LocationServiceHelper; import net.osmand.plus.routing.RouteSegmentSearchResult; +import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.router.RouteSegmentResult; @@ -47,8 +49,6 @@ import net.osmand.util.MapUtils; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -75,8 +75,6 @@ public class OsmAndLocationProvider implements SensorEventListener { private static final float ACCURACY_FOR_GPX_AND_ROUTING = 50; - private static final int GPS_TIMEOUT_REQUEST = 0; - private static final int GPS_DIST_REQUEST = 0; private static final int NOT_SWITCH_TO_NETWORK_WHEN_GPS_LOST_MS = 12000; private static final long LOCATION_TIMEOUT_TO_BE_STALE = 1000 * 60 * 2; // 2 minutes @@ -85,9 +83,8 @@ public class OsmAndLocationProvider implements SensorEventListener { private static final long AGPS_TO_REDOWNLOAD = 16 * 60 * 60 * 1000; // 16 hours private static final int REQUESTS_BEFORE_CHECK_LOCATION = 100; - private AtomicInteger locationRequestsCounter = new AtomicInteger(); - private AtomicInteger staleLocationRequestsCounter = new AtomicInteger(); - + private final AtomicInteger locationRequestsCounter = new AtomicInteger(); + private final AtomicInteger staleLocationRequestsCounter = new AtomicInteger(); private long lastTimeGPSLocationFixed = 0; @@ -122,23 +119,23 @@ public class OsmAndLocationProvider implements SensorEventListener { // Current screen orientation private int currentScreenOrientation; - private OsmandApplication app; + private final OsmandApplication app; - private NavigationInfo navigationInfo; - private CurrentPositionHelper currentPositionHelper; - private OsmAndLocationSimulation locationSimulation; + private final NavigationInfo navigationInfo; + private final CurrentPositionHelper currentPositionHelper; + private final OsmAndLocationSimulation locationSimulation; + private final LocationServiceHelper locationServiceHelper; private net.osmand.Location location = null; private GPSInfo gpsInfo = new GPSInfo(); - private List locationListeners = new ArrayList(); - private List compassListeners = new ArrayList(); + private List locationListeners = new ArrayList<>(); + private List compassListeners = new ArrayList<>(); private Object gpsStatusListener; private float[] mRotationM = new float[9]; - - public class SimulationProvider { + public static class SimulationProvider { private int currentRoad; private int currentSegment; private QuadPoint currentPoint; @@ -228,6 +225,7 @@ public class OsmAndLocationProvider implements SensorEventListener { navigationInfo = new NavigationInfo(app); currentPositionHelper = new CurrentPositionHelper(app); locationSimulation = new OsmAndLocationSimulation(app, this); + locationServiceHelper = app.createLocationServiceHelper(); addLocationListener(navigationInfo); addCompassListener(navigationInfo); } @@ -236,7 +234,7 @@ public class OsmAndLocationProvider implements SensorEventListener { final LocationManager service = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); if (app.getSettings().isInternetConnectionAvailable()) { if (System.currentTimeMillis() - app.getSettings().AGPS_DATA_LAST_TIME_DOWNLOADED.get() > AGPS_TO_REDOWNLOAD) { - //force an updated check for internet connectivity here before destroying A-GPS-data + // force an updated check for internet connectivity here before destroying A-GPS-data if (app.getSettings().isInternetConnectionAvailable(true)) { redownloadAGPS(); } @@ -245,27 +243,34 @@ public class OsmAndLocationProvider implements SensorEventListener { if (isLocationPermissionAvailable(app)) { registerGpsStatusListener(service); try { - service.requestLocationUpdates(LocationManager.GPS_PROVIDER, GPS_TIMEOUT_REQUEST, GPS_DIST_REQUEST, gpsListener); + locationServiceHelper.requestLocationUpdates(new LocationServiceHelper.LocationCallback() { + @Override + public void onLocationResult(@NonNull List locations) { + net.osmand.Location location = null; + if (!locations.isEmpty()) { + location = locations.get(locations.size() - 1); + lastTimeGPSLocationFixed = System.currentTimeMillis(); + } + if (!locationSimulation.isRouteAnimating()) { + setLocation(location); + } + } + }); + } catch (SecurityException e) { + // Location service permission not granted } catch (IllegalArgumentException e) { - Log.d(PlatformUtil.TAG, "GPS location provider not available"); //$NON-NLS-1$ + // GPS location provider not available } // try to always ask for network provide : it is faster way to find location - - List providers = service.getProviders(true); - if (providers == null) { - return; - } - for (String provider : providers) { - if (provider == null || provider.equals(LocationManager.GPS_PROVIDER)) { - continue; - } - try { - NetworkListener networkListener = new NetworkListener(); - service.requestLocationUpdates(provider, GPS_TIMEOUT_REQUEST, GPS_DIST_REQUEST, networkListener); - networkListeners.add(networkListener); - } catch (IllegalArgumentException e) { - Log.d(PlatformUtil.TAG, provider + " location provider not available"); //$NON-NLS-1$ - } + if (locationServiceHelper.isNetworkLocationUpdatesSupported()) { + locationServiceHelper.requestNetworkLocationUpdates(new LocationServiceHelper.LocationCallback() { + @Override + public void onLocationResult(@NonNull List locations) { + if (!locations.isEmpty() && !useOnlyGPS() && !locationSimulation.isRouteAnimating()) { + setLocation(locations.get(locations.size() - 1)); + } + } + }); } } } @@ -285,8 +290,9 @@ public class OsmAndLocationProvider implements SensorEventListener { } } - private void registerGpsStatusListener(final LocationManager service) { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + @SuppressLint("MissingPermission") + private void registerGpsStatusListener(@NonNull final LocationManager service) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { gpsStatusListener = new GnssStatus.Callback() { @Override @@ -321,7 +327,7 @@ public class OsmAndLocationProvider implements SensorEventListener { updateLocation(location); } }; - service.registerGnssStatusCallback((GnssStatus.Callback) gpsStatusListener); + service.registerGnssStatusCallback((GnssStatus.Callback) gpsStatusListener, null); } else { gpsStatusListener = new Listener() { private GpsStatus gpsStatus; @@ -336,14 +342,12 @@ public class OsmAndLocationProvider implements SensorEventListener { } } - private void updateGPSInfo(GpsStatus s) { + private void updateGPSInfo(@Nullable GpsStatus s) { boolean fixed = false; int n = 0; int u = 0; if (s != null) { - Iterator iterator = s.getSatellites().iterator(); - while (iterator.hasNext()) { - GpsSatellite g = iterator.next(); + for (GpsSatellite g : s.getSatellites()) { n++; if (g.usedInFix()) { u++; @@ -355,7 +359,8 @@ public class OsmAndLocationProvider implements SensorEventListener { gpsInfo.foundSatellites = n; gpsInfo.usedSatellites = u; } - + + @NonNull public GPSInfo getGPSInfo(){ return gpsInfo; } @@ -364,51 +369,35 @@ public class OsmAndLocationProvider implements SensorEventListener { currentScreenOrientation = orientation; } - public void addLocationListener(OsmAndLocationListener listener){ - if(!locationListeners.contains(listener)) { + public void addLocationListener(@NonNull OsmAndLocationListener listener) { + if (!locationListeners.contains(listener)) { locationListeners.add(listener); } } - public void removeLocationListener(OsmAndLocationListener listener){ + public void removeLocationListener(@NonNull OsmAndLocationListener listener) { locationListeners.remove(listener); } - public void addCompassListener(OsmAndCompassListener listener){ - if(!compassListeners.contains(listener)) { + public void addCompassListener(@NonNull OsmAndCompassListener listener) { + if (!compassListeners.contains(listener)) { compassListeners.add(listener); } } - public void removeCompassListener(OsmAndCompassListener listener){ + public void removeCompassListener(@NonNull OsmAndCompassListener listener) { compassListeners.remove(listener); } - public net.osmand.Location getFirstTimeRunDefaultLocation() { - if (!isLocationPermissionAvailable(app)) { - return null; - } - LocationManager service = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); - List ps = service.getProviders(true); - if(ps == null) { - return null; - } - List providers = new ArrayList(ps); - // note, passive provider is from API_LEVEL 8 but it is a constant, we can check for it. - // constant should not be changed in future - int passiveFirst = providers.indexOf("passive"); // LocationManager.PASSIVE_PROVIDER - // put passive provider to first place - if (passiveFirst > -1) { - providers.add(0, providers.remove(passiveFirst)); - } - // find location - for (String provider : providers) { - net.osmand.Location location = convertLocation(service.getLastKnownLocation(provider), app); - if (location != null) { - return location; + @Nullable + public net.osmand.Location getFirstTimeRunDefaultLocation(@Nullable final OsmAndLocationListener locationListener) { + return isLocationPermissionAvailable(app) + ? locationServiceHelper.getFirstTimeRunDefaultLocation(locationListener != null ? new LocationServiceHelper.LocationCallback() { + @Override + public void onLocationResult(@NonNull List locations) { + locationListener.updateLocation(locations.isEmpty() ? null : locations.get(0)); } - } - return null; + } : null) : null; } public synchronized void registerOrUnregisterCompassListener(boolean register) { @@ -467,10 +456,7 @@ public class OsmAndLocationProvider implements SensorEventListener { } private boolean isRunningOnEmulator() { - if (Build.DEVICE.equals("generic")) { //$NON-NLS-1$ - return true; - } - return false; + return Build.DEVICE.equals("generic"); } @Override @@ -600,78 +586,25 @@ public class OsmAndLocationProvider implements SensorEventListener { return MapUtils.unifyRotationTo360((float) (Math.atan2(sinA, cosA) * 180 / Math.PI)); } - private void updateLocation(net.osmand.Location loc) { for (OsmAndLocationListener l : locationListeners) { l.updateLocation(loc); } } - - - private LocationListener gpsListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - if (location != null) { - // lastTimeGPSLocationFixed = location.getTime(); - lastTimeGPSLocationFixed = System.currentTimeMillis(); - } - if(!locationSimulation.isRouteAnimating()) { - setLocation(convertLocation(location, app)); - } - } - - @Override - public void onProviderDisabled(String provider) { - } - - @Override - public void onProviderEnabled(String provider) { - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; - private LinkedList networkListeners = new LinkedList(); - private boolean useOnlyGPS() { - if(app.getRoutingHelper().isFollowingMode()) { + if (app.getRoutingHelper().isFollowingMode()) { return true; } - if((System.currentTimeMillis() - lastTimeGPSLocationFixed) < NOT_SWITCH_TO_NETWORK_WHEN_GPS_LOST_MS) { + if ((System.currentTimeMillis() - lastTimeGPSLocationFixed) < NOT_SWITCH_TO_NETWORK_WHEN_GPS_LOST_MS) { return true; } - if(isRunningOnEmulator()) { + if (isRunningOnEmulator()) { return true; } return false; } - // Working with location checkListeners - private class NetworkListener implements LocationListener { - - @Override - public void onLocationChanged(Location location) { - if (!useOnlyGPS() && !locationSimulation.isRouteAnimating()) { - setLocation(convertLocation(location, app)); - } - } - - @Override - public void onProviderDisabled(String provider) { - } - - @Override - public void onProviderEnabled(String provider) { - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - - }; - private void stopLocationRequests() { LocationManager service = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); if (gpsStatusListener != null) { @@ -681,9 +614,10 @@ public class OsmAndLocationProvider implements SensorEventListener { service.removeGpsStatusListener((Listener) gpsStatusListener); } } - service.removeUpdates(gpsListener); - while (!networkListeners.isEmpty()) { - service.removeUpdates(networkListeners.poll()); + try { + locationServiceHelper.removeLocationUpdates(); + } catch (SecurityException e) { + // Location service permission not granted } } @@ -979,7 +913,7 @@ public class OsmAndLocationProvider implements SensorEventListener { } public static boolean isNotSimulatedLocation(net.osmand.Location l) { - if(l != null) { + if (l != null) { return !SIMULATED_PROVIDER.equals(l.getProvider()); } return true; @@ -1000,7 +934,7 @@ public class OsmAndLocationProvider implements SensorEventListener { networkenabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); } catch(Exception ex) {} - if(!gpsenabled && !networkenabled) { + if (!gpsenabled && !networkenabled) { // notify user AlertDialog.Builder dialog = new AlertDialog.Builder(context); dialog.setMessage(context.getResources().getString(R.string.gps_network_not_enabled)); @@ -1019,11 +953,8 @@ public class OsmAndLocationProvider implements SensorEventListener { } public static boolean isLocationPermissionAvailable(Context context) { - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - return false; - } - return true; + return ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED; } public static void requestFineLocationPermissionIfNeeded(Activity activity) { diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index 8deb9c81a8..842a36237e 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -57,6 +57,7 @@ import net.osmand.plus.download.DownloadService; import net.osmand.plus.download.IndexItem; import net.osmand.plus.helpers.AvoidSpecificRoads; import net.osmand.plus.helpers.DayNightHelper; +import net.osmand.plus.helpers.LocationServiceHelper; import net.osmand.plus.helpers.LockHelper; import net.osmand.plus.helpers.WaypointHelper; import net.osmand.plus.helpers.enums.DrivingRegion; @@ -300,7 +301,11 @@ public class OsmandApplication extends MultiDexApplication { public QuickActionRegistry getQuickActionRegistry() { return quickActionRegistry; } - + + public LocationServiceHelper createLocationServiceHelper() { + return new LocationServiceHelperImpl(this); + } + public void setAppCustomization(OsmAndAppCustomization appCustomization) { this.appCustomization = appCustomization; this.appCustomization.setup(this); diff --git a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java index af1570ecaa..e04f055b35 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java +++ b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java @@ -388,7 +388,7 @@ public abstract class OsmandPlugin { } private static boolean updateMarketPlugin(@NonNull OsmandApplication app, @NonNull Set enabledPlugins, @NonNull OsmandPlugin plugin) { - boolean marketEnabled = Version.isMarketEnabled(app); + boolean marketEnabled = Version.isMarketEnabled(); boolean pckg = plugin.pluginAvailable(app); boolean paid = plugin.isPaid(); if ((Version.isDeveloperVersion(app) || !Version.isProductionVersion(app)) && !paid) { diff --git a/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java b/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java index 63d82ecc26..472899862b 100644 --- a/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java +++ b/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java @@ -445,7 +445,7 @@ public class TargetPointsHelper { Location lastKnownLocation = ctx.getLocationProvider().getLastKnownLocation(); LatLon latLon = lastKnownLocation != null ? new LatLon(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()) : null; - RoutingHelperUtils.checkAndUpdateStartLocation(ctx, latLon); + RoutingHelperUtils.checkAndUpdateStartLocation(ctx, latLon, false); setMyLocationPoint(latLon, false, null); } } diff --git a/OsmAnd/src/net/osmand/plus/Version.java b/OsmAnd/src/net/osmand/plus/Version.java index 48dd9b1feb..1ad99c96e8 100644 --- a/OsmAnd/src/net/osmand/plus/Version.java +++ b/OsmAnd/src/net/osmand/plus/Version.java @@ -1,9 +1,10 @@ package net.osmand.plus; - import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import androidx.annotation.NonNull; + import net.osmand.plus.inapp.InAppPurchaseHelper; import java.io.File; @@ -16,26 +17,29 @@ public class Version { private final String appName; private final static String FREE_VERSION_NAME = "net.osmand"; private final static String FREE_DEV_VERSION_NAME = "net.osmand.dev"; - private final static String FREE_CUSTOM_VERSION_NAME = "net.osmand.freecustom"; private final static String UTM_REF = "&referrer=utm_source%3Dosmand"; - - public static boolean isGpsStatusEnabled(OsmandApplication ctx) { - return isGooglePlayEnabled(ctx) && !isBlackberry(ctx); - } - - public static boolean isBlackberry(OsmandApplication ctx) { - return ctx.getString(R.string.versionFeatures).contains("+blackberry"); - } - - public static boolean isHuawei(OsmandApplication ctx) { - return ctx.getPackageName().endsWith(".huawei"); - } - - public static boolean isMarketEnabled(OsmandApplication ctx) { - return isGooglePlayEnabled(ctx) || isAmazonEnabled(ctx); + + public static boolean isHuawei() { + return getBuildFlavor().contains("huawei"); } - public static boolean isGooglePlayInstalled(OsmandApplication ctx) { + private static boolean isAmazon() { + return getBuildFlavor().contains("amazon"); + } + + private static String getBuildFlavor() { + return net.osmand.plus.BuildConfig.FLAVOR; + } + + public static boolean isGooglePlayEnabled() { + return !isHuawei() && !isAmazon(); + } + + public static boolean isMarketEnabled() { + return isGooglePlayEnabled() || isAmazon(); + } + + public static boolean isGooglePlayInstalled(@NonNull OsmandApplication ctx) { try { ctx.getPackageManager().getPackageInfo("com.android.vending", 0); } catch (PackageManager.NameNotFoundException e) { @@ -44,10 +48,10 @@ public class Version { return true; } - public static String marketPrefix(OsmandApplication ctx) { - if (isAmazonEnabled(ctx)) { + public static String marketPrefix(@NonNull OsmandApplication ctx) { + if (isAmazon()) { return "amzn://apps/android?p="; - } else if (isGooglePlayEnabled(ctx) && isGooglePlayInstalled(ctx)) { + } else if (isGooglePlayEnabled() && isGooglePlayInstalled(ctx)) { return "market://details?id="; } return "https://osmand.net/apps?id="; @@ -57,22 +61,11 @@ public class Version { return marketPrefix(ctx) + appName + UTM_REF; } - private static boolean isAmazonEnabled(OsmandApplication ctx) { - return ctx.getString(R.string.versionFeatures).contains("+amazon"); - } - - public static boolean isGooglePlayEnabled(OsmandApplication ctx) { - return ctx.getString(R.string.versionFeatures).contains("+play_market"); - } - - private Version(OsmandApplication ctx) { String appVersion = ""; - int versionCode = -1; try { PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); appVersion = packageInfo.versionName; //Version suffix ctx.getString(R.string.app_version_suffix) already appended in build.gradle - versionCode = packageInfo.versionCode; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } @@ -123,8 +116,7 @@ public class Version { public static boolean isFreeVersion(OsmandApplication ctx){ return ctx.getPackageName().equals(FREE_VERSION_NAME) || ctx.getPackageName().equals(FREE_DEV_VERSION_NAME) || - ctx.getPackageName().equals(FREE_CUSTOM_VERSION_NAME) || - isHuawei(ctx); + isHuawei(); } public static boolean isPaidVersion(OsmandApplication ctx) { diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 10fa1a066b..eb6dcbfbb7 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -70,6 +70,8 @@ import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; import net.osmand.plus.OnDismissDialogFragmentListener; import net.osmand.plus.OsmAndConstants; +import net.osmand.plus.OsmAndLocationProvider; +import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener; import net.osmand.plus.OsmAndLocationSimulation; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; @@ -118,6 +120,7 @@ import net.osmand.plus.measurementtool.LoginBottomSheetFragment; import net.osmand.plus.measurementtool.MeasurementEditingContext; import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.SnapTrackWarningFragment; +import net.osmand.plus.monitoring.TripRecordingBottomSheet; import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; import net.osmand.plus.routepreparationmenu.ChooseRouteFragment; @@ -346,11 +349,19 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven if (!settings.isLastKnownMapLocation()) { // show first time when application ran - net.osmand.Location location = app.getLocationProvider().getFirstTimeRunDefaultLocation(); + final WeakReference activityRef = new WeakReference<>(this); + net.osmand.Location location = app.getLocationProvider().getFirstTimeRunDefaultLocation(new OsmAndLocationListener() { + @Override + public void updateLocation(Location location) { + MapActivity a = activityRef.get(); + if (AndroidUtils.isActivityNotDestroyed(a) && app.getLocationProvider().getLastKnownLocation() == null) { + setMapInitialLatLon(a.mapView, location); + } + } + }); mapViewTrackingUtilities.setMapLinkedToLocation(true); if (location != null) { - mapView.setLatLon(location.getLatitude(), location.getLongitude()); - mapView.setIntZoom(14); + setMapInitialLatLon(mapView, location); } } addDialogProvider(mapActions); @@ -375,6 +386,13 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven mIsDestroyed = false; } + private void setMapInitialLatLon(@NonNull OsmandMapTileView mapView, @Nullable Location location) { + if (location != null) { + mapView.setLatLon(location.getLatitude(), location.getLongitude()); + mapView.setIntZoom(14); + } + } + public void exitFromFullScreen(View view) { AndroidUtils.exitFromFullScreen(this, view); } @@ -1408,6 +1426,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven updateMapSettings(); app.getPoiFilters().loadSelectedPoiFilters(); mapViewTrackingUtilities.updateSettings(); + mapViewTrackingUtilities.resetDrivingRegionUpdate(); //app.getRoutingHelper().setAppMode(settings.getApplicationMode()); if (mapLayers.getMapInfoLayer() != null) { mapLayers.getMapInfoLayer().recreateControls(); @@ -2204,6 +2223,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(MeasurementToolFragment.TAG); } + public TripRecordingBottomSheet getTripRecordingBottomSheet() { + return getFragment(TripRecordingBottomSheet.TAG); + } + public ChooseRouteFragment getChooseRouteFragment() { return getFragment(ChooseRouteFragment.TAG); } @@ -2220,7 +2243,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(SnapTrackWarningFragment.TAG); } - @NonNull public TrackMenuFragment getTrackMenuFragment() { return getFragment(TrackMenuFragment.TAG); } diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java index c526ddec2a..b5dd0dc3ea 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java @@ -903,7 +903,7 @@ public class MapActivityActions implements DialogProvider { } }).createItem()); - if (Version.isGooglePlayEnabled(app) || Version.isHuawei(app) || Version.isDeveloperVersion(app)) { + if (Version.isGooglePlayEnabled() || Version.isHuawei() || Version.isDeveloperVersion(app)) { optionsMenuHelper.addItem(new ItemBuilder().setTitleId(R.string.osm_live, mapActivity) .setId(DRAWER_OSMAND_LIVE_ID) .setIcon(R.drawable.ic_action_osm_live) diff --git a/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java b/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java index 47f7a17444..ee7b882c54 100644 --- a/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java @@ -5,7 +5,6 @@ import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,16 +15,12 @@ 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.Version; -import net.osmand.plus.download.DownloadActivity; import net.osmand.plus.inapp.InAppPurchaseHelper; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseInitCallback; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType; import net.osmand.plus.liveupdates.OsmLiveRestartBottomSheetDialogFragment; -import net.osmand.plus.srtmplugin.SRTMPlugin; import org.apache.commons.logging.Log; @@ -59,7 +54,7 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper(); if (app.getSettings().isInternetConnectionAvailable() && isInAppPurchaseAllowed() - && isInAppPurchaseSupported(purchaseHelper)) { + && isInAppPurchaseSupported()) { this.purchaseHelper = purchaseHelper; } } @@ -160,9 +155,8 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In return false; } - public boolean isInAppPurchaseSupported(InAppPurchaseHelper purchaseHelper) { - OsmandApplication app = getMyApplication(); - return Version.isGooglePlayEnabled(app) || Version.isHuawei(app); + public boolean isInAppPurchaseSupported() { + return Version.isGooglePlayEnabled() || Version.isHuawei(); } @Override @@ -210,7 +204,7 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In } onInAppPurchaseItemPurchased(sku); fireInAppPurchaseItemPurchasedOnFragments(fragmentManager, sku, active); - if (purchaseHelper != null && purchaseHelper.getContourLines().getSku().equals(sku)) { + if (purchaseHelper != null && purchaseHelper.getFullVersion().getSku().equals(sku)) { if (!(this instanceof MapActivity)) { finish(); } diff --git a/OsmAnd/src/net/osmand/plus/activities/actions/ShareDialog.java b/OsmAnd/src/net/osmand/plus/activities/actions/ShareDialog.java index 4858e3ccfe..f033e59bef 100644 --- a/OsmAnd/src/net/osmand/plus/activities/actions/ShareDialog.java +++ b/OsmAnd/src/net/osmand/plus/activities/actions/ShareDialog.java @@ -193,7 +193,7 @@ public class ShareDialog { intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); activity.startActivity(intent); } else { - if (Version.isMarketEnabled((OsmandApplication) activity.getApplication())) { + if (Version.isMarketEnabled()) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setMessage(activity.getString(R.string.zxing_barcode_scanner_not_found)); builder.setPositiveButton(activity.getString(R.string.shared_string_yes), new DialogInterface.OnClickListener() { diff --git a/OsmAnd/src/net/osmand/plus/activities/actions/StartGPSStatus.java b/OsmAnd/src/net/osmand/plus/activities/actions/StartGPSStatus.java index 79abd4190a..c8c9a70785 100644 --- a/OsmAnd/src/net/osmand/plus/activities/actions/StartGPSStatus.java +++ b/OsmAnd/src/net/osmand/plus/activities/actions/StartGPSStatus.java @@ -180,7 +180,7 @@ public class StartGPSStatus extends OsmAndAction { intent.addCategory(Intent.CATEGORY_LAUNCHER); mapActivity.startActivity(intent); } else { - if (Version.isMarketEnabled(getMyApplication())) { + if (Version.isMarketEnabled()) { AlertDialog.Builder builder = new AlertDialog.Builder(mapActivity); builder.setMessage(mapActivity. getString(R.string.gps_status_app_not_found)); builder.setPositiveButton(mapActivity.getString(R.string.shared_string_yes), new DialogInterface.OnClickListener() { diff --git a/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java b/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java index fa66a5c91a..14a6623eae 100644 --- a/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java +++ b/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java @@ -13,8 +13,6 @@ import net.osmand.data.LatLon; import net.osmand.data.RotatedTileBox; import net.osmand.map.IMapLocationListener; import net.osmand.map.WorldRegion; -import net.osmand.plus.mapmarkers.MapMarker; -import net.osmand.plus.mapmarkers.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndLocationProvider; import net.osmand.plus.OsmAndLocationProvider.OsmAndCompassListener; @@ -22,10 +20,14 @@ import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.dashboard.DashboardOnMap; +import net.osmand.plus.helpers.enums.DrivingRegion; import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu; +import net.osmand.plus.mapmarkers.MapMarker; +import net.osmand.plus.mapmarkers.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.routing.RoutingHelperUtils; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.views.AnimateDraggingMapThread; import net.osmand.plus.views.OsmandMapTileView; @@ -176,7 +178,7 @@ public class MapViewTrackingUtilities implements OsmAndLocationListener, IMapLoc locationProvider = location.getProvider(); if (settings.DRIVING_REGION_AUTOMATIC.get() && !drivingRegionUpdated && !app.isApplicationInitializing()) { drivingRegionUpdated = true; - RoutingHelperUtils.checkAndUpdateStartLocation(app, location); + RoutingHelperUtils.checkAndUpdateStartLocation(app, location, true); } } if (mapView != null) { @@ -489,7 +491,15 @@ public class MapViewTrackingUtilities implements OsmAndLocationListener, IMapLoc @Override protected void onPostExecute(WorldRegion worldRegion) { if (worldRegion != null) { + DrivingRegion oldRegion = app.getSettings().DRIVING_REGION.get(); + app.setupDrivingRegion(worldRegion); + + DrivingRegion currentRegion = app.getSettings().DRIVING_REGION.get(); + if (oldRegion.leftHandDriving != currentRegion.leftHandDriving) { + ApplicationMode mode = app.getRoutingHelper().getAppMode(); + app.getRoutingHelper().onSettingsChanged(mode, true); + } } } } diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java index 9a7215867f..256a845dc7 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java @@ -5,8 +5,6 @@ import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; @@ -120,7 +118,7 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment case SEA_DEPTH_MAPS: return InAppPurchaseHelper.isDepthContoursPurchased(ctx); case CONTOUR_LINES_HILLSHADE_MAPS: - return OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) != null; + return OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) != null || InAppPurchaseHelper.isContourLinesPurchased(ctx); } return false; } @@ -216,7 +214,7 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment infoDescription.setText(getInfoDescription()); } TextViewEx planInfoDescription = (TextViewEx) view.findViewById(R.id.plan_info_description); - planInfoDescription.setText(Version.isHuawei(app) + planInfoDescription.setText(Version.isHuawei() ? R.string.osm_live_payment_subscription_management_hw : R.string.osm_live_payment_subscription_management); ViewGroup osmLiveCard = buildOsmLiveCard(ctx, cardsContainer); if (osmLiveCard != null) { diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanFreeBannerDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanFreeBannerDialogFragment.java index bfdeb1b632..aa5a41a1a8 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanFreeBannerDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanFreeBannerDialogFragment.java @@ -30,7 +30,8 @@ public class ChoosePlanFreeBannerDialogFragment extends ChoosePlanDialogFragment private final OsmAndFeature[] planTypeFeatures = { OsmAndFeature.WIKIPEDIA_OFFLINE, - OsmAndFeature.WIKIVOYAGE_OFFLINE, + OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, + OsmAndFeature.SEA_DEPTH_MAPS, OsmAndFeature.UNLIMITED_DOWNLOADS, OsmAndFeature.MONTHLY_MAP_UPDATES, }; diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java index d3a48a5a15..b204557717 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java @@ -1,16 +1,6 @@ package net.osmand.plus.chooseplan; -import android.app.Activity; -import android.view.View; - -import androidx.annotation.Nullable; - -import net.osmand.plus.R; -import net.osmand.plus.activities.OsmandInAppPurchaseActivity; -import net.osmand.plus.inapp.InAppPurchaseHelper; -import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; - -public class ChoosePlanHillshadeSrtmDialogFragment extends ChoosePlanDialogFragment { +public class ChoosePlanHillshadeSrtmDialogFragment extends ChoosePlanFreeBannerDialogFragment { public static final String TAG = ChoosePlanHillshadeSrtmDialogFragment.class.getSimpleName(); private final OsmAndFeature[] osmLiveFeatures = { @@ -29,6 +19,10 @@ public class ChoosePlanHillshadeSrtmDialogFragment extends ChoosePlanDialogFragm private final OsmAndFeature[] planTypeFeatures = { OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, + OsmAndFeature.SEA_DEPTH_MAPS, + OsmAndFeature.WIKIPEDIA_OFFLINE, + OsmAndFeature.UNLIMITED_DOWNLOADS, + OsmAndFeature.MONTHLY_MAP_UPDATES, }; private final OsmAndFeature[] selectedPlanTypeFeatures = {}; @@ -51,48 +45,4 @@ public class ChoosePlanHillshadeSrtmDialogFragment extends ChoosePlanDialogFragm public OsmAndFeature[] getSelectedPlanTypeFeatures() { return selectedPlanTypeFeatures; } - - @Override - public int getPlanTypeHeaderImageId() { - return R.drawable.img_logo_38dp_contour_lines; - } - - @Override - public String getPlanTypeHeaderTitle() { - return getString(R.string.srtm_plugin_name); - } - - @Override - public String getPlanTypeHeaderDescription() { - return getString(R.string.paid_plugin); - } - - @Override - public String getPlanTypeButtonDescription() { - return getString(R.string.in_app_purchase_desc); - } - - @Override - public void setPlanTypeButtonClickListener(View button) { - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Activity activity = getActivity(); - if (activity != null) { - OsmandInAppPurchaseActivity.purchaseContourLines(activity); - dismiss(); - } - } - }); - } - - @Nullable - @Override - public InAppPurchase getPlanTypePurchase() { - InAppPurchaseHelper purchaseHelper = getOsmandApplication().getInAppPurchaseHelper(); - if (purchaseHelper != null) { - return purchaseHelper.getContourLines(); - } - return null; - } } diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanSeaDepthMapsDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanSeaDepthMapsDialogFragment.java index 211c788c38..8a07c25bef 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanSeaDepthMapsDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanSeaDepthMapsDialogFragment.java @@ -1,21 +1,11 @@ package net.osmand.plus.chooseplan; -import android.app.Activity; -import android.view.View; - -import androidx.annotation.Nullable; - -import net.osmand.plus.R; -import net.osmand.plus.activities.OsmandInAppPurchaseActivity; -import net.osmand.plus.inapp.InAppPurchaseHelper; -import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; - -public class ChoosePlanSeaDepthMapsDialogFragment extends ChoosePlanDialogFragment { +public class ChoosePlanSeaDepthMapsDialogFragment extends ChoosePlanFreeBannerDialogFragment { public static final String TAG = ChoosePlanSeaDepthMapsDialogFragment.class.getSimpleName(); private final OsmAndFeature[] osmLiveFeatures = { - OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, OsmAndFeature.SEA_DEPTH_MAPS, + OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, OsmAndFeature.WIKIPEDIA_OFFLINE, OsmAndFeature.WIKIVOYAGE_OFFLINE, OsmAndFeature.DAILY_MAP_UPDATES, @@ -23,12 +13,16 @@ public class ChoosePlanSeaDepthMapsDialogFragment extends ChoosePlanDialogFragme OsmAndFeature.UNLOCK_ALL_FEATURES, }; private final OsmAndFeature[] selectedOsmLiveFeatures = { - OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, OsmAndFeature.SEA_DEPTH_MAPS, + OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, }; private final OsmAndFeature[] planTypeFeatures = { OsmAndFeature.SEA_DEPTH_MAPS, + OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, + OsmAndFeature.WIKIPEDIA_OFFLINE, + OsmAndFeature.UNLIMITED_DOWNLOADS, + OsmAndFeature.MONTHLY_MAP_UPDATES, }; private final OsmAndFeature[] selectedPlanTypeFeatures = {}; @@ -51,47 +45,4 @@ public class ChoosePlanSeaDepthMapsDialogFragment extends ChoosePlanDialogFragme public OsmAndFeature[] getSelectedPlanTypeFeatures() { return selectedPlanTypeFeatures; } - - @Override - public int getPlanTypeHeaderImageId() { - return R.drawable.img_logo_38dp_sea_depth; - } - - @Override - public String getPlanTypeHeaderTitle() { - return getString(R.string.index_item_depth_contours_osmand_ext); - } - - @Override - public String getPlanTypeHeaderDescription() { - return getString(R.string.in_app_purchase); - } - - @Override - public String getPlanTypeButtonDescription() { - return getString(R.string.in_app_purchase_desc); - } - - @Override - public void setPlanTypeButtonClickListener(View button) { - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Activity activity = getActivity(); - if (activity != null) { - OsmandInAppPurchaseActivity.purchaseDepthContours(activity); - } - } - }); - } - - @Nullable - @Override - public InAppPurchase getPlanTypePurchase() { - InAppPurchaseHelper purchaseHelper = getOsmandApplication().getInAppPurchaseHelper(); - if (purchaseHelper != null) { - return purchaseHelper.getDepthContours(); - } - return null; - } } diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikipediaDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikipediaDialogFragment.java index 8729bdbbeb..a6b222734e 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikipediaDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikipediaDialogFragment.java @@ -19,7 +19,6 @@ public class ChoosePlanWikipediaDialogFragment extends ChoosePlanFreeBannerDialo private final OsmAndFeature[] planTypeFeatures = { OsmAndFeature.WIKIPEDIA_OFFLINE, - OsmAndFeature.WIKIVOYAGE_OFFLINE, OsmAndFeature.UNLIMITED_DOWNLOADS, OsmAndFeature.MONTHLY_MAP_UPDATES, }; diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikivoyageDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikivoyageDialogFragment.java index 9272e3d067..b58b9b6372 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikivoyageDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanWikivoyageDialogFragment.java @@ -1,6 +1,30 @@ package net.osmand.plus.chooseplan; -public class ChoosePlanWikivoyageDialogFragment extends ChoosePlanWikipediaDialogFragment { +public class ChoosePlanWikivoyageDialogFragment extends ChoosePlanOsmLiveBannerDialogFragment { public static final String TAG = ChoosePlanWikivoyageDialogFragment.class.getSimpleName(); + private final OsmAndFeature[] osmLiveFeatures = { + OsmAndFeature.WIKIVOYAGE_OFFLINE, + OsmAndFeature.WIKIPEDIA_OFFLINE, + OsmAndFeature.DAILY_MAP_UPDATES, + OsmAndFeature.UNLIMITED_DOWNLOADS, + OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS, + OsmAndFeature.SEA_DEPTH_MAPS, + OsmAndFeature.UNLOCK_ALL_FEATURES, + }; + private final OsmAndFeature[] selectedOsmLiveFeatures = { + OsmAndFeature.WIKIVOYAGE_OFFLINE, + OsmAndFeature.WIKIPEDIA_OFFLINE, + }; + + @Override + public OsmAndFeature[] getOsmLiveFeatures() { + return osmLiveFeatures; + } + + @Override + public OsmAndFeature[] getSelectedOsmLiveFeatures() { + return selectedOsmLiveFeatures; + } + } diff --git a/OsmAnd/src/net/osmand/plus/development/DevelopmentSettingsFragment.java b/OsmAnd/src/net/osmand/plus/development/DevelopmentSettingsFragment.java index 8cee128154..d34a306b79 100644 --- a/OsmAnd/src/net/osmand/plus/development/DevelopmentSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/development/DevelopmentSettingsFragment.java @@ -71,16 +71,12 @@ public class DevelopmentSettingsFragment extends BaseSettingsFragment { private void setupSafeModePref() { SwitchPreferenceEx safeMode = findPreference(settings.SAFE_MODE.getId()); - if (!Version.isBlackberry(app)) { - safeMode.setDescription(getString(R.string.safe_mode_description)); - safeMode.setIconSpaceReserved(false); - // disable the switch if the library cannot be used - if ((NativeOsmandLibrary.isLoaded() && !NativeOsmandLibrary.isSupported()) || settings.NATIVE_RENDERING_FAILED.get()) { - safeMode.setEnabled(false); - safeMode.setChecked(true); - } - } else { - safeMode.setVisible(false); + safeMode.setDescription(getString(R.string.safe_mode_description)); + safeMode.setIconSpaceReserved(false); + // disable the switch if the library cannot be used + if ((NativeOsmandLibrary.isLoaded() && !NativeOsmandLibrary.isSupported()) || settings.NATIVE_RENDERING_FAILED.get()) { + safeMode.setEnabled(false); + safeMode.setChecked(true); } } diff --git a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java index ca7382dd6e..7a0bcb0e79 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java @@ -251,7 +251,7 @@ public class ConfigureMapMenu { OsmandPlugin.registerLayerContextMenu(activity.getMapView(), adapter, activity); boolean srtmDisabled = OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) == null - && !InAppPurchaseHelper.isSubscribedToLiveUpdates(app); + && !InAppPurchaseHelper.isContourLinesPurchased(app); if (srtmDisabled) { SRTMPlugin srtmPlugin = OsmandPlugin.getPlugin(SRTMPlugin.class); if (srtmPlugin != null) { diff --git a/OsmAnd/src/net/osmand/plus/dialogs/RateUsBottomSheetDialogFragment.java b/OsmAnd/src/net/osmand/plus/dialogs/RateUsBottomSheetDialogFragment.java index 2dcd62c6bd..188031ea1d 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/RateUsBottomSheetDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/RateUsBottomSheetDialogFragment.java @@ -108,7 +108,7 @@ public class RateUsBottomSheetDialogFragment extends MenuBottomSheetDialogFragme public static boolean shouldShow(OsmandApplication app) { long firstInstalledDays = app.getAppInitializer().getFirstInstalledDays(); //Do not show dialog if not google play version or more than 350 days left from the first start - if (!Version.isGooglePlayEnabled(app) || firstInstalledDays > 350) { + if (!Version.isGooglePlayEnabled() || firstInstalledDays > 350) { return false; } OsmandSettings settings = app.getSettings(); diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadActivity.java b/OsmAnd/src/net/osmand/plus/download/DownloadActivity.java index 52bb44bf5f..d076f329f0 100644 --- a/OsmAnd/src/net/osmand/plus/download/DownloadActivity.java +++ b/OsmAnd/src/net/osmand/plus/download/DownloadActivity.java @@ -684,11 +684,12 @@ public class DownloadActivity extends AbstractDownloadActivity implements Downlo } public void initAppStatusVariables() { + OsmandApplication app = getMyApplication(); srtmDisabled = OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) == null - && !InAppPurchaseHelper.isSubscribedToLiveUpdates(getMyApplication()); + && !InAppPurchaseHelper.isContourLinesPurchased(app); nauticalPluginDisabled = OsmandPlugin.getEnabledPlugin(NauticalMapsPlugin.class) == null; - freeVersion = Version.isFreeVersion(getMyApplication()); - OsmandPlugin srtmPlugin = OsmandPlugin.getPlugin(SRTMPlugin.class); + freeVersion = Version.isFreeVersion(app); + SRTMPlugin srtmPlugin = OsmandPlugin.getPlugin(SRTMPlugin.class); srtmNeedsInstallation = srtmPlugin == null || srtmPlugin.needsInstallation(); } diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadResources.java b/OsmAnd/src/net/osmand/plus/download/DownloadResources.java index f28310645e..4f820bac2a 100644 --- a/OsmAnd/src/net/osmand/plus/download/DownloadResources.java +++ b/OsmAnd/src/net/osmand/plus/download/DownloadResources.java @@ -352,9 +352,7 @@ public class DownloadResources extends DownloadResourceGroup { continue; } if (ii.getType() == DownloadActivityType.DEPTH_CONTOUR_FILE) { - if (InAppPurchaseHelper.isDepthContoursPurchased(app) - || InAppPurchaseHelper.isSubscribedToLiveUpdates(app) - || nauticalMaps.size() == 0) { + if (InAppPurchaseHelper.isDepthContoursPurchased(app) || nauticalMaps.size() == 0) { nauticalMaps.addItem(ii); } continue; diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadValidationManager.java b/OsmAnd/src/net/osmand/plus/download/DownloadValidationManager.java index 92fa82079f..8e03b2c1e5 100644 --- a/OsmAnd/src/net/osmand/plus/download/DownloadValidationManager.java +++ b/OsmAnd/src/net/osmand/plus/download/DownloadValidationManager.java @@ -217,7 +217,7 @@ public class DownloadValidationManager { AlertDialog.Builder msg = new AlertDialog.Builder(getActivity()); msg.setTitle(R.string.free_version_title); msg.setMessage(msgTx); - if (Version.isMarketEnabled(getMyApplication())) { + if (Version.isMarketEnabled()) { msg.setPositiveButton(R.string.install_paid, new DialogInterface.OnClickListener() { @Override diff --git a/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java b/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java index 9ad1a0f89e..70041a525e 100644 --- a/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java +++ b/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java @@ -134,8 +134,7 @@ public class ItemViewHolder { srtmDisabled = context.isSrtmDisabled(); nauticalPluginDisabled = context.isNauticalPluginDisabled(); srtmNeedsInstallation = context.isSrtmNeedsInstallation(); - depthContoursPurchased = InAppPurchaseHelper.isDepthContoursPurchased(context.getMyApplication()) - || InAppPurchaseHelper.isSubscribedToLiveUpdates(context.getMyApplication()); + depthContoursPurchased = InAppPurchaseHelper.isDepthContoursPurchased(context.getMyApplication()); } public void bindIndexItem(final IndexItem indexItem) { diff --git a/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java b/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java index d3ebe9d6ae..0721350197 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java @@ -43,10 +43,10 @@ public class DayNightHelper implements SensorEventListener { private static final Log log = PlatformUtil.getLog(DayNightHelper.class); - private final OsmandApplication osmandApplication; + private final OsmandApplication app; - public DayNightHelper(OsmandApplication osmandApplication) { - this.osmandApplication = osmandApplication; + public DayNightHelper(OsmandApplication app) { + this.app = app; } private DayNightHelper listener; @@ -56,11 +56,11 @@ public class DayNightHelper implements SensorEventListener { private StateChangedListener sensorStateListener; public boolean isNightModeForMapControls() { - return isNightModeForMapControlsForProfile(osmandApplication.getSettings().APPLICATION_MODE.get()); + return isNightModeForMapControlsForProfile(app.getSettings().APPLICATION_MODE.get()); } public boolean isNightModeForMapControlsForProfile(ApplicationMode mode) { - if (osmandApplication.getSettings().isLightContentForMode(mode)) { + if (app.getSettings().isLightContentForMode(mode)) { return isNightModeForProfile(mode); } else { return true; @@ -72,11 +72,11 @@ public class DayNightHelper implements SensorEventListener { * @return true if day is supposed to be */ public boolean isNightMode() { - return isNightModeForProfile(osmandApplication.getSettings().APPLICATION_MODE.get()); + return isNightModeForProfile(app.getSettings().APPLICATION_MODE.get()); } public boolean isNightModeForProfile(ApplicationMode mode) { - DayNightMode dayNightMode = osmandApplication.getSettings().DAYNIGHT_MODE.getModeValue(mode); + DayNightMode dayNightMode = app.getSettings().DAYNIGHT_MODE.getModeValue(mode); if (dayNightMode.isDay()) { return false; } else if (dayNightMode.isNight()) { @@ -108,24 +108,23 @@ public class DayNightHelper implements SensorEventListener { } public SunriseSunset getSunriseSunset() { - Location lastKnownLocation = osmandApplication.getLocationProvider().getLastKnownLocation(); - if(lastKnownLocation == null) { - lastKnownLocation = osmandApplication.getLocationProvider().getFirstTimeRunDefaultLocation(); + Location lastKnownLocation = app.getLocationProvider().getLastKnownLocation(); + if (lastKnownLocation == null) { + lastKnownLocation = app.getLocationProvider().getFirstTimeRunDefaultLocation(null); } if (lastKnownLocation == null) { return null; } double longitude = lastKnownLocation.getLongitude(); Date actualTime = new Date(); - SunriseSunset daynightSwitch = new SunriseSunset(lastKnownLocation.getLatitude(), + return new SunriseSunset(lastKnownLocation.getLatitude(), longitude < 0 ? 360 + longitude : longitude, actualTime, TimeZone.getDefault()); - return daynightSwitch; } public void stopSensorIfNeeded() { if (listener != null) { - SensorManager mSensorManager = (SensorManager) osmandApplication + SensorManager mSensorManager = (SensorManager) app .getSystemService(Context.SENSOR_SERVICE); Sensor mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); mSensorManager.unregisterListener(listener, mLight); @@ -135,9 +134,9 @@ public class DayNightHelper implements SensorEventListener { public void startSensorIfNeeded(StateChangedListener sensorStateListener) { this.sensorStateListener = sensorStateListener; - DayNightMode dayNightMode = osmandApplication.getSettings().DAYNIGHT_MODE.get(); + DayNightMode dayNightMode = app.getSettings().DAYNIGHT_MODE.get(); if (listener == null && dayNightMode.isSensor()) { - SensorManager mSensorManager = (SensorManager) osmandApplication.getSystemService(Context.SENSOR_SERVICE); + SensorManager mSensorManager = (SensorManager) app.getSystemService(Context.SENSOR_SERVICE); Sensor mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); List list = mSensorManager.getSensorList(Sensor.TYPE_LIGHT); log.info("Light sensors:" + list.size()); //$NON-NLS-1$ diff --git a/OsmAnd/src/net/osmand/plus/helpers/LocationServiceHelper.java b/OsmAnd/src/net/osmand/plus/helpers/LocationServiceHelper.java new file mode 100644 index 0000000000..472dcf3b7f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/helpers/LocationServiceHelper.java @@ -0,0 +1,30 @@ +package net.osmand.plus.helpers; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.Location; + +import java.util.List; + +public abstract class LocationServiceHelper { + + public static abstract class LocationCallback { + + public void onLocationResult(@NonNull List locations) { + } + + public void onLocationAvailability(boolean locationAvailable) { + } + } + + public abstract void requestLocationUpdates(@NonNull LocationCallback locationCallback); + + public abstract boolean isNetworkLocationUpdatesSupported(); + + public abstract void requestNetworkLocationUpdates(@NonNull LocationCallback locationCallback); + + public abstract void removeLocationUpdates(); + + public abstract Location getFirstTimeRunDefaultLocation(@Nullable LocationCallback locationCallback); +} diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index c7464c6b1f..48fd643099 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -153,11 +153,15 @@ public abstract class InAppPurchaseHelper { } public static boolean isDepthContoursPurchased(@NonNull OsmandApplication ctx) { - return Version.isDeveloperBuild(ctx) || ctx.getSettings().DEPTH_CONTOURS_PURCHASED.get(); + return Version.isDeveloperBuild(ctx) + || Version.isPaidVersion(ctx) + || ctx.getSettings().DEPTH_CONTOURS_PURCHASED.get(); } public static boolean isContourLinesPurchased(@NonNull OsmandApplication ctx) { - return Version.isDeveloperBuild(ctx) || ctx.getSettings().CONTOUR_LINES_PURCHASED.get(); + return Version.isDeveloperBuild(ctx) + || Version.isPaidVersion(ctx) + || ctx.getSettings().CONTOUR_LINES_PURCHASED.get(); } public InAppPurchases getInAppPurchases() { @@ -212,7 +216,7 @@ public abstract class InAppPurchaseHelper { } protected void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppCommand command) { - if (isDeveloperVersion || (!Version.isGooglePlayEnabled(ctx) && !Version.isHuawei(ctx))) { + if (isDeveloperVersion || (!Version.isGooglePlayEnabled() && !Version.isHuawei())) { notifyDismissProgress(taskType); stop(true); return; diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java index ac6b8287b4..22d6d97830 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java @@ -85,7 +85,7 @@ public class MapDataMenuController extends MenuController { } srtmDisabled = OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) == null - && !InAppPurchaseHelper.isSubscribedToLiveUpdates(app); + && !InAppPurchaseHelper.isContourLinesPurchased(app); OsmandPlugin srtmPlugin = OsmandPlugin.getPlugin(SRTMPlugin.class); srtmNeedsInstallation = srtmPlugin == null || srtmPlugin.needsInstallation(); diff --git a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java index 16c60911df..42bae2ab62 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java @@ -100,12 +100,12 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public void updateLocation(Location location) { liveMonitoringHelper.updateLocation(location); } - + @Override public int getLogoResourceId() { return R.drawable.ic_action_gps_info; } - + @Override public Drawable getAssetResourceImage() { return app.getUIUtilities().getIcon(R.drawable.trip_recording); @@ -140,7 +140,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { private void registerWidget(MapActivity activity) { MapInfoLayer layer = activity.getMapLayers().getMapInfoLayer(); monitoringControl = createMonitoringControl(activity); - + layer.registerSideWidget(monitoringControl, R.drawable.ic_action_play_dark, R.string.map_widget_monitoring, "monitoring", false, 30); layer.recreateControls(); @@ -161,7 +161,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { } } } - + public static final int[] SECONDS = new int[] {0, 1, 2, 3, 5, 10, 15, 20, 30, 60, 90}; 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}; @@ -287,7 +287,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { controlDialog(map, true); } - + }); return monitoringControl; } @@ -422,7 +422,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public void saveCurrentTrack() { saveCurrentTrack(null, null); } - + public void saveCurrentTrack(@Nullable final Runnable onComplete) { saveCurrentTrack(onComplete, null); } @@ -464,7 +464,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { SaveGPXBottomSheetFragment.showInstance(((FragmentActivity) a).getSupportFragmentManager(), result.getFilenames()); } } - + if (onComplete != null) { onComplete.run(); } @@ -505,15 +505,9 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { }; if (choice.value || map == null) { runnable.run(); - } else { - showIntervalChooseDialog(map, app.getString(R.string.save_track_interval_globally) + " : %s", - app.getString(R.string.save_track_to_gpx_globally), SECONDS, MINUTES, choice, vs, showTrackSelection, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - runnable.run(); - } - }); + } else if (map instanceof FragmentActivity) { + FragmentActivity activity = (FragmentActivity) map; + TripRecordingBottomSheet.showInstance(activity.getSupportFragmentManager()); } } @@ -588,7 +582,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { tv.setText(String.format(patternMsg, s)); } }); - + for (int i = 0; i < secondsLength + minutesLength - 1; i++) { if (i < secondsLength) { if (v.value <= seconds[i] * 1000) { diff --git a/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java new file mode 100644 index 0000000000..54b444ff30 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java @@ -0,0 +1,272 @@ +package net.osmand.plus.monitoring; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.SpannableString; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.SwitchCompat; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.slider.RangeSlider; + +import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; +import net.osmand.plus.NavigationService; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.UiUtilities.DialogButtonType; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.MenuBottomSheetDialogFragment; +import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.FontCache; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.track.TrackAppearanceFragment; + +import static net.osmand.plus.UiUtilities.CompoundButtonType.PROFILE_DEPENDENT; +import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.MINUTES; +import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.SECONDS; + +public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment { + + public static final String TAG = TripRecordingBottomSheet.class.getSimpleName(); + + private OsmandApplication app; + private OsmandSettings settings; + + private ImageView upDownBtn; + private SwitchCompat confirmEveryRun; + private RangeSlider intervalSlider; + private TextView intervalValueView; + private LinearLayout container; + private boolean infoExpanded; + + @Override + public void createMenuItems(Bundle savedInstanceState) { + app = requiredMyApplication(); + settings = app.getSettings(); + Context context = requireContext(); + + LayoutInflater inflater = UiUtilities.getInflater(context, nightMode); + View itemView = inflater.inflate(R.layout.trip_recording_fragment, null, false); + items.add(new BottomSheetItemWithDescription.Builder() + .setCustomView(itemView) + .create()); + + final int paddingSmall = getResources().getDimensionPixelSize(R.dimen.content_padding_small); + + LinearLayout showTrackOnMapView = itemView.findViewById(R.id.show_track_on_map); + TextView showTrackOnMapTitle = showTrackOnMapView.findViewById(R.id.title); + showTrackOnMapTitle.setText(R.string.show_track_on_map); + + ImageView trackAppearanceIcon = showTrackOnMapView.findViewById(R.id.icon_after_divider); + + int color = settings.CURRENT_TRACK_COLOR.get(); + String width = settings.CURRENT_TRACK_WIDTH.get(); + boolean showArrows = settings.CURRENT_TRACK_SHOW_ARROWS.get(); + Drawable drawable = TrackAppearanceFragment.getTrackIcon(app, width, showArrows, color); + + trackAppearanceIcon.setImageDrawable(drawable); + trackAppearanceIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + hide(); + SelectedGpxFile selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack(); + TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile); + } + } + }); + + upDownBtn = itemView.findViewById(R.id.up_down_button); + upDownBtn.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + toggleInfoView(); + } + }); + + final int secondsLength = SECONDS.length; + final int minutesLength = MINUTES.length; + + intervalValueView = itemView.findViewById(R.id.interval_value); + updateIntervalLegend(); + + container = itemView.findViewById(R.id.always_ask_and_range_slider_container); + intervalSlider = itemView.findViewById(R.id.interval_slider); + intervalSlider.setValueTo(secondsLength + minutesLength - 1); + container.setVisibility(View.GONE); + intervalSlider.addOnChangeListener(new RangeSlider.OnChangeListener() { + + @Override + public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) { + int progress = (int) value; + if (progress == 0) { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(0); + } else if (progress < secondsLength) { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(SECONDS[progress] * 1000); + } else { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(MINUTES[progress - secondsLength] * 60 * 1000); + } + updateIntervalLegend(); + } + }); + for (int i = 0; i < secondsLength + minutesLength; i++) { + if (i < secondsLength) { + if (settings.SAVE_GLOBAL_TRACK_INTERVAL.get() <= SECONDS[i] * 1000) { + intervalSlider.setValues((float) i); + break; + } + } else { + if (settings.SAVE_GLOBAL_TRACK_INTERVAL.get() <= MINUTES[i - secondsLength] * 1000 * 60) { + intervalSlider.setValues((float) i); + break; + } + } + } + boolean checked = !settings.SAVE_GLOBAL_TRACK_REMEMBER.get(); + confirmEveryRun = itemView.findViewById(R.id.confirm_every_run); + confirmEveryRun.setChecked(checked); + setBackgroundAndPadding(checked, paddingSmall); + confirmEveryRun.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setBackgroundAndPadding(isChecked, paddingSmall); + settings.SAVE_GLOBAL_TRACK_REMEMBER.set(!isChecked); + } + }); + + SwitchCompat showTrackOnMapButton = showTrackOnMapView.findViewById(R.id.switch_button); + showTrackOnMapButton.setChecked(app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null); + showTrackOnMapButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + app.getSelectedGpxHelper().selectGpxFile(app.getSavingTrackHelper().getCurrentGpx(), isChecked, false); + } + }); + UiUtilities.setupCompoundButton(showTrackOnMapButton, nightMode, PROFILE_DEPENDENT); + + updateUpDownBtn(); + } + + private void updateIntervalLegend() { + String text = getString(R.string.save_track_interval_globally); + String textValue; + int interval = settings.SAVE_GLOBAL_TRACK_INTERVAL.get(); + if (interval == 0) { + textValue = getString(R.string.int_continuosly); + } else { + int seconds = interval / 1000; + if (seconds <= SECONDS[SECONDS.length - 1]) { + textValue = seconds + " " + getString(R.string.int_seconds); + } else { + textValue = (seconds / 60) + " " + getString(R.string.int_min); + } + } + String textAll = getString(R.string.ltr_or_rtl_combine_via_colon, text, textValue); + Typeface typeface = FontCache.getRobotoMedium(app); + SpannableString spannableString = UiUtilities.createCustomFontSpannable(typeface, textAll, textValue); + intervalValueView.setText(spannableString); + } + + public void show() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.show(); + } + } + + public void hide() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.hide(); + } + } + + private void setBackgroundAndPadding(boolean isChecked, int paddingSmall) { + if (nightMode) { + confirmEveryRun.setBackgroundResource( + isChecked ? R.drawable.layout_bg_dark_solid : R.drawable.layout_bg_dark); + } else { + confirmEveryRun.setBackgroundResource( + isChecked ? R.drawable.layout_bg_solid : R.drawable.layout_bg); + } + confirmEveryRun.setPadding(paddingSmall, 0, paddingSmall, 0); + } + + private void updateUpDownBtn() { + int iconId = infoExpanded ? R.drawable.ic_action_arrow_down : R.drawable.ic_action_arrow_up; + upDownBtn.setImageDrawable(getContentIcon(iconId)); + } + + private void toggleInfoView() { + infoExpanded = !infoExpanded; + AndroidUiHelper.updateVisibility(container, infoExpanded); + updateUpDownBtn(); + } + + @Override + protected boolean useVerticalButtons() { + return true; + } + + @Override + protected int getRightBottomButtonTextId() { + return R.string.start_recording; + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_cancel; + } + + @Override + protected DialogButtonType getRightBottomButtonType() { + return DialogButtonType.PRIMARY; + } + + @Override + public int getSecondDividerHeight() { + return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_icon_margin); + } + + @Override + protected void onRightBottomButtonClick() { + app.getSavingTrackHelper().startNewSegment(); + settings.SAVE_GLOBAL_TRACK_TO_GPX.set(true); + app.startNavigationService(NavigationService.USED_BY_GPX); + dismiss(); + } + + @Nullable + public MapActivity getMapActivity() { + Activity activity = getActivity(); + if (activity instanceof MapActivity) { + return (MapActivity) activity; + } + return null; + } + + public static void showInstance(@NonNull FragmentManager fragmentManager) { + if (!fragmentManager.isStateSaved()) { + TripRecordingBottomSheet fragment = new TripRecordingBottomSheet(); + fragment.show(fragmentManager, TAG); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/EngineParameter.java b/OsmAnd/src/net/osmand/plus/onlinerouting/EngineParameter.java new file mode 100644 index 0000000000..fa3ccd81c7 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/EngineParameter.java @@ -0,0 +1,10 @@ +package net.osmand.plus.onlinerouting; + +public enum EngineParameter { + KEY, + VEHICLE_KEY, + CUSTOM_NAME, + NAME_INDEX, + CUSTOM_URL, + API_KEY +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/EngineType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/EngineType.java deleted file mode 100644 index fe5080f14c..0000000000 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/EngineType.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.osmand.plus.onlinerouting; - -public enum EngineType { - - GRAPHHOPPER("Graphhopper", "https://graphhopper.com/api/1/route"), - OSRM("OSRM", "https://router.project-osrm.org/route/v1/"), - ORS("Openroute Service", "https://api.openrouteservice.org/v2/directions/"); - - private String title; - private String standardUrl; - - EngineType(String title, String standardUrl) { - this.title = title; - this.standardUrl = standardUrl; - } - - public String getTitle() { - return title; - } - - public String getStandardUrl() { - return standardUrl; - } -} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java deleted file mode 100644 index 3193d86d09..0000000000 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java +++ /dev/null @@ -1,109 +0,0 @@ -package net.osmand.plus.onlinerouting; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import net.osmand.plus.R; -import net.osmand.util.Algorithms; - -import java.util.HashMap; -import java.util.Map; - -public class OnlineRoutingEngine { - - public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_"; - - public enum EngineParameter { - CUSTOM_NAME, - CUSTOM_URL, - API_KEY - } - - private String stringKey; - private EngineType type; - private String vehicleKey; - private Map params = new HashMap<>(); - - public OnlineRoutingEngine(@NonNull String stringKey, - @NonNull EngineType type, - @NonNull String vehicleKey, - @Nullable Map params) { - this(stringKey, type, vehicleKey); - if (!Algorithms.isEmpty(params)) { - this.params.putAll(params); - } - } - - public OnlineRoutingEngine(@NonNull String stringKey, - @NonNull EngineType type, - @NonNull String vehicleKey) { - this.stringKey = stringKey; - this.type = type; - this.vehicleKey = vehicleKey; - } - - public String getStringKey() { - return stringKey; - } - - public EngineType getType() { - return type; - } - - public String getBaseUrl() { - String customUrl = getParameter(EngineParameter.CUSTOM_URL); - if (Algorithms.isEmpty(customUrl)) { - return type.getStandardUrl(); - } - return customUrl; - } - - public String getVehicleKey() { - return vehicleKey; - } - - public Map getParams() { - return params; - } - - public String getParameter(EngineParameter paramKey) { - return params.get(paramKey.name()); - } - - public void putParameter(EngineParameter paramKey, String paramValue) { - params.put(paramKey.name(), paramValue); - } - - public String getName(@NonNull Context ctx) { - String customName = getParameter(EngineParameter.CUSTOM_NAME); - if (customName != null) { - return customName; - } else { - return getStandardName(ctx); - } - } - - private String getStandardName(@NonNull Context ctx) { - return getStandardName(ctx, type, vehicleKey); - } - - public static String getStandardName(@NonNull Context ctx, - @NonNull EngineType type, - @NonNull String vehicleKey) { - String vehicleTitle = VehicleType.toHumanString(ctx, vehicleKey); - String pattern = ctx.getString(R.string.ltr_or_rtl_combine_via_dash); - return String.format(pattern, type.getTitle(), vehicleTitle); - } - - public static OnlineRoutingEngine createNewEngine(@NonNull EngineType type, - @NonNull String vehicleKey, - @Nullable Map params) { - return new OnlineRoutingEngine(generateKey(), type, vehicleKey, params); - } - - private static String generateKey() { - return ONLINE_ROUTING_ENGINE_PREFIX + System.currentTimeMillis(); - } -} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java deleted file mode 100644 index 59df4ef698..0000000000 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java +++ /dev/null @@ -1,609 +0,0 @@ -package net.osmand.plus.onlinerouting; - -import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; - -import net.osmand.AndroidNetworkUtils; -import net.osmand.AndroidNetworkUtils.OnRequestResultListener; -import net.osmand.AndroidUtils; -import net.osmand.CallbackWithObject; -import net.osmand.data.LatLon; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.UiUtilities.DialogButtonType; -import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.base.BaseOsmAndFragment; -import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; -import net.osmand.plus.onlinerouting.OnlineRoutingCard.OnTextChangedListener; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter; -import net.osmand.plus.routepreparationmenu.cards.BaseCard; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.util.Algorithms; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.List; - -public class OnlineRoutingEngineFragment extends BaseOsmAndFragment { - - public static final String TAG = OnlineRoutingEngineFragment.class.getSimpleName(); - - private static final String ENGINE_NAME_KEY = "engine_name"; - private static final String ENGINE_SERVER_KEY = "engine_server"; - private static final String ENGINE_SERVER_URL_KEY = "engine_server_url"; - private static final String ENGINE_VEHICLE_TYPE_KEY = "engine_vehicle_type"; - private static final String ENGINE_CUSTOM_VEHICLE_KEY = "engine_custom_vehicle"; - private static final String ENGINE_API_KEY_KEY = "engine_api_key"; - private static final String EXAMPLE_LOCATION_KEY = "example_location"; - private static final String APP_MODE_KEY = "app_mode"; - private static final String EDITED_ENGINE_KEY = "edited_engine_key"; - - private OsmandApplication app; - private MapActivity mapActivity; - private OnlineRoutingHelper helper; - - private View view; - private ViewGroup segmentsContainer; - private OnlineRoutingCard nameCard; - private OnlineRoutingCard typeCard; - private OnlineRoutingCard vehicleCard; - private OnlineRoutingCard apiKeyCard; - private OnlineRoutingCard exampleCard; - private View testResultsContainer; - - private ApplicationMode appMode; - - private OnlineRoutingEngineObject engine; - private ExampleLocation selectedLocation; - private String editedEngineKey; - - private enum ExampleLocation { - - AMSTERDAM("Amsterdam", - new LatLon(52.379189, 4.899431), - new LatLon(52.308056, 4.764167)), - - BERLIN("Berlin", - new LatLon(52.520008, 13.404954), - new LatLon(52.3666652, 13.501997992)), - - NEW_YORK("New York", - new LatLon(43.000000, -75.000000), - new LatLon(40.641766, -73.780968)), - - PARIS("Paris", - new LatLon(48.864716, 2.349014), - new LatLon(48.948437, 2.434931)); - - ExampleLocation(String name, LatLon cityCenterLatLon, LatLon cityAirportLatLon) { - this.name = name; - this.cityCenterLatLon = cityCenterLatLon; - this.cityAirportLatLon = cityAirportLatLon; - } - - private String name; - private LatLon cityCenterLatLon; - private LatLon cityAirportLatLon; - - public String getName() { - return name; - } - - public LatLon getCityCenterLatLon() { - return cityCenterLatLon; - } - - public LatLon getCityAirportLatLon() { - return cityAirportLatLon; - } - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - app = requireMyApplication(); - mapActivity = getMapActivity(); - helper = app.getOnlineRoutingHelper(); - engine = new OnlineRoutingEngineObject(); - if (savedInstanceState != null) { - restoreState(savedInstanceState); - } else { - initState(); - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - view = getInflater().inflate( - R.layout.online_routing_engine_fragment, container, false); - segmentsContainer = (ViewGroup) view.findViewById(R.id.segments_container); - if (Build.VERSION.SDK_INT >= 21) { - AndroidUtils.addStatusBarPadding21v(getContext(), view); - } - setupToolbar((Toolbar) view.findViewById(R.id.toolbar)); - - setupNameCard(); - setupTypeCard(); - setupVehicleCard(); - setupApiKeyCard(); - setupExampleCard(); - setupResultsContainer(); - addSpaceSegment(); - - setupButtons(); - - updateCardViews(nameCard, typeCard, vehicleCard, exampleCard); - return view; - } - - private void setupNameCard() { - nameCard = new OnlineRoutingCard(mapActivity, isNightMode()); - nameCard.build(mapActivity); - nameCard.setDescription(getString(R.string.select_nav_profile_dialog_message)); - nameCard.setEditedText(engine.getName(app)); - nameCard.setFieldBoxLabelText(getString(R.string.shared_string_name)); - nameCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean changedByUser, String text) { - if (changedByUser) { - engine.customName = text; - } - } - }); - nameCard.showDivider(); - segmentsContainer.addView(nameCard.getView()); - } - - private void setupTypeCard() { - typeCard = new OnlineRoutingCard(mapActivity, isNightMode()); - typeCard.build(mapActivity); - typeCard.setHeaderTitle(getString(R.string.shared_string_type)); - List serverItems = new ArrayList<>(); - for (EngineType server : EngineType.values()) { - serverItems.add(new HorizontalSelectionItem(server.getTitle(), server)); - } - typeCard.setSelectionMenu(serverItems, engine.type.getTitle(), - new CallbackWithObject() { - @Override - public boolean processResult(HorizontalSelectionItem result) { - EngineType type = (EngineType) result.getObject(); - if (engine.type != type) { - engine.type = type; - updateCardViews(nameCard, typeCard, exampleCard); - return true; - } - return false; - } - }); - typeCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean editedByUser, String text) { - if (editedByUser) { - engine.customServerUrl = text; - updateCardViews(exampleCard); - } - } - }); - typeCard.setFieldBoxLabelText(getString(R.string.shared_string_server_url)); - typeCard.showDivider(); - segmentsContainer.addView(typeCard.getView()); - } - - private void setupVehicleCard() { - vehicleCard = new OnlineRoutingCard(mapActivity, isNightMode()); - vehicleCard.build(mapActivity); - vehicleCard.setHeaderTitle(getString(R.string.shared_string_vehicle)); - List vehicleItems = new ArrayList<>(); - for (VehicleType vehicle : VehicleType.values()) { - vehicleItems.add(new HorizontalSelectionItem(vehicle.getTitle(app), vehicle)); - } - vehicleCard.setSelectionMenu(vehicleItems, engine.vehicleType.getTitle(app), - new CallbackWithObject() { - @Override - public boolean processResult(HorizontalSelectionItem result) { - VehicleType vehicle = (VehicleType) result.getObject(); - if (engine.vehicleType != vehicle) { - engine.vehicleType = vehicle; - updateCardViews(nameCard, vehicleCard, exampleCard); - return true; - } - return false; - } - }); - vehicleCard.setFieldBoxLabelText(getString(R.string.shared_string_custom)); - vehicleCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean editedByUser, String text) { - if (editedByUser) { - engine.customVehicleKey = text; - updateCardViews(nameCard, exampleCard); - } - } - }); - vehicleCard.setEditedText(engine.customVehicleKey); - vehicleCard.setFieldBoxHelperText(getString(R.string.shared_string_enter_param)); - vehicleCard.showDivider(); - segmentsContainer.addView(vehicleCard.getView()); - } - - private void setupApiKeyCard() { - apiKeyCard = new OnlineRoutingCard(mapActivity, isNightMode()); - apiKeyCard.build(mapActivity); - apiKeyCard.setHeaderTitle(getString(R.string.shared_string_api_key)); - apiKeyCard.setFieldBoxLabelText(getString(R.string.keep_it_empty_if_not)); - apiKeyCard.setEditedText(engine.apiKey); - apiKeyCard.showDivider(); - apiKeyCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean editedByUser, String text) { - engine.apiKey = text; - updateCardViews(exampleCard); - } - }); - segmentsContainer.addView(apiKeyCard.getView()); - } - - private void setupExampleCard() { - exampleCard = new OnlineRoutingCard(mapActivity, isNightMode()); - exampleCard.build(mapActivity); - exampleCard.setHeaderTitle(getString(R.string.shared_string_example)); - List locationItems = new ArrayList<>(); - for (ExampleLocation location : ExampleLocation.values()) { - locationItems.add(new HorizontalSelectionItem(location.getName(), location)); - } - exampleCard.setSelectionMenu(locationItems, selectedLocation.getName(), - new CallbackWithObject() { - @Override - public boolean processResult(HorizontalSelectionItem result) { - ExampleLocation location = (ExampleLocation) result.getObject(); - if (selectedLocation != location) { - selectedLocation = location; - updateCardViews(exampleCard); - return true; - } - return false; - } - }); - exampleCard.setFieldBoxHelperText(getString(R.string.online_routing_example_hint)); - exampleCard.setButton(getString(R.string.test_route_calculation), new View.OnClickListener() { - @Override - public void onClick(View v) { - testEngineWork(); - } - }); - segmentsContainer.addView(exampleCard.getView()); - } - - private void setupResultsContainer() { - testResultsContainer = getInflater().inflate( - R.layout.bottom_sheet_item_with_descr_64dp, segmentsContainer, false); - testResultsContainer.setVisibility(View.GONE); - segmentsContainer.addView(testResultsContainer); - } - - private void addSpaceSegment() { - int space = (int) getResources().getDimension(R.dimen.empty_state_text_button_padding_top); - View bottomSpaceView = new View(app); - bottomSpaceView.setLayoutParams( - new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, space)); - segmentsContainer.addView(bottomSpaceView); - } - - private void setupToolbar(Toolbar toolbar) { - ImageView navigationIcon = toolbar.findViewById(R.id.close_button); - navigationIcon.setImageResource(R.drawable.ic_action_close); - navigationIcon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); - TextView title = toolbar.findViewById(R.id.toolbar_title); - toolbar.findViewById(R.id.toolbar_subtitle).setVisibility(View.GONE); - View actionBtn = toolbar.findViewById(R.id.action_button); - if (isEditingMode()) { - title.setText(getString(R.string.edit_online_routing_engine)); - ImageView ivBtn = toolbar.findViewById(R.id.action_button_icon); - ivBtn.setImageDrawable( - getIcon(R.drawable.ic_action_delete_dark, R.color.color_osm_edit_delete)); - actionBtn.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - deleteEngine(); - dismiss(); - } - }); - } else { - title.setText(getString(R.string.add_online_routing_engine)); - actionBtn.setVisibility(View.GONE); - } - } - - private void updateCardViews(BaseCard... cardsToUpdate) { - for (BaseCard card : cardsToUpdate) { - if (nameCard.equals(card)) { - if (Algorithms.isEmpty(engine.customName)) { - String name; - if (Algorithms.isEmpty(engine.getVehicleKey())) { - name = engine.type.getTitle(); - } else { - name = OnlineRoutingEngine.getStandardName(app, engine.type, engine.getVehicleKey()); - } - nameCard.setEditedText(name); - } - - } else if (typeCard.equals(card)) { - typeCard.setHeaderSubtitle(engine.type.getTitle()); - typeCard.setEditedText(engine.getBaseUrl()); - if (engine.type == EngineType.GRAPHHOPPER || engine.type == EngineType.ORS) { - apiKeyCard.show(); - } else { - apiKeyCard.hide(); - } - - } else if (vehicleCard.equals(card)) { - VehicleType vt = VehicleType.getVehicleByKey(engine.getVehicleKey()); - vehicleCard.setHeaderSubtitle(vt.getTitle(app)); - if (vt == VehicleType.CUSTOM) { - vehicleCard.showFieldBox(); - vehicleCard.setEditedText(engine.getVehicleKey()); - } else { - vehicleCard.hideFieldBox(); - } - - } else if (exampleCard.equals(card)) { - exampleCard.setEditedText(getTestUrl()); - } - } - } - - private void setupButtons() { - boolean nightMode = isNightMode(); - View cancelButton = view.findViewById(R.id.dismiss_button); - UiUtilities.setupDialogButton(nightMode, cancelButton, - DialogButtonType.SECONDARY, R.string.shared_string_cancel); - cancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); - - view.findViewById(R.id.buttons_divider).setVisibility(View.VISIBLE); - - View saveButton = view.findViewById(R.id.right_bottom_button); - UiUtilities.setupDialogButton(nightMode, saveButton, - UiUtilities.DialogButtonType.PRIMARY, R.string.shared_string_save); - saveButton.setVisibility(View.VISIBLE); - saveButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - saveChanges(); - dismiss(); - } - }); - } - - private void saveChanges() { - OnlineRoutingEngine engineToSave; - if (isEditingMode()) { - engineToSave = new OnlineRoutingEngine(editedEngineKey, engine.type, engine.getVehicleKey()); - } else { - engineToSave = OnlineRoutingEngine.createNewEngine(engine.type, engine.getVehicleKey(), null); - } - - engineToSave.putParameter(EngineParameter.CUSTOM_NAME, engine.customName); - engineToSave.putParameter(EngineParameter.CUSTOM_URL, engine.customServerUrl); - if (engine.type == EngineType.GRAPHHOPPER || engine.type == EngineType.ORS) { - engineToSave.putParameter(EngineParameter.API_KEY, engine.apiKey); - } - - helper.saveEngine(engineToSave); - } - - private void deleteEngine() { - helper.deleteEngine(editedEngineKey); - } - - private String getTestUrl() { - List path = new ArrayList<>(); - path.add(selectedLocation.getCityCenterLatLon()); - path.add(selectedLocation.getCityAirportLatLon()); - OnlineRoutingEngine tmpEngine = - OnlineRoutingEngine.createNewEngine(engine.type, engine.getVehicleKey(), null); - tmpEngine.putParameter(EngineParameter.CUSTOM_URL, engine.customServerUrl); - tmpEngine.putParameter(EngineParameter.API_KEY, engine.apiKey); - return helper.createFullUrl(tmpEngine, path); - } - - private void testEngineWork() { - final EngineType type = engine.type; - final ExampleLocation location = selectedLocation; - AndroidNetworkUtils.sendRequestAsync(app, exampleCard.getEditedText(), null, - null, false, false, new OnRequestResultListener() { - @Override - public void onResult(String response) { - boolean resultOk = false; - if (response != null) { - try { - JSONObject obj = new JSONObject(response); - - if (type == EngineType.GRAPHHOPPER) { - resultOk = obj.has("paths"); - } else if (type == EngineType.OSRM) { - resultOk = obj.has("routes"); - } else if (type == EngineType.ORS) { - resultOk = obj.has("features"); - } - } catch (JSONException e) { - - } - } - showTestResults(resultOk, location); - } - }); - } - - private void showTestResults(boolean resultOk, ExampleLocation location) { - testResultsContainer.setVisibility(View.VISIBLE); - ImageView ivImage = testResultsContainer.findViewById(R.id.icon); - TextView tvTitle = testResultsContainer.findViewById(R.id.title); - TextView tvDescription = testResultsContainer.findViewById(R.id.description); - if (resultOk) { - ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_gdirections_dark)); - tvTitle.setText(getString(R.string.shared_string_ok)); - } else { - ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_alert)); - tvTitle.setText(getString(R.string.message_error_recheck_parameters)); - } - tvDescription.setText(location.getName()); - } - - private boolean isEditingMode() { - return editedEngineKey != null; - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - saveState(outState); - } - - private void saveState(Bundle outState) { - outState.putString(ENGINE_NAME_KEY, engine.customName); - outState.putString(ENGINE_SERVER_KEY, engine.type.name()); - outState.putString(ENGINE_SERVER_URL_KEY, engine.customServerUrl); - outState.putString(ENGINE_VEHICLE_TYPE_KEY, engine.vehicleType.name()); - outState.putString(ENGINE_CUSTOM_VEHICLE_KEY, engine.customVehicleKey); - outState.putString(ENGINE_API_KEY_KEY, engine.apiKey); - outState.putString(EXAMPLE_LOCATION_KEY, selectedLocation.name()); - if (appMode != null) { - outState.putString(APP_MODE_KEY, appMode.getStringKey()); - } - outState.putString(EDITED_ENGINE_KEY, editedEngineKey); - } - - private void restoreState(Bundle savedState) { - engine.customName = savedState.getString(ENGINE_NAME_KEY); - engine.type = EngineType.valueOf(savedState.getString(ENGINE_SERVER_KEY)); - engine.customServerUrl = savedState.getString(ENGINE_SERVER_URL_KEY); - engine.vehicleType = VehicleType.valueOf(savedState.getString(ENGINE_VEHICLE_TYPE_KEY)); - engine.customVehicleKey = savedState.getString(ENGINE_CUSTOM_VEHICLE_KEY); - engine.apiKey = savedState.getString(ENGINE_API_KEY_KEY); - selectedLocation = ExampleLocation.valueOf(savedState.getString(EXAMPLE_LOCATION_KEY)); - appMode = ApplicationMode.valueOfStringKey(savedState.getString(APP_MODE_KEY), null); - editedEngineKey = savedState.getString(EDITED_ENGINE_KEY); - } - - private void initState() { - engine.type = EngineType.values()[0]; - engine.vehicleType = VehicleType.values()[0]; - selectedLocation = ExampleLocation.values()[0]; - - if (isEditingMode()) { - OnlineRoutingEngine editedEngine = helper.getEngineByKey(editedEngineKey); - if (editedEngine != null) { - engine.customName = editedEngine.getParameter(EngineParameter.CUSTOM_NAME); - engine.type = editedEngine.getType(); - String vehicleKey = editedEngine.getVehicleKey(); - if (vehicleKey != null) { - VehicleType vehicleType = VehicleType.getVehicleByKey(vehicleKey); - if (vehicleType == VehicleType.CUSTOM) { - engine.customVehicleKey = vehicleKey; - } - engine.vehicleType = vehicleType; - } - engine.apiKey = editedEngine.getParameter(EngineParameter.API_KEY); - } - } - } - - private void dismiss() { - FragmentActivity activity = getActivity(); - if (activity != null) { - activity.onBackPressed(); - } - } - - private boolean isNightMode() { - return !app.getSettings().isLightContentForMode(appMode); - } - - @Nullable - private MapActivity getMapActivity() { - FragmentActivity activity = getActivity(); - if (activity instanceof MapActivity) { - return (MapActivity) activity; - } else { - return null; - } - } - - private LayoutInflater getInflater() { - return UiUtilities.getInflater(mapActivity, isNightMode()); - } - - public static void showInstance(@NonNull FragmentActivity activity, - @NonNull ApplicationMode appMode, - String editedEngineKey) { - FragmentManager fm = activity.getSupportFragmentManager(); - if (!fm.isStateSaved() && fm.findFragmentByTag(OnlineRoutingEngineFragment.TAG) == null) { - OnlineRoutingEngineFragment fragment = new OnlineRoutingEngineFragment(); - fragment.appMode = appMode; - fragment.editedEngineKey = editedEngineKey; - fm.beginTransaction() - .add(R.id.fragmentContainer, fragment, TAG) - .addToBackStack(TAG).commitAllowingStateLoss(); - } - } - - private static class OnlineRoutingEngineObject { - private String customName; - private EngineType type; - private String customServerUrl; - private VehicleType vehicleType; - private String customVehicleKey; - private String apiKey; - - public String getVehicleKey() { - if (vehicleType == VehicleType.CUSTOM) { - return customVehicleKey; - } - return vehicleType.getKey(); - } - - public String getName(Context ctx) { - if (customName != null) { - return customName; - } - return OnlineRoutingEngine.getStandardName(ctx, type, getVehicleKey()); - } - - public String getBaseUrl() { - if (Algorithms.isEmpty(customServerUrl)) { - return type.getStandardUrl(); - } - return customServerUrl; - } - } -} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingFactory.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingFactory.java new file mode 100644 index 0000000000..4b92000847 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingFactory.java @@ -0,0 +1,36 @@ +package net.osmand.plus.onlinerouting; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.onlinerouting.engine.EngineType; +import net.osmand.plus.onlinerouting.engine.GraphhopperEngine; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; +import net.osmand.plus.onlinerouting.engine.OrsEngine; +import net.osmand.plus.onlinerouting.engine.OsrmEngine; + +import java.util.Map; + +public class OnlineRoutingFactory { + + public static OnlineRoutingEngine createEngine(@NonNull EngineType type) { + return createEngine(type, null); + } + + @NonNull + public static OnlineRoutingEngine createEngine(@NonNull EngineType type, + @Nullable Map params) { + switch (type) { + case GRAPHHOPPER: + return new GraphhopperEngine(params); + case OSRM: + return new OsrmEngine(params); + case ORS: + return new OrsEngine(params); + default: + throw new IllegalArgumentException( + "Online routing type {" + type.name() + "} not supported"); + } + } + +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java index b54746ddd6..9b3d042699 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java @@ -1,32 +1,26 @@ package net.osmand.plus.onlinerouting; import androidx.annotation.NonNull; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; +import androidx.annotation.Nullable; import net.osmand.PlatformUtil; import net.osmand.data.LatLon; import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.OsmandApplication; import net.osmand.plus.Version; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.util.Algorithms; -import net.osmand.util.GeoPolylineParserUtil; import org.apache.commons.logging.Log; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.net.URLConnection; +import java.net.HttpURLConnection; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -39,10 +33,10 @@ public class OnlineRoutingHelper { private OsmandSettings settings; private Map cachedEngines; - public OnlineRoutingHelper(OsmandApplication app) { + public OnlineRoutingHelper(@NonNull OsmandApplication app) { this.app = app; this.settings = app.getSettings(); - loadFromSettings(); + this.cachedEngines = loadSavedEngines(); } @NonNull @@ -50,104 +44,52 @@ public class OnlineRoutingHelper { return new ArrayList<>(cachedEngines.values()); } - public OnlineRoutingEngine getEngineByKey(String stringKey) { + @NonNull + public List getEnginesExceptMentionedKeys(@Nullable String... excludeKeys) { + List engines = getEngines(); + if (excludeKeys != null) { + for (String key : excludeKeys) { + OnlineRoutingEngine engine = getEngineByKey(key); + engines.remove(engine); + } + } + return engines; + } + + @Nullable + public OnlineRoutingEngine getEngineByKey(@Nullable String stringKey) { return cachedEngines.get(stringKey); } + @Nullable + public OnlineRoutingEngine getEngineByName(@Nullable String name) { + for (OnlineRoutingEngine engine : getEngines()) { + if (Algorithms.objectEquals(engine.getName(app), name)) { + return engine; + } + } + return null; + } + + @NonNull public List calculateRouteOnline(@NonNull OnlineRoutingEngine engine, @NonNull List path) throws IOException, JSONException { - String fullUrl = createFullUrl(engine, path); - String content = makeRequest(fullUrl); - return parseResponse(engine, content); + String url = engine.getFullUrl(path); + String content = makeRequest(url); + return engine.parseServerResponse(content); } - public String createFullUrl(OnlineRoutingEngine engine, List path) { - StringBuilder sb = new StringBuilder(engine.getBaseUrl()); - String vehicle = engine.getVehicleKey(); - String apiKey = engine.getParameter(EngineParameter.API_KEY); - switch (engine.getType()) { - - case GRAPHHOPPER: - sb.append("?"); - for (LatLon point : path) { - sb.append("point=") - .append(point.getLatitude()) - .append(',') - .append(point.getLongitude()) - .append('&'); - } - sb.append("vehicle=").append(vehicle); - - if (!Algorithms.isEmpty(apiKey)) { - sb.append('&').append("key=").append(apiKey); - } - break; - - case OSRM: - sb.append(vehicle).append('/'); - for (int i = 0; i < path.size(); i++) { - LatLon point = path.get(i); - sb.append(point.getLongitude()).append(',').append(point.getLatitude()); - if (i < path.size() - 1) { - sb.append(';'); - } - } - break; - - case ORS: - if (path.size() > 1) { - sb.append("driving-car").append('?'); // todo only for testing - if (!Algorithms.isEmpty(apiKey)) { - sb.append("api_key=").append(apiKey); - } - LatLon start = path.get(0); - LatLon end = path.get(path.size() - 1); - sb.append('&').append("start=") - .append(start.getLatitude()).append(',').append(start.getLongitude()); - sb.append('&').append("end=") - .append(end.getLatitude()).append(',').append(end.getLongitude()); - } - break; - - } - return sb.toString(); - } - - private List parseResponse(OnlineRoutingEngine engine, String content) throws JSONException { - JSONObject obj = new JSONObject(content); - - switch (engine.getType()) { - - case GRAPHHOPPER: - return GeoPolylineParserUtil.parse( - obj.getJSONArray("paths").getJSONObject(0).getString("points"), - GeoPolylineParserUtil.PRECISION_5); - - case OSRM: - return GeoPolylineParserUtil.parse( - obj.getJSONArray("routes").getJSONObject(0).getString("geometry"), - GeoPolylineParserUtil.PRECISION_5); - - case ORS: - JSONArray array = obj.getJSONArray("features").getJSONObject(0) - .getJSONObject("geometry").getJSONArray("coordinates"); - List track = new ArrayList<>(); - for (int i = 0; i < array.length(); i++) { - JSONArray point = array.getJSONArray(i); - double lat = Double.parseDouble(point.getString(0)); - double lon = Double.parseDouble(point.getString(1)); - track.add(new LatLon(lat, lon)); - } - return track; - } - return new ArrayList<>(); - } - - private String makeRequest(String url) throws IOException { - URLConnection connection = NetworkUtils.getHttpURLConnection(url); + @NonNull + public String makeRequest(@NonNull String url) throws IOException { + HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); connection.setRequestProperty("User-Agent", Version.getFullVersion(app)); StringBuilder content = new StringBuilder(); - BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + BufferedReader reader; + if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } else { + reader = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + } String s; while ((s = reader.readLine()) != null) { content.append(s); @@ -160,32 +102,49 @@ public class OnlineRoutingHelper { } public void saveEngine(@NonNull OnlineRoutingEngine engine) { - String stringKey = engine.getStringKey(); - cachedEngines.put(stringKey, engine); - saveToSettings(); - } - - public void deleteEngine(@NonNull String stringKey) { - OnlineRoutingEngine engine = getEngineByKey(stringKey); - if (engine != null) { - deleteEngine(engine); - } + deleteInaccessibleParameters(engine); + String key = createEngineKeyIfNeeded(engine); + cachedEngines.put(key, engine); + saveCacheToSettings(); } public void deleteEngine(@NonNull OnlineRoutingEngine engine) { String stringKey = engine.getStringKey(); - if (cachedEngines.containsKey(stringKey)) { + deleteEngine(stringKey); + } + + public void deleteEngine(@Nullable String stringKey) { + if (stringKey != null) { cachedEngines.remove(stringKey); - saveToSettings(); + saveCacheToSettings(); } } - private void loadFromSettings() { + private void deleteInaccessibleParameters(@NonNull OnlineRoutingEngine engine) { + for (EngineParameter key : EngineParameter.values()) { + if (!engine.isParameterAllowed(key)) { + engine.remove(key); + } + } + } + + @NonNull + private String createEngineKeyIfNeeded(@NonNull OnlineRoutingEngine engine) { + String key = engine.get(EngineParameter.KEY); + if (Algorithms.isEmpty(key)) { + key = OnlineRoutingEngine.generateKey(); + engine.put(EngineParameter.KEY, key); + } + return key; + } + + @NonNull + private Map loadSavedEngines() { Map cachedEngines = new LinkedHashMap<>(); for (OnlineRoutingEngine engine : readFromSettings()) { cachedEngines.put(engine.getStringKey(), engine); } - this.cachedEngines = cachedEngines; + return cachedEngines; } @NonNull @@ -195,19 +154,19 @@ public class OnlineRoutingHelper { if (!Algorithms.isEmpty(jsonString)) { try { JSONObject json = new JSONObject(jsonString); - readFromJson(json, engines); - } catch (JSONException e) { + OnlineRoutingUtils.readFromJson(json, engines); + } catch (JSONException | IllegalArgumentException e) { LOG.debug("Error when reading engines from JSON ", e); } } return engines; } - private void saveToSettings() { + private void saveCacheToSettings() { if (!Algorithms.isEmpty(cachedEngines)) { try { JSONObject json = new JSONObject(); - writeToJson(json, getEngines()); + OnlineRoutingUtils.writeToJson(json, getEngines()); settings.ONLINE_ROUTING_ENGINES.set(json.toString()); } catch (JSONException e) { LOG.debug("Error when writing engines to JSON ", e); @@ -216,39 +175,4 @@ public class OnlineRoutingHelper { settings.ONLINE_ROUTING_ENGINES.set(null); } } - - public static void readFromJson(JSONObject json, List engines) throws JSONException { - if (!json.has("items")) { - return; - } - Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); - JSONArray itemsJson = json.getJSONArray("items"); - for (int i = 0; i < itemsJson.length(); i++) { - JSONObject object = itemsJson.getJSONObject(i); - String key = object.getString("key"); - String vehicleKey = object.getString("vehicle"); - EngineType engineType = EngineType.valueOf(object.getString("type")); - String paramsString = object.getString("params"); - HashMap params = gson.fromJson(paramsString, type); - engines.add(new OnlineRoutingEngine(key, engineType, vehicleKey, params)); - } - } - - public static void writeToJson(JSONObject json, List engines) throws JSONException { - JSONArray jsonArray = new JSONArray(); - Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); - for (OnlineRoutingEngine engine : engines) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("key", engine.getStringKey()); - jsonObject.put("type", engine.getType().name()); - jsonObject.put("vehicle", engine.getVehicleKey()); - jsonObject.put("params", gson.toJson(engine.getParams(), type)); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } } diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingUtils.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingUtils.java new file mode 100644 index 0000000000..90eb4ea83c --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingUtils.java @@ -0,0 +1,91 @@ +package net.osmand.plus.onlinerouting; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import net.osmand.plus.onlinerouting.engine.EngineType; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; + +public class OnlineRoutingUtils { + + private static final String ITEMS = "items"; + private static final String TYPE = "type"; + private static final String PARAMS = "params"; + + public static void generateUniqueName(@NonNull Context ctx, + @NonNull OnlineRoutingEngine engine, + @NonNull List otherEngines) { + engine.remove(EngineParameter.NAME_INDEX); + if (hasNameDuplicate(ctx, engine.getName(ctx), otherEngines)) { + int index = 0; + do { + engine.put(EngineParameter.NAME_INDEX, String.valueOf(++index)); + } while (hasNameDuplicate(ctx, engine.getName(ctx), otherEngines)); + } + } + + public static boolean hasNameDuplicate(@NonNull Context ctx, + @NonNull String engineName, + @NonNull List otherEngines) { + for (OnlineRoutingEngine engine : otherEngines) { + if (Algorithms.objectEquals(engine.getName(ctx), engineName)) { + return true; + } + } + return false; + } + + public static void readFromJson(@NonNull JSONObject json, + @NonNull List engines) throws JSONException { + if (!json.has("items")) { + return; + } + Gson gson = new Gson(); + Type typeToken = new TypeToken>() { + }.getType(); + JSONArray itemsJson = json.getJSONArray(ITEMS); + for (int i = 0; i < itemsJson.length(); i++) { + JSONObject object = itemsJson.getJSONObject(i); + if (object.has(TYPE) && object.has(PARAMS)) { + EngineType type = EngineType.getTypeByName(object.getString(TYPE)); + String paramsString = object.getString(PARAMS); + HashMap params = gson.fromJson(paramsString, typeToken); + OnlineRoutingEngine engine = OnlineRoutingFactory.createEngine(type, params); + if (!Algorithms.isEmpty(engine.getStringKey())) { + engines.add(engine); + } + } + } + } + + public static void writeToJson(@NonNull JSONObject json, + @NonNull List engines) throws JSONException { + JSONArray jsonArray = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + for (OnlineRoutingEngine engine : engines) { + if (Algorithms.isEmpty(engine.getStringKey())) { + continue; + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put(TYPE, engine.getType().name()); + jsonObject.put(PARAMS, gson.toJson(engine.getParams(), type)); + jsonArray.put(jsonObject); + } + json.put(ITEMS, jsonArray); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java index 3e3fea6660..9936f8efbf 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java @@ -3,48 +3,26 @@ package net.osmand.plus.onlinerouting; import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; -import net.osmand.plus.R; -import net.osmand.util.Algorithms; +public class VehicleType { + private final String key; + @StringRes + private final int titleId; -public enum VehicleType { - CAR("car", R.string.routing_engine_vehicle_type_car), - BIKE("bike", R.string.routing_engine_vehicle_type_bike), - FOOT("foot", R.string.routing_engine_vehicle_type_foot), - DRIVING("driving", R.string.routing_engine_vehicle_type_driving), - CUSTOM("", R.string.shared_string_custom); - - VehicleType(String key, int titleId) { + public VehicleType(@NonNull String key, + @StringRes int titleId) { this.key = key; this.titleId = titleId; } - private String key; - private int titleId; - + @NonNull public String getKey() { return key; } - public String getTitle(Context ctx) { + @NonNull + public String getTitle(@NonNull Context ctx) { return ctx.getString(titleId); } - - public static String toHumanString(@NonNull Context ctx, - @NonNull String key) { - VehicleType vehicleType = getVehicleByKey(key); - if (vehicleType == CUSTOM) { - return Algorithms.capitalizeFirstLetter(key); - } - return vehicleType.getTitle(ctx); - } - - public static VehicleType getVehicleByKey(String key) { - for (VehicleType v : values()) { - if (Algorithms.objectEquals(v.getKey(), key)) { - return v; - } - } - return CUSTOM; - } -} \ No newline at end of file +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/EngineType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/EngineType.java new file mode 100644 index 0000000000..431ad33a3d --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/EngineType.java @@ -0,0 +1,34 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.util.Algorithms; + +public enum EngineType { + GRAPHHOPPER("Graphhopper"), + OSRM("OSRM"), + ORS("Openroute Service"); + + private final String title; + + EngineType(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + @NonNull + public static EngineType getTypeByName(@Nullable String name) { + if (!Algorithms.isEmpty(name)) { + for (EngineType type : values()) { + if (type.name().equals(name)) { + return type; + } + } + } + return values()[0]; + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java new file mode 100644 index 0000000000..df9381352b --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java @@ -0,0 +1,95 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.util.GeoPolylineParserUtil; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; +import java.util.Map; + +import static net.osmand.util.Algorithms.isEmpty; + +public class GraphhopperEngine extends OnlineRoutingEngine { + + public GraphhopperEngine(@Nullable Map params) { + super(params); + } + + @Override + public @NonNull EngineType getType() { + return EngineType.GRAPHHOPPER; + } + + @NonNull + @Override + public String getStandardUrl() { + return "https://graphhopper.com/api/1/route"; + } + + @Override + protected void collectAllowedParameters() { + allowParameters(EngineParameter.API_KEY); + } + + @Override + protected void collectAllowedVehicles(@NonNull List vehicles) { + vehicles.add(new VehicleType("car", R.string.routing_engine_vehicle_type_car)); + vehicles.add(new VehicleType("bike", R.string.routing_engine_vehicle_type_bike)); + vehicles.add(new VehicleType("foot", R.string.routing_engine_vehicle_type_foot)); + vehicles.add(new VehicleType("hike", R.string.routing_engine_vehicle_type_hiking)); + vehicles.add(new VehicleType("mtb", R.string.routing_engine_vehicle_type_mtb)); + vehicles.add(new VehicleType("racingbike", R.string.routing_engine_vehicle_type_racingbike)); + vehicles.add(new VehicleType("scooter", R.string.routing_engine_vehicle_type_scooter)); + vehicles.add(new VehicleType("truck", R.string.routing_engine_vehicle_type_truck)); + vehicles.add(new VehicleType("small_truck", R.string.routing_engine_vehicle_type_small_truck)); + } + + @Override + protected void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path) { + sb.append("?"); + for (LatLon point : path) { + sb.append("point=") + .append(point.getLatitude()) + .append(',') + .append(point.getLongitude()) + .append('&'); + } + String vehicle = get(EngineParameter.VEHICLE_KEY); + if (!isEmpty(vehicle)) { + sb.append("vehicle=").append(vehicle); + } + String apiKey = get(EngineParameter.API_KEY); + if (!isEmpty(apiKey)) { + sb.append('&').append("key=").append(apiKey); + } + } + + @NonNull + @Override + public List parseServerResponse(@NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + return GeoPolylineParserUtil.parse( + obj.getJSONArray("paths").getJSONObject(0).getString("points"), + GeoPolylineParserUtil.PRECISION_5); + } + + @Override + public boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + if (obj.has("message")) { + String message = obj.getString("message"); + sb.append(message); + } + return obj.has("paths"); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java new file mode 100644 index 0000000000..511f31cc67 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java @@ -0,0 +1,198 @@ +package net.osmand.plus.onlinerouting.engine; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingFactory; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.util.Algorithms; + +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static net.osmand.util.Algorithms.isEmpty; + +public abstract class OnlineRoutingEngine implements Cloneable { + + public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_"; + public static final VehicleType CUSTOM_VEHICLE = new VehicleType("", R.string.shared_string_custom); + + private final Map params = new HashMap<>(); + private final List allowedVehicles = new ArrayList<>(); + private final Set allowedParameters = new HashSet<>(); + + public OnlineRoutingEngine(@Nullable Map params) { + if (!isEmpty(params)) { + this.params.putAll(params); + } + collectAllowedVehiclesInternal(); + collectAllowedParametersInternal(); + } + + @NonNull + public abstract EngineType getType(); + + @Nullable + public String getStringKey() { + return get(EngineParameter.KEY); + } + + @NonNull + public String getName(@NonNull Context ctx) { + String name = get(EngineParameter.CUSTOM_NAME); + if (name == null) { + name = getStandardName(ctx); + } + String index = get(EngineParameter.NAME_INDEX); + return !isEmpty(index) ? name + " " + index : name; + } + + @NonNull + public String getStandardName(@NonNull Context ctx) { + String vehicleTitle = getSelectedVehicleName(ctx); + if (isEmpty(vehicleTitle)) { + return getType().getTitle(); + } else { + String pattern = ctx.getString(R.string.ltr_or_rtl_combine_via_dash); + return String.format(pattern, getType().getTitle(), vehicleTitle); + } + } + + @NonNull + public String getBaseUrl() { + String customUrl = get(EngineParameter.CUSTOM_URL); + if (isEmpty(customUrl)) { + return getStandardUrl(); + } + return customUrl; + } + + @NonNull + public String getFullUrl(@NonNull List path) { + StringBuilder sb = new StringBuilder(getBaseUrl()); + makeFullUrl(sb, path); + return sb.toString(); + } + + protected abstract void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path); + + @NonNull + public abstract List parseServerResponse(@NonNull String content) throws JSONException; + + @NonNull + public abstract String getStandardUrl(); + + @NonNull + public Map getParams() { + return params; + } + + @Nullable + public String get(@NonNull EngineParameter key) { + return params.get(key.name()); + } + + public void put(@NonNull EngineParameter key, @NonNull String value) { + params.put(key.name(), value); + } + + public void remove(@NonNull EngineParameter key) { + params.remove(key.name()); + } + + private void collectAllowedVehiclesInternal() { + allowedVehicles.clear(); + collectAllowedVehicles(allowedVehicles); + allowedVehicles.add(CUSTOM_VEHICLE); + } + + protected abstract void collectAllowedVehicles(@NonNull List vehicles); + + @NonNull + public List getAllowedVehicles() { + return Collections.unmodifiableList(allowedVehicles); + } + + private void collectAllowedParametersInternal() { + allowedParameters.clear(); + allowParameters(EngineParameter.KEY, EngineParameter.VEHICLE_KEY, + EngineParameter.CUSTOM_NAME, EngineParameter.NAME_INDEX, EngineParameter.CUSTOM_URL); + collectAllowedParameters(); + } + + protected abstract void collectAllowedParameters(); + + public boolean isParameterAllowed(EngineParameter key) { + return allowedParameters.contains(key); + } + + protected void allowParameters(@NonNull EngineParameter ... allowedParams) { + allowedParameters.addAll(Arrays.asList(allowedParams)); + } + + @Nullable + private String getSelectedVehicleName(@NonNull Context ctx) { + String key = get(EngineParameter.VEHICLE_KEY); + VehicleType vt = getVehicleTypeByKey(key); + if (!vt.equals(CUSTOM_VEHICLE)) { + return vt.getTitle(ctx); + } + return key != null ? Algorithms.capitalizeFirstLetter(key) : null; + } + + @NonNull + public VehicleType getSelectedVehicleType() { + String key = get(EngineParameter.VEHICLE_KEY); + return getVehicleTypeByKey(key); + } + + @NonNull + public VehicleType getVehicleTypeByKey(@Nullable String vehicleKey) { + if (!isEmpty(vehicleKey)) { + for (VehicleType vt : allowedVehicles) { + if (Algorithms.objectEquals(vt.getKey(), vehicleKey)) { + return vt; + } + } + } + return CUSTOM_VEHICLE; + } + + public abstract boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException; + + @NonNull + @Override + public Object clone() { + return OnlineRoutingFactory.createEngine(getType(), getParams()); + } + + @NonNull + public static String generateKey() { + return ONLINE_ROUTING_ENGINE_PREFIX + System.currentTimeMillis(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OnlineRoutingEngine)) return false; + + OnlineRoutingEngine engine = (OnlineRoutingEngine) o; + if (getType() != engine.getType()) return false; + return Algorithms.objectEquals(getParams(), engine.getParams()); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java new file mode 100644 index 0000000000..7c57737d46 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java @@ -0,0 +1,104 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.VehicleType; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static net.osmand.util.Algorithms.isEmpty; + +public class OrsEngine extends OnlineRoutingEngine { + + public OrsEngine(@Nullable Map params) { + super(params); + } + + @Override + public @NonNull EngineType getType() { + return EngineType.ORS; + } + + @NonNull + @Override + public String getStandardUrl() { + return "https://api.openrouteservice.org/v2/directions/"; + } + + @Override + protected void collectAllowedParameters() { + allowParameters(EngineParameter.API_KEY); + } + + @Override + protected void collectAllowedVehicles(@NonNull List vehicles) { + vehicles.add(new VehicleType("driving-car", R.string.routing_engine_vehicle_type_car)); + vehicles.add(new VehicleType("driving-hgv", R.string.routing_engine_vehicle_type_hgv)); + vehicles.add(new VehicleType("cycling-regular", R.string.routing_engine_vehicle_type_cycling_regular)); + vehicles.add(new VehicleType("cycling-road", R.string.routing_engine_vehicle_type_cycling_road)); + vehicles.add(new VehicleType("cycling-mountain", R.string.routing_engine_vehicle_type_cycling_mountain)); + vehicles.add(new VehicleType("cycling-electric", R.string.routing_engine_vehicle_type_cycling_electric)); + vehicles.add(new VehicleType("foot-walking", R.string.routing_engine_vehicle_type_walking)); + vehicles.add(new VehicleType("foot-hiking", R.string.routing_engine_vehicle_type_hiking)); + vehicles.add(new VehicleType("wheelchair", R.string.routing_engine_vehicle_type_wheelchair)); + } + + @Override + protected void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path) { + if (path.size() > 1) { + String vehicleKey = get(EngineParameter.VEHICLE_KEY); + if (!isEmpty(vehicleKey)) { + sb.append(vehicleKey); + } + sb.append('?'); + String apiKey = get(EngineParameter.API_KEY); + if (!isEmpty(apiKey)) { + sb.append("api_key=").append(apiKey); + } + LatLon start = path.get(0); + LatLon end = path.get(path.size() - 1); + sb.append('&').append("start=") + .append(start.getLongitude()).append(',').append(start.getLatitude()); + sb.append('&').append("end=") + .append(end.getLongitude()).append(',').append(end.getLatitude()); + } + } + + @NonNull + @Override + public List parseServerResponse(@NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + JSONArray array = obj.getJSONArray("features").getJSONObject(0) + .getJSONObject("geometry").getJSONArray("coordinates"); + List track = new ArrayList<>(); + for (int i = 0; i < array.length(); i++) { + JSONArray point = array.getJSONArray(i); + double lon = Double.parseDouble(point.getString(0)); + double lat = Double.parseDouble(point.getString(1)); + track.add(new LatLon(lat, lon)); + } + return track; + } + + @Override + public boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + if (obj.has("error")) { + String message = obj.getString("error"); + sb.append(message); + } + return obj.has("features"); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java new file mode 100644 index 0000000000..1ef9c1a622 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java @@ -0,0 +1,84 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.util.GeoPolylineParserUtil; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; +import java.util.Map; + +import static net.osmand.util.Algorithms.isEmpty; + +public class OsrmEngine extends OnlineRoutingEngine { + + public OsrmEngine(@Nullable Map params) { + super(params); + } + + @Override + public @NonNull EngineType getType() { + return EngineType.OSRM; + } + + @NonNull + @Override + public String getStandardUrl() { + return "https://router.project-osrm.org/route/v1/"; + } + + @Override + protected void collectAllowedParameters() { } + + @Override + protected void collectAllowedVehicles(@NonNull List vehicles) { + vehicles.add(new VehicleType("car", R.string.routing_engine_vehicle_type_car)); + vehicles.add(new VehicleType("bike", R.string.routing_engine_vehicle_type_bike)); + vehicles.add(new VehicleType("foot", R.string.routing_engine_vehicle_type_foot)); + } + + @Override + protected void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path) { + String vehicleKey = get(EngineParameter.VEHICLE_KEY); + if (!isEmpty(vehicleKey)) { + sb.append(vehicleKey).append('/'); + } + for (int i = 0; i < path.size(); i++) { + LatLon point = path.get(i); + sb.append(point.getLongitude()).append(',').append(point.getLatitude()); + if (i < path.size() - 1) { + sb.append(';'); + } + } + sb.append('?'); + sb.append("overview=full"); + } + + @NonNull + @Override + public List parseServerResponse(@NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + return GeoPolylineParserUtil.parse( + obj.getJSONArray("routes").getJSONObject(0).getString("geometry"), + GeoPolylineParserUtil.PRECISION_5); + } + + @Override + public boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + if (obj.has("message")) { + String message = obj.getString("message"); + sb.append(message); + } + return obj.has("routes"); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/ui/ExampleLocation.java b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/ExampleLocation.java new file mode 100644 index 0000000000..0a384002f7 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/ExampleLocation.java @@ -0,0 +1,52 @@ +package net.osmand.plus.onlinerouting.ui; + +import androidx.annotation.NonNull; + +import net.osmand.data.LatLon; + +public enum ExampleLocation { + + AMSTERDAM("Amsterdam", + new LatLon(52.379189, 4.899431), + new LatLon(52.308056, 4.764167)), + + BERLIN("Berlin", + new LatLon(52.520008, 13.404954), + new LatLon(52.3666652, 13.501997992)), + + NEW_YORK("New York", + new LatLon(43.000000, -75.000000), + new LatLon(40.641766, -73.780968)), + + PARIS("Paris", + new LatLon(48.864716, 2.349014), + new LatLon(48.948437, 2.434931)); + + ExampleLocation(@NonNull String name, + @NonNull LatLon cityCenterLatLon, + @NonNull LatLon cityAirportLatLon) { + this.name = name; + this.cityCenterLatLon = cityCenterLatLon; + this.cityAirportLatLon = cityAirportLatLon; + } + + private String name; + private LatLon cityCenterLatLon; + private LatLon cityAirportLatLon; + + @NonNull + public String getName() { + return name; + } + + @NonNull + public LatLon getCityCenterLatLon() { + return cityCenterLatLon; + } + + @NonNull + public LatLon getCityAirportLatLon() { + return cityAirportLatLon; + } + +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingCard.java similarity index 69% rename from OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java rename to OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingCard.java index 1b9e7f3b29..4403d512f4 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingCard.java @@ -1,14 +1,17 @@ -package net.osmand.plus.onlinerouting; +package net.osmand.plus.onlinerouting.ui; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; import android.widget.EditText; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -22,7 +25,10 @@ import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.widgets.OsmandTextFieldBoxes; import java.util.List; @@ -39,13 +45,18 @@ public class OnlineRoutingCard extends BaseCard { private OsmandTextFieldBoxes textFieldBoxes; private EditText editText; private TextView tvHelperText; + private TextView tvErrorText; private View bottomDivider; private View button; private OnTextChangedListener onTextChangedListener; + private boolean fieldBoxHelperTextShowed; - public OnlineRoutingCard(@NonNull MapActivity mapActivity, boolean nightMode) { + private ApplicationMode appMode; + + public OnlineRoutingCard(@NonNull MapActivity mapActivity, boolean nightMode, ApplicationMode appMode) { super(mapActivity); this.nightMode = nightMode; + this.appMode = appMode; } @Override @@ -64,9 +75,13 @@ public class OnlineRoutingCard extends BaseCard { textFieldBoxes = view.findViewById(R.id.field_box); editText = view.findViewById(R.id.edit_text); tvHelperText = view.findViewById(R.id.helper_text); + tvErrorText = view.findViewById(R.id.error_text); bottomDivider = view.findViewById(R.id.bottom_divider); button = view.findViewById(R.id.button); + int activeColor = ContextCompat.getColor(app, appMode.getIconColorInfo().getColor(nightMode)); + textFieldBoxes.setPrimaryColor(activeColor); + editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -80,7 +95,7 @@ public class OnlineRoutingCard extends BaseCard { public void afterTextChanged(Editable s) { if (onTextChangedListener != null) { boolean editedByUser = editText.getTag() == null; - String text = editText.getText().toString(); + String text = editText.getText().toString().trim(); onTextChangedListener.onTextChanged(editedByUser, text); } } @@ -107,9 +122,9 @@ public class OnlineRoutingCard extends BaseCard { tvHeaderSubtitle.setText(subtitle); } - public void setSelectionMenu(List items, - String selectedItemTitle, - final CallbackWithObject callback) { + public void setSelectionMenu(@NonNull List items, + @NonNull String selectedItemTitle, + @NonNull final CallbackWithObject callback) { showElements(rvSelectionMenu); rvSelectionMenu.setLayoutManager( new LinearLayoutManager(app, RecyclerView.HORIZONTAL, false)); @@ -122,11 +137,25 @@ public class OnlineRoutingCard extends BaseCard { if (callback.processResult(item)) { adapter.setSelectedItem(item); } + Object obj = item.getObject(); + updateBottomMarginSelectionMenu(obj); } }); + Object item = adapter.getItemByTitle(selectedItemTitle).getObject(); + updateBottomMarginSelectionMenu(item); rvSelectionMenu.setAdapter(adapter); } + private void updateBottomMarginSelectionMenu(Object item) { + if (item instanceof VehicleType) { + VehicleType vt = (VehicleType) item; + boolean hasPadding = vt.equals(OnlineRoutingEngine.CUSTOM_VEHICLE); + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) rvSelectionMenu.getLayoutParams(); + int contentPadding = app.getResources().getDimensionPixelSize(R.dimen.content_padding); + params.bottomMargin = hasPadding ? contentPadding : 0; + } + } + public void setDescription(@NonNull String description) { showElements(tvDescription); tvDescription.setText(description); @@ -139,15 +168,31 @@ public class OnlineRoutingCard extends BaseCard { public void setFieldBoxHelperText(@NonNull String helperText) { showElements(fieldBoxContainer, tvHelperText); + fieldBoxHelperTextShowed = true; tvHelperText.setText(helperText); } + public void showFieldBoxError(@NonNull String errorText) { + showElements(fieldBoxContainer, tvErrorText); + hideElements(tvHelperText); + tvErrorText.setText(errorText); + } + + public void hideFieldBoxError() { + hideElements(tvErrorText); + if (fieldBoxHelperTextShowed) { + showElements(tvHelperText); + } + } + public void setEditedText(@NonNull String text) { - editText.setTag(""); // needed to indicate that the text was edited programmatically + showElements(fieldBoxContainer); + editText.setTag(""); // indicate that the text was edited programmatically editText.setText(text); editText.setTag(null); } + @NonNull public String getEditedText() { return editText.getText().toString(); } @@ -156,7 +201,8 @@ public class OnlineRoutingCard extends BaseCard { showElements(bottomDivider); } - public void setButton(String title, OnClickListener listener) { + public void setButton(@NonNull String title, + @NonNull OnClickListener listener) { showElements(button); button.setOnClickListener(listener); UiUtilities.setupDialogButton(nightMode, button, DialogButtonType.PRIMARY, title); @@ -186,11 +232,11 @@ public class OnlineRoutingCard extends BaseCard { AndroidUiHelper.setVisibility(View.GONE, views); } - public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) { + public void setOnTextChangedListener(@Nullable OnTextChangedListener onTextChangedListener) { this.onTextChangedListener = onTextChangedListener; } public interface OnTextChangedListener { - void onTextChanged(boolean editedByUser, String text); + void onTextChanged(boolean editedByUser, @NonNull String text); } } diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingEngineFragment.java b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingEngineFragment.java new file mode 100644 index 0000000000..adfbfb1bc0 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingEngineFragment.java @@ -0,0 +1,714 @@ +package net.osmand.plus.onlinerouting.ui; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ScrollView; +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.Toolbar; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import net.osmand.AndroidUtils; +import net.osmand.CallbackWithObject; +import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.UiUtilities.DialogButtonType; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingFactory; +import net.osmand.plus.onlinerouting.OnlineRoutingHelper; +import net.osmand.plus.onlinerouting.OnlineRoutingUtils; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.plus.onlinerouting.ui.OnlineRoutingCard.OnTextChangedListener; +import net.osmand.plus.onlinerouting.engine.EngineType; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.util.Algorithms; + +import org.json.JSONException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine.CUSTOM_VEHICLE; + +public class OnlineRoutingEngineFragment extends BaseOsmAndFragment { + + public static final String TAG = OnlineRoutingEngineFragment.class.getSimpleName(); + + private static final String ENGINE_TYPE_KEY = "engine_type"; + private static final String ENGINE_CUSTOM_VEHICLE_KEY = "engine_custom_vehicle"; + private static final String EXAMPLE_LOCATION_KEY = "example_location"; + private static final String APP_MODE_KEY = "app_mode"; + private static final String EDITED_ENGINE_KEY = "edited_engine_key"; + + private OsmandApplication app; + private ApplicationMode appMode; + private MapActivity mapActivity; + private OnlineRoutingHelper helper; + + private View view; + private ViewGroup segmentsContainer; + private OnlineRoutingCard nameCard; + private OnlineRoutingCard typeCard; + private OnlineRoutingCard vehicleCard; + private OnlineRoutingCard apiKeyCard; + private OnlineRoutingCard exampleCard; + private View testResultsContainer; + private View saveButton; + private ScrollView scrollView; + private OnGlobalLayoutListener onGlobalLayout; + private boolean isKeyboardShown = false; + + private OnlineRoutingEngine engine; + private OnlineRoutingEngine initEngine; + private String customVehicleKey; + private ExampleLocation selectedLocation; + private String editedEngineKey; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + app = requireMyApplication(); + mapActivity = getMapActivity(); + helper = app.getOnlineRoutingHelper(); + if (savedInstanceState != null) { + restoreState(savedInstanceState); + } else { + initState(); + } + requireMyActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + public void handleOnBackPressed() { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + showExitDialog(); + } + } + }); + } + + @SuppressLint("ClickableViewAccessibility") + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + view = getInflater().inflate( + R.layout.online_routing_engine_fragment, container, false); + segmentsContainer = (ViewGroup) view.findViewById(R.id.segments_container); + scrollView = (ScrollView) segmentsContainer.getParent(); + if (Build.VERSION.SDK_INT >= 21) { + AndroidUtils.addStatusBarPadding21v(getContext(), view); + } + setupToolbar((Toolbar) view.findViewById(R.id.toolbar)); + + setupNameCard(); + setupTypeCard(); + setupVehicleCard(); + setupApiKeyCard(); + setupExampleCard(); + setupResultsContainer(); + setupButtons(); + + generateUniqueNameIfNeeded(); + updateCardViews(nameCard, typeCard, vehicleCard, exampleCard); + + scrollView.setOnTouchListener(new View.OnTouchListener() { + int scrollViewY = 0; + + @Override + public boolean onTouch(View v, MotionEvent event) { + int y = scrollView.getScrollY(); + if (isKeyboardShown && scrollViewY != y) { + scrollViewY = y; + View focus = mapActivity.getCurrentFocus(); + if (focus != null) { + AndroidUtils.hideSoftKeyboard(mapActivity, focus); + focus.clearFocus(); + } + } + return false; + } + }); + + onGlobalLayout = new ViewTreeObserver.OnGlobalLayoutListener() { + private int layoutHeightPrevious; + private int layoutHeightMin; + + @Override + public void onGlobalLayout() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + view.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + + Rect visibleDisplayFrame = new Rect(); + view.getWindowVisibleDisplayFrame(visibleDisplayFrame); + int layoutHeight = visibleDisplayFrame.bottom; + + if (layoutHeight < layoutHeightPrevious) { + isKeyboardShown = true; + layoutHeightMin = layoutHeight; + } else { + isKeyboardShown = layoutHeight == layoutHeightMin; + } + + if (layoutHeight != layoutHeightPrevious) { + FrameLayout.LayoutParams rootViewLayout = (FrameLayout.LayoutParams) view.getLayoutParams(); + rootViewLayout.height = layoutHeight; + view.requestLayout(); + layoutHeightPrevious = layoutHeight; + } + + view.post(new Runnable() { + @Override + public void run() { + view.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayout); + } + }); + + } + }; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayout); + } + + return view; + } + + private void setupToolbar(Toolbar toolbar) { + ImageView navigationIcon = toolbar.findViewById(R.id.close_button); + navigationIcon.setImageResource(R.drawable.ic_action_close); + navigationIcon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showExitDialog(); + } + }); + TextView title = toolbar.findViewById(R.id.toolbar_title); + toolbar.findViewById(R.id.toolbar_subtitle).setVisibility(View.GONE); + View actionBtn = toolbar.findViewById(R.id.action_button); + if (isEditingMode()) { + title.setText(getString(R.string.edit_online_routing_engine)); + ImageView ivBtn = toolbar.findViewById(R.id.action_button_icon); + ivBtn.setImageDrawable( + getIcon(R.drawable.ic_action_delete_dark, R.color.color_osm_edit_delete)); + actionBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onDeleteEngine(); + dismiss(); + } + }); + } else { + title.setText(getString(R.string.add_online_routing_engine)); + actionBtn.setVisibility(View.GONE); + } + } + + private void setupNameCard() { + nameCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + nameCard.build(mapActivity); + nameCard.setDescription(getString(R.string.select_nav_profile_dialog_message)); + nameCard.setEditedText(engine.getName(app)); + nameCard.setFieldBoxLabelText(getString(R.string.shared_string_name)); + nameCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean changedByUser, @NonNull String text) { + if (changedByUser) { + engine.put(EngineParameter.CUSTOM_NAME, text); + engine.remove(EngineParameter.NAME_INDEX); + checkCustomNameUnique(engine); + } + } + }); + nameCard.showDivider(); + segmentsContainer.addView(nameCard.getView()); + } + + private void setupTypeCard() { + typeCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + typeCard.build(mapActivity); + typeCard.setHeaderTitle(getString(R.string.shared_string_type)); + List serverItems = new ArrayList<>(); + for (EngineType server : EngineType.values()) { + serverItems.add(new HorizontalSelectionItem(server.getTitle(), server)); + } + typeCard.setSelectionMenu(serverItems, engine.getType().getTitle(), + new CallbackWithObject() { + @Override + public boolean processResult(HorizontalSelectionItem result) { + EngineType type = (EngineType) result.getObject(); + if (engine.getType() != type) { + changeEngineType(type); + return true; + } + return false; + } + }); + typeCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean editedByUser, @NonNull String text) { + if (editedByUser) { + engine.put(EngineParameter.CUSTOM_URL, text); + updateCardViews(exampleCard); + } + } + }); + typeCard.setFieldBoxLabelText(getString(R.string.shared_string_server_url)); + typeCard.showDivider(); + segmentsContainer.addView(typeCard.getView()); + } + + private void setupVehicleCard() { + vehicleCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + vehicleCard.build(mapActivity); + vehicleCard.setHeaderTitle(getString(R.string.shared_string_vehicle)); + vehicleCard.setFieldBoxLabelText(getString(R.string.shared_string_custom)); + vehicleCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean editedByUser, @NonNull String text) { + if (editedByUser) { + customVehicleKey = text; + engine.put(EngineParameter.VEHICLE_KEY, customVehicleKey); + updateCardViews(nameCard, exampleCard); + } + } + }); + vehicleCard.setEditedText(customVehicleKey); + vehicleCard.setFieldBoxHelperText(getString(R.string.shared_string_enter_param)); + vehicleCard.showDivider(); + segmentsContainer.addView(vehicleCard.getView()); + setupVehicleTypes(); + } + + private void setupVehicleTypes() { + List vehicleItems = new ArrayList<>(); + for (VehicleType vehicle : engine.getAllowedVehicles()) { + vehicleItems.add(new HorizontalSelectionItem(vehicle.getTitle(app), vehicle)); + } + vehicleCard.setSelectionMenu(vehicleItems, engine.getSelectedVehicleType().getTitle(app), + new CallbackWithObject() { + @Override + public boolean processResult(HorizontalSelectionItem result) { + VehicleType vehicle = (VehicleType) result.getObject(); + if (!Algorithms.objectEquals(engine.getSelectedVehicleType(), vehicle)) { + String vehicleKey = vehicle.equals(CUSTOM_VEHICLE) ? customVehicleKey : vehicle.getKey(); + engine.put(EngineParameter.VEHICLE_KEY, vehicleKey); + generateUniqueNameIfNeeded(); + updateCardViews(nameCard, vehicleCard, exampleCard); + return true; + } + return false; + } + }); + } + + private void setupApiKeyCard() { + apiKeyCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + apiKeyCard.build(mapActivity); + apiKeyCard.setHeaderTitle(getString(R.string.shared_string_api_key)); + apiKeyCard.setFieldBoxLabelText(getString(R.string.keep_it_empty_if_not)); + String apiKey = engine.get(EngineParameter.API_KEY); + if (apiKey != null) { + apiKeyCard.setEditedText(apiKey); + } + apiKeyCard.showDivider(); + apiKeyCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean editedByUser, @NonNull String text) { + if (Algorithms.isBlank(text)) { + engine.remove(EngineParameter.API_KEY); + } else { + engine.put(EngineParameter.API_KEY, text); + } + updateCardViews(exampleCard); + } + }); + segmentsContainer.addView(apiKeyCard.getView()); + } + + private void setupExampleCard() { + exampleCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + exampleCard.build(mapActivity); + exampleCard.setHeaderTitle(getString(R.string.shared_string_example)); + List locationItems = new ArrayList<>(); + for (ExampleLocation location : ExampleLocation.values()) { + locationItems.add(new HorizontalSelectionItem(location.getName(), location)); + } + exampleCard.setSelectionMenu(locationItems, selectedLocation.getName(), + new CallbackWithObject() { + @Override + public boolean processResult(HorizontalSelectionItem result) { + ExampleLocation location = (ExampleLocation) result.getObject(); + if (selectedLocation != location) { + selectedLocation = location; + updateCardViews(exampleCard); + return true; + } + return false; + } + }); + exampleCard.setDescription(getString(R.string.online_routing_example_hint)); + exampleCard.showFieldBox(); + exampleCard.setButton(getString(R.string.test_route_calculation), new View.OnClickListener() { + @Override + public void onClick(View v) { + testEngineWork(); + } + }); + segmentsContainer.addView(exampleCard.getView()); + } + + private void setupResultsContainer() { + testResultsContainer = getInflater().inflate( + R.layout.bottom_sheet_item_with_descr_64dp, segmentsContainer, false); + testResultsContainer.setVisibility(View.INVISIBLE); + segmentsContainer.addView(testResultsContainer); + } + + private void setupButtons() { + boolean nightMode = isNightMode(); + View cancelButton = view.findViewById(R.id.dismiss_button); + UiUtilities.setupDialogButton(nightMode, cancelButton, + DialogButtonType.SECONDARY, R.string.shared_string_cancel); + cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showExitDialog(); + } + }); + + view.findViewById(R.id.buttons_divider).setVisibility(View.VISIBLE); + + saveButton = view.findViewById(R.id.right_bottom_button); + UiUtilities.setupDialogButton(nightMode, saveButton, + UiUtilities.DialogButtonType.PRIMARY, R.string.shared_string_save); + saveButton.setVisibility(View.VISIBLE); + saveButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onSaveEngine(); + dismiss(); + } + }); + } + + private void changeEngineType(EngineType type) { + OnlineRoutingEngine tmp = (OnlineRoutingEngine) engine.clone(); + engine = OnlineRoutingFactory.createEngine(type, tmp.getParams()); + + // after changing the type, select the vehicle + // with the same name that was selected before + VehicleType previous = tmp.getSelectedVehicleType(); + VehicleType next = null; + for (VehicleType vt : engine.getAllowedVehicles()) { + if (Algorithms.objectEquals(previous.getTitle(app), vt.getTitle(app))) { + next = vt; + break; + } + } + String vehicleKey; + if (next != null) { + vehicleKey = next.equals(CUSTOM_VEHICLE) ? customVehicleKey : next.getKey(); + } else { + vehicleKey = engine.getAllowedVehicles().get(0).getKey(); + } + engine.put(EngineParameter.VEHICLE_KEY, vehicleKey); + + setupVehicleTypes(); + generateUniqueNameIfNeeded(); + updateCardViews(nameCard, typeCard, vehicleCard, exampleCard); + } + + private void generateUniqueNameIfNeeded() { + if (engine.get(EngineParameter.CUSTOM_NAME) == null) { + List cachedEngines = helper.getEnginesExceptMentionedKeys(editedEngineKey); + OnlineRoutingUtils.generateUniqueName(app, engine, cachedEngines); + } + } + + private void checkCustomNameUnique(@NonNull OnlineRoutingEngine engine) { + if (hasNameDuplicate(engine)) { + nameCard.showFieldBoxError(getString(R.string.message_name_is_already_exists)); + saveButton.setEnabled(false); + } else { + nameCard.hideFieldBoxError(); + saveButton.setEnabled(true); + } + } + + private boolean hasNameDuplicate(@NonNull OnlineRoutingEngine engine) { + List cachedEngines = helper.getEnginesExceptMentionedKeys(editedEngineKey); + return OnlineRoutingUtils.hasNameDuplicate(app, engine.getName(app), cachedEngines); + } + + private void onSaveEngine() { + if (engine != null) { + helper.saveEngine(engine); + } + } + + private void onDeleteEngine() { + helper.deleteEngine(engine); + } + + private boolean isEditingMode() { + return editedEngineKey != null; + } + + private String getTestUrl() { + List path = new ArrayList<>(); + path.add(selectedLocation.getCityCenterLatLon()); + path.add(selectedLocation.getCityAirportLatLon()); + return engine.getFullUrl(path); + } + + private void testEngineWork() { + final OnlineRoutingEngine requestedEngine = (OnlineRoutingEngine) engine.clone(); + final ExampleLocation location = selectedLocation; + new Thread(new Runnable() { + @Override + public void run() { + StringBuilder message = new StringBuilder(); + boolean resultOk = false; + try { + String response = helper.makeRequest(exampleCard.getEditedText()); + resultOk = requestedEngine.parseServerMessage(message, response); + } catch (IOException | JSONException e) { + message.append(e.toString()); + } + showTestResults(resultOk, message.toString(), location); + } + }).start(); + } + + private void showTestResults(final boolean resultOk, + final @NonNull String message, + final @NonNull ExampleLocation location) { + app.runInUIThread(new Runnable() { + @Override + public void run() { + testResultsContainer.setVisibility(View.VISIBLE); + ImageView ivImage = testResultsContainer.findViewById(R.id.icon); + TextView tvTitle = testResultsContainer.findViewById(R.id.title); + TextView tvDescription = testResultsContainer.findViewById(R.id.description); + if (resultOk) { + ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_gdirections_dark)); + tvTitle.setText(getString(R.string.shared_string_ok)); + } else { + ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_alert)); + tvTitle.setText(String.format(getString(R.string.message_server_error), message)); + } + tvDescription.setText(location.getName()); + } + }); + } + + private void updateCardViews(@NonNull BaseCard... cardsToUpdate) { + for (BaseCard card : cardsToUpdate) { + if (nameCard.equals(card)) { + if (Algorithms.isEmpty(engine.get(EngineParameter.CUSTOM_NAME))) { + nameCard.setEditedText(engine.getName(app)); + } + + } else if (typeCard.equals(card)) { + typeCard.setHeaderSubtitle(engine.getType().getTitle()); + typeCard.setEditedText(engine.getBaseUrl()); + if (engine.isParameterAllowed(EngineParameter.API_KEY)) { + apiKeyCard.show(); + } else { + apiKeyCard.hide(); + } + + } else if (vehicleCard.equals(card)) { + VehicleType vt = engine.getSelectedVehicleType(); + vehicleCard.setHeaderSubtitle(vt.getTitle(app)); + if (vt.equals(CUSTOM_VEHICLE)) { + vehicleCard.showFieldBox(); + vehicleCard.setEditedText(customVehicleKey); + } else { + vehicleCard.hideFieldBox(); + } + + } else if (exampleCard.equals(card)) { + exampleCard.setEditedText(getTestUrl()); + } + } + } + + public void showExitDialog() { + View focus = view.findFocus(); + AndroidUtils.hideSoftKeyboard(mapActivity, focus); + if (!engine.equals(initEngine)) { + AlertDialog.Builder dismissDialog = createWarningDialog(mapActivity, + R.string.shared_string_dismiss, R.string.exit_without_saving, R.string.shared_string_cancel); + dismissDialog.setPositiveButton(R.string.shared_string_exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + dismissDialog.show(); + } else { + dismiss(); + } + } + + private AlertDialog.Builder createWarningDialog(Activity activity, int title, int message, int negButton) { + Context themedContext = UiUtilities.getThemedContext(activity, isNightMode()); + AlertDialog.Builder warningDialog = new AlertDialog.Builder(themedContext); + warningDialog.setTitle(getString(title)); + warningDialog.setMessage(getString(message)); + warningDialog.setNegativeButton(negButton, null); + return warningDialog; + } + + private void dismiss() { + FragmentActivity activity = getActivity(); + if (activity != null) { + FragmentManager fragmentManager = activity.getSupportFragmentManager(); + if (!fragmentManager.isStateSaved()) { + fragmentManager.popBackStack(); + } + } + } + + private boolean isNightMode() { + return !app.getSettings().isLightContentForMode(getAppMode()); + } + + @NonNull + private ApplicationMode getAppMode() { + return appMode != null ? appMode : app.getSettings().getApplicationMode(); + } + + @Nullable + private MapActivity getMapActivity() { + FragmentActivity activity = getActivity(); + if (activity instanceof MapActivity) { + return (MapActivity) activity; + } else { + return null; + } + } + + private LayoutInflater getInflater() { + return UiUtilities.getInflater(mapActivity, isNightMode()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayout); + } else { + view.getViewTreeObserver().removeGlobalOnLayoutListener(onGlobalLayout); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + saveState(outState); + } + + private void saveState(@NonNull Bundle outState) { + outState.putString(ENGINE_TYPE_KEY, engine.getType().name()); + for (EngineParameter key : EngineParameter.values()) { + String value = engine.get(key); + if (value != null) { + outState.putString(key.name(), value); + } + } + outState.putString(ENGINE_CUSTOM_VEHICLE_KEY, customVehicleKey); + outState.putString(EXAMPLE_LOCATION_KEY, selectedLocation.name()); + outState.putString(APP_MODE_KEY, getAppMode().getStringKey()); + outState.putString(EDITED_ENGINE_KEY, editedEngineKey); + } + + private void restoreState(@NonNull Bundle savedState) { + editedEngineKey = savedState.getString(EngineParameter.KEY.name()); + initEngine = createInitStateEngine(); + String typeKey = savedState.getString(ENGINE_TYPE_KEY); + EngineType type = EngineType.getTypeByName(typeKey); + engine = OnlineRoutingFactory.createEngine(type); + for (EngineParameter key : EngineParameter.values()) { + String value = savedState.getString(key.name()); + if (value != null) { + engine.put(key, value); + } + } + customVehicleKey = savedState.getString(ENGINE_CUSTOM_VEHICLE_KEY); + selectedLocation = ExampleLocation.valueOf(savedState.getString(EXAMPLE_LOCATION_KEY)); + appMode = ApplicationMode.valueOfStringKey(savedState.getString(APP_MODE_KEY), null); + } + + private void initState() { + initEngine = createInitStateEngine(); + selectedLocation = ExampleLocation.values()[0]; + engine = (OnlineRoutingEngine) initEngine.clone(); + if (Algorithms.objectEquals(engine.getSelectedVehicleType(), CUSTOM_VEHICLE)) { + customVehicleKey = engine.get(EngineParameter.VEHICLE_KEY); + } else { + customVehicleKey = ""; + } + } + + private OnlineRoutingEngine createInitStateEngine() { + OnlineRoutingEngine engine; + OnlineRoutingEngine editedEngine = helper.getEngineByKey(editedEngineKey); + if (editedEngine != null) { + engine = (OnlineRoutingEngine) editedEngine.clone(); + } else { + engine = OnlineRoutingFactory.createEngine(EngineType.values()[0]); + String vehicle = engine.getAllowedVehicles().get(0).getKey(); + engine.put(EngineParameter.VEHICLE_KEY, vehicle); + if (editedEngineKey != null) { + engine.put(EngineParameter.KEY, editedEngineKey); + } + } + return engine; + } + + public static void showInstance(@NonNull FragmentActivity activity, + @NonNull ApplicationMode appMode, + @Nullable String editedEngineKey) { + FragmentManager fm = activity.getSupportFragmentManager(); + if (!fm.isStateSaved() && fm.findFragmentByTag(OnlineRoutingEngineFragment.TAG) == null) { + OnlineRoutingEngineFragment fragment = new OnlineRoutingEngineFragment(); + fragment.appMode = appMode; + fragment.editedEngineKey = editedEngineKey; + fm.beginTransaction() + .add(R.id.fragmentContainer, fragment, TAG) + .addToBackStack(TAG).commitAllowingStateLoss(); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java b/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java index 46a35ea67e..fd0f593462 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java +++ b/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java @@ -1,12 +1,27 @@ package net.osmand.plus.profiles; +import androidx.annotation.NonNull; + import net.osmand.plus.R; public class OnlineRoutingEngineDataObject extends ProfileDataObject { + private int order; + public OnlineRoutingEngineDataObject(String name, String description, - String stringKey) { + String stringKey, + int order) { super(name, description, stringKey, R.drawable.ic_world_globe_dark, false, null); + this.order = order; + } + + @Override + public int compareTo(@NonNull ProfileDataObject profileDataObject) { + if (profileDataObject instanceof OnlineRoutingEngineDataObject) { + OnlineRoutingEngineDataObject another = (OnlineRoutingEngineDataObject) profileDataObject; + return (this.order < another.order) ? -1 : ((this.order == another.order) ? 0 : 1); + } + return 0; } } diff --git a/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java b/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java index 28967ba2f3..6306dee9cc 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java +++ b/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java @@ -5,7 +5,7 @@ import android.content.Context; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.profiles.RoutingProfileDataObject.RoutingProfilesResources; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.router.GeneralRouter; @@ -56,7 +56,22 @@ public class ProfileDataUtils { Collections.sort(fileNames, new Comparator() { @Override public int compare(String s, String t1) { - return s.equals(OSMAND_NAVIGATION) ? -1 : t1.equals(OSMAND_NAVIGATION) ? 1 : s.compareToIgnoreCase(t1); + // OsmAnd navigation should be at the top of the list + if (s.equals(OSMAND_NAVIGATION)) { + return -1; + } else if (t1.equals(OSMAND_NAVIGATION)) { + return 1; + + // Online navigation should be at the bottom of the list + } else if (s.equals(ONLINE_NAVIGATION)) { + return 1; + } else if (t1.equals(ONLINE_NAVIGATION)) { + return -1; + + // Other sorted by file names + } else { + return s.compareToIgnoreCase(t1); + } } }); for (String fileName : fileNames) { @@ -71,9 +86,11 @@ public class ProfileDataUtils { public static List getOnlineRoutingProfiles(OsmandApplication app) { List objects = new ArrayList<>(); - for (OnlineRoutingEngine engine : app.getOnlineRoutingHelper().getEngines()) { + List engines = app.getOnlineRoutingHelper().getEngines(); + for (int i = 0; i < engines.size(); i++) { + OnlineRoutingEngine engine = engines.get(i); objects.add(new OnlineRoutingEngineDataObject( - engine.getName(app), engine.getBaseUrl(), engine.getStringKey())); + engine.getName(app), engine.getBaseUrl(), engine.getStringKey(), i)); } return objects; } diff --git a/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java b/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java index 98ca4771cc..eb97a8ae85 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java @@ -39,8 +39,9 @@ import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.bottomsheets.BasePreferenceBottomSheet; -import net.osmand.plus.onlinerouting.OnlineRoutingEngineFragment; +import net.osmand.plus.onlinerouting.ui.OnlineRoutingEngineFragment; import net.osmand.router.RoutingConfiguration; +import net.osmand.util.Algorithms; import org.apache.commons.logging.Log; @@ -250,7 +251,7 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet { tvTitle.setText(profile.getName()); tvDescription.setText(profile.getDescription()); - boolean isSelected = setupSelected && profile.getStringKey().equals(selectedItemKey); + boolean isSelected = setupSelected && Algorithms.objectEquals(profile.getStringKey(), selectedItemKey); int iconColor; if (dialogMode == DialogMode.NAVIGATION_PROFILE) { iconColor = isSelected ? activeColorResId : iconDefaultColorResId; diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java index db1c3c96d6..47b4b037df 100644 --- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java +++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java @@ -639,7 +639,7 @@ public class ResourceManager { collectFiles(context.getAppPath(IndexConstants.WIKI_INDEX_DIR), IndexConstants.BINARY_MAP_INDEX_EXT, files); collectFiles(context.getAppPath(IndexConstants.WIKIVOYAGE_INDEX_DIR), IndexConstants.BINARY_TRAVEL_GUIDE_MAP_INDEX_EXT, files); } - if (OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) != null || InAppPurchaseHelper.isSubscribedToLiveUpdates(context)) { + if (OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) != null || InAppPurchaseHelper.isContourLinesPurchased(context)) { collectFiles(context.getAppPath(IndexConstants.SRTM_INDEX_DIR), IndexConstants.BINARY_MAP_INDEX_EXT, files); } diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java index 89c17f1c2b..96017bc96c 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java @@ -2,7 +2,6 @@ package net.osmand.plus.routing; import android.content.Context; -import android.os.Build; import android.os.Bundle; import android.util.Base64; @@ -20,16 +19,14 @@ import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.LatLon; import net.osmand.data.LocationPoint; import net.osmand.data.WptLocationPoint; -import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine; import net.osmand.plus.onlinerouting.OnlineRoutingHelper; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.R; import net.osmand.plus.TargetPointsHelper; import net.osmand.plus.TargetPointsHelper.TargetPoint; -import net.osmand.plus.Version; import net.osmand.plus.render.NativeOsmandLibrary; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.router.GeneralRouter; @@ -48,21 +45,16 @@ import net.osmand.router.RoutingConfiguration.Builder; import net.osmand.router.RoutingContext; import net.osmand.router.TurnType; import net.osmand.util.Algorithms; -import net.osmand.util.GeoPolylineParserUtil; import net.osmand.util.MapUtils; import org.json.JSONException; -import org.json.JSONObject; import org.xml.sax.SAXException; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.net.MalformedURLException; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1210,8 +1202,12 @@ public class RouteProvider { private RouteCalculationResult findOnlineRoute(RouteCalculationParams params) throws IOException, JSONException { OnlineRoutingHelper helper = params.ctx.getOnlineRoutingHelper(); String stringKey = params.mode.getRoutingProfile(); - List route = helper.calculateRouteOnline(helper.getEngineByKey(stringKey), getFullPathFromParams(params)); - if (!route.isEmpty()) { + OnlineRoutingEngine engine = helper.getEngineByKey(stringKey); + List route = null; + if (engine != null) { + route = helper.calculateRouteOnline(engine, getFullPathFromParams(params)); + } + if (!Algorithms.isEmpty(route)) { List res = new ArrayList<>(); for (LatLon pt : route) { WptPt wpt = new WptPt(); diff --git a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java index 03af123cc8..1addb2f50a 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java +++ b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java @@ -194,7 +194,7 @@ public class RoutingHelper { } public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, List intermediatePoints, Location currentLocation) { - RoutingHelperUtils.checkAndUpdateStartLocation(app, currentLocation); + RoutingHelperUtils.checkAndUpdateStartLocation(app, currentLocation, false); RouteCalculationResult previousRoute = route; clearCurrentRoute(finalLocation, intermediatePoints); // to update route diff --git a/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java b/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java index b756238eca..6803a2920f 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java +++ b/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java @@ -8,9 +8,6 @@ import net.osmand.data.LatLon; import net.osmand.data.QuadRect; import net.osmand.plus.OsmandApplication; import net.osmand.plus.TargetPointsHelper; -import net.osmand.plus.helpers.enums.MetricsConstants; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.util.MapUtils; import java.util.List; @@ -164,19 +161,20 @@ public class RoutingHelperUtils { } - public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, LatLon newStartLocation) { + public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, LatLon newStartLocation, boolean force) { if (newStartLocation != null) { LatLon lastStartLocation = app.getSettings().getLastStartPoint(); - if (lastStartLocation == null || MapUtils.getDistance(newStartLocation, lastStartLocation) > CACHE_RADIUS) { + if (lastStartLocation == null || MapUtils.getDistance(newStartLocation, lastStartLocation) > CACHE_RADIUS || force) { app.getMapViewTrackingUtilities().detectDrivingRegion(newStartLocation); app.getSettings().setLastStartPoint(newStartLocation); } } } - public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, Location nextStartLocation) { + public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, Location nextStartLocation, boolean force) { if (nextStartLocation != null) { - checkAndUpdateStartLocation(app, new LatLon(nextStartLocation.getLatitude(), nextStartLocation.getLongitude())); + LatLon newStartLocation = new LatLon(nextStartLocation.getLatitude(), nextStartLocation.getLongitude()); + checkAndUpdateStartLocation(app, newStartLocation, force); } } } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java index 779a97d1df..6da9eb590e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java @@ -7,9 +7,11 @@ import androidx.annotation.Nullable; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingUtils; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.onlinerouting.OnlineRoutingHelper; +import net.osmand.util.Algorithms; import org.json.JSONException; import org.json.JSONObject; @@ -19,7 +21,8 @@ import java.util.List; public class OnlineRoutingSettingsItem extends CollectionSettingsItem { - private OnlineRoutingHelper onlineRoutingHelper; + private OnlineRoutingHelper helper; + private List otherEngines; public OnlineRoutingSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { super(app, null, items); @@ -36,8 +39,9 @@ public class OnlineRoutingSettingsItem extends CollectionSettingsItem(onlineRoutingHelper.getEngines()); + helper = app.getOnlineRoutingHelper(); + existingItems = new ArrayList<>(helper.getEngines()); + otherEngines = new ArrayList<>(existingItems); } @NonNull @@ -66,12 +70,18 @@ public class OnlineRoutingSettingsItem extends CollectionSettingsItem points = article.getGpxFile().getPoints(); WptPt gpxPoint = null; String coordinates = url.replace(PREFIX_GEO, ""); double lat; double lon; try { - lat = Double.valueOf(coordinates.substring(0, coordinates.indexOf(","))); - lon = Double.valueOf(coordinates.substring(coordinates.indexOf(",") + 1, - coordinates.length())); + lat = Double.parseDouble(coordinates.substring(0, coordinates.indexOf(","))); + lon = Double.parseDouble(coordinates.substring(coordinates.indexOf(",") + 1)); } catch (NumberFormatException e) { Log.w(TAG, e.getMessage(), e); return true; } for (WptPt point : points) { - if (point.getLatitude() == lat && point.getLongitude() == lon) { + if (MapUtils.getDistance(point.getLatitude(), point.getLongitude(), lat, lon) < ROUNDING_ERROR) { gpxPoint = point; break; } } if (gpxPoint != null) { final OsmandSettings settings = app.getSettings(); - settings.setMapLocationToShow(lat, lon, settings.getLastKnownMapZoom(), + settings.setMapLocationToShow(gpxPoint.getLatitude(), gpxPoint.getLongitude(), + settings.getLastKnownMapZoom(), new PointDescription(PointDescription.POINT_TYPE_WPT, gpxPoint.name), false, gpxPoint); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java index 960608a784..1018874d26 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java @@ -104,7 +104,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { WikivoyageSearchResult articleItem = listAdapter.getArticleItem(groupPosition, childPosition); - sendResults(articleItem.getArticleId()); + sendResults(articleItem.getArticleTitle()); dismiss(); return true; } @@ -113,10 +113,10 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { WikivoyageSearchResult articleItem = (WikivoyageSearchResult) listAdapter.getGroup(groupPosition); - if (Algorithms.isEmpty(articleItem.getArticleRouteId())) { + if (Algorithms.isEmpty(articleItem.getArticleTitle())) { Toast.makeText(getContext(), R.string.wiki_article_not_found, Toast.LENGTH_LONG).show(); } else { - sendResults(articleItem.getArticleId()); + sendResults(articleItem.getArticleTitle()); dismiss(); } return true; @@ -150,14 +150,14 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr return nightMode ? R.color.wikivoyage_bottom_bar_bg_dark : R.color.list_background_color_light; } - private void sendResults(TravelArticleIdentifier articleId) { - WikivoyageArticleDialogFragment.showInstance(getMyApplication(), getFragmentManager(), articleId, selectedLang); + private void sendResults(String title) { + WikivoyageArticleDialogFragment.showInstanceByTitle(getMyApplication(), getFragmentManager(), title, selectedLang); } public static boolean showInstance(@NonNull FragmentManager fm, - @Nullable Fragment targetFragment, - @NonNull TravelArticleIdentifier articleId, - @NonNull String selectedLang) { + @Nullable Fragment targetFragment, + @NonNull TravelArticleIdentifier articleId, + @NonNull String selectedLang) { try { Bundle args = new Bundle(); args.putParcelable(ARTICLE_ID_KEY, articleId); @@ -239,7 +239,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr boolean isLastChild, View convertView, ViewGroup parent) { WikivoyageSearchResult articleItem = getArticleItem(groupPosition, childPosition); String childTitle = articleItem.getArticleTitle(); - boolean selected = articleItem.getArticleId().equals(articleId) || parentsList.contains(childTitle); + boolean selected = childTitle.equals(article.getTitle()) || parentsList.contains(childTitle); if (convertView == null) { convertView = LayoutInflater.from(context) diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java index 0989a7c2e1..cf38b7265f 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java @@ -31,6 +31,7 @@ public class TravelArticle { String title; String content; String isPartOf; + String isParentOf = ""; double lat = Double.NaN; double lon = Double.NaN; String imageTitle; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java index 983663f3d1..cdc166e231 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java @@ -172,6 +172,7 @@ public class TravelObfHelper implements TravelHelper { res.title = Algorithms.isEmpty(title) ? amenity.getName() : title; res.content = amenity.getDescription(lang); res.isPartOf = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IS_PART, lang)); + res.isParentOf = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IS_PARENT_OF, lang)); res.lat = amenity.getLocation().getLatitude(); res.lon = amenity.getLocation().getLongitude(); res.imageTitle = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, null)); @@ -192,7 +193,8 @@ public class TravelObfHelper implements TravelHelper { GPXFile gpxFile = null; List pointList = getPointList(article); if (!Algorithms.isEmpty(pointList)) { - gpxFile = new GPXFile(article.getTitle(), article.getLang(), ""); + gpxFile = new GPXFile(article.getTitle(), article.getLang(), article.getContent()); + gpxFile.metadata.link = TravelArticle.getImageUrl(article.getImageTitle(), false); for (Amenity amenity : pointList) { WptPt wptPt = createWptPt(amenity, article.getLang()); gpxFile.addPoint(wptPt); @@ -400,64 +402,29 @@ public class TravelObfHelper implements TravelHelper { Map> navMap = new HashMap<>(); Set headers = new LinkedHashSet<>(); Map headerObjs = new HashMap<>(); - Map> amenityMap = new HashMap<>(); - for (BinaryMapIndexReader reader : getReaders()) { - try { - SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, - Integer.MAX_VALUE, 0, Integer.MAX_VALUE, -1, getSearchFilter(false), new ResultMatcher() { - - @Override - public boolean publish(Amenity amenity) { - String isPartOf = amenity.getTagContent(Amenity.IS_PART, lang); - if (Algorithms.stringsEqual(title, isPartOf)) { - return true; - } else if (parts != null && parts.length > 0) { - String title = amenity.getName(lang); - title = Algorithms.isEmpty(title) ? amenity.getName() : title; - for (int i = 0; i < parts.length; i++) { - String part = parts[i]; - if (i == 0 && Algorithms.stringsEqual(part, title) || Algorithms.stringsEqual(part, isPartOf)) { - return true; - } - } - } - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - }); - List amenities = reader.searchPoi(req); - if (!Algorithms.isEmpty(amenities)) { - amenityMap.put(reader.getFile(), amenities); - } - } catch (Exception e) { - LOG.error(e.getMessage(), e); - } - } if (parts != null && parts.length > 0) { headers.addAll(Arrays.asList(parts)); - headers.add(title); } - if (!Algorithms.isEmpty(amenityMap)) { - for (Entry> entry : amenityMap.entrySet()) { - File file = entry.getKey(); - for (Amenity amenity : entry.getValue()) { - Set nameLangs = getLanguages(amenity); - if (nameLangs.contains(lang)) { - TravelArticle a = readArticle(file, amenity, lang, false); - WikivoyageSearchResult rs = new WikivoyageSearchResult(a, new ArrayList<>(nameLangs)); - List l = navMap.get(rs.isPartOf); - if (l == null) { - l = new ArrayList<>(); - navMap.put(rs.isPartOf, l); - } - l.add(rs); - if (headers.contains(a.getTitle())) { - headerObjs.put(a.getTitle(), rs); - } + + for (String header : headers) { + TravelArticle parentArticle = getParentArticleByTitle(header, lang); + if (parentArticle == null) { + continue; + } + navMap.put(header, new ArrayList()); + String[] isParentOf = parentArticle.isParentOf.split(";"); + for (String childTitle : isParentOf) { + if (!childTitle.isEmpty()) { + WikivoyageSearchResult searchResult = new WikivoyageSearchResult("", childTitle, null, + null, Collections.singletonList(lang)); + List resultList = navMap.get(header); + if (resultList == null) { + resultList = new ArrayList<>(); + navMap.put(header, resultList); + } + resultList.add(searchResult); + if (headers.contains(childTitle)) { + headerObjs.put(childTitle, searchResult); } } } @@ -482,6 +449,41 @@ public class TravelObfHelper implements TravelHelper { return res; } + private TravelArticle getParentArticleByTitle(final String title, final String lang) { + TravelArticle article = null; + final List amenities = new ArrayList<>(); + for (BinaryMapIndexReader reader : getReaders()) { + try { + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( + 0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(false), + new ResultMatcher() { + boolean done = false; + + @Override + public boolean publish(Amenity amenity) { + if (Algorithms.stringsEqual(title, Algorithms.emptyIfNull(amenity.getName(lang)))) { + amenities.add(amenity); + done = true; + } + return false; + } + + @Override + public boolean isCancelled() { + return done; + } + }, null); + reader.searchPoiByName(req); + } catch (IOException e) { + LOG.error(e.getMessage()); + } + if (!Algorithms.isEmpty(amenities)) { + article = readArticle(reader.getFile(), amenities.get(0), lang, false); + } + } + return article; + } + @Override public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) { TravelArticle article = getCachedArticle(articleId, lang, true); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java index c53027fb7a..f4e4408cba 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java @@ -11,6 +11,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -48,8 +49,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv private static boolean SHOW_TRAVEL_UPDATE_CARD = true; private static boolean SHOW_TRAVEL_NEEDED_MAPS_CARD = true; - @Nullable - private ExploreRvAdapter adapter = new ExploreRvAdapter(); + private final ExploreRvAdapter adapter = new ExploreRvAdapter(); private boolean nightMode; @Nullable @@ -64,10 +64,9 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv @Nullable private IndexItem mainIndexItem; - private List neededIndexItems = new ArrayList<>(); + private final List neededIndexItems = new ArrayList<>(); private boolean waitForIndexes; - @SuppressWarnings("RedundantCast") @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -117,7 +116,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv @Override public void downloadInProgress() { OsmandApplication app = getMyApplication(); - if (app != null && adapter != null) { + if (app != null) { IndexItem current = app.getDownloadThread().getCurrentDownloadingItem(); if (current != null && current != currentDownloadingIndexItem) { currentDownloadingIndexItem = current; @@ -147,7 +146,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv @Override public void savedArticlesUpdated() { - if (adapter != null && isAdded()) { + if (isAdded()) { adapter.notifyDataSetChanged(); } } @@ -155,7 +154,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv @Nullable private WikivoyageExploreActivity getExploreActivity() { Activity activity = getActivity(); - if (activity != null && activity instanceof WikivoyageExploreActivity) { + if (activity instanceof WikivoyageExploreActivity) { return (WikivoyageExploreActivity) activity; } else { return null; @@ -163,33 +162,27 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv } public void invalidateAdapter() { - if (adapter != null) { - adapter.notifyDataSetChanged(); - } + adapter.notifyDataSetChanged(); } public void populateData() { final List items = new ArrayList<>(); - final OsmandApplication app = getMyApplication(); + final FragmentActivity activity = getActivity(); + final OsmandApplication app = activity != null ? (OsmandApplication) activity.getApplication() : null; if (app != null) { - if (adapter != null) { - FragmentManager fm = getFragmentManager(); - if (fm != null) { - if (!Version.isPaidVersion(app)) { - items.add(new OpenBetaTravelCard(app, nightMode, fm)); - } - if (app.getTravelHelper().isAnyTravelBookPresent()) { - items.add(new HeaderTravelCard(app, nightMode, getString(R.string.popular_destinations))); - - List popularArticles = app.getTravelHelper().getPopularArticles(); - for (TravelArticle article : popularArticles) { - items.add(new ArticleTravelCard(app, nightMode, article, fm)); - } - } - } - items.add(new StartEditingTravelCard(app, getMyActivity(), nightMode)); - adapter.setItems(items); + if (!Version.isPaidVersion(app) && !OpenBetaTravelCard.isClosed()) { + items.add(new OpenBetaTravelCard(activity, nightMode)); } + if (app.getTravelHelper().isAnyTravelBookPresent()) { + items.add(new HeaderTravelCard(app, nightMode, getString(R.string.popular_destinations))); + + List popularArticles = app.getTravelHelper().getPopularArticles(); + for (TravelArticle article : popularArticles) { + items.add(new ArticleTravelCard(app, nightMode, article, activity.getSupportFragmentManager())); + } + } + items.add(new StartEditingTravelCard(activity, nightMode)); + adapter.setItems(items); final DownloadIndexesThread downloadThread = app.getDownloadThread(); if (!downloadThread.getIndexes().isDownloadedFromInternet) { waitForIndexes = true; @@ -230,7 +223,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv private void addDownloadUpdateCard() { final OsmandApplication app = getMyApplication(); - if (app != null && adapter != null) { + if (app != null) { final DownloadIndexesThread downloadThread = app.getDownloadThread(); boolean outdated = mainIndexItem != null && mainIndexItem.isOutdated(); @@ -248,7 +241,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv downloadUpdateCard.setListener(new TravelDownloadUpdateCard.ClickListener() { @Override public void onPrimaryButtonClick() { - if (mainIndexItem != null && downloadManager != null && adapter != null) { + if (mainIndexItem != null && downloadManager != null) { downloadManager.startDownload(getMyActivity(), mainIndexItem); adapter.updateDownloadUpdateCard(false); } @@ -256,7 +249,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv @Override public void onSecondaryButtonClick() { - if (downloadUpdateCard.isLoading() && adapter != null) { + if (downloadUpdateCard.isLoading()) { downloadThread.cancelDownload(mainIndexItem); adapter.updateDownloadUpdateCard(false); } else if (!downloadUpdateCard.isDownload()) { @@ -266,7 +259,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv Activity activity = getActivity(); if (activity != null) { Intent newIntent = new Intent(activity, - getMyApplication().getAppCustomization().getDownloadActivity()); + ((OsmandApplication) activity.getApplication()).getAppCustomization().getDownloadActivity()); newIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); activity.startActivity(newIntent); } @@ -281,12 +274,12 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv private void addNeededMapsCard() { final OsmandApplication app = getMyApplication(); - if (app != null && !neededIndexItems.isEmpty() && adapter != null && SHOW_TRAVEL_NEEDED_MAPS_CARD) { + if (app != null && !neededIndexItems.isEmpty() && SHOW_TRAVEL_NEEDED_MAPS_CARD) { neededMapsCard = new TravelNeededMapsCard(app, nightMode, neededIndexItems); neededMapsCard.setListener(new TravelNeededMapsCard.CardListener() { @Override public void onPrimaryButtonClick() { - if (adapter != null && downloadManager != null) { + if (downloadManager != null) { downloadManager.startDownload(getMyActivity(), getAllItemsForDownload()); adapter.updateNeededMapsCard(false); } @@ -295,10 +288,8 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv @Override public void onSecondaryButtonClick() { if (neededMapsCard.isDownloading()) { - if (adapter != null) { - app.getDownloadThread().cancelDownload(neededIndexItems); - adapter.updateNeededMapsCard(false); - } + app.getDownloadThread().cancelDownload(neededIndexItems); + adapter.updateNeededMapsCard(false); } else { SHOW_TRAVEL_NEEDED_MAPS_CARD = false; removeNeededMapsCard(); @@ -319,9 +310,7 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv } else if (!item.isDownloaded() && downloadManager != null) { downloadManager.startDownload(getMyActivity(), item); } - if (adapter != null) { - adapter.updateNeededMapsCard(false); - } + adapter.updateNeededMapsCard(false); } } }); @@ -341,63 +330,61 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv } private void removeDownloadUpdateCard() { - if (adapter != null) { - adapter.removeDownloadUpdateCard(); - } + adapter.removeDownloadUpdateCard(); downloadUpdateCard = null; } private void removeNeededMapsCard() { - if (adapter != null) { - adapter.removeNeededMapsCard(); - } + adapter.removeNeededMapsCard(); neededMapsCard = null; } private static class ProcessIndexItemsTask extends AsyncTask>> { - private static DownloadActivityType[] types = new DownloadActivityType[]{ + private static final DownloadActivityType[] types = new DownloadActivityType[]{ DownloadActivityType.NORMAL_FILE, DownloadActivityType.WIKIPEDIA_FILE }; - private OsmandApplication app; - private WeakReference weakFragment; + private final OsmandApplication app; + private final WeakReference weakFragment; - private String fileName; + private final String fileName; ProcessIndexItemsTask(ExploreTabFragment fragment) { app = fragment.getMyApplication(); weakFragment = new WeakReference<>(fragment); - fileName = app.getTravelHelper().getWikivoyageFileName(); + fileName = app != null ? app.getTravelHelper().getWikivoyageFileName() : null; } @Override protected Pair> doInBackground(Void... voids) { - IndexItem mainItem = app.getDownloadThread().getIndexes().getWikivoyageItem(fileName); + if (fileName != null) { + IndexItem mainItem = app.getDownloadThread().getIndexes().getWikivoyageItem(fileName); - List neededItems = new ArrayList<>(); - for (TravelArticle article : app.getTravelHelper().getBookmarksHelper().getSavedArticles()) { - LatLon latLon = new LatLon(article.getLat(), article.getLon()); - try { - for (DownloadActivityType type : types) { - IndexItem item = DownloadResources.findSmallestIndexItemAt(app, latLon, type); - if (item != null && !item.isDownloaded() && !neededItems.contains(item)) { - neededItems.add(item); + List neededItems = new ArrayList<>(); + for (TravelArticle article : app.getTravelHelper().getBookmarksHelper().getSavedArticles()) { + LatLon latLon = new LatLon(article.getLat(), article.getLon()); + try { + for (DownloadActivityType type : types) { + IndexItem item = DownloadResources.findSmallestIndexItemAt(app, latLon, type); + if (item != null && !item.isDownloaded() && !neededItems.contains(item)) { + neededItems.add(item); + } } + } catch (IOException e) { + // ignore } - } catch (IOException e) { - e.printStackTrace(); } + return new Pair<>(mainItem, neededItems); } - - return new Pair<>(mainItem, neededItems); + return null; } @Override protected void onPostExecute(Pair> res) { ExploreTabFragment fragment = weakFragment.get(); - if (fragment != null && fragment.isResumed()) { + if (res != null && fragment != null && fragment.isResumed()) { fragment.addIndexItemCards(res.first, res.second); } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/BaseTravelCard.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/BaseTravelCard.java index f64d44617a..0b31e1d31b 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/BaseTravelCard.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/BaseTravelCard.java @@ -17,7 +17,7 @@ public abstract class BaseTravelCard { protected OsmandApplication app; protected boolean nightMode; - public BaseTravelCard(OsmandApplication app, boolean nightMode) { + public BaseTravelCard(@NonNull OsmandApplication app, boolean nightMode) { this.app = app; this.nightMode = nightMode; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/OpenBetaTravelCard.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/OpenBetaTravelCard.java index 9ed0824250..1d8c8464fa 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/OpenBetaTravelCard.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/OpenBetaTravelCard.java @@ -5,22 +5,28 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.RecyclerView; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; +import net.osmand.plus.wikivoyage.explore.WikivoyageExploreActivity; public class OpenBetaTravelCard extends BaseTravelCard { public static final int TYPE = 0; - private FragmentManager fragmentManager; + private static boolean closed = false; - public OpenBetaTravelCard(OsmandApplication app, boolean nightMode, FragmentManager fragmentManager) { - super(app, nightMode); - this.fragmentManager = fragmentManager; + private final FragmentActivity activity; + + public OpenBetaTravelCard(@NonNull FragmentActivity activity, boolean nightMode) { + super((OsmandApplication) activity.getApplication(), nightMode); + this.activity = activity; + } + + public static boolean isClosed() { + return closed; } @Override @@ -30,11 +36,14 @@ public class OpenBetaTravelCard extends BaseTravelCard { holder.title.setText(R.string.welcome_to_open_beta); holder.description.setText(R.string.welcome_to_open_beta_description); holder.backgroundImage.setImageResource(R.drawable.img_help_wikivoyage_articles); - holder.button.setText(R.string.get_unlimited_access); + //holder.button.setText(R.string.get_unlimited_access); + holder.button.setText(R.string.shared_string_close); holder.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ChoosePlanDialogFragment.showWikivoyageInstance(fragmentManager); + closed = true; + ((WikivoyageExploreActivity) activity).updateFragments(); + //ChoosePlanDialogFragment.showWikivoyageInstance(activity.getSupportFragmentManager()); } }); } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/StartEditingTravelCard.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/StartEditingTravelCard.java index ee3a9aad1a..f81365438e 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/StartEditingTravelCard.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/StartEditingTravelCard.java @@ -1,13 +1,12 @@ package net.osmand.plus.wikivoyage.explore.travelcards; -import android.app.Activity; -import android.content.Context; import android.net.Uri; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.RecyclerView; import net.osmand.plus.OsmandApplication; @@ -18,11 +17,11 @@ public class StartEditingTravelCard extends BaseTravelCard { public static final int TYPE = 1; - private Context context; + private final FragmentActivity activity; - public StartEditingTravelCard(OsmandApplication app, Activity context, boolean nightMode) { - super(app, nightMode); - this.context = context; + public StartEditingTravelCard(@NonNull FragmentActivity activity, boolean nightMode) { + super((OsmandApplication) activity.getApplication(), nightMode); + this.activity = activity; } @Override @@ -36,7 +35,7 @@ public class StartEditingTravelCard extends BaseTravelCard { holder.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - WikipediaDialogFragment.showFullArticle(context, + WikipediaDialogFragment.showFullArticle(activity, Uri.parse("https://" + app.getLanguage().toLowerCase() + ".m.wikivoyage.org"), nightMode); } }); diff --git a/build.gradle b/build.gradle index 2f7736470a..e59a694b17 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } dependencies { //classpath 'com.android.tools.build:gradle:2.+' - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.1.2' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"