diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a831f18e4d..991dc2191e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,69 +1,66 @@ # All non-assigned. * @organicmaps/mergers # Visual design. -/android/app/src/main/res/drawable*/ @organicmaps/design -/android/app/src/main/res/font/ @organicmaps/design -/android/app/src/main/res/mipmap*/ @organicmaps/design -/data/*.ttf @organicmaps/design -/data/resources-svg/ @organicmaps/design -/data/search-icons/ @organicmaps/design -/iphone/Maps/Images.xcassets/ @organicmaps/design +android/app/src/main/res/drawable*/ @organicmaps/design +android/app/src/main/res/font/ @organicmaps/design +android/app/src/main/res/mipmap*/ @organicmaps/design +data/*.ttf @organicmaps/design +data/resources*/ @organicmaps/design +data/search-icons/ @organicmaps/design +data/styles/default/light/**/*.png @organicmaps/design +data/styles/default/light/**/*.svg @organicmaps/design +data/styles/default/dark/**/*.png @organicmaps/design +data/styles/default/dark/**/*.svg @organicmaps/design +iphone/Maps/Images.xcassets/ @organicmaps/design # Android. -/android/ @organicmaps/android -/android/app/src/main/java/app/organicmaps/car/ @organicmaps/android-auto -/docs/ANDROID_LOCATION_TEST.md @organicmaps/android -/docs/JAVA_STYLE.md @organicmaps/android -# no owner for translation changes -/android/app/src/main/res/values*/strings.xml +android/ @organicmaps/android +android/app/src/main/java/app/organicmaps/car/ @organicmaps/android-auto +docs/ANDROID_LOCATION_TEST.md @organicmaps/android +docs/JAVA_STYLE.md @organicmaps/android # iOS. -/iphone/ @organicmaps/ios -/xcode/ @organicmaps/ios -/docs/OBJC_STYLE.md @organicmaps/ios -# no owner for translation changes -/iphone/plist.txt -/iphone/Maps/LocalizedStrings/ +iphone/ @organicmaps/ios +xcode/ @organicmaps/ios +docs/OBJC_STYLE.md @organicmaps/ios # Qt -/qt/ @organicmaps/qt +qt/ @organicmaps/qt # Rendering -/drape/ @organicmaps/rendering -/drape_frontend/ @organicmaps/rendering +drape/ @organicmaps/rendering +drape_frontend/ @organicmaps/rendering # Map Data. -/tools/python/maps_generator/ @organicmaps/data -/generator/ @organicmaps/data -/topography_generator/ @organicmaps/data -/data/borders/ @organicmaps/data -/data/conf/isolines/ @organicmaps/data -/docs/SUBWAY_GENERATION.md @organicmaps/data -/docs/MAPS.md @organicmaps/data -/docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @organicmaps/data -# no owner (changed often to add a new POI) -/generator/generator_tests/osm_type_test.cpp +tools/python/maps_generator/ @organicmaps/data +generator/ @organicmaps/data +topography_generator/ @organicmaps/data +data/borders/ @organicmaps/data +data/conf/isolines/ @organicmaps/data +docs/SUBWAY_GENERATION.md @organicmaps/data +docs/MAPS.md @organicmaps/data +docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @organicmaps/data # Map Styles. -/data/styles/ @organicmaps/styles -/data/types.txt @organicmaps/styles -/data/visibility.txt @organicmaps/styles -/data/mapcss-mapping.csv @organicmaps/styles -/data/replaced_tags.txt @organicmaps/styles -/data/classificator.txt @organicmaps/styles -/data/drules_* @organicmaps/styles -/docs/STYLES.md -/tools/kothic/ @organicmaps/styles +data/styles/ @organicmaps/styles +data/types.txt @organicmaps/styles +data/visibility.txt @organicmaps/styles +data/mapcss-mapping.csv @organicmaps/styles +data/replaced_tags.txt @organicmaps/styles +data/classificator.txt @organicmaps/styles +data/drules_* @organicmaps/styles +docs/STYLES.md +tools/kothic/ @organicmaps/styles # DevOps. -/.github/workflows @organicmaps/devops -/android/*gradle* @organicmaps/devops -/docs/RELEASE_MANAGEMENT.md @organicmaps/devops -/xcode/fastlane/ @organicmaps/devops +.github/workflows @organicmaps/devops +android/*gradle* @organicmaps/devops +docs/RELEASE_MANAGEMENT.md @organicmaps/devops +xcode/fastlane/ @organicmaps/devops # Growth. README.md @organicmaps/growth -/.github/FUNDING.yml @organicmaps/growth -/android/app/src/fdroid/play/ @organicmaps/growth -/android/app/src/google/play/ @organicmaps/growth -/iphone/metadata/ @organicmaps/growth +.github/FUNDING.yml @organicmaps/growth +android/app/src/fdroid/play/ @organicmaps/growth +android/app/src/google/play/ @organicmaps/growth +iphone/metadata/ @organicmaps/growth # Legal. LEGAL @organicmaps/legal LICENSE @organicmaps/legal NOTICE @organicmaps/legal CONTRIBUTORS @organicmaps/legal -/docs/CODE_OF_CONDUCT.md @organicmaps/legal -/docs/DCO.md @organicmaps/legal -/docs/GOVERNANCE.md @organicmaps/legal +docs/CODE_OF_CONDUCT.md @organicmaps/legal +docs/DCO.md @organicmaps/legal +docs/GOVERNANCE.md @organicmaps/legal diff --git a/.github/workflows/android-release.yaml b/.github/workflows/android-release.yaml index fe2a2c999b..db205b7f51 100644 --- a/.github/workflows/android-release.yaml +++ b/.github/workflows/android-release.yaml @@ -29,7 +29,7 @@ jobs: version=$(tools/unix/version.sh ios_version) # +1 because below a "Bump versions" commit is created. # TODO: Find a way to refactor FDroid versioning without that additional commit. - build=$(($(tools/unix/version.sh count) + 1)) + build=$(($(tools/unix/version.sh ios_build) + 1)) code=$(($(tools/unix/version.sh android_code) + 1)) tag=$version-$build-android echo "::set-output name=version::$version" diff --git a/CMakeLists.txt b/CMakeLists.txt index 95913ee8c2..30c1a0e496 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,16 @@ find_package(Threads REQUIRED) # Scripts +if (NOT CMAKE_HOST_WIN32) + execute_process( + COMMAND "${OMIM_ROOT}/tools/unix/check_cert.sh" + RESULT_VARIABLE CheckCertResult + ) + if (CheckCertResult) + message(FATAL_ERROR "Certificate check failed") + endif() +endif() + if (NOT PLATFORM_IPHONE AND NOT PLATFORM_ANDROID) list(APPEND qt_components Core Network) if (NOT SKIP_QT_GUI OR NOT SKIP_TESTS OR PYBINDINGS) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 4c177e7ed7..e2a8243541 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,73 +1,35 @@ -This file contains a list of people who have contributed to this project. -Its not neccesarily comprehensive. -Feel free to add yourself here along with your first contribution! +This file contains a list of people who have contributed to the +public version of MAPS.ME and Organic Maps. + +Original MAPS.ME (MapsWithMe) design and implementation: + Yury Melnichek + Alexander Borsuk + Viktor Govako + Siarhei Rachytski -------------------------------------------------------------------------------- -Organic Maps (formerly OMaps) contributors: -(in alphabetic order) +Organic Maps contributions: -------------------------------------------------------------------------------- + Alexander Borsuk + Roman Tsisyk + Viktor Govako + Caspar Nuël + Konstantin Pastbin + Nishant Bhandari + Sebastiao Sousa + Harry Bond + +Organic Maps translations: + Karina Kordon + Konstantin Pastbin + Metehan Özyürek + Joan Montané + Luna Rose -Alexander Borsuk -Alexey Krasilnikov -Andrew Shkrob -Anton Makouski -Arnaud Vergnet -Arthur-GYT -Atemu -Caspar Nuël -cyber-toad -David Martinez -dbf -Dzmitry Strekha -Dzmitry Yarmolenka -Fabian Wüthrich -Ferenc Géczi -Filip Czaplicki -FinixFighter -fparri -Francesco Gazzetta -gallegonovato -Gonzalo Pesquero -Harry Bond -Jaime Marquinez Ferrandiz -Jean-Baptiste Charron -Jenny Em -Joan Montané -Karina Kordon -Kavi Khalique -Kiryl Kaveryn -Kiryl Razhdzestvenski -Konstantin Pastbin -Loïc Hernaut -Lukas Hamm -Lukas Kronberger -Luna Rose -map-per -Markku Huotari -Mateusz Konieczny -Matheus Gomes -MbTy1 -Meenbeese -Metehan Özyürek -Michał Brzozowski -Nishant Bhandari -Ognjen Blagojevic -Osyotr -renderexpert -Roman Kuznetsov -Roman Tsisyk -Rudo Kemper -Sebastiao Sousa -Sergiy Kozyr -Tobias G. -Veniamin Gvozdikov -Viktor Govako -Will Bradley -------------------------------------------------------------------------------- -MAPS.ME at Mail.Ru Group (prior to the Organic Maps (OMaps) fork in 2020/2021): +MAPS.ME contributions (before Organic Maps was forked in 2020-2021): -------------------------------------------------------------------------------- - Code contributions: Dmitry Yunitski Lev Dragunov @@ -138,13 +100,3 @@ Special thanks to: Yuri Gurski Dmitry Matveev Anna Yakovleva - --------------------------------------------------------------------------------- -MAPS.ME (originally MapsWithMe) design and implementation at MapsWithMe GmbH -(from 2010 till the acquisition by Mail.ru Group in 2014): --------------------------------------------------------------------------------- - -Yury Melnichek -Alexander Borsuk -Viktor Govako -Siarhei Rachytski diff --git a/android/app/build.gradle b/android/app/build.gradle index ce3873bc2d..e710eb6550 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,7 +22,7 @@ buildscript { googleFirebaseServicesDefault dependencies { - classpath 'com.android.tools.build:gradle:8.7.3' + classpath 'com.android.tools.build:gradle:8.7.2' if (googleFirebaseServicesEnabled) { println('Building with Google Firebase Services') @@ -100,7 +100,7 @@ android { // All properties are read from gradle.properties file compileSdk propCompileSdkVersion.toInteger() - ndkVersion '27.2.12479018' + ndkVersion '27.1.12297006' defaultConfig { // Default package name is taken from the manifest and should be app.organicmaps @@ -363,7 +363,7 @@ android { } dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.3' // Google Play Location Services // @@ -379,7 +379,7 @@ dependencies { huaweiImplementation 'com.google.android.gms:play-services-location:21.3.0' // This is the microG project's re-implementation which is permissible on // F-droid because it's Apache-2.0. - fdroidImplementation 'org.microg.gms:play-services-location:0.3.6.244735' + fdroidImplementation 'org.microg.gms:play-services-location:0.3.4.240913' // Google Firebase Services if (googleFirebaseServicesEnabled) { @@ -396,11 +396,11 @@ dependencies { // We don't use Kotlin, but some dependencies are actively using it. // See https://stackoverflow.com/a/75719642 implementation 'androidx.core:core:1.15.0' - implementation(platform('org.jetbrains.kotlin:kotlin-bom:2.1.10')) + implementation(platform('org.jetbrains.kotlin:kotlin-bom:2.0.21')) implementation 'androidx.annotation:annotation:1.9.1' implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'androidx.car.app:app:1.7.0-rc01' - implementation 'androidx.car.app:app-projected:1.7.0-rc01' + implementation 'androidx.car.app:app:1.7.0-beta03' + implementation 'androidx.car.app:app-projected:1.7.0-beta03' implementation 'androidx.constraintlayout:constraintlayout:2.2.0' implementation 'androidx.fragment:fragment:1.8.5' implementation 'androidx.preference:preference:1.2.1' @@ -417,7 +417,7 @@ dependencies { // Test Dependencies androidTestImplementation 'androidx.test.ext:junit:1.2.1' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:5.15.2' + testImplementation 'org.mockito:mockito-core:5.12.0' testImplementation 'org.mockito:mockito-inline:5.2.0' } diff --git a/android/app/src/fdroid/play/listings/ar/release-notes.txt b/android/app/src/fdroid/play/listings/ar/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ar/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ar/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/az-AZ/release-notes.txt b/android/app/src/fdroid/play/listings/az-AZ/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/az-AZ/release-notes.txt +++ b/android/app/src/fdroid/play/listings/az-AZ/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/be/release-notes.txt b/android/app/src/fdroid/play/listings/be/release-notes.txt index b052b5da47..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/be/release-notes.txt +++ b/android/app/src/fdroid/play/listings/be/release-notes.txt @@ -1,8 +1,8 @@ -• Новыя дадзеныя OpenStreetMap ад 21 студзеня -• Адлюстраванне абмежавання хуткасці ў рэжыме навігацыі -• Android Auto: сартаванне метак, палепшаны запыты на дазвол месцазнаходжання -• На карту дададзены вежы -• Дададзена настройка для адключэння спасылак на гатэлі з Kayak.com і магчымасць адмовы пры першым выкарыстанні -• Дададзены сербская (кірыліца) і латышская мовы +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…і шматлікае іншае на omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/bg/release-notes.txt b/android/app/src/fdroid/play/listings/bg/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/bg/release-notes.txt +++ b/android/app/src/fdroid/play/listings/bg/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/bn-BD/release-notes.txt b/android/app/src/fdroid/play/listings/bn-BD/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/bn-BD/release-notes.txt +++ b/android/app/src/fdroid/play/listings/bn-BD/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ca/release-notes.txt b/android/app/src/fdroid/play/listings/ca/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ca/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ca/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/cs-CZ/release-notes.txt b/android/app/src/fdroid/play/listings/cs-CZ/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/cs-CZ/release-notes.txt +++ b/android/app/src/fdroid/play/listings/cs-CZ/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/da-DK/release-notes.txt b/android/app/src/fdroid/play/listings/da-DK/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/da-DK/release-notes.txt +++ b/android/app/src/fdroid/play/listings/da-DK/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/de-DE/release-notes.txt b/android/app/src/fdroid/play/listings/de-DE/release-notes.txt index df2aa3aab8..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/de-DE/release-notes.txt +++ b/android/app/src/fdroid/play/listings/de-DE/release-notes.txt @@ -1,8 +1,8 @@ -• Neue OpenStreetMap-Daten vom 21. Januar -• Anzeige eines Geschwindigkeitsbegrenzungsschildes im Navigationsmodus -• Serbische (Kyrillisch) und Lettische Sprachen hinzugefügt -• Android Auto: Lesezeichen sortieren, verbessertes Erlebnis bei der Anforderung von Standortberechtigungen -• Turm-POIs hinzugefügt -• Eine Einstellung für Kayak.com-Hotellinks und eine Opt-out-Option bei der ersten Nutzung hinzugefügt. +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…weitere Details unter omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/el-GR/release-notes.txt b/android/app/src/fdroid/play/listings/el-GR/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/el-GR/release-notes.txt +++ b/android/app/src/fdroid/play/listings/el-GR/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/en-US/release-notes.txt b/android/app/src/fdroid/play/listings/en-US/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/en-US/release-notes.txt +++ b/android/app/src/fdroid/play/listings/en-US/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/es-ES/release-notes.txt b/android/app/src/fdroid/play/listings/es-ES/release-notes.txt index 0a9fd32cdb..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/es-ES/release-notes.txt +++ b/android/app/src/fdroid/play/listings/es-ES/release-notes.txt @@ -1,8 +1,8 @@ -• Nuevos datos de OpenStreetMap del 21 de enero -• Límite de velocidad en el modo de navegación -• Añadidos idiomas serbio (cirílico) y letón -• Android Auto: opción de ordenar marcadores, mejorada la experiencia al solicitar ubicación -• Añadidos PDI de torres -• Añadidas opciones para enlaces de hotel de Kayak.com y de exclusión al primer uso +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…más detalles en omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/et/release-notes.txt b/android/app/src/fdroid/play/listings/et/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/et/release-notes.txt +++ b/android/app/src/fdroid/play/listings/et/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/eu-ES/release-notes.txt b/android/app/src/fdroid/play/listings/eu-ES/release-notes.txt index 0b7543ed85..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/eu-ES/release-notes.txt +++ b/android/app/src/fdroid/play/listings/eu-ES/release-notes.txt @@ -1,8 +1,8 @@ -• Urtarrilaren 21ko OpenStreetMap-eko datu berriak -• Abiadura muga erakusten da nabigazio moduan -• Serbiera (zirilikoa) eta letoniera hizkuntzak gehitu dira -• Android Auto: gogokoak ordena daitezke, kokapen permisuaren eskaeraren esperientzia hobetu da -• Dorreen interes puntuak gehitu dira -• Kayak.com-eko hotelen estekak eta lehenengo erabileran ez erakusteko aukera gehitu dira +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…zehaztasun gehiago omaps.org/news webgunean +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/fa/release-notes.txt b/android/app/src/fdroid/play/listings/fa/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/fa/release-notes.txt +++ b/android/app/src/fdroid/play/listings/fa/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/fi-FI/release-notes.txt b/android/app/src/fdroid/play/listings/fi-FI/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/fi-FI/release-notes.txt +++ b/android/app/src/fdroid/play/listings/fi-FI/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/fr-FR/release-notes.txt b/android/app/src/fdroid/play/listings/fr-FR/release-notes.txt index c36173293d..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/fr-FR/release-notes.txt +++ b/android/app/src/fdroid/play/listings/fr-FR/release-notes.txt @@ -1,8 +1,8 @@ -• Nouvelles données OpenStreetMap du 21 janvier -• Afficher un panneau de limitation de vitesse en mode navigation -• Ajout des langues serbe (cyrillique) et lettone -• Android Auto : trier les signets, expérience de demande d'autorisation de localisation améliorée -• Ajout de POI de tour -• Ajout d'un paramètre pour les liens d'hôtel Kayak.com et d'une option de désinscription lors de la première utilisation +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…plus de détails sur omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/gl-ES/release-notes.txt b/android/app/src/fdroid/play/listings/gl-ES/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/gl-ES/release-notes.txt +++ b/android/app/src/fdroid/play/listings/gl-ES/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/gu/release-notes.txt b/android/app/src/fdroid/play/listings/gu/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/gu/release-notes.txt +++ b/android/app/src/fdroid/play/listings/gu/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/hi-IN/release-notes.txt b/android/app/src/fdroid/play/listings/hi-IN/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/hi-IN/release-notes.txt +++ b/android/app/src/fdroid/play/listings/hi-IN/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/hr/release-notes.txt b/android/app/src/fdroid/play/listings/hr/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/hr/release-notes.txt +++ b/android/app/src/fdroid/play/listings/hr/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/hu-HU/release-notes.txt b/android/app/src/fdroid/play/listings/hu-HU/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/hu-HU/release-notes.txt +++ b/android/app/src/fdroid/play/listings/hu-HU/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/id/release-notes.txt b/android/app/src/fdroid/play/listings/id/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/id/release-notes.txt +++ b/android/app/src/fdroid/play/listings/id/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/it-IT/release-notes.txt b/android/app/src/fdroid/play/listings/it-IT/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/it-IT/release-notes.txt +++ b/android/app/src/fdroid/play/listings/it-IT/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/iw-IL/release-notes.txt b/android/app/src/fdroid/play/listings/iw-IL/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/iw-IL/release-notes.txt +++ b/android/app/src/fdroid/play/listings/iw-IL/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ja-JP/release-notes.txt b/android/app/src/fdroid/play/listings/ja-JP/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ja-JP/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ja-JP/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ka-GE/release-notes.txt b/android/app/src/fdroid/play/listings/ka-GE/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ka-GE/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ka-GE/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/kk/release-notes.txt b/android/app/src/fdroid/play/listings/kk/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/kk/release-notes.txt +++ b/android/app/src/fdroid/play/listings/kk/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/km-KH/release-notes.txt b/android/app/src/fdroid/play/listings/km-KH/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/km-KH/release-notes.txt +++ b/android/app/src/fdroid/play/listings/km-KH/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/kn-IN/release-notes.txt b/android/app/src/fdroid/play/listings/kn-IN/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/kn-IN/release-notes.txt +++ b/android/app/src/fdroid/play/listings/kn-IN/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ko-KR/release-notes.txt b/android/app/src/fdroid/play/listings/ko-KR/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ko-KR/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ko-KR/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/lo-LA/release-notes.txt b/android/app/src/fdroid/play/listings/lo-LA/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/lo-LA/release-notes.txt +++ b/android/app/src/fdroid/play/listings/lo-LA/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/lt/release-notes.txt b/android/app/src/fdroid/play/listings/lt/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/lt/release-notes.txt +++ b/android/app/src/fdroid/play/listings/lt/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/lv/release-notes.txt b/android/app/src/fdroid/play/listings/lv/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/lv/release-notes.txt +++ b/android/app/src/fdroid/play/listings/lv/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/mk-MK/release-notes.txt b/android/app/src/fdroid/play/listings/mk-MK/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/mk-MK/release-notes.txt +++ b/android/app/src/fdroid/play/listings/mk-MK/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ml-IN/release-notes.txt b/android/app/src/fdroid/play/listings/ml-IN/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ml-IN/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ml-IN/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/mr-IN/release-notes.txt b/android/app/src/fdroid/play/listings/mr-IN/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/mr-IN/release-notes.txt +++ b/android/app/src/fdroid/play/listings/mr-IN/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ms/release-notes.txt b/android/app/src/fdroid/play/listings/ms/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ms/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ms/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ne-NP/release-notes.txt b/android/app/src/fdroid/play/listings/ne-NP/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ne-NP/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ne-NP/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/nl-NL/release-notes.txt b/android/app/src/fdroid/play/listings/nl-NL/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/nl-NL/release-notes.txt +++ b/android/app/src/fdroid/play/listings/nl-NL/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/no-NO/release-notes.txt b/android/app/src/fdroid/play/listings/no-NO/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/no-NO/release-notes.txt +++ b/android/app/src/fdroid/play/listings/no-NO/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/pl-PL/release-notes.txt b/android/app/src/fdroid/play/listings/pl-PL/release-notes.txt index 3d4be6ae08..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/pl-PL/release-notes.txt +++ b/android/app/src/fdroid/play/listings/pl-PL/release-notes.txt @@ -1,8 +1,8 @@ -• Nowe dane OpenStreetMap z 21 stycznia -• Wyświetlanie znaku ograniczenia prędkości w trybie nawigacji -• Dodano języki serbski (cyrylica) i łotewski -• Android Auto: sortowanie zakładek, ulepszono prośbę o pozwolenie na lokalizację -• Dodano POI dla wieży -• Dodano ustawienie dla linków do hoteli na Kayak.com oraz możliwość wyłączenia ich przy pierwszym uruchomieniu +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…więcej szczegółów na omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/pt-BR/release-notes.txt b/android/app/src/fdroid/play/listings/pt-BR/release-notes.txt index e650e516cf..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/pt-BR/release-notes.txt +++ b/android/app/src/fdroid/play/listings/pt-BR/release-notes.txt @@ -1,8 +1,8 @@ -• Novos dados do OpenStreetMap de 21 de janeiro -• Exibe um sinal de limite de velocidade no modo de navegação -• Adicionados os idiomas sérvio (cirílico) e letão -• Android Auto: classificar favoritos, experiência aprimorada de solicitação de permissão de localização -• Adicionados POIs de torre -• Adicionada uma configuração para links de hotéis Kayak.com e uma opção de cancelamento de primeiro uso +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…mais detalhes em omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/pt-PT/release-notes.txt b/android/app/src/fdroid/play/listings/pt-PT/release-notes.txt index d94a826494..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/pt-PT/release-notes.txt +++ b/android/app/src/fdroid/play/listings/pt-PT/release-notes.txt @@ -1,8 +1,8 @@ -• Novos dados do OpenStreetMap a 21 de janeiro -• Exibir um sinal de limite de velocidade no modo de navegação -• Adicionados os idiomas sérvio (cirílico) e letão -• Android Auto: classificar favoritos, experiência melhorada de pedido de permissão de localização -• Adicionados POIs de torre -• Foi adicionada uma configuração para o Kayak.com Hotel Links e uma opção de cancelamento de primeira utilização +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…mais detalhes em omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/ro/release-notes.txt b/android/app/src/fdroid/play/listings/ro/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ro/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ro/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/ru-RU/release-notes.txt b/android/app/src/fdroid/play/listings/ru-RU/release-notes.txt index fa886873b0..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ru-RU/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ru-RU/release-notes.txt @@ -1,8 +1,8 @@ -• Новые данные OpenStreetMap от 21 января -• Отображение ограничения скорости в режиме навигации -• Android Auto: сортировка меток, улучшены запросы на разрешение местоположения -• На карту добавлены башни -• Добавлена ​​настройка для отключения ссылок на отели с Kayak.com и возможность отказа при первом использовании -• Добавлены сербский (кириллица) и латышский языки +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…и многое другое на omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/si-LK/release-notes.txt b/android/app/src/fdroid/play/listings/si-LK/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/si-LK/release-notes.txt +++ b/android/app/src/fdroid/play/listings/si-LK/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/sk/release-notes.txt b/android/app/src/fdroid/play/listings/sk/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/sk/release-notes.txt +++ b/android/app/src/fdroid/play/listings/sk/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/sl/release-notes.txt b/android/app/src/fdroid/play/listings/sl/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/sl/release-notes.txt +++ b/android/app/src/fdroid/play/listings/sl/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/sr/release-notes.txt b/android/app/src/fdroid/play/listings/sr/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/sr/release-notes.txt +++ b/android/app/src/fdroid/play/listings/sr/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/sv-SE/release-notes.txt b/android/app/src/fdroid/play/listings/sv-SE/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/sv-SE/release-notes.txt +++ b/android/app/src/fdroid/play/listings/sv-SE/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/th/release-notes.txt b/android/app/src/fdroid/play/listings/th/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/th/release-notes.txt +++ b/android/app/src/fdroid/play/listings/th/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/tr-TR/release-notes.txt b/android/app/src/fdroid/play/listings/tr-TR/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/tr-TR/release-notes.txt +++ b/android/app/src/fdroid/play/listings/tr-TR/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/uk/release-notes.txt b/android/app/src/fdroid/play/listings/uk/release-notes.txt index 0768e9d739..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/uk/release-notes.txt +++ b/android/app/src/fdroid/play/listings/uk/release-notes.txt @@ -1,8 +1,8 @@ -• Нові дані OpenStreetMap від 21 січня -• Знак перевищення швидкості підчас навігації -• Додано перклади сербською (кирилиця) та латиською мовами -• Android Auto: сортування міток, покращено процес запиту на отримання дозволу на розміщення -• Вежі відображаються на мапі -• Додано налаштування для посилань до Kayak.com для готелів та демонструється запит при першому використанні +• New OpenStreetMap data as of January 8 +• Dispay a speed limit sign in the navigation mode +• Added Serbian (Cyrillic) and Latvian languages +• Android Auto: sort bookmarks, improved location permission request experience +• Added tower POIs +• Added a setting for Kayak.com Hotel Links and a first-use opt-out option -…більше подробиць на omaps.org/news +…more details at omaps.org/news diff --git a/android/app/src/fdroid/play/listings/ur/release-notes.txt b/android/app/src/fdroid/play/listings/ur/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/ur/release-notes.txt +++ b/android/app/src/fdroid/play/listings/ur/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/vi/release-notes.txt b/android/app/src/fdroid/play/listings/vi/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/vi/release-notes.txt +++ b/android/app/src/fdroid/play/listings/vi/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/zh-CN/release-notes.txt b/android/app/src/fdroid/play/listings/zh-CN/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/zh-CN/release-notes.txt +++ b/android/app/src/fdroid/play/listings/zh-CN/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/zh-HK/release-notes.txt b/android/app/src/fdroid/play/listings/zh-HK/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/zh-HK/release-notes.txt +++ b/android/app/src/fdroid/play/listings/zh-HK/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/listings/zh-TW/release-notes.txt b/android/app/src/fdroid/play/listings/zh-TW/release-notes.txt index bcb36d5c86..4cf01fa7a5 100644 --- a/android/app/src/fdroid/play/listings/zh-TW/release-notes.txt +++ b/android/app/src/fdroid/play/listings/zh-TW/release-notes.txt @@ -1,4 +1,4 @@ -• New OpenStreetMap data as of January 21 +• New OpenStreetMap data as of January 8 • Dispay a speed limit sign in the navigation mode • Added Serbian (Cyrillic) and Latvian languages • Android Auto: sort bookmarks, improved location permission request experience diff --git a/android/app/src/fdroid/play/version.yaml b/android/app/src/fdroid/play/version.yaml index dd7e63b29c..745d42c337 100644 --- a/android/app/src/fdroid/play/version.yaml +++ b/android/app/src/fdroid/play/version.yaml @@ -1 +1 @@ -version: 2025.01.26-9-FDroid+25012609 +version: 2024.11.27-12-FDroid+24112712 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 86bdadc494..67ed66ef78 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -37,7 +37,6 @@ - @@ -122,7 +119,7 @@ - + @@ -346,8 +343,7 @@ + android:configChanges="orientation|screenLayout|screenSize"/> - - @@ -383,7 +372,6 @@ @@ -391,7 +379,6 @@ @@ -409,7 +396,6 @@ @@ -417,7 +403,6 @@ @@ -425,7 +410,6 @@ @@ -464,16 +448,14 @@ - - - - - + - - - - diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 91a173d7b1..25a839155c 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -22,11 +22,13 @@ set(SRC app/organicmaps/vulkan/android_vulkan_context_factory.hpp # JNI sources + app/organicmaps/sdk/search/DisplayedCategories.cpp + app/organicmaps/sdk/search/SearchEngine.cpp + app/organicmaps/sdk/search/SearchRecents.cpp app/organicmaps/core/jni_helper.cpp app/organicmaps/core/jni_java_methods.cpp app/organicmaps/core/logging.cpp app/organicmaps/bookmarks/data/BookmarkManager.cpp - app/organicmaps/DisplayedCategories.cpp app/organicmaps/DownloadResourcesLegacyActivity.cpp app/organicmaps/editor/Editor.cpp app/organicmaps/editor/OpeningHours.cpp @@ -38,8 +40,6 @@ set(SRC app/organicmaps/MapManager.cpp app/organicmaps/MwmApplication.cpp app/organicmaps/routing/RoutingOptions.cpp - app/organicmaps/SearchEngine.cpp - app/organicmaps/SearchRecents.cpp app/organicmaps/settings/UnitLocale.cpp app/organicmaps/settings/MapLanguageCode.cpp app/organicmaps/sound/tts.cpp diff --git a/android/app/src/main/cpp/app/organicmaps/Framework.cpp b/android/app/src/main/cpp/app/organicmaps/Framework.cpp index 7b47eef574..d749e7a390 100644 --- a/android/app/src/main/cpp/app/organicmaps/Framework.cpp +++ b/android/app/src/main/cpp/app/organicmaps/Framework.cpp @@ -1578,12 +1578,6 @@ Java_app_organicmaps_Framework_nativeAddRoutePoint(JNIEnv * env, jclass, jstring frm()->GetRoutingManager().AddRoutePoint(std::move(data)); } -JNIEXPORT void JNICALL -Java_app_organicmaps_Framework_nativeRemoveRoutePoints(JNIEnv * env, jclass) -{ - frm()->GetRoutingManager().RemoveRoutePoints(); -} - JNIEXPORT void JNICALL Java_app_organicmaps_Framework_nativeRemoveRoutePoint(JNIEnv * env, jclass, jint markType, jint intermediateIndex) @@ -1633,13 +1627,6 @@ Java_app_organicmaps_Framework_nativeGetRoutePoints(JNIEnv * env, jclass) }); } -JNIEXPORT void JNICALL -Java_app_organicmaps_Framework_nativeMoveRoutePoint(JNIEnv * env, jclass, - jint currentIndex, jint targetIndex) -{ - frm()->GetRoutingManager().MoveRoutePoint(currentIndex, targetIndex); -} - JNIEXPORT jobject JNICALL Java_app_organicmaps_Framework_nativeGetTransitRouteInfo(JNIEnv * env, jclass) { @@ -2006,74 +1993,4 @@ Java_app_organicmaps_Framework_nativeGetKayakHotelLink(JNIEnv * env, jclass, jst return url.empty() ? nullptr : jni::ToJavaString(env, url); } -JNIEXPORT jboolean JNICALL -Java_app_organicmaps_Framework_nativeShouldShowProducts(JNIEnv * env, jclass) -{ - return frm()->ShouldShowProducts(); -} - -JNIEXPORT jobject JNICALL -Java_app_organicmaps_Framework_nativeGetProductsConfiguration(JNIEnv * env, jclass) -{ - auto config = frm()->GetProductsConfiguration(); - if (!config) return nullptr; - - static jclass const productClass = jni::GetGlobalClassRef( - env, - "app/organicmaps/products/Product" - ); - static jmethodID const productConstructor = jni::GetConstructorID( - env, - productClass, - "(Ljava/lang/String;Ljava/lang/String;)V" - ); - - jobjectArray products = jni::ToJavaArray( - env, - productClass, - config->GetProducts(), - [](JNIEnv * env, products::ProductsConfig::Product const & product) - { - jni::TScopedLocalRef const title(env, jni::ToJavaString(env, product.GetTitle())); - jni::TScopedLocalRef const link(env, jni::ToJavaString(env, product.GetLink())); - - return env->NewObject( - productClass, - productConstructor, - title.get(), - link.get() - ); - }); - - static jclass const productsConfigClass = jni::GetGlobalClassRef( - env, - "app/organicmaps/products/ProductsConfig" - ); - static jmethodID const productsConfigConstructor = jni::GetConstructorID( - env, - productsConfigClass, - "(Ljava/lang/String;[Lapp/organicmaps/products/Product;)V" - ); - - jni::TScopedLocalRef const placePagePrompt(env, jni::ToJavaString(env, config->GetPlacePagePrompt())); - return env->NewObject(productsConfigClass, productsConfigConstructor, placePagePrompt.get(), products); -} - -JNIEXPORT void JNICALL -Java_app_organicmaps_Framework_nativeDidCloseProductsPopup(JNIEnv * env, jclass, jstring reason) -{ - frm()->DidCloseProductsPopup(frm()->FromString(jni::ToNativeString(env, reason))); -} - -JNIEXPORT void JNICALL -Java_app_organicmaps_Framework_nativeDidSelectProduct(JNIEnv * env, jclass, jstring title, jstring link) -{ - products::ProductsConfig::Product product( - jni::ToNativeString(env, title), - jni::ToNativeString(env, link) - ); - - frm()->DidSelectProduct(product); -} - } // extern "C" diff --git a/android/app/src/main/cpp/app/organicmaps/SearchRecents.cpp b/android/app/src/main/cpp/app/organicmaps/SearchRecents.cpp deleted file mode 100644 index 9d5e9f818a..0000000000 --- a/android/app/src/main/cpp/app/organicmaps/SearchRecents.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "Framework.hpp" - -#include "search/result.hpp" - -#include "app/organicmaps/core/jni_helper.hpp" -#include "app/organicmaps/core/jni_java_methods.hpp" - -using SearchRequest = search::QuerySaver::SearchRequest; - -extern "C" -{ - JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchRecents_nativeGetList(JNIEnv * env, jclass thiz, jobject result) - { - auto const & items = g_framework->NativeFramework()->GetSearchAPI().GetLastSearchQueries(); - if (items.empty()) - return; - - auto const listAddMethod = jni::ListBuilder::Instance(env).m_add; - - for (SearchRequest const & item : items) - { - jni::TScopedLocalRef str(env, jni::ToJavaString(env, item.second)); - env->CallBooleanMethod(result, listAddMethod, str.get()); - } - } - - JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchRecents_nativeAdd(JNIEnv * env, jclass thiz, jstring locale, jstring query) - { - SearchRequest const sr(jni::ToNativeString(env, locale), jni::ToNativeString(env, query)); - g_framework->NativeFramework()->GetSearchAPI().SaveSearchQuery(sr); - } - - JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchRecents_nativeClear(JNIEnv * env, jclass thiz) - { - g_framework->NativeFramework()->GetSearchAPI().ClearSearchHistory(); - } -} diff --git a/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp b/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp index 7c4dc611b9..50f772c7b5 100644 --- a/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp +++ b/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp @@ -28,7 +28,7 @@ void InjectMetadata(JNIEnv * env, jclass const clazz, jobject const mapObject, o //jobject CreatePopularity(JNIEnv * env, place_page::Info const & info) //{ // static jclass const popularityClass = -// jni::GetGlobalClassRef(env, "app/organicmaps/search/Popularity"); +// jni::GetGlobalClassRef(env, "app/organicmaps/sdk/search/Popularity"); // static jmethodID const popularityConstructor = // jni::GetConstructorID(env, popularityClass, "(I)V"); // auto const popularityValue = info.GetPopularity(); @@ -57,7 +57,7 @@ jobject CreateMapObject(JNIEnv * env, place_page::Info const & info, int mapObje "Ljava/lang/String;" // appId "Lapp/organicmaps/routing/RoutePointInfo;" // routePointInfo "I" // openingMode - "Lapp/organicmaps/search/Popularity;" // popularity + "Lapp/organicmaps/sdk/search/Popularity;" // popularity "Ljava/lang/String;" // description "I" // roadWarnType "[Ljava/lang/String;" // rawTypes @@ -105,7 +105,7 @@ jobject CreateBookmark(JNIEnv *env, const place_page::Info &info, "(Lapp/organicmaps/bookmarks/data/FeatureId;JJLjava/lang/String;" "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;" "Lapp/organicmaps/routing/RoutePointInfo;" - "ILapp/organicmaps/search/Popularity;Ljava/lang/String;" + "ILapp/organicmaps/sdk/search/Popularity;Ljava/lang/String;" "[Ljava/lang/String;)V"); static jmethodID const featureCtorId = jni::GetConstructorID(env, g_featureIdClazz, "(Ljava/lang/String;JI)V"); @@ -165,13 +165,12 @@ jobject CreateElevationInfo(JNIEnv * env, ElevationInfo const & info) "[Lapp/organicmaps/bookmarks/data/ElevationInfo$Point;" "IIIIIJ)V"); jni::TScopedLocalObjectArrayRef jPoints(env, ToElevationPointArray(env, info.GetPoints())); - // TODO (KK): elevation info should have only the elevation data - see the https://github.com/organicmaps/organicmaps/pull/10063 return env->NewObject(g_elevationInfoClazz, ctorId, jPoints.get(), -// static_cast(info.GetAscent()), -// static_cast(info.GetDescent()), -// static_cast(info.GetMinAltitude()), -// static_cast(info.GetMaxAltitude()), + static_cast(info.GetAscent()), + static_cast(info.GetDescent()), + static_cast(info.GetMinAltitude()), + static_cast(info.GetMaxAltitude()), static_cast(info.GetDifficulty())); } diff --git a/android/app/src/main/cpp/app/organicmaps/bookmarks/data/BookmarkManager.cpp b/android/app/src/main/cpp/app/organicmaps/bookmarks/data/BookmarkManager.cpp index f801f37b51..bf237a4382 100644 --- a/android/app/src/main/cpp/app/organicmaps/bookmarks/data/BookmarkManager.cpp +++ b/android/app/src/main/cpp/app/organicmaps/bookmarks/data/BookmarkManager.cpp @@ -591,15 +591,6 @@ Java_app_organicmaps_bookmarks_data_BookmarkManager_nativeSetAllCategoriesVisibi frm()->GetBookmarkManager().SetAllCategoriesVisibility(static_cast(visible)); } -JNIEXPORT void JNICALL -Java_app_organicmaps_bookmarks_data_BookmarkManager_nativePrepareTrackFileForSharing(JNIEnv * env, jclass, jlong trackId, jint kmlFileType) -{ - frm()->GetBookmarkManager().PrepareTrackFileForSharing(static_cast(trackId), [env](BookmarkManager::SharingResult const & result) - { - OnPreparedFileForSharing(env, result); - }, static_cast(kmlFileType)); -} - JNIEXPORT void JNICALL Java_app_organicmaps_bookmarks_data_BookmarkManager_nativePrepareFileForSharing(JNIEnv * env, jclass, jlongArray catIds, jint kmlFileType) { diff --git a/android/app/src/main/cpp/app/organicmaps/editor/Editor.cpp b/android/app/src/main/cpp/app/organicmaps/editor/Editor.cpp index b0a15d22d1..a62eb8c21f 100644 --- a/android/app/src/main/cpp/app/organicmaps/editor/Editor.cpp +++ b/android/app/src/main/cpp/app/organicmaps/editor/Editor.cpp @@ -149,8 +149,7 @@ Java_app_organicmaps_editor_Editor_nativeHasWifi(JNIEnv *, jclass) JNIEXPORT void JNICALL Java_app_organicmaps_editor_Editor_nativeSetHasWifi(JNIEnv *, jclass, jboolean hasWifi) { - if (hasWifi != (g_editableMapObject.GetInternet() == feature::Internet::Wlan)) - g_editableMapObject.SetInternet(hasWifi ? feature::Internet::Wlan : feature::Internet::Unknown); + g_editableMapObject.SetInternet(hasWifi ? feature::Internet::Wlan : feature::Internet::Unknown); } JNIEXPORT jboolean JNICALL @@ -363,11 +362,7 @@ Java_app_organicmaps_editor_Editor_nativeStartEdit(JNIEnv *, jclass) { ::Framework * frm = g_framework->NativeFramework(); if (!frm->HasPlacePageInfo()) - { - ASSERT(g_editableMapObject.GetEditingLifecycle() == osm::EditingLifecycle::CREATED, - ("PlacePageInfo should only be empty for new features.")); return; - } place_page::Info const & info = g_framework->GetPlacePageInfo(); CHECK(frm->GetEditableMapObject(info.GetID(), g_editableMapObject), ("Invalid feature in the place page.")); diff --git a/android/app/src/main/cpp/app/organicmaps/DisplayedCategories.cpp b/android/app/src/main/cpp/app/organicmaps/sdk/search/DisplayedCategories.cpp similarity index 75% rename from android/app/src/main/cpp/app/organicmaps/DisplayedCategories.cpp rename to android/app/src/main/cpp/app/organicmaps/sdk/search/DisplayedCategories.cpp index 3fc3674401..1e05eaa6a3 100644 --- a/android/app/src/main/cpp/app/organicmaps/DisplayedCategories.cpp +++ b/android/app/src/main/cpp/app/organicmaps/sdk/search/DisplayedCategories.cpp @@ -5,8 +5,7 @@ extern "C" { -JNIEXPORT jobjectArray JNICALL -Java_app_organicmaps_search_DisplayedCategories_nativeGetKeys(JNIEnv * env, jclass clazz) +JNIEXPORT jobjectArray JNICALL Java_app_organicmaps_sdk_search_DisplayedCategories_nativeGetKeys(JNIEnv * env, jclass) { ::Framework * fr = g_framework->NativeFramework(); ASSERT(fr, ()); diff --git a/android/app/src/main/cpp/app/organicmaps/SearchEngine.cpp b/android/app/src/main/cpp/app/organicmaps/sdk/search/SearchEngine.cpp similarity index 90% rename from android/app/src/main/cpp/app/organicmaps/SearchEngine.cpp rename to android/app/src/main/cpp/app/organicmaps/sdk/search/SearchEngine.cpp index 90fc262910..afc5420a7e 100644 --- a/android/app/src/main/cpp/app/organicmaps/SearchEngine.cpp +++ b/android/app/src/main/cpp/app/organicmaps/sdk/search/SearchEngine.cpp @@ -39,7 +39,7 @@ Results g_results; // Timestamp of last search query. Results with older stamps are ignored. jlong g_queryTimestamp; -// Implements 'NativeSearchListener' java interface. +// Implements 'SearchListener' java interface. jobject g_javaListener; jmethodID g_updateResultsId; jmethodID g_endResultsId; @@ -52,7 +52,7 @@ jmethodID g_descriptionConstructor; jclass g_popularityClass; jmethodID g_popularityConstructor; -// Implements 'NativeMapSearchListener' java interface. +// Implements 'MapSearchListener' java interface. jmethodID g_mapResultsMethod; jclass g_mapResultClass; jmethodID g_mapResultCtor; @@ -232,21 +232,21 @@ void OnBookmarksSearchResults(search::BookmarksSearchParams::Results results, extern "C" { JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchEngine_nativeInit(JNIEnv * env, jobject thiz) + Java_app_organicmaps_sdk_search_SearchEngine_nativeInit(JNIEnv * env, jobject thiz) { g_javaListener = env->NewGlobalRef(thiz); // public void onResultsUpdate(@NonNull SearchResult[] results, long timestamp) g_updateResultsId = jni::GetMethodID(env, g_javaListener, "onResultsUpdate", - "([Lapp/organicmaps/search/SearchResult;J)V"); + "([Lapp/organicmaps/sdk/search/SearchResult;J)V"); // public void onResultsEnd(long timestamp) g_endResultsId = jni::GetMethodID(env, g_javaListener, "onResultsEnd", "(J)V"); - g_resultClass = jni::GetGlobalClassRef(env, "app/organicmaps/search/SearchResult"); + g_resultClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/search/SearchResult"); g_resultConstructor = jni::GetConstructorID( env, g_resultClass, - "(Ljava/lang/String;Lapp/organicmaps/search/SearchResult$Description;DD[I[I" - "Lapp/organicmaps/search/Popularity;)V"); + "(Ljava/lang/String;Lapp/organicmaps/sdk/search/SearchResult$Description;DD[I[I" + "Lapp/organicmaps/sdk/search/Popularity;)V"); g_suggestConstructor = jni::GetConstructorID(env, g_resultClass, "(Ljava/lang/String;Ljava/lang/String;DD[I[I)V"); - g_descriptionClass = jni::GetGlobalClassRef(env, "app/organicmaps/search/SearchResult$Description"); + g_descriptionClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/search/SearchResult$Description"); /* Description(FeatureId featureId, String featureType, String region, Distance distance, String description, int openNow, int minutesUntilOpen, int minutesUntilClosed, @@ -257,12 +257,12 @@ extern "C" "Ljava/lang/String;Ljava/lang/String;Lapp/organicmaps/util/Distance;" "Ljava/lang/String;IIIZ)V"); - g_popularityClass = jni::GetGlobalClassRef(env, "app/organicmaps/search/Popularity"); + g_popularityClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/search/Popularity"); g_popularityConstructor = jni::GetConstructorID(env, g_popularityClass, "(I)V"); g_mapResultsMethod = jni::GetMethodID(env, g_javaListener, "onMapSearchResults", - "([Lapp/organicmaps/search/NativeMapSearchListener$Result;JZ)V"); - g_mapResultClass = jni::GetGlobalClassRef(env, "app/organicmaps/search/NativeMapSearchListener$Result"); + "([Lapp/organicmaps/sdk/search/MapSearchListener$Result;JZ)V"); + g_mapResultClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/search/MapSearchListener$Result"); g_mapResultCtor = jni::GetConstructorID(env, g_mapResultClass, "(Ljava/lang/String;Ljava/lang/String;)V"); g_updateBookmarksResultsId = @@ -271,7 +271,7 @@ extern "C" jni::GetMethodID(env, g_javaListener, "onBookmarkSearchResultsEnd", "([JJ)V"); } - JNIEXPORT jboolean JNICALL Java_app_organicmaps_search_SearchEngine_nativeRunSearch( + JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_search_SearchEngine_nativeRunSearch( JNIEnv * env, jclass clazz, jbyteArray bytes, jboolean isCategory, jstring lang, jlong timestamp, jboolean hasPosition, jdouble lat, jdouble lon) { @@ -288,7 +288,7 @@ extern "C" return searchStarted; } - JNIEXPORT void JNICALL Java_app_organicmaps_search_SearchEngine_nativeRunInteractiveSearch( + JNIEXPORT void JNICALL Java_app_organicmaps_sdk_search_SearchEngine_nativeRunInteractiveSearch( JNIEnv * env, jclass clazz, jbyteArray bytes, jboolean isCategory, jstring lang, jlong timestamp, jboolean isMapAndTable, jboolean hasPosition, jdouble lat, jdouble lon) { @@ -321,7 +321,7 @@ extern "C" } } - JNIEXPORT void JNICALL Java_app_organicmaps_search_SearchEngine_nativeRunSearchMaps( + JNIEXPORT void JNICALL Java_app_organicmaps_sdk_search_SearchEngine_nativeRunSearchMaps( JNIEnv * env, jclass clazz, jbyteArray bytes, jstring lang, jlong timestamp) { storage::DownloaderSearchParams params{ @@ -334,7 +334,7 @@ extern "C" g_queryTimestamp = timestamp; } - JNIEXPORT jboolean JNICALL Java_app_organicmaps_search_SearchEngine_nativeRunSearchInBookmarks( + JNIEXPORT jboolean JNICALL Java_app_organicmaps_sdk_search_SearchEngine_nativeRunSearchInBookmarks( JNIEnv * env, jclass clazz, jbyteArray query, jlong catId, jlong timestamp) { search::BookmarksSearchParams params{ @@ -350,25 +350,25 @@ extern "C" } JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchEngine_nativeShowResult(JNIEnv * env, jclass clazz, jint index) + Java_app_organicmaps_sdk_search_SearchEngine_nativeShowResult(JNIEnv * env, jclass clazz, jint index) { g_framework->NativeFramework()->ShowSearchResult(g_results[index]); } JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchEngine_nativeCancelInteractiveSearch(JNIEnv * env, jclass clazz) + Java_app_organicmaps_sdk_search_SearchEngine_nativeCancelInteractiveSearch(JNIEnv * env, jclass clazz) { g_framework->NativeFramework()->GetSearchAPI().CancelSearch(search::Mode::Viewport); } JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchEngine_nativeCancelEverywhereSearch(JNIEnv * env, jclass clazz) + Java_app_organicmaps_sdk_search_SearchEngine_nativeCancelEverywhereSearch(JNIEnv * env, jclass clazz) { g_framework->NativeFramework()->GetSearchAPI().CancelSearch(search::Mode::Everywhere); } JNIEXPORT void JNICALL - Java_app_organicmaps_search_SearchEngine_nativeCancelAllSearches(JNIEnv * env, jclass clazz) + Java_app_organicmaps_sdk_search_SearchEngine_nativeCancelAllSearches(JNIEnv * env, jclass clazz) { g_framework->NativeFramework()->GetSearchAPI().CancelAllSearches(); } diff --git a/android/app/src/main/cpp/app/organicmaps/sdk/search/SearchRecents.cpp b/android/app/src/main/cpp/app/organicmaps/sdk/search/SearchRecents.cpp new file mode 100644 index 0000000000..303303c152 --- /dev/null +++ b/android/app/src/main/cpp/app/organicmaps/sdk/search/SearchRecents.cpp @@ -0,0 +1,37 @@ +#include "app/organicmaps/Framework.hpp" +#include "app/organicmaps/core/jni_helper.hpp" +#include "app/organicmaps/core/jni_java_methods.hpp" + +#include "search/result.hpp" + +using SearchRequest = search::QuerySaver::SearchRequest; + +extern "C" +{ +JNIEXPORT void JNICALL Java_app_organicmaps_sdk_search_SearchRecents_nativeGetList(JNIEnv * env, jclass, jobject result) +{ + auto const & items = g_framework->NativeFramework()->GetSearchAPI().GetLastSearchQueries(); + if (items.empty()) + return; + + auto const listAddMethod = jni::ListBuilder::Instance(env).m_add; + + for (SearchRequest const & item : items) + { + jni::TScopedLocalRef str(env, jni::ToJavaString(env, item.second)); + env->CallBooleanMethod(result, listAddMethod, str.get()); + } +} + +JNIEXPORT void JNICALL Java_app_organicmaps_sdk_search_SearchRecents_nativeAdd(JNIEnv * env, jclass, jstring locale, + jstring query) +{ + SearchRequest const sr(jni::ToNativeString(env, locale), jni::ToNativeString(env, query)); + g_framework->NativeFramework()->GetSearchAPI().SaveSearchQuery(sr); +} + +JNIEXPORT void JNICALL Java_app_organicmaps_sdk_search_SearchRecents_nativeClear(JNIEnv * env, jclass) +{ + g_framework->NativeFramework()->GetSearchAPI().ClearSearchHistory(); +} +} diff --git a/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp b/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp index 833f2fa238..e99a34b0ed 100644 --- a/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp +++ b/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp @@ -49,12 +49,6 @@ Java_app_organicmaps_util_StringUtils_nativeFilterContainsNormalized(JNIEnv * en return jni::ToJavaStringArray(env, filtered); } -JNIEXPORT jint JNICALL Java_app_organicmaps_util_StringUtils_nativeFormatSpeed( - JNIEnv * env, jclass thiz, jdouble metersPerSecond) -{ - return measurement_utils::FormatSpeed(metersPerSecond, measurement_utils::GetMeasurementUnits()); -} - JNIEXPORT jobject JNICALL Java_app_organicmaps_util_StringUtils_nativeFormatSpeedAndUnits( JNIEnv * env, jclass thiz, jdouble metersPerSecond) { diff --git a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java index 2eb49fc6d3..3be0c88c4c 100644 --- a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java +++ b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java @@ -390,7 +390,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity mProgress.setProgressCompat(0, true); mCountryDownloadListenerSlot = MapManager.nativeSubscribe(mCountryDownloadListener); - MapManager.startDownload(mCurrentCountry); + MapManager.nativeDownload(mCurrentCountry); setAction(PROCEED_TO_MAP); } else diff --git a/android/app/src/main/java/app/organicmaps/Framework.java b/android/app/src/main/java/app/organicmaps/Framework.java index 358c03014b..63d70abde3 100644 --- a/android/app/src/main/java/app/organicmaps/Framework.java +++ b/android/app/src/main/java/app/organicmaps/Framework.java @@ -15,8 +15,6 @@ import app.organicmaps.api.RequestType; import app.organicmaps.bookmarks.data.DistanceAndAzimut; import app.organicmaps.bookmarks.data.FeatureId; import app.organicmaps.bookmarks.data.MapObject; -import app.organicmaps.products.Product; -import app.organicmaps.products.ProductsConfig; import app.organicmaps.routing.JunctionInfo; import app.organicmaps.routing.RouteMarkData; import app.organicmaps.routing.RoutePointInfo; @@ -333,20 +331,11 @@ public class Framework public static native int nativeGetBestRouter(double srcLat, double srcLon, double dstLat, double dstLon); - public static void addRoutePoint(RouteMarkData point) - { - Framework.nativeAddRoutePoint(point.mTitle, point.mSubtitle, point.mPointType, - point.mIntermediateIndex, point.mIsMyPosition, - point.mLat, point.mLon); - } - public static native void nativeAddRoutePoint(String title, String subtitle, @RoutePointInfo.RouteMarkType int markType, int intermediateIndex, boolean isMyPosition, double lat, double lon); - public static native void nativeRemoveRoutePoints(); - public static native void nativeRemoveRoutePoint(@RoutePointInfo.RouteMarkType int markType, int intermediateIndex); @@ -355,9 +344,6 @@ public class Framework public static native boolean nativeCouldAddIntermediatePoint(); @NonNull public static native RouteMarkData[] nativeGetRoutePoints(); - - public static native void nativeMoveRoutePoint(int currentIndex, int targetIndex); - @NonNull public static native TransitRouteInfo nativeGetTransitRouteInfo(); /** @@ -475,13 +461,4 @@ public class Framework @Nullable public static native String nativeGetKayakHotelLink(@NonNull String countryIsoCode, @NonNull String uri, long firstDaySec, long lastDaySec); - - public static native boolean nativeShouldShowProducts(); - - @Nullable - public static native ProductsConfig nativeGetProductsConfiguration(); - - public static native void nativeDidCloseProductsPopup(String reason); - - public static native void nativeDidSelectProduct(String title, String link); } diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index 416f528e24..631a84d7ab 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -77,7 +77,7 @@ import app.organicmaps.maplayer.MapButtonsViewModel; import app.organicmaps.maplayer.ToggleMapLayerFragment; import app.organicmaps.maplayer.isolines.IsolinesManager; import app.organicmaps.maplayer.isolines.IsolinesState; -import app.organicmaps.routing.ManageRouteBottomSheet; +import app.organicmaps.maplayer.subway.SubwayManager; import app.organicmaps.routing.NavigationController; import app.organicmaps.routing.NavigationService; import app.organicmaps.routing.RoutePointInfo; @@ -89,7 +89,7 @@ import app.organicmaps.routing.RoutingPlanFragment; import app.organicmaps.routing.RoutingPlanInplaceController; import app.organicmaps.search.FloatingSearchToolbarController; import app.organicmaps.search.SearchActivity; -import app.organicmaps.search.SearchEngine; +import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.search.SearchFragment; import app.organicmaps.settings.DrivingOptionsActivity; import app.organicmaps.settings.RoadType; @@ -150,7 +150,6 @@ public class MwmActivity extends BaseMwmFragmentActivity public static final String EXTRA_TRACK_ID = "track_id"; public static final String EXTRA_UPDATE_THEME = "update_theme"; private static final String EXTRA_CONSUMED = "mwm.extra.intent.processed"; - private boolean mPreciseLocationDialogShown = false; private static final String[] DOCKED_FRAGMENTS = { SearchFragment.class.getName(), DownloaderFragment.class.getName(), @@ -158,11 +157,7 @@ public class MwmActivity extends BaseMwmFragmentActivity EditorHostFragment.class.getName(), ReportFragment.class.getName() }; - public final ActivityResultLauncher startDrivingOptionsForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> - { - if( activityResult.getResultCode() == Activity.RESULT_OK) - rebuildLastRoute(); - }); + public static final int REQ_CODE_DRIVING_OPTIONS = 6; private static final String MAIN_MENU_ID = "MAIN_MENU_BOTTOM_SHEET"; private static final String LAYERS_MENU_ID = "LAYERS_MENU_BOTTOM_SHEET"; @@ -234,8 +229,6 @@ public class MwmActivity extends BaseMwmFragmentActivity @NonNull private DisplayManager mDisplayManager; - ManageRouteBottomSheet mManageRouteBottomSheet; - private boolean mRemoveDisplayListener = true; private int mLastUiMode = Configuration.UI_MODE_TYPE_UNDEFINED; @@ -593,14 +586,14 @@ public class MwmActivity extends BaseMwmFragmentActivity ViewCompat.setOnApplyWindowInsetsListener(mPointChooser, (view, windowInsets) -> { UiUtils.setViewInsetsPaddingBottom(mPointChooser, windowInsets); UiUtils.setViewInsetsPaddingNoBottom(mPointChooserToolbar, windowInsets); - final int trackRecorderOffset = TrackRecorder.nativeIsTrackRecordingEnabled() ? UiUtils.dimen(this, R.dimen.map_button_size) : 0; + mNavBarHeight = isFullscreen() ? 0 : windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom; // For the first loading, set compass top margin to status bar size // The top inset will be then be updated by the routing controller if (mCurrentWindowInsets == null) - { - updateCompassOffset(trackRecorderOffset + windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top, windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right); - } + updateCompassOffset(windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top, windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right); + else + updateCompassOffset(-1, windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right); refreshLightStatusBar(); updateBottomWidgetsOffset(windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).left); mCurrentWindowInsets = windowInsets; @@ -621,7 +614,7 @@ public class MwmActivity extends BaseMwmFragmentActivity if (!mIsTabletLayout) { - mRoutingPlanInplaceController = new RoutingPlanInplaceController(this, startDrivingOptionsForResult, this, this); + mRoutingPlanInplaceController = new RoutingPlanInplaceController(this, this, this); removeCurrentFragment(false); } @@ -698,16 +691,6 @@ public class MwmActivity extends BaseMwmFragmentActivity } } - /** Hides/shows UI while keeping state - * @param isUiHidden True to hide the UI - **/ - public void hideOrShowUIWithoutClosingPlacePage(boolean isUiHidden) - { - // Used instead of closeBottomSheet to preserve state and hide instantly - UiUtils.showIf(!isUiHidden, findViewById(R.id.place_page_container_fragment)); - mMapButtonsViewModel.setButtonsHidden(isUiHidden); - } - private void showSearchToolbar() { mSearchController.show(); @@ -817,7 +800,6 @@ public class MwmActivity extends BaseMwmFragmentActivity showBottomSheet(MAIN_MENU_ID); } case help -> showHelp(); - case trackRecordingStatus -> showTrackSaveDialog(); } } @@ -1036,6 +1018,18 @@ public class MwmActivity extends BaseMwmFragmentActivity mPowerSaveDisclaimerShown = savedInstanceState.getBoolean(POWER_SAVE_DISCLAIMER_SHOWN, false); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode != Activity.RESULT_OK) + return; + + if (requestCode == REQ_CODE_DRIVING_OPTIONS) + rebuildLastRoute(); + } + private void rebuildLastRoute() { RoutingController.get().attach(this); @@ -1317,16 +1311,6 @@ public class MwmActivity extends BaseMwmFragmentActivity Framework.nativeGetChoosePositionMode() == Framework.ChoosePositionMode.NONE; } - @Override - public boolean dispatchGenericMotionEvent(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { - int exponent = event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0 ? -1 : 1; - Map.onScale(Math.pow(1.7f, exponent), event.getX(), event.getY(), true); - return true; - } - return super.onGenericMotionEvent(event); - } - @Override public boolean onTouch(View view, MotionEvent event) { @@ -1504,30 +1488,14 @@ public class MwmActivity extends BaseMwmFragmentActivity if (mCurrentWindowInsets == null) { return; } - int offsetY = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; - int offsetX = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right; + int offset = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; if (show && mRoutingPlanInplaceController != null) { final int height = mRoutingPlanInplaceController.calcHeight(); if (height != 0) - offsetY = height; + offset = height; } - final int orientation = getResources().getConfiguration().orientation; - final boolean isTrackRecordingEnabled = TrackRecorder.nativeIsTrackRecordingEnabled(); - if (isTrackRecordingEnabled && (orientation != Configuration.ORIENTATION_LANDSCAPE)) - offsetY += UiUtils.dimen(this, R.dimen.map_button_size); - if (orientation == Configuration.ORIENTATION_LANDSCAPE) - { - if (show) - { - final boolean isSmallScreen = UiUtils.getDisplayTotalHeight(this) < UiUtils.dimen(this, R.dimen.dp_400); - if (!isSmallScreen || TrackRecorder.nativeIsTrackRecordingEnabled()) - offsetX += UiUtils.dimen(this, R.dimen.map_button_size); - } - else if (isTrackRecordingEnabled) - offsetY += UiUtils.dimen(this, R.dimen.map_button_size); - } - updateCompassOffset(offsetY, offsetX); + updateCompassOffset(offset); } @Override @@ -1694,6 +1662,12 @@ public class MwmActivity extends BaseMwmFragmentActivity mRoutingPlanInplaceController.showDrivingOptionView(); } + @Override + public boolean isSubwayEnabled() + { + return SubwayManager.from(this).isEnabled(); + } + @Override public void onCommonBuildError(int lastResultCode, @NonNull String[] lastMissingMaps) { @@ -1709,7 +1683,7 @@ public class MwmActivity extends BaseMwmFragmentActivity mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) .setTitle(R.string.unable_to_calc_alert_title) .setMessage(R.string.unable_to_calc_alert_subtitle) - .setPositiveButton(R.string.settings, (dialog, which) -> DrivingOptionsActivity.start(this, startDrivingOptionsForResult)) + .setPositiveButton(R.string.settings, (dialog, which) -> DrivingOptionsActivity.start(this)) .setNegativeButton(R.string.cancel, null) .setOnDismissListener(dialog -> mAlertDialog = null) .show(); @@ -1822,7 +1796,7 @@ public class MwmActivity extends BaseMwmFragmentActivity } // Check for any location permissions. - if (!LocationUtils.checkLocationPermission(this)) + if (!LocationUtils.checkCoarseLocationPermission(this)) { Logger.w(LOCATION_TAG, "Permissions ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION are not granted"); // Calls onMyPositionModeChanged(NOT_FOLLOW_NO_POSITION). @@ -1960,50 +1934,12 @@ public class MwmActivity extends BaseMwmFragmentActivity mLocationPermissionRequestedForRecording = false; if (LocationUtils.checkLocationPermission(this)) { - final boolean hasFineLocationPermission = LocationUtils.checkFineLocationPermission(this); - if (LocationState.getMode() == LocationState.NOT_FOLLOW_NO_POSITION) LocationState.nativeSwitchToNextMode(); - if (requestedForRecording && hasFineLocationPermission) + if (requestedForRecording && LocationUtils.checkFineLocationPermission(this)) startTrackRecording(); - if (hasFineLocationPermission) - { - Logger.i(LOCATION_TAG, "ACCESS_FINE_LOCATION permission granted"); - } - else - { - Logger.w(LOCATION_TAG, "Only ACCESS_COARSE_LOCATION permission granted"); - if (mLocationErrorDialog != null && mLocationErrorDialog.isShowing()) - { - Logger.w(LOCATION_TAG, "Don't show 'Precise Location denied' dialog because another dialog is in progress"); - return; - } - if (!mPreciseLocationDialogShown) - { - mPreciseLocationDialogShown = true; - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) - .setTitle("⚠ " + getString(R.string.limited_accuracy)) - .setMessage(R.string.precise_location_is_disabled_long_text) - .setNegativeButton(R.string.close, (dialog, which) -> dialog.dismiss()) - .setCancelable(true) - .setOnDismissListener(dialog -> mLocationErrorDialog = null); - final Intent intent = Utils.makeSystemLocationSettingIntent(this); - if (intent != null) - { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - builder.setPositiveButton(R.string.location_settings, (dialog, which) -> startActivity(intent)); - } - mLocationErrorDialog = builder.show(); - } - else - { - Toast.makeText(this, R.string.precise_location_is_disabled_long_text, Toast.LENGTH_LONG).show(); - } - } return; } @@ -2154,15 +2090,6 @@ public class MwmActivity extends BaseMwmFragmentActivity RoutingController.get().start(); } - @Override - public void onManageRouteOpen() - { - // Create and show 'Manage Route' Bottom Sheet panel. - mManageRouteBottomSheet = new ManageRouteBottomSheet(); - mManageRouteBottomSheet.setCancelable(false); - mManageRouteBottomSheet.show(getSupportFragmentManager(), "ManageRouteBottomSheet"); - } - private boolean requestBatterySaverPermission() { if (!PowerManagment.isSystemPowerSaveMode(this)) @@ -2340,11 +2267,6 @@ public class MwmActivity extends BaseMwmFragmentActivity requestPostNotificationsPermission(); - if (mCurrentWindowInsets != null) - { - final int offset = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; - updateCompassOffset(offset + UiUtils.dimen(this, R.dimen.map_button_size)); - } Toast.makeText(this, R.string.track_recording, Toast.LENGTH_SHORT).show(); TrackRecordingService.startForegroundService(getApplicationContext()); mMapButtonsViewModel.setTrackRecorderState(true); @@ -2353,18 +2275,6 @@ public class MwmActivity extends BaseMwmFragmentActivity private void stopTrackRecording() { - if (mCurrentWindowInsets != null) - { - int offsetY = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; - final int offsetX = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right; - if (RoutingController.get().isPlanning() && mRoutingPlanInplaceController != null) - { - final int height = mRoutingPlanInplaceController.calcHeight(); - if (height != 0) - offsetY = height; - } - updateCompassOffset(offsetY, offsetX); - } TrackRecordingService.stopService(getApplicationContext()); mMapButtonsViewModel.setTrackRecorderState(false); } @@ -2399,15 +2309,15 @@ public class MwmActivity extends BaseMwmFragmentActivity mAlertDialog = new StackedButtonsDialog.Builder(this) .setTitle(R.string.track_recording_alert_title) .setCancelable(false) - // Negative/Positive/Neutral do not have their usual meaning here. - .setNegativeButton(R.string.continue_recording, (dialog, which) -> { + // Negative/Positive/Neutral doesn't do not have the usual meaning here. + .setPositiveButton(R.string.continue_recording, (dialog, which) -> { mAlertDialog = null; }) .setNeutralButton(R.string.stop_without_saving, (dialog, which) -> { stopTrackRecording(); mAlertDialog = null; }) - .setPositiveButton(R.string.save, (dialog, which) -> { + .setNegativeButton(R.string.save, (dialog, which) -> { saveAndStopTrackRecording(); mAlertDialog = null; }) diff --git a/android/app/src/main/java/app/organicmaps/MwmApplication.java b/android/app/src/main/java/app/organicmaps/MwmApplication.java index 7170508325..056beb7985 100644 --- a/android/app/src/main/java/app/organicmaps/MwmApplication.java +++ b/android/app/src/main/java/app/organicmaps/MwmApplication.java @@ -16,14 +16,13 @@ import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; -import java.io.IOException; -import java.lang.ref.WeakReference; - import app.organicmaps.background.OsmUploadWork; -import app.organicmaps.bookmarks.data.BookmarkManager; -import app.organicmaps.display.DisplayManager; import app.organicmaps.downloader.Android7RootCertificateWorkaround; import app.organicmaps.downloader.DownloaderNotifier; +import app.organicmaps.bookmarks.data.BookmarkManager; +import app.organicmaps.display.DisplayManager; +import app.organicmaps.downloader.CountryItem; +import app.organicmaps.downloader.MapManager; import app.organicmaps.location.LocationHelper; import app.organicmaps.location.LocationState; import app.organicmaps.location.SensorHelper; @@ -34,7 +33,7 @@ import app.organicmaps.maplayer.subway.SubwayManager; import app.organicmaps.maplayer.traffic.TrafficManager; import app.organicmaps.routing.NavigationService; import app.organicmaps.routing.RoutingController; -import app.organicmaps.search.SearchEngine; +import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.settings.StoragePathManager; import app.organicmaps.sound.TtsPlayer; import app.organicmaps.util.Config; @@ -47,6 +46,10 @@ import app.organicmaps.util.Utils; import app.organicmaps.util.log.Logger; import app.organicmaps.util.log.LogsManager; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.List; + public class MwmApplication extends Application implements Application.ActivityLifecycleCallbacks { @NonNull @@ -75,6 +78,9 @@ public class MwmApplication extends Application implements Application.ActivityL private volatile boolean mFrameworkInitialized; private volatile boolean mPlatformInitialized; + @NonNull + private final MapManager.StorageCallback mStorageCallbacks = new StorageCallbackImpl(); + @Nullable private WeakReference mTopActivity; @@ -121,9 +127,6 @@ public class MwmApplication extends Application implements Application.ActivityL return (MwmApplication) context.getApplicationContext(); } - @NonNull - public static MwmApplication sInstance; - @NonNull public static SharedPreferences prefs(@NonNull Context context) { @@ -135,9 +138,6 @@ public class MwmApplication extends Application implements Application.ActivityL { super.onCreate(); Logger.i(TAG, "Initializing application"); - - sInstance = this; - LogsManager.INSTANCE.initFileLogging(this); Android7RootCertificateWorkaround.initializeIfNeeded(this); @@ -230,6 +230,8 @@ public class MwmApplication extends Application implements Application.ActivityL nativeInitFramework(onComplete); + MapManager.nativeSubscribe(mStorageCallbacks); + initNativeStrings(); ThemeSwitcher.INSTANCE.initialize(this); SearchEngine.INSTANCE.initialize(); @@ -361,4 +363,25 @@ public class MwmApplication extends Application implements Application.ActivityL mLocationHelper.stop(); } } + + private class StorageCallbackImpl implements MapManager.StorageCallback + { + @Override + public void onStatusChanged(List data) + { + for (MapManager.StorageCallbackData item : data) + if (item.isLeafNode && item.newStatus == CountryItem.STATUS_FAILED) + { + if (MapManager.nativeIsAutoretryFailed()) + { + DownloaderNotifier.notifyDownloadFailed(MwmApplication.this, item.countryId); + } + + return; + } + } + + @Override + public void onProgress(String countryId, long localSize, long remoteSize) {} + } } diff --git a/android/app/src/main/java/app/organicmaps/SplashActivity.java b/android/app/src/main/java/app/organicmaps/SplashActivity.java index 37c7cd976b..6ba79d2dc5 100644 --- a/android/app/src/main/java/app/organicmaps/SplashActivity.java +++ b/android/app/src/main/java/app/organicmaps/SplashActivity.java @@ -19,7 +19,6 @@ import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; import app.organicmaps.display.DisplayManager; -import app.organicmaps.downloader.DownloaderActivity; import app.organicmaps.intent.Factory; import app.organicmaps.location.LocationHelper; import app.organicmaps.util.Config; @@ -91,7 +90,7 @@ public class SplashActivity extends AppCompatActivity super.onResume(); if (mCanceled) return; - if (!Config.isLocationRequested() && !LocationUtils.checkLocationPermission(this)) + if (!Config.isLocationRequested() && !LocationUtils.checkCoarseLocationPermission(this)) { Logger.d(TAG, "Requesting location permissions"); mPermissionRequest.launch(new String[]{ @@ -179,13 +178,7 @@ public class SplashActivity extends AppCompatActivity // Re-use original intent with the known safe subset of flags to retain security permissions. // https://github.com/organicmaps/organicmaps/issues/6944 final Intent intent = Objects.requireNonNull(getIntent()); - - if (isManageSpaceActivity(intent)) { - intent.setComponent(new ComponentName(this, DownloaderActivity.class)); - } else { - intent.setComponent(new ComponentName(this, DownloadResourcesLegacyActivity.class)); - } - + intent.setComponent(new ComponentName(this, DownloadResourcesLegacyActivity.class)); // FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_RESET_TASK_IF_NEEDED break the cold start. // https://github.com/organicmaps/organicmaps/pull/7287 // FORWARD_RESULT_FLAG conflicts with the ActivityResultLauncher. @@ -203,15 +196,4 @@ public class SplashActivity extends AppCompatActivity startActivity(intent); finish(); } - - private boolean isManageSpaceActivity(Intent intent) { - var component = intent.getComponent(); - - if (!Intent.ACTION_VIEW.equals(intent.getAction())) return false; - if (component == null) return false; - - var manageSpaceActivityName = BuildConfig.APPLICATION_ID + ".ManageSpaceActivity"; - - return manageSpaceActivityName.equals(component.getClassName()); - } } diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesAdapter.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesAdapter.java index 2495b9ed65..21ef9197bd 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesAdapter.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesAdapter.java @@ -142,7 +142,7 @@ public class BookmarkCategoriesAdapter extends BaseBookmarkCategoryAdapter { Holders.GeneralViewHolder generalViewHolder = (Holders.GeneralViewHolder) holder; - generalViewHolder.getImage().setImageResource(R.drawable.ic_add_list); + generalViewHolder.getImage().setImageResource(R.drawable.ic_import); generalViewHolder.getText().setText(R.string.bookmarks_create_new_group); } case TYPE_ACTION_IMPORT -> diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesFragment.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesFragment.java index cc31cfb4cf..2b6a487b61 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesFragment.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesFragment.java @@ -15,7 +15,6 @@ import android.view.View; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CallSuper; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; @@ -58,6 +57,9 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment startBookmarkListForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> { - if( activityResult.getResultCode() == Activity.RESULT_OK) - onDeleteActionSelected(getSelectedCategory()); - }); - - private final ActivityResultLauncher startImportDirectoryForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> - { - if( activityResult.getResultCode() == Activity.RESULT_OK) - onImportDirectoryResult(activityResult.getData()); - }); - - private final ActivityResultLauncher startBookmarkSettingsForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> { - // not handled at the moment - }); - - @Override @LayoutRes protected int getLayoutRes() @@ -273,7 +259,7 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment onDeleteActionSelected(getSelectedCategory()); + case REQ_CODE_IMPORT_DIRECTORY -> + { + if (data == null) + throw new AssertionError("Data is null"); - final Context context = requireActivity(); - final Uri rootUri = data.getData(); - final ProgressDialog dialog = new ProgressDialog(context, R.style.MwmTheme_ProgressDialog); - dialog.setMessage(getString(R.string.wait_several_minutes)); - dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - dialog.setIndeterminate(true); - dialog.setCancelable(false); - dialog.show(); - Logger.d(TAG, "Importing bookmarks from " + rootUri); - MwmApplication app = MwmApplication.from(context); - final File tempDir = new File(StorageUtils.getTempPath(app)); - final ContentResolver resolver = context.getContentResolver(); - ThreadPool.getStorage().execute(() -> { - AtomicInteger found = new AtomicInteger(0); - StorageUtils.listContentProviderFilesRecursively( + final Context context = requireActivity(); + final Uri rootUri = data.getData(); + final ProgressDialog dialog = new ProgressDialog(context, R.style.MwmTheme_ProgressDialog); + dialog.setMessage(getString(R.string.wait_several_minutes)); + dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + dialog.setIndeterminate(true); + dialog.setCancelable(false); + dialog.show(); + Logger.d(TAG, "Importing bookmarks from " + rootUri); + MwmApplication app = MwmApplication.from(context); + final File tempDir = new File(StorageUtils.getTempPath(app)); + final ContentResolver resolver = context.getContentResolver(); + ThreadPool.getStorage().execute(() -> { + AtomicInteger found = new AtomicInteger(0); + StorageUtils.listContentProviderFilesRecursively( resolver, rootUri, uri -> { if (BookmarkManager.INSTANCE.importBookmarksFile(resolver, uri, tempDir)) found.incrementAndGet(); }); - UiThread.run(() -> { - if (dialog.isShowing()) - dialog.dismiss(); - int found_val = found.get(); - String message = context.getResources().getQuantityString( + UiThread.run(() -> { + if (dialog.isShowing()) + dialog.dismiss(); + int found_val = found.get(); + String message = context.getResources().getQuantityString( R.plurals.bookmarks_detect_message, found_val, found_val); - Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show(); - }); - }); + Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show(); + }); + }); + } + default -> throw new AssertionError("Invalid requestCode: " + requestCode); + } } @Override diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategorySettingsActivity.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategorySettingsActivity.java index e96c65bdc0..ad8df7db69 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategorySettingsActivity.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategorySettingsActivity.java @@ -2,7 +2,6 @@ package app.organicmaps.bookmarks; import android.content.Intent; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -12,6 +11,7 @@ import app.organicmaps.bookmarks.data.BookmarkCategory; public class BookmarkCategorySettingsActivity extends BaseMwmFragmentActivity { + public static final int REQUEST_CODE = 107; public static final String EXTRA_BOOKMARK_CATEGORY = "bookmark_category"; @Override @@ -32,11 +32,11 @@ public class BookmarkCategorySettingsActivity extends BaseMwmFragmentActivity return BookmarkCategorySettingsFragment.class; } - public static void startForResult(@NonNull Fragment fragment, ActivityResultLauncher startBookmarkSettingsForResult, + public static void startForResult(@NonNull Fragment fragment, @NonNull BookmarkCategory category) { android.content.Intent intent = new Intent(fragment.requireActivity(), BookmarkCategorySettingsActivity.class) .putExtra(EXTRA_BOOKMARK_CATEGORY, category); - startBookmarkSettingsForResult.launch(intent); + fragment.startActivityForResult(intent, REQUEST_CODE); } } diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListActivity.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListActivity.java index 353df29639..98b4758139 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListActivity.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListActivity.java @@ -3,7 +3,6 @@ package app.organicmaps.bookmarks; import android.content.Intent; import android.os.Bundle; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.StyleRes; @@ -58,11 +57,11 @@ public class BookmarkListActivity extends BaseToolbarActivity return R.layout.bookmarks_activity; } - static void startForResult(@NonNull Fragment fragment, ActivityResultLauncher startBookmarkListForResult, @NonNull BookmarkCategory category) + static void startForResult(@NonNull Fragment fragment, @NonNull BookmarkCategory category) { Bundle args = new Bundle(); Intent intent = new Intent(fragment.requireActivity(), BookmarkListActivity.class); intent.putExtra(BookmarksListFragment.EXTRA_CATEGORY, category); - startBookmarkListForResult.launch(intent); + fragment.startActivityForResult(intent, BookmarkCategoriesFragment.REQ_CODE_DELETE_CATEGORY); } } diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java index 7a59c0a7fe..6e414f7f9a 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java @@ -14,7 +14,6 @@ import android.view.ViewGroup; import android.widget.ImageView; import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -39,8 +38,8 @@ import app.organicmaps.bookmarks.data.KmlFileType; import app.organicmaps.bookmarks.data.SortedBlock; import app.organicmaps.bookmarks.data.Track; import app.organicmaps.location.LocationHelper; -import app.organicmaps.search.NativeBookmarkSearchListener; -import app.organicmaps.search.SearchEngine; +import app.organicmaps.sdk.search.BookmarkSearchListener; +import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.util.Graphics; import app.organicmaps.util.SharingUtils; import app.organicmaps.util.UiUtils; @@ -62,7 +61,7 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment shareLauncher; - private final ActivityResultLauncher startBookmarkListForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> { - System.out.println("resultCode: " + activityResult.getResultCode()); - handleActivityResult(); - }); - - private final ActivityResultLauncher startBookmarkSettingsForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> { - System.out.println("resultCode: " + activityResult.getResultCode()); - handleActivityResult(); - }); @SuppressWarnings("NotNullFieldNotInitialized") @NonNull @@ -150,9 +140,7 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment { - BookmarkListActivity.startForResult(this, startBookmarkListForResult, item); - }); + adapter.setOnClickListener((v, item) -> BookmarkListActivity.startForResult(this, item)); return adapter; } @@ -768,7 +756,7 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment items = new ArrayList<>(); items.add(new MenuBottomSheetItem(R.string.edit, R.drawable.ic_edit, this::onTrackEditActionSelected)); - items.add(new MenuBottomSheetItem(R.string.export_file, R.drawable.ic_file_kmz, () -> onShareTrackSelected(track.getTrackId(), KmlFileType.Text))); - items.add(new MenuBottomSheetItem(R.string.export_file_gpx, R.drawable.ic_file_gpx, () -> onShareTrackSelected(track.getTrackId(), KmlFileType.Gpx))); items.add(new MenuBottomSheetItem(R.string.delete, R.drawable.ic_delete, () -> onDeleteTrackSelected(track.getTrackId()))); return items; } - private void onShareTrackSelected(long trackId, KmlFileType kmlFileType) - { - BookmarksSharingHelper.INSTANCE.prepareTrackForSharing(requireActivity(), trackId, kmlFileType); - } - @Override public void onPreparedFileForSharing(@NonNull BookmarkSharingResult result) { BookmarksSharingHelper.INSTANCE.onPreparedFileForSharing(requireActivity(), shareLauncher, result); } - private void handleActivityResult() + @Override + @SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630 + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); getBookmarkListAdapter().notifyDataSetChanged(); ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar(); actionBar.setTitle(mCategoryDataSource.getData().getName()); diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksSharingHelper.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksSharingHelper.java index 26e9b03413..168b062b1c 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksSharingHelper.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksSharingHelper.java @@ -36,12 +36,6 @@ public enum BookmarksSharingHelper BookmarkManager.INSTANCE.prepareCategoriesForSharing(new long[]{catId}, kmlFileType); } - public void prepareTrackForSharing(@NonNull Activity context, long trackId, KmlFileType kmlFileType) - { - showProgressDialog(context); - BookmarkManager.INSTANCE.prepareTrackForSharing(trackId, kmlFileType); - } - private void showProgressDialog(@NonNull Activity context) { mProgressDialog = new ProgressDialog(context, R.style.MwmTheme_ProgressDialog); diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/data/Bookmark.java b/android/app/src/main/java/app/organicmaps/bookmarks/data/Bookmark.java index e5323d040f..151dd5a2ee 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/data/Bookmark.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/data/Bookmark.java @@ -11,7 +11,7 @@ import androidx.core.os.ParcelCompat; import app.organicmaps.Framework; import app.organicmaps.routing.RoutePointInfo; -import app.organicmaps.search.Popularity; +import app.organicmaps.sdk.search.Popularity; import app.organicmaps.util.Constants; // TODO consider refactoring to remove hack with MapObject unmarshalling itself and Bookmark at the same time. diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java b/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java index a7015715da..c30bfdd7b7 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/data/BookmarkManager.java @@ -527,11 +527,6 @@ public enum BookmarkManager nativePrepareFileForSharing(catIds, kmlFileType.ordinal()); } - public void prepareTrackForSharing(long trackId, KmlFileType kmlFileType) - { - nativePrepareTrackFileForSharing(trackId, kmlFileType.ordinal()); - } - public void setNotificationsEnabled(boolean enabled) { nativeSetNotificationsEnabled(enabled); @@ -811,8 +806,6 @@ public enum BookmarkManager private static native void nativePrepareFileForSharing(long[] catIds, int kmlFileType); - private static native void nativePrepareTrackFileForSharing(long trackId, int kmlFileType); - private static native boolean nativeIsCategoryEmpty(long catId); private static native void nativeSetNotificationsEnabled(boolean enabled); diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/data/MapObject.java b/android/app/src/main/java/app/organicmaps/bookmarks/data/MapObject.java index 869674fb74..75b3d67aa9 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/data/MapObject.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/data/MapObject.java @@ -12,7 +12,7 @@ import androidx.core.os.ParcelCompat; import app.organicmaps.Framework; import app.organicmaps.routing.RoutePointInfo; -import app.organicmaps.search.Popularity; +import app.organicmaps.sdk.search.Popularity; import app.organicmaps.util.Utils; import app.organicmaps.widget.placepage.PlacePageData; diff --git a/android/app/src/main/java/app/organicmaps/car/CarAppService.java b/android/app/src/main/java/app/organicmaps/car/CarAppService.java index 49823f0a83..b6d9ff4570 100644 --- a/android/app/src/main/java/app/organicmaps/car/CarAppService.java +++ b/android/app/src/main/java/app/organicmaps/car/CarAppService.java @@ -1,5 +1,6 @@ package app.organicmaps.car; +import android.app.Notification; import android.content.ComponentName; import android.content.Intent; import android.net.Uri; @@ -22,6 +23,7 @@ import androidx.lifecycle.LifecycleOwner; import app.organicmaps.BuildConfig; import app.organicmaps.R; import app.organicmaps.api.Const; +import app.organicmaps.routing.NavigationService; public final class CarAppService extends androidx.car.app.CarAppService { @@ -51,7 +53,20 @@ public final class CarAppService extends androidx.car.app.CarAppService public Session onCreateSession(@Nullable SessionInfo sessionInfo) { createNotificationChannel(); - return new CarAppSession(sessionInfo); + startForeground(NOTIFICATION_ID, getNotification()); + final CarAppSession carAppSession = new CarAppSession(sessionInfo); + carAppSession.getLifecycle().addObserver(new DefaultLifecycleObserver() + { + @Override + public void onDestroy(@NonNull LifecycleOwner owner) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + stopForeground(STOP_FOREGROUND_REMOVE); + else + stopForeground(true); + } + }); + return carAppSession; } @NonNull @@ -89,4 +104,13 @@ public final class CarAppService extends androidx.car.app.CarAppService .build(); notificationManager.createNotificationChannel(notificationChannel); } + + @NonNull + private Notification getNotification() + { + return NavigationService.getNotificationBuilder(this) + .setChannelId(ANDROID_AUTO_NOTIFICATION_CHANNEL_ID) + .setContentTitle(getString(R.string.aa_connected_to_car_notification_title)) + .build(); + } } diff --git a/android/app/src/main/java/app/organicmaps/car/screens/download/DownloaderScreen.java b/android/app/src/main/java/app/organicmaps/car/screens/download/DownloaderScreen.java index 1eabac4711..bf898b5e66 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/download/DownloaderScreen.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/download/DownloaderScreen.java @@ -109,7 +109,7 @@ class DownloaderScreen extends BaseScreen for (final var item : mMissingMaps.entrySet()) { item.getValue().update(); - MapManager.startDownload(item.getKey()); + MapManager.nativeDownload(item.getKey()); } } @@ -150,11 +150,12 @@ class DownloaderScreen extends BaseScreen return getCarContext().getString(R.string.downloader_loading_ios); final long downloadedSize = getDownloadedSize(); - final String progressPercent = StringUtils.formatPercent((double) downloadedSize / mTotalSize); + final float progress = (float) downloadedSize / mTotalSize * 100; final String totalSizeStr = StringUtils.getFileSizeString(getCarContext(), mTotalSize); final String downloadedSizeStr = StringUtils.getFileSizeString(getCarContext(), downloadedSize); - return progressPercent + "\n" + downloadedSizeStr + " / " + totalSizeStr; + return StringUtils.formatUsingSystemLocale("%.2f%%\n%s", + progress, downloadedSizeStr + " / " + totalSizeStr); } private long getDownloadedSize() diff --git a/android/app/src/main/java/app/organicmaps/car/screens/permissions/RequestPermissionsScreenWithNotification.java b/android/app/src/main/java/app/organicmaps/car/screens/permissions/RequestPermissionsScreenWithNotification.java index b916deaa76..c9412a899c 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/permissions/RequestPermissionsScreenWithNotification.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/permissions/RequestPermissionsScreenWithNotification.java @@ -113,7 +113,7 @@ public class RequestPermissionsScreenWithNotification extends BaseScreen impleme .setOngoing(true) .setShowWhen(false) .setOnlyAlertOnce(true) - .setSmallIcon(R.drawable.ic_location_crosshair) + .setSmallIcon(R.drawable.ic_my_location) .setColor(ContextCompat.getColor(getCarContext(), R.color.notification)) .setContentTitle(getCarContext().getString(R.string.aa_request_permission_notification)) .setContentIntent(pendingIntent); diff --git a/android/app/src/main/java/app/organicmaps/car/screens/search/SearchOnMapScreen.java b/android/app/src/main/java/app/organicmaps/car/screens/search/SearchOnMapScreen.java index 9ef211e46e..92710669bb 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/search/SearchOnMapScreen.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/search/SearchOnMapScreen.java @@ -23,13 +23,13 @@ import app.organicmaps.car.SurfaceRenderer; import app.organicmaps.car.screens.base.BaseMapScreen; import app.organicmaps.car.util.UiHelpers; import app.organicmaps.location.LocationHelper; -import app.organicmaps.search.NativeSearchListener; -import app.organicmaps.search.SearchEngine; -import app.organicmaps.search.SearchRecents; -import app.organicmaps.search.SearchResult; +import app.organicmaps.sdk.search.SearchListener; +import app.organicmaps.sdk.search.SearchEngine; +import app.organicmaps.sdk.search.SearchRecents; +import app.organicmaps.sdk.search.SearchResult; import app.organicmaps.util.Language; -public class SearchOnMapScreen extends BaseMapScreen implements NativeSearchListener +public class SearchOnMapScreen extends BaseMapScreen implements SearchListener { private final int MAX_RESULTS_SIZE; diff --git a/android/app/src/main/java/app/organicmaps/car/screens/search/SearchScreen.java b/android/app/src/main/java/app/organicmaps/car/screens/search/SearchScreen.java index 5e97657468..e4dc95dacb 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/search/SearchScreen.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/search/SearchScreen.java @@ -21,13 +21,13 @@ import app.organicmaps.bookmarks.data.MapObject; import app.organicmaps.car.SurfaceRenderer; import app.organicmaps.car.screens.base.BaseMapScreen; import app.organicmaps.location.LocationHelper; -import app.organicmaps.search.NativeSearchListener; -import app.organicmaps.search.SearchEngine; -import app.organicmaps.search.SearchRecents; -import app.organicmaps.search.SearchResult; +import app.organicmaps.sdk.search.SearchListener; +import app.organicmaps.sdk.search.SearchEngine; +import app.organicmaps.sdk.search.SearchRecents; +import app.organicmaps.sdk.search.SearchResult; import app.organicmaps.util.Language; -public class SearchScreen extends BaseMapScreen implements SearchTemplate.SearchCallback, NativeSearchListener +public class SearchScreen extends BaseMapScreen implements SearchTemplate.SearchCallback, SearchListener { private final int MAX_RESULTS_SIZE; diff --git a/android/app/src/main/java/app/organicmaps/car/screens/search/SearchUiHelpers.java b/android/app/src/main/java/app/organicmaps/car/screens/search/SearchUiHelpers.java index 0caf27e0aa..b44fdecdc4 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/search/SearchUiHelpers.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/search/SearchUiHelpers.java @@ -13,7 +13,7 @@ import androidx.car.app.model.ForegroundCarColorSpan; import app.organicmaps.R; import app.organicmaps.car.util.Colors; import app.organicmaps.car.util.RoutingHelpers; -import app.organicmaps.search.SearchResult; +import app.organicmaps.sdk.search.SearchResult; import java.util.ArrayList; import java.util.List; diff --git a/android/app/src/main/java/app/organicmaps/car/util/SuggestionsHelpers.java b/android/app/src/main/java/app/organicmaps/car/util/SuggestionsHelpers.java index 301b337dc0..40a75755c0 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/SuggestionsHelpers.java +++ b/android/app/src/main/java/app/organicmaps/car/util/SuggestionsHelpers.java @@ -8,7 +8,7 @@ import androidx.car.app.suggestion.model.Suggestion; import androidx.core.graphics.drawable.IconCompat; import app.organicmaps.R; -import app.organicmaps.search.SearchRecents; +import app.organicmaps.sdk.search.SearchRecents; import java.util.ArrayList; import java.util.List; diff --git a/android/app/src/main/java/app/organicmaps/dialog/ProgressDialogFragment.java b/android/app/src/main/java/app/organicmaps/dialog/ProgressDialogFragment.java new file mode 100644 index 0000000000..4b7a2ebb72 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/dialog/ProgressDialogFragment.java @@ -0,0 +1,69 @@ +package app.organicmaps.dialog; + +import android.app.Activity; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; + +import app.organicmaps.R; + +public class ProgressDialogFragment extends DialogFragment +{ + private static final String ARG_MESSAGE = "title"; + private static final String ARG_CANCELABLE = "cancelable"; + private static final String ARG_RETAIN_INSTANCE = "retain_instance"; + + public ProgressDialogFragment() + { + // Do nothing by default. + } + + protected void setCancelResult() + { + Fragment targetFragment = getTargetFragment(); + if (targetFragment != null) + targetFragment.onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, null); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + Bundle args = requireArguments(); + setRetainInstance(args.getBoolean(ARG_RETAIN_INSTANCE, true)); + setCancelable(args.getBoolean(ARG_CANCELABLE, false)); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.indeterminated_progress_dialog, container, false); + Bundle args = requireArguments(); + TextView messageView = view.findViewById(R.id.message); + messageView.setText(args.getString(ARG_MESSAGE)); + return view; + } + + @Override + public void onCancel(DialogInterface dialog) + { + setCancelResult(); + } + + @Override + public void onDestroyView() + { + if (getDialog() != null && getRetainInstance()) + getDialog().setDismissMessage(null); + super.onDestroyView(); + } +} diff --git a/android/app/src/main/java/app/organicmaps/downloader/BottomPanel.java b/android/app/src/main/java/app/organicmaps/downloader/BottomPanel.java index 3700f4fe93..f62c68a870 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/BottomPanel.java +++ b/android/app/src/main/java/app/organicmaps/downloader/BottomPanel.java @@ -30,7 +30,7 @@ class BottomPanel public void onClick(View v) { final String country = mFragment.getCurrentRoot(); - MapManager.warnOn3gUpdate(mFragment.requireActivity(), country, () -> MapManager.startUpdate(country)); + MapManager.warnOn3gUpdate(mFragment.requireActivity(), country, () -> MapManager.nativeUpdate(country)); } }; diff --git a/android/app/src/main/java/app/organicmaps/downloader/CountryItem.java b/android/app/src/main/java/app/organicmaps/downloader/CountryItem.java index 8f0e276626..7fd0e8eee4 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/CountryItem.java +++ b/android/app/src/main/java/app/organicmaps/downloader/CountryItem.java @@ -63,9 +63,7 @@ public final class CountryItem implements Comparable public int errorCode; public boolean present; - /** - * This value represents the percentage of download (values span from 0 to 100) - */ + // Progress public float progress; public long downloadedBytes; public long bytesToDownload; diff --git a/android/app/src/main/java/app/organicmaps/downloader/CountrySuggestFragment.java b/android/app/src/main/java/app/organicmaps/downloader/CountrySuggestFragment.java index 3a843fb4c2..803e9a28ae 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/CountrySuggestFragment.java +++ b/android/app/src/main/java/app/organicmaps/downloader/CountrySuggestFragment.java @@ -192,7 +192,8 @@ public class CountrySuggestFragment extends BaseMwmFragment implements View.OnCl private void updateProgress() { - String text = getString(R.string.downloader_downloading) + " " + StringUtils.formatPercent(mDownloadingCountry.progress / 100); + String text = StringUtils.formatUsingSystemLocale("%1$s %2$.2f%%", getString(R.string.downloader_downloading), + mDownloadingCountry.progress); mTvProgress.setText(text); mWpvDownloadProgress.setProgress(Math.round(mDownloadingCountry.progress)); } diff --git a/android/app/src/main/java/app/organicmaps/downloader/DownloaderAdapter.java b/android/app/src/main/java/app/organicmaps/downloader/DownloaderAdapter.java index d1d38dadd2..69083e34f2 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderAdapter.java +++ b/android/app/src/main/java/app/organicmaps/downloader/DownloaderAdapter.java @@ -91,7 +91,7 @@ class DownloaderAdapter extends RecyclerView.Adapter MapManager.startUpdate(item.id)); + MapManager.warnOn3gUpdate(adapter.mActivity, item.id, () -> MapManager.nativeUpdate(item.id)); } private void onExploreActionSelected(CountryItem item, DownloaderAdapter adapter) @@ -150,7 +150,6 @@ class DownloaderAdapter extends RecyclerView.Adapter { - MapManager.warn3gAndRetry(mActivity, mItem.id, null); + RetryFailedDownloadConfirmationListener listener = + new RetryFailedDownloadConfirmationListener(mActivity.getApplication()); + MapManager.warn3gAndRetry(mActivity, mItem.id, listener); } case CountryItem.STATUS_UPDATABLE -> - MapManager.warnOn3gUpdate(mActivity, mItem.id, () -> MapManager.startUpdate(mItem.id)); + MapManager.warnOn3gUpdate(mActivity, mItem.id, () -> MapManager.nativeUpdate(mItem.id)); default -> throw new IllegalArgumentException("Inappropriate item status: " + mItem.status); } } diff --git a/android/app/src/main/java/app/organicmaps/downloader/DownloaderFragment.java b/android/app/src/main/java/app/organicmaps/downloader/DownloaderFragment.java index 6be72a9021..5ec51e2e2c 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderFragment.java +++ b/android/app/src/main/java/app/organicmaps/downloader/DownloaderFragment.java @@ -5,8 +5,6 @@ import android.os.Bundle; import android.view.View; import android.view.WindowManager; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CallSuper; import androidx.annotation.Keep; import androidx.annotation.NonNull; @@ -17,8 +15,8 @@ import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; import app.organicmaps.base.BaseMwmRecyclerFragment; import app.organicmaps.base.OnBackPressListener; -import app.organicmaps.search.NativeMapSearchListener; -import app.organicmaps.search.SearchEngine; +import app.organicmaps.sdk.search.MapSearchListener; +import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.widget.PlaceholderView; import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; import app.organicmaps.util.bottomsheet.MenuBottomSheetItem; @@ -41,8 +39,6 @@ public class DownloaderFragment extends BaseMwmRecyclerFragment startVoiceRecognitionForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> mToolbarController.onVoiceRecognitionResult(activityResult)); - private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) @@ -52,13 +48,13 @@ public class DownloaderFragment extends BaseMwmRecyclerFragment rs = new ArrayList<>(); for (Result result : results) { - CountryItem item = CountryItem.fill(result.countryId); - item.searchResultName = result.matchedString; + CountryItem item = CountryItem.fill(result.countryId()); + item.searchResultName = result.matchedString(); rs.add(item); } @@ -214,6 +210,13 @@ public class DownloaderFragment extends BaseMwmRecyclerFragment= Build.VERSION_CODES.TIRAMISU && - ContextCompat.checkSelfPermission(mContext, POST_NOTIFICATIONS) != PERMISSION_GRANTED) + ContextCompat.checkSelfPermission(context, POST_NOTIFICATIONS) != PERMISSION_GRANTED) { Logger.w(TAG, "Permission POST_NOTIFICATIONS is not granted, skipping notification"); return; } - final String title = mContext.getString(R.string.app_name); + final String title = context.getString(R.string.app_name); final String countryName = MapManager.nativeGetName(countryId); - final String content = mContext.getString(R.string.download_country_failed, countryName); + final String content = context.getString(R.string.download_country_failed, countryName); - var contentPendingIntent = getNotificationPendingIntent(countryId); + final int FLAG_IMMUTABLE = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? 0 : PendingIntent.FLAG_IMMUTABLE; + final Intent contentIntent = MwmActivity.createShowMapIntent(context, countryId); + contentIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + final PendingIntent contentPendingIntent = PendingIntent.getActivity(context, 0, contentIntent, + PendingIntent.FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); - final Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID) + final Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID) .setAutoCancel(true) .setCategory(NotificationCompat.CATEGORY_ERROR) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_splash) - .setColor(ContextCompat.getColor(mContext, R.color.notification)) + .setColor(ContextCompat.getColor(context, R.color.notification)) .setContentTitle(title) .setContentText(content) .setShowWhen(true) - .setTicker(getTicker(mContext, title, content)) + .setTicker(getTicker(context, title, content)) .setContentIntent(contentPendingIntent) .setOnlyAlertOnce(true) .build(); Logger.i(TAG, "Notifying about failed map download"); - final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mContext); + final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(NOTIFICATION_ID, notification); } - public void notifyProgress() { - notifyProgress(null, 0, 0); - } - - public void notifyProgress(@Nullable String countryId, int maxProgress, int progress) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && - ContextCompat.checkSelfPermission(mContext, POST_NOTIFICATIONS) != PERMISSION_GRANTED) - { - Logger.w(TAG, "Permission POST_NOTIFICATIONS is not granted, skipping notification"); - return; - } - - NotificationManagerCompat.from(mContext).notify(NOTIFICATION_ID, buildProgressNotification(countryId, maxProgress, progress)); - } - - @NonNull - public Notification buildProgressNotification() + static void cancelNotification(@NonNull Context context) { - return buildProgressNotification(null, 0, 0); - } - - @NonNull - public Notification buildProgressNotification(@Nullable String countryId, int maxProgress, int progress) - { - var builder = startNotification(countryId); - - builder.setProgress(maxProgress, progress, maxProgress == 0); - builder.setContentText("Download in progress"); - - return builder.build(); - } - - @NonNull - private NotificationCompat.Builder startNotification(@Nullable String countryId) - { - final String title = mContext.getString(R.string.app_name); - - return new NotificationCompat.Builder(mContext, CHANNEL_ID) - .setAutoCancel(true) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setSmallIcon(R.drawable.ic_splash) - .setColor(ContextCompat.getColor(mContext, R.color.notification)) - .setShowWhen(true) - .setContentTitle(title) - .setContentIntent(getNotificationPendingIntent(countryId)); - } - - @NonNull - private PendingIntent getNotificationPendingIntent(@Nullable String countryId) { - final int FLAG_IMMUTABLE = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? 0 : PendingIntent.FLAG_IMMUTABLE; - final Intent contentIntent = MwmActivity.createShowMapIntent(mContext, countryId); - contentIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return PendingIntent.getActivity(mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); + Logger.i(TAG, "Cancelling notification about failed map download"); + final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + notificationManager.cancel(NOTIFICATION_ID); } @NonNull diff --git a/android/app/src/main/java/app/organicmaps/downloader/DownloaderService.java b/android/app/src/main/java/app/organicmaps/downloader/DownloaderService.java deleted file mode 100644 index ee4fcd7550..0000000000 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderService.java +++ /dev/null @@ -1,142 +0,0 @@ -package app.organicmaps.downloader; - -import static android.Manifest.permission.POST_NOTIFICATIONS; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import android.app.ForegroundServiceStartNotAllowedException; -import android.app.Service; -import android.content.Intent; -import android.os.Build; -import android.os.IBinder; - -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; - -import java.util.List; - -import app.organicmaps.MwmApplication; -import app.organicmaps.util.log.Logger; - -public class DownloaderService extends Service implements MapManager.StorageCallback -{ - private static final String TAG = DownloaderService.class.getSimpleName(); - - private final DownloaderNotifier mNotifier = new DownloaderNotifier(this); - private int mSubscriptionSlot; - - @Override - public void onCreate() - { - super.onCreate(); - - Logger.i(TAG); - - mSubscriptionSlot = MapManager.nativeSubscribe(this); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - Logger.i(TAG, "Downloading: " + MapManager.nativeIsDownloading()); - - var notification = mNotifier.buildProgressNotification(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - { - try - { - startForeground(DownloaderNotifier.NOTIFICATION_ID, notification); - } catch (ForegroundServiceStartNotAllowedException e) - { - Logger.e(TAG, "Oops! ForegroundService is not allowed", e); - } - } else - { - startForeground(DownloaderNotifier.NOTIFICATION_ID, notification); - } - - return START_NOT_STICKY; - } - - @Nullable - @Override - public IBinder onBind(Intent intent) - { - return null; - } - - @Override - public void onStatusChanged(List data) - { - var isDownloading = MapManager.nativeIsDownloading(); - var hasFailed = hasDownloadFailed(data); - - Logger.i(TAG, "Downloading: " + isDownloading + " failure: " + hasFailed); - - if (!isDownloading) - { - if (hasFailed) - { - // Detach service from the notification to keep after the service is stopped. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - { - stopForeground(Service.STOP_FOREGROUND_DETACH); - } else - { - stopForeground(false); - } - } - stopSelf(); - } - } - - @Override - public void onProgress(String countryId, long localSize, long remoteSize) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && - ContextCompat.checkSelfPermission(this, POST_NOTIFICATIONS) != PERMISSION_GRANTED) - { - Logger.w(TAG, "Permission POST_NOTIFICATIONS is not granted, skipping notification"); - return; - } - - // TODO: How to calculate progress? - mNotifier.notifyProgress(); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - - Logger.i(TAG, "onDestroy"); - - MapManager.nativeUnsubscribe(mSubscriptionSlot); - } - - /** - * Start the foreground service to keep the user informed about the status of region downloads. - */ - public static void startForegroundService() - { - Logger.i(TAG); - var context = MwmApplication.sInstance; - ContextCompat.startForegroundService(context, new Intent(context, DownloaderService.class)); - } - - private boolean hasDownloadFailed(List data) - { - for (MapManager.StorageCallbackData item : data) - { - if (item.isLeafNode && item.newStatus == CountryItem.STATUS_FAILED) - { - if (MapManager.nativeIsAutoretryFailed()) - { - mNotifier.notifyDownloadFailed(item.countryId); - return true; - } - } - } - - return false; - } -} diff --git a/android/app/src/main/java/app/organicmaps/downloader/DownloaderToolbarController.java b/android/app/src/main/java/app/organicmaps/downloader/DownloaderToolbarController.java index fddbcbc91f..9e79dc556a 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderToolbarController.java +++ b/android/app/src/main/java/app/organicmaps/downloader/DownloaderToolbarController.java @@ -58,9 +58,10 @@ class DownloaderToolbarController extends SearchToolbarController } @Override - protected void startVoiceRecognition(Intent intent) + @SuppressWarnings("deprecated") // https://github.com/organicmaps/organicmaps/issues/3630 + protected void startVoiceRecognition(Intent intent, int code) { - mFragment.startVoiceRecognitionForResult.launch(intent); + mFragment.startActivityForResult(intent, code); } @Override diff --git a/android/app/src/main/java/app/organicmaps/downloader/ExpandRetryConfirmationListener.java b/android/app/src/main/java/app/organicmaps/downloader/ExpandRetryConfirmationListener.java index c931f04bee..ec5e90140a 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/ExpandRetryConfirmationListener.java +++ b/android/app/src/main/java/app/organicmaps/downloader/ExpandRetryConfirmationListener.java @@ -1,22 +1,28 @@ package app.organicmaps.downloader; +import android.app.Application; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import app.organicmaps.util.Utils; -class ExpandRetryConfirmationListener implements Runnable +class ExpandRetryConfirmationListener extends RetryFailedDownloadConfirmationListener { @Nullable private final Utils.Proc mDialogClickListener; - ExpandRetryConfirmationListener(@Nullable Utils.Proc dialogClickListener) + ExpandRetryConfirmationListener(@NonNull Application app, + @Nullable Utils.Proc dialogClickListener) { + super(app); mDialogClickListener = dialogClickListener; } @Override public void run() { + super.run(); if (mDialogClickListener == null) return; mDialogClickListener.invoke(true); diff --git a/android/app/src/main/java/app/organicmaps/downloader/MapManager.java b/android/app/src/main/java/app/organicmaps/downloader/MapManager.java index 266baa9cdb..8348d5ef6d 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/MapManager.java +++ b/android/app/src/main/java/app/organicmaps/downloader/MapManager.java @@ -106,8 +106,8 @@ public final class MapManager }) .setPositiveButton(R.string.downloader_retry, (dialog, which) -> { Application app = activity.getApplication(); - ExpandRetryConfirmationListener listener - = new ExpandRetryConfirmationListener(dialogClickListener); + RetryFailedDownloadConfirmationListener listener + = new ExpandRetryConfirmationListener(app, dialogClickListener); warn3gAndRetry(activity, errorData.countryId, listener); }).create(); dlg.setCanceledOnTouchOutside(false); @@ -208,7 +208,7 @@ public final class MapManager return warnOn3g(activity, countryId, () -> { if (onAcceptListener != null) onAcceptListener.run(); - startDownload(countryId); + nativeDownload(countryId); }); } @@ -217,45 +217,10 @@ public final class MapManager return warnOn3g(activity, countryId, () -> { if (onAcceptListener != null) onAcceptListener.run(); - retryDownload(countryId); + nativeRetry(countryId); }); } - /** - * Enqueues failed items under given {@code root} node in downloader. - */ - public static void retryDownload(@NonNull String countryId) { - DownloaderService.startForegroundService(); - nativeRetry(countryId); - } - - /** - * Enqueues given {@code root} node with its children in downloader. - */ - public static void startUpdate(@NonNull String root) { - DownloaderService.startForegroundService(); - nativeUpdate(root); - } - - /** - * Enqueues the given list of nodes and its children in downloader. - */ - public static void startDownload(String... countries) { - DownloaderService.startForegroundService(); - for (var countryId : countries) - { - nativeDownload(countryId); - } - } - - /** - * Enqueues given {@code root} node and its children in downloader. - */ - public static void startDownload(@NonNull String countryId) { - DownloaderService.startForegroundService(); - nativeDownload(countryId); - } - /** * Retrieves ID of root node. */ @@ -349,17 +314,17 @@ public final class MapManager /** * Enqueues given {@code root} node and its children in downloader. */ - private static native void nativeDownload(String root); + public static native void nativeDownload(String root); /** * Enqueues failed items under given {@code root} node in downloader. */ - private static native void nativeRetry(String root); + public static native void nativeRetry(String root); /** * Enqueues given {@code root} node with its children in downloader. */ - private static native void nativeUpdate(String root); + public static native void nativeUpdate(String root); /** * Removes given currently downloading {@code root} node and its children from downloader. diff --git a/android/app/src/main/java/app/organicmaps/downloader/OnmapDownloader.java b/android/app/src/main/java/app/organicmaps/downloader/OnmapDownloader.java index b3503a453f..c61f92e631 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/OnmapDownloader.java +++ b/android/app/src/main/java/app/organicmaps/downloader/OnmapDownloader.java @@ -140,7 +140,8 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener { mProgress.setPending(false); mProgress.setProgress(Math.round(mCurrentCountry.progress)); - sizeText = mActivity.getString(R.string.downloader_downloading) + " " + StringUtils.formatPercent(mCurrentCountry.progress / 100); + sizeText = StringUtils.formatUsingSystemLocale("%1$s %2$.2f%%", + mActivity.getString(R.string.downloader_downloading), mCurrentCountry.progress); } else { @@ -166,7 +167,7 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener if (TextUtils.equals(mCurrentCountry.id, country) && MapManager.nativeHasSpaceToDownloadCountry(country)) { - MapManager.startDownload(mCurrentCountry.id); + MapManager.nativeDownload(mCurrentCountry.id); } } } @@ -210,11 +211,12 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener boolean retry = (mCurrentCountry.status == CountryItem.STATUS_FAILED); if (retry) { - MapManager.retryDownload(mCurrentCountry.id); + DownloaderNotifier.cancelNotification(mActivity.getApplicationContext()); + MapManager.nativeRetry(mCurrentCountry.id); } else { - MapManager.startDownload(mCurrentCountry.id); + MapManager.nativeDownload(mCurrentCountry.id); mActivity.requestPostNotificationsPermission(); } })); diff --git a/android/app/src/main/java/app/organicmaps/downloader/RetryFailedDownloadConfirmationListener.java b/android/app/src/main/java/app/organicmaps/downloader/RetryFailedDownloadConfirmationListener.java new file mode 100644 index 0000000000..ecc252d2ec --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/downloader/RetryFailedDownloadConfirmationListener.java @@ -0,0 +1,22 @@ +package app.organicmaps.downloader; + +import android.app.Application; + +import androidx.annotation.NonNull; + +public class RetryFailedDownloadConfirmationListener implements Runnable +{ + @NonNull + private final Application mApplication; + + RetryFailedDownloadConfirmationListener(@NonNull Application application) + { + mApplication = application; + } + + @Override + public void run() + { + DownloaderNotifier.cancelNotification(mApplication); + } +} diff --git a/android/app/src/main/java/app/organicmaps/editor/EditorFragment.java b/android/app/src/main/java/app/organicmaps/editor/EditorFragment.java index 86ab7b1e7d..736c374604 100644 --- a/android/app/src/main/java/app/organicmaps/editor/EditorFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/EditorFragment.java @@ -50,8 +50,6 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe private View mCardName; private View mCardAddress; private View mCardDetails; - private View mCardSocialMedia; - private View mCardBuilding; private RecyclerView mNamesView; @@ -95,6 +93,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe private TextView mStreet; private TextInputEditText mHouseNumber; + private View mBlockLevels; private TextInputEditText mBuildingLevels; // Define Metadata entries, that have more tricky logic, separately. @@ -136,7 +135,6 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe private View mEditOpeningHours; private TextInputEditText mDescription; private final Map mDetailsBlocks = new HashMap<>(); - private final Map mSocialMediaBlocks = new HashMap<>(); private TextView mReset; private EditorHostFragment mParent; @@ -302,29 +300,29 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe { UiUtils.showIf(Editor.nativeIsNameEditable(), mCardName); UiUtils.showIf(Editor.nativeIsAddressEditable(), mCardAddress); - UiUtils.showIf(Editor.nativeIsBuilding() && !Editor.nativeIsPointType(), mCardBuilding); + UiUtils.showIf(Editor.nativeIsBuilding() && !Editor.nativeIsPointType(), mBlockLevels); final int[] editableDetails = Editor.nativeGetEditableProperties(); + if (editableDetails.length == 0) + { + UiUtils.hide(mCardDetails); + return; + } - setCardVisibility(mCardDetails, mDetailsBlocks, editableDetails); - setCardVisibility(mCardSocialMedia, mSocialMediaBlocks, editableDetails); - } - - private void setCardVisibility(View card, Map blocks, int[] editableDetails) { - for (var e : blocks.entrySet()) + for (var e : mDetailsBlocks.entrySet()) UiUtils.hide(e.getValue()); - boolean anyBlockElement = false; + boolean anyEditableDetails = false; for (int type : editableDetails) { - final View blockElement = blocks.get(Metadata.MetadataType.fromInt(type)); - if (blockElement == null) + final View detailsBlock = mDetailsBlocks.get(Metadata.MetadataType.fromInt(type)); + if (detailsBlock == null) continue; - anyBlockElement = true; - UiUtils.show(blockElement); + anyEditableDetails = true; + UiUtils.show(detailsBlock); } - UiUtils.showIf(anyBlockElement, card); + UiUtils.showIf(anyEditableDetails, mCardDetails); } private void refreshOpeningTime() @@ -414,22 +412,20 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe mCardName = view.findViewById(R.id.cv__name); mCardAddress = view.findViewById(R.id.cv__address); mCardDetails = view.findViewById(R.id.cv__details); - mCardSocialMedia = view.findViewById(R.id.cv__social_media); - mCardBuilding = view.findViewById(R.id.cv__building); initNamesView(view); // Address view.findViewById(R.id.block_street).setOnClickListener(this); mStreet = view.findViewById(R.id.street); View blockHouseNumber = view.findViewById(R.id.block_building); - mHouseNumber = findInputAndInitBlock(blockHouseNumber, R.drawable.ic_building, R.string.house_number); + mHouseNumber = findInputAndInitBlock(blockHouseNumber, 0, R.string.house_number); mInputHouseNumber = blockHouseNumber.findViewById(R.id.custom_input); - initBlock(view, Metadata.MetadataType.FMD_POSTCODE, R.id.block_zipcode, R.drawable.ic_address, R.string.editor_zip_code, 0); + initBlock(view, Metadata.MetadataType.FMD_POSTCODE, R.id.block_zipcode, 0, R.string.editor_zip_code, 0); // Details - View mBlockLevels = view.findViewById(R.id.block_levels); - mBuildingLevels = findInputAndInitBlock(mBlockLevels, R.drawable.ic_floor, + mBlockLevels = view.findViewById(R.id.block_levels); + mBuildingLevels = findInputAndInitBlock(mBlockLevels, 0, getString(R.string.editor_storey_number, Editor.nativeGetMaxEditableBuildingLevels())); mBuildingLevels.setInputType(InputType.TYPE_CLASS_NUMBER); mInputBuildingLevels = mBlockLevels.findViewById(R.id.custom_input); @@ -497,13 +493,12 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe mDetailsBlocks.put(Metadata.MetadataType.FMD_WEBSITE, websiteBlock); mDetailsBlocks.put(Metadata.MetadataType.FMD_WEBSITE_MENU, websiteMenuBlock); mDetailsBlocks.put(Metadata.MetadataType.FMD_EMAIL, emailBlock); + mDetailsBlocks.put(Metadata.MetadataType.FMD_CONTACT_FACEBOOK, facebookContactBlock); + mDetailsBlocks.put(Metadata.MetadataType.FMD_CONTACT_INSTAGRAM, instagramContactBlock); + mDetailsBlocks.put(Metadata.MetadataType.FMD_CONTACT_TWITTER, twitterContactBlock); + mDetailsBlocks.put(Metadata.MetadataType.FMD_CONTACT_VK, vkContactBlock); + mDetailsBlocks.put(Metadata.MetadataType.FMD_CONTACT_LINE, lineContactBlock); mDetailsBlocks.put(Metadata.MetadataType.FMD_OPERATOR, operatorBlock); - - mSocialMediaBlocks.put(Metadata.MetadataType.FMD_CONTACT_FACEBOOK, facebookContactBlock); - mSocialMediaBlocks.put(Metadata.MetadataType.FMD_CONTACT_INSTAGRAM, instagramContactBlock); - mSocialMediaBlocks.put(Metadata.MetadataType.FMD_CONTACT_TWITTER, twitterContactBlock); - mSocialMediaBlocks.put(Metadata.MetadataType.FMD_CONTACT_VK, vkContactBlock); - mSocialMediaBlocks.put(Metadata.MetadataType.FMD_CONTACT_LINE, lineContactBlock); } private static TextInputEditText findInput(View blockWithInput) diff --git a/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java b/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java index 85866b8d28..bbf000e272 100644 --- a/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java @@ -350,7 +350,7 @@ public class EditorHostFragment extends BaseMwmToolbarFragment implements View.O private void processEditedFeatures() { Context context = requireContext(); - if (OsmOAuth.isAuthorized(context)) + if (OsmOAuth.isAuthorized(context) || !ConnectionState.INSTANCE.isConnected()) { Utils.navigateToParent(requireActivity()); return; diff --git a/android/app/src/main/java/app/organicmaps/intent/Factory.java b/android/app/src/main/java/app/organicmaps/intent/Factory.java index d8c213602c..4dd859322f 100644 --- a/android/app/src/main/java/app/organicmaps/intent/Factory.java +++ b/android/app/src/main/java/app/organicmaps/intent/Factory.java @@ -3,7 +3,6 @@ package app.organicmaps.intent; import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; -import android.util.Log; import androidx.annotation.NonNull; import androidx.core.content.IntentCompat; @@ -22,7 +21,7 @@ import app.organicmaps.bookmarks.data.MapObject; import app.organicmaps.editor.OsmLoginActivity; import app.organicmaps.routing.RoutingController; import app.organicmaps.search.SearchActivity; -import app.organicmaps.search.SearchEngine; +import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.util.StorageUtils; import app.organicmaps.util.concurrency.ThreadPool; @@ -99,7 +98,7 @@ public class Factory RoutingController.get().prepare(MapObject.createMapObject(FeatureId.EMPTY, MapObject.API_POINT, from.mName, "", from.mLat, from.mLon), MapObject.createMapObject(FeatureId.EMPTY, MapObject.API_POINT, - to.mName, "", to.mLat, to.mLon)); + to.mName, "", to.mLat, to.mLon), true); return true; case RequestType.SEARCH: { diff --git a/android/app/src/main/java/app/organicmaps/location/TrackRecordingService.java b/android/app/src/main/java/app/organicmaps/location/TrackRecordingService.java index a217f355d8..b91adc936a 100644 --- a/android/app/src/main/java/app/organicmaps/location/TrackRecordingService.java +++ b/android/app/src/main/java/app/organicmaps/location/TrackRecordingService.java @@ -200,8 +200,6 @@ public class TrackRecordingService extends Service implements LocationListener .setSmallIcon(R.drawable.warning_icon) .setContentTitle(context.getString(R.string.current_location_unknown_error_title)) .setContentText(context.getString(R.string.dialog_routing_location_turn_wifi)) - .setStyle(new NotificationCompat.BigTextStyle() - .bigText(context.getString(R.string.dialog_routing_location_turn_wifi))) .addAction(0, context.getString(R.string.navigation_stop_button), getExitPendingIntent(context)) .setContentIntent(getPendingIntent(context)) .setColor(ContextCompat.getColor(context, R.color.notification_warning)); diff --git a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java index e1d4c125b7..8f53e4387d 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java @@ -1,9 +1,6 @@ package app.organicmaps.maplayer; -import android.animation.ArgbEvaluator; -import android.animation.ObjectAnimator; import android.content.Context; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextUtils; import android.util.TypedValue; @@ -55,8 +52,6 @@ public class MapButtonsController extends Fragment private View mBottomButtonsFrame; @Nullable private LayersButton mToggleMapLayerButton; - @Nullable - FloatingActionButton mTrackRecordingStatusButton; @Nullable private MyPositionButton mNavMyPosition; @@ -73,11 +68,7 @@ public class MapButtonsController extends Fragment private final Observer mButtonHiddenObserver = this::setButtonsHidden; private final Observer mMyPositionModeObserver = this::updateNavMyPositionButton; private final Observer mSearchOptionObserver = this::onSearchOptionChange; - private final Observer mTrackRecorderObserver = (enable) -> { - updateMenuBadge(enable); - showButton(enable, MapButtons.trackRecordingStatus); - }; - private final Observer mTopButtonMarginObserver = this::updateTopButtonsMargin; + private final Observer mTrackRecorderObserver = this::updateMenuBadge; @Nullable @Override @@ -128,10 +119,6 @@ public class MapButtonsController extends Fragment mToggleMapLayerButton.setOnClickListener(view -> mMapButtonClickListener.onMapButtonClick(MapButtons.toggleMapLayer)); mToggleMapLayerButton.setVisibility(View.VISIBLE); } - mMapButtonsViewModel.setTopButtonsMarginTop(-1); - mTrackRecordingStatusButton = mFrame.findViewById(R.id.track_recording_status); - if (mTrackRecordingStatusButton != null) - mTrackRecordingStatusButton.setOnClickListener(view -> mMapButtonClickListener.onMapButtonClick(MapButtons.trackRecordingStatus)); final View menuButton = mFrame.findViewById(R.id.menu_button); if (menuButton != null) { @@ -170,9 +157,6 @@ public class MapButtonsController extends Fragment mButtonsMap.put(MapButtons.menu, menuButton); if (helpButton != null) mButtonsMap.put(MapButtons.help, helpButton); - if (mTrackRecordingStatusButton != null) - mButtonsMap.put(MapButtons.trackRecordingStatus, mTrackRecordingStatusButton); - showButton(false, MapButtons.trackRecordingStatus); return mFrame; } @@ -200,28 +184,6 @@ public class MapButtonsController extends Fragment case bookmarks: case menu: UiUtils.showIf(show, buttonView); - break; - case trackRecordingStatus: - UiUtils.showIf(show, buttonView); - animateIconBlinking(show, (FloatingActionButton) buttonView); - } - } - - void animateIconBlinking(boolean show, @NonNull FloatingActionButton button) - { - if (show) - { - Drawable drawable = button.getDrawable(); - ObjectAnimator colorAnimator = ObjectAnimator.ofArgb( - drawable, - "tint", - 0xFF757575, - 0xFFFF0000); - colorAnimator.setDuration(2500); - colorAnimator.setEvaluator(new ArgbEvaluator()); - colorAnimator.setRepeatCount(ObjectAnimator.INFINITE); - colorAnimator.setRepeatMode(ObjectAnimator.REVERSE); - colorAnimator.start(); } } @@ -230,15 +192,6 @@ public class MapButtonsController extends Fragment return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); } - private void updateTopButtonsMargin(int margin) - { - if (margin == -1 || mTrackRecordingStatusButton == null) - return; - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mTrackRecordingStatusButton.getLayoutParams(); - params.topMargin = margin; - mTrackRecordingStatusButton.setLayoutParams(params); - } - @OptIn(markerClass = ExperimentalBadgeUtils.class) private void updateMenuBadge(Boolean enable) { @@ -399,7 +352,6 @@ public class MapButtonsController extends Fragment mMapButtonsViewModel.getMyPositionMode().observe(activity, mMyPositionModeObserver); mMapButtonsViewModel.getSearchOption().observe(activity, mSearchOptionObserver); mMapButtonsViewModel.getTrackRecorderState().observe(activity, mTrackRecorderObserver); - mMapButtonsViewModel.getTopButtonsMarginTop().observe(activity, mTopButtonMarginObserver); } public void onResume() @@ -426,7 +378,6 @@ public class MapButtonsController extends Fragment public void onStop() { super.onStop(); - mMapButtonsViewModel.getTopButtonsMarginTop().removeObserver(mTopButtonMarginObserver); mPlacePageViewModel.getPlacePageDistanceToTop().removeObserver(mPlacePageDistanceToTopObserver); mMapButtonsViewModel.getButtonsHidden().removeObserver(mButtonHiddenObserver); mMapButtonsViewModel.getMyPositionMode().removeObserver(mMyPositionModeObserver); @@ -456,8 +407,7 @@ public class MapButtonsController extends Fragment search, bookmarks, menu, - help, - trackRecordingStatus + help } public interface MapButtonClickListener diff --git a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java index ab3bf73dee..d4acfcb045 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsViewModel.java @@ -9,7 +9,6 @@ public class MapButtonsViewModel extends ViewModel { private final MutableLiveData mButtonsHidden = new MutableLiveData<>(false); private final MutableLiveData mBottomButtonsHeight = new MutableLiveData<>(0f); - private final MutableLiveData mTopButtonsMarginTop = new MutableLiveData<>(-1); private final MutableLiveData mLayoutMode = new MutableLiveData<>(MapButtonsController.LayoutMode.regular); private final MutableLiveData mMyPositionMode = new MutableLiveData<>(); private final MutableLiveData mSearchOption = new MutableLiveData<>(); @@ -35,16 +34,6 @@ public class MapButtonsViewModel extends ViewModel mBottomButtonsHeight.setValue(height); } - public MutableLiveData getTopButtonsMarginTop() - { - return mTopButtonsMarginTop; - } - - public void setTopButtonsMarginTop(int margin) - { - mTopButtonsMarginTop.setValue(margin); - } - public MutableLiveData getLayoutMode() { return mLayoutMode; diff --git a/android/app/src/main/java/app/organicmaps/maplayer/SearchWheel.java b/android/app/src/main/java/app/organicmaps/maplayer/SearchWheel.java index 81a9968ee2..077534ab84 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/SearchWheel.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/SearchWheel.java @@ -16,7 +16,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import app.organicmaps.R; import app.organicmaps.routing.RoutingController; -import app.organicmaps.search.SearchEngine; +import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.util.Graphics; import app.organicmaps.util.UiUtils; import app.organicmaps.util.concurrency.UiThread; diff --git a/android/app/src/main/java/app/organicmaps/products/Product.java b/android/app/src/main/java/app/organicmaps/products/Product.java deleted file mode 100644 index 18e354c84e..0000000000 --- a/android/app/src/main/java/app/organicmaps/products/Product.java +++ /dev/null @@ -1,20 +0,0 @@ -package app.organicmaps.products; - -import androidx.annotation.Keep; -import androidx.annotation.Nullable; - -// Called from JNI. -@Keep -@SuppressWarnings("unused") -public class Product { - @Nullable - public String title; - - @Nullable - public String link; - - public Product(@Nullable String title, @Nullable String link) { - this.title = title; - this.link = link; - } -} diff --git a/android/app/src/main/java/app/organicmaps/products/ProductsConfig.java b/android/app/src/main/java/app/organicmaps/products/ProductsConfig.java deleted file mode 100644 index 9fb8d50f6f..0000000000 --- a/android/app/src/main/java/app/organicmaps/products/ProductsConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package app.organicmaps.products; - -import androidx.annotation.Keep; -import androidx.annotation.Nullable; - -// Called from JNI. -@Keep -@SuppressWarnings("unused") -public class ProductsConfig { - public ProductsConfig(@Nullable String placePagePrompt, @Nullable Product[] products) { - this.placePagePrompt = placePagePrompt; - this.products = products; - } - - @Nullable - public String placePagePrompt; - - @Nullable - public Product[] products; -} diff --git a/android/app/src/main/java/app/organicmaps/routing/ManageRouteAdapter.java b/android/app/src/main/java/app/organicmaps/routing/ManageRouteAdapter.java deleted file mode 100644 index 4e6fa6c5a6..0000000000 --- a/android/app/src/main/java/app/organicmaps/routing/ManageRouteAdapter.java +++ /dev/null @@ -1,265 +0,0 @@ -package app.organicmaps.routing; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.RectF; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.recyclerview.widget.RecyclerView; - -import app.organicmaps.R; -import app.organicmaps.bookmarks.data.MapObject; -import app.organicmaps.util.StringUtils; -import app.organicmaps.util.UiUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; - -public class ManageRouteAdapter extends RecyclerView.Adapter -{ - Context mContext; - ArrayList mRoutePoints; - ManageRouteListener mManageRouteListener; - - public interface ManageRouteListener - { - void startDrag(RecyclerView.ViewHolder viewHolder); - void showMyLocationIcon(boolean showMyLocationIcon); - void onRoutePointDeleted(RecyclerView.ViewHolder viewHolder); - } - - public ManageRouteAdapter(Context context, RouteMarkData[] routeMarkData, ManageRouteListener listener) - { - mContext = context; - mRoutePoints = new ArrayList<>(Arrays.asList(routeMarkData)); - mManageRouteListener = listener; - - updateMyLocationIcon(); - } - - @NonNull - @Override - public ManageRouteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) - { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.manage_route_list_item, - parent, false); - - return new ManageRouteViewHolder(view); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public void onBindViewHolder(@NonNull ManageRouteViewHolder holder, int position) - { - // Set route point icon. - int iconId; - - switch (mRoutePoints.get(position).mPointType) - { - case RoutePointInfo.ROUTE_MARK_START: // Starting point. - if (mRoutePoints.get(position).mIsMyPosition) - iconId = R.drawable.ic_location_arrow_blue; - else - iconId = R.drawable.route_point_start; - break; - - case RoutePointInfo.ROUTE_MARK_INTERMEDIATE: // Intermediate stop. - TypedArray iconArray = mContext.getResources().obtainTypedArray(R.array.route_stop_icons); - iconId = iconArray.getResourceId(mRoutePoints.get(position).mIntermediateIndex, - R.drawable.route_point_20); - iconArray.recycle(); - break; - - case RoutePointInfo.ROUTE_MARK_FINISH: // Destination point. - iconId = R.drawable.route_point_finish; - break; - - default: // Unknown route type. - iconId = R.drawable.warning_icon; - break; - } - - // Set icon widget. - holder.mImageViewIcon.setImageDrawable(AppCompatResources.getDrawable(mContext, iconId)); - - // Set title & subtitle. - String title, subtitle; - - if (mRoutePoints.get(position).mIsMyPosition) - { - // My position point. - title = mContext.getString(R.string.core_my_position); - - if (mRoutePoints.get(position).mPointType != RoutePointInfo.ROUTE_MARK_START) - subtitle = mRoutePoints.get(position).mTitle; - else - { - // Hide my position coordinates if it's the starting point of the route. - subtitle = ""; - } - } - else - { - title = mRoutePoints.get(position).mTitle; - subtitle = mRoutePoints.get(position).mSubtitle; - } - - holder.mTextViewTitle.setText(title); - holder.mTextViewSubtitle.setText(subtitle); - UiUtils.showIf(subtitle != null && !subtitle.isEmpty(), holder.mTextViewSubtitle); - - // Show 'Delete' icon button only if we have intermediate stops. - UiUtils.showIf(mRoutePoints.size() > 2, holder.mImageViewDelete); - - // Detection of touch events on holder view. - holder.mItemView.setOnTouchListener((v, event) -> { - - if (event.getAction() == MotionEvent.ACTION_DOWN) - { - RectF deleteButtonRect = new RectF(holder.mImageViewDelete.getLeft(), - holder.mImageViewDelete.getTop(), - holder.mImageViewDelete.getRight(), - holder.mImageViewDelete.getBottom()); - - if (holder.mImageViewDelete.isShown() && deleteButtonRect.contains(event.getX(), event.getY())) - { - // User has clicked on the 'Delete' icon button. - mManageRouteListener.onRoutePointDeleted(holder); - } - else - { - // Call start drag listener on touch. - mManageRouteListener.startDrag(holder); - } - } - - return false; - }); - } - - @Override - public int getItemCount() - { - return mRoutePoints.size(); - } - - public void moveRoutePoint(int draggedItemIndex, int targetIndex) - { - if (draggedItemIndex == targetIndex) // Dragged to same spot. Do nothing. - return; - - Collections.swap(mRoutePoints, draggedItemIndex, targetIndex); - - updateRoutePointsData(); - - notifyItemMoved(draggedItemIndex, targetIndex); - } - - public void deleteRoutePoint(RecyclerView.ViewHolder viewHolder) - { - mRoutePoints.remove(viewHolder.getAbsoluteAdapterPosition()); - - updateRoutePointsData(); - - notifyItemRemoved(viewHolder.getAbsoluteAdapterPosition()); - } - - public void setMyLocationAsStartingPoint(MapObject myLocation) - { - String latLonString = StringUtils.formatUsingUsLocale("%.6f, %.6f", - myLocation.getLat(), - myLocation.getLon()); - - // Replace route point in first position with 'My Position". - mRoutePoints.set(0, new RouteMarkData(latLonString, "", RoutePointInfo.ROUTE_MARK_START, - 0, true, true, false, myLocation.getLat(), - myLocation.getLon())); - - // Update data. - updateRoutePointsData(); - - // Update adapter. - notifyItemChanged(0); - - // Show 'My location' crosshair button. - if (mManageRouteListener != null) - mManageRouteListener.showMyLocationIcon(true); - } - - private void updateMyLocationIcon() - { - boolean containsMyLocationPoint = false; - - for (RouteMarkData routePoint : mRoutePoints) - { - if (routePoint.mIsMyPosition) - { - containsMyLocationPoint = true; - break; - } - } - - if (mManageRouteListener != null) - mManageRouteListener.showMyLocationIcon(!containsMyLocationPoint); - } - - private void updateRoutePointsData() - { - assert(mRoutePoints.size() >= 2); - - // Set starting point. - mRoutePoints.get(0).mPointType = RoutePointInfo.ROUTE_MARK_START; - - // Set finish point. - mRoutePoints.get(mRoutePoints.size() - 1).mPointType = RoutePointInfo.ROUTE_MARK_FINISH; - - // Set intermediate point(s). - for (int pos = 1; pos < mRoutePoints.size() - 1; pos++) - { - mRoutePoints.get(pos).mPointType = RoutePointInfo.ROUTE_MARK_INTERMEDIATE; - mRoutePoints.get(pos).mIntermediateIndex = pos - 1; - } - } - - public ArrayList getRoutePoints() - { - return mRoutePoints; - } - - static class ManageRouteViewHolder extends RecyclerView.ViewHolder - { - @NonNull - public final View mItemView; - - @NonNull - public final ImageView mImageViewIcon; - - @NonNull - public final TextView mTextViewTitle; - - @NonNull - public final TextView mTextViewSubtitle; - - @NonNull - public final ImageView mImageViewDelete; - - ManageRouteViewHolder(@NonNull View itemView) - { - super(itemView); - mItemView = itemView; - mImageViewIcon = itemView.findViewById(R.id.type_icon); - mTextViewTitle = itemView.findViewById(R.id.title); - mTextViewSubtitle = itemView.findViewById(R.id.subtitle); - mImageViewDelete = itemView.findViewById(R.id.delete_icon); - } - } -} diff --git a/android/app/src/main/java/app/organicmaps/routing/ManageRouteBottomSheet.java b/android/app/src/main/java/app/organicmaps/routing/ManageRouteBottomSheet.java deleted file mode 100644 index 0ca75f3c1d..0000000000 --- a/android/app/src/main/java/app/organicmaps/routing/ManageRouteBottomSheet.java +++ /dev/null @@ -1,266 +0,0 @@ -package app.organicmaps.routing; - -import android.app.Dialog; -import android.content.res.Resources; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import app.organicmaps.Framework; -import app.organicmaps.MwmApplication; -import app.organicmaps.R; -import app.organicmaps.bookmarks.data.MapObject; -import app.organicmaps.util.UiUtils; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import com.google.android.material.divider.MaterialDividerItemDecoration; - -import java.util.ArrayList; - -import static androidx.recyclerview.widget.ItemTouchHelper.*; - -public class ManageRouteBottomSheet extends BottomSheetDialogFragment - implements View.OnClickListener, ManageRouteAdapter.ManageRouteListener -{ - ManageRouteAdapter mManageRouteAdapter; - ItemTouchHelper mTouchHelper; - ImageView mMyLocationImageView; - - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) - { - View v = inflater.inflate(R.layout.manage_route_bottom_sheet, container, false); - - Button cancelButton = v.findViewById(R.id.btn__cancel); - cancelButton.setOnClickListener(this); - - Button planButton = v.findViewById(R.id.btn__plan); - planButton.setOnClickListener(this); - - mMyLocationImageView = v.findViewById(R.id.image_my_location); - mMyLocationImageView.setOnClickListener(this); - - RecyclerView manageRouteList = v.findViewById(R.id.manage_route_list); - LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); - manageRouteList.setLayoutManager(layoutManager); - RecyclerView.ItemDecoration decoration = new MaterialDividerItemDecoration(getContext(), - layoutManager.getOrientation()); - manageRouteList.addItemDecoration(decoration); - - mManageRouteAdapter = new ManageRouteAdapter(getContext(), Framework.nativeGetRoutePoints(), this); - - manageRouteList.setAdapter(mManageRouteAdapter); - - // Enable drag & drop in route list. - mTouchHelper = new ItemTouchHelper(new ManageRouteItemTouchHelperCallback(mManageRouteAdapter, - getResources())); - mTouchHelper.attachToRecyclerView(manageRouteList); - - return v; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) - { - Dialog dialog = super.onCreateDialog(savedInstanceState); - - // Expand bottom sheet dialog. - dialog.setOnShowListener(dialogInterface -> { - - FrameLayout bottomSheet = ((BottomSheetDialog) dialogInterface).findViewById( - com.google.android.material.R.id.design_bottom_sheet); - - if (bottomSheet != null) - BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED); - }); - - // Set key listener to detect back button pressed. - dialog.setOnKeyListener((dialog1, keyCode, event) -> { - - if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) - { - // Dismiss the fragment - dismiss(); - return true; - } - - // Otherwise, do nothing else. - return false; - }); - - return dialog; - } - - @Override - public void onClick(View v) - { - int buttonId = v.getId(); - - if (buttonId == R.id.btn__cancel) - { - // Close dialog if 'Cancel' button is pressed. - dismiss(); - } - else if (buttonId == R.id.image_my_location) - { - // Get current location. - MapObject myLocation = MwmApplication.from(getContext()).getLocationHelper().getMyPosition(); - - // Set 'My Location' as starting point of the route. - if (myLocation != null) - mManageRouteAdapter.setMyLocationAsStartingPoint(myLocation); - } - else if (buttonId == R.id.btn__plan) - { - // Get route points from adapter. - ArrayList newRoutePoints = mManageRouteAdapter.getRoutePoints(); - - // Make sure that the new route contains at least 2 points (start and destination). - assert(newRoutePoints.size() >= 2); - - // Remove all existing route points. - Framework.nativeRemoveRoutePoints(); - - // First, add the destination point. - Framework.addRoutePoint(newRoutePoints.get(newRoutePoints.size() - 1)); - - // Secondly, add the starting point. - Framework.addRoutePoint(newRoutePoints.get(0)); - - // And then, add all intermediate points. - for (int pos = 1; pos < newRoutePoints.size() - 1; pos++) - Framework.addRoutePoint(newRoutePoints.get(pos)); - - // Intermediate route points are added sorted by distance. - // We have to make sure that they follow the requested order. - RouteMarkData[] finalRoutePoints = Framework.nativeGetRoutePoints(); - - for (int first = 1; first < newRoutePoints.size() - 1; first++) - { - int secondIndex = -1; - - for (int second = first; second < newRoutePoints.size() - 1; second++) - { - if (finalRoutePoints[first].equals(newRoutePoints.get(second))) - { - secondIndex = second; - break; - } - } - - if (secondIndex < 0) - { - // Something went bad. Intermediate point not found in the route points. - break; - } - - if (first != secondIndex) - { - // Intermediate point needs to be moved. - Framework.nativeMoveRoutePoint(secondIndex, first); - - // Refresh final route points. - finalRoutePoints = Framework.nativeGetRoutePoints(); - } - } - - // Launch route planning. - RoutingController.get().launchPlanning(); - - // Dismiss (close) manage route bottom sheet. - dismiss(); - } - } - - @Override - public void startDrag(RecyclerView.ViewHolder viewHolder) - { - // Start dragging. - mTouchHelper.startDrag(viewHolder); - } - - @Override - public void showMyLocationIcon(boolean showMyLocationIcon) - { - // Get current location. - MapObject myLocation = MwmApplication.from(getContext()).getLocationHelper().getMyPosition(); - - UiUtils.showIf(showMyLocationIcon && myLocation != null, mMyLocationImageView); - } - - @Override - public void onRoutePointDeleted(RecyclerView.ViewHolder viewHolder) - { - mManageRouteAdapter.deleteRoutePoint(viewHolder); - - mManageRouteAdapter.notifyDataSetChanged(); - } - - private static class ManageRouteItemTouchHelperCallback extends ItemTouchHelper.Callback - { - private final ManageRouteAdapter mManageRouteAdapter; - - public ManageRouteItemTouchHelperCallback(ManageRouteAdapter adapter, Resources resources) - { - mManageRouteAdapter = adapter; - } - - @Override - public int getMovementFlags(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder) - { - // Enable up & down dragging. No left-right swiping is enabled. - return makeMovementFlags(UP | DOWN, 0); - } - - @Override - public boolean isLongPressDragEnabled() - { - return false; - } - - @Override - public boolean isItemViewSwipeEnabled() - { - return false; - } - - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder, - @NonNull RecyclerView.ViewHolder target) - { - mManageRouteAdapter.moveRoutePoint(viewHolder.getAbsoluteAdapterPosition(), - target.getAbsoluteAdapterPosition()); - return true; - } - - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) - { - } - - @Override - public void clearView(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder) - { - super.clearView(recyclerView, viewHolder); - - // Called when dragging action has finished. - mManageRouteAdapter.notifyDataSetChanged(); - } - } -} diff --git a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java index fe78c86a10..843793731f 100644 --- a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java +++ b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java @@ -12,18 +12,15 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; -import androidx.lifecycle.ViewModelProvider; import app.organicmaps.Framework; import app.organicmaps.R; import app.organicmaps.location.LocationHelper; -import app.organicmaps.maplayer.MapButtonsViewModel; import app.organicmaps.maplayer.traffic.TrafficManager; -import app.organicmaps.util.StringUtils; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; -import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.widget.LanesView; import app.organicmaps.widget.SpeedLimitView; +import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.widget.menu.NavMenu; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -47,8 +44,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, @NonNull private final SpeedLimitView mSpeedLimit; - private final MapButtonsViewModel mMapButtonsViewModel; - private final NavMenu mNavMenu; View.OnClickListener mOnSettingsClickListener; @@ -64,8 +59,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, public NavigationController(AppCompatActivity activity, View.OnClickListener onSettingsClickListener, NavMenu.OnMenuSizeChangedListener onMenuSizeChangedListener) { - mMapButtonsViewModel = new ViewModelProvider(activity).get(MapButtonsViewModel.class); - mFrame = activity.findViewById(R.id.navigation_frame); mNavMenu = new NavMenu(activity, this, onMenuSizeChangedListener); mOnSettingsClickListener = onSettingsClickListener; @@ -163,10 +156,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, UiUtils.visibleIf(hasStreet, mStreetFrame); if (!TextUtils.isEmpty(info.nextStreet)) mNextStreet.setText(info.nextStreet); - int margin = UiUtils.dimen(mFrame.getContext(), R.dimen.nav_frame_padding); - if (hasStreet) - margin += mStreetFrame.getHeight(); - mMapButtonsViewModel.setTopButtonsMarginTop(margin); } public void show(boolean show) @@ -260,10 +249,10 @@ public class NavigationController implements TrafficManager.TrafficCallback, { final Location location = LocationHelper.from(mFrame.getContext()).getSavedLocation(); if (location == null) { - mSpeedLimit.setSpeedLimit(0, false); + mSpeedLimit.setSpeedLimitMps(0); return; } - final boolean speedLimitExceeded = info.speedLimitMps < location.getSpeed(); - mSpeedLimit.setSpeedLimit(StringUtils.nativeFormatSpeed(info.speedLimitMps), speedLimitExceeded); + mSpeedLimit.setCurrentSpeed(location.getSpeed()); + mSpeedLimit.setSpeedLimitMps(info.speedLimitMps); } } diff --git a/android/app/src/main/java/app/organicmaps/routing/RouteMarkData.java b/android/app/src/main/java/app/organicmaps/routing/RouteMarkData.java index 33b1300e04..8004267f80 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RouteMarkData.java +++ b/android/app/src/main/java/app/organicmaps/routing/RouteMarkData.java @@ -16,8 +16,8 @@ public class RouteMarkData @Nullable public final String mSubtitle; @RoutePointInfo.RouteMarkType - public int mPointType; - public int mIntermediateIndex; + public final int mPointType; + public final int mIntermediateIndex; public final boolean mIsVisible; public final boolean mIsMyPosition; public final boolean mIsPassed; @@ -39,12 +39,4 @@ public class RouteMarkData mLat = lat; mLon = lon; } - - public boolean equals(RouteMarkData other) - { - return mTitle != null && other.mTitle != null && - mTitle.compareTo(other.mTitle) == 0 && - mSubtitle != null && other.mSubtitle != null && - mSubtitle.compareTo(other.mSubtitle) == 0; - } } diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuController.java b/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuController.java index e9745a52b5..748cb17855 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuController.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuController.java @@ -151,8 +151,6 @@ final class RoutingBottomMenuController implements View.OnClickListener Resources res = mContext.getResources(); mTransitViewDecorator = new DotDividerItemDecoration(dividerDrawable, res.getDimensionPixelSize(R.dimen.margin_base), res.getDimensionPixelSize(R.dimen.margin_half)); - Button manageRouteButton = altitudeChartFrame.findViewById(R.id.btn__manage_route); - manageRouteButton.setOnClickListener(this); } void showAltitudeChartAndRoutingDetails() @@ -255,7 +253,7 @@ final class RoutingBottomMenuController implements View.OnClickListener if (LocationHelper.from(mContext).getMyPosition() != null) { UiUtils.show(mActionButton); - Drawable icon = ContextCompat.getDrawable(mContext, R.drawable.ic_location_crosshair); + Drawable icon = ContextCompat.getDrawable(mContext, R.drawable.ic_my_location); int colorAccent = ContextCompat.getColor(mContext, UiUtils.getStyledResourceId(mContext, androidx.appcompat.R.attr.colorAccent)); mActionIcon.setImageDrawable(Graphics.tint(icon, colorAccent)); @@ -487,7 +485,5 @@ final class RoutingBottomMenuController implements View.OnClickListener int pointType = (Integer) mActionMessage.getTag(); mListener.onSearchRoutePoint(pointType); } - else if (id == R.id.btn__manage_route && mListener != null) - mListener.onManageRouteOpen(); } } diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuListener.java b/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuListener.java index 135f6b3213..70b2238a76 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuListener.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingBottomMenuListener.java @@ -5,5 +5,4 @@ public interface RoutingBottomMenuListener void onUseMyPositionAsStart(); void onSearchRoutePoint(@RoutePointInfo.RouteMarkType int type); void onRoutingStart(); - void onManageRouteOpen(); } diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingController.java b/android/app/src/main/java/app/organicmaps/routing/RoutingController.java index 1abbf6fc4c..0468a02e5a 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingController.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingController.java @@ -60,7 +60,7 @@ public class RoutingController default void onResetToPlanningState() {} default void onBuiltRoute() {} default void onDrivingOptionsWarning() {} - + default boolean isSubwayEnabled() { return false; } default void onCommonBuildError(int lastResultCode, @NonNull String[] lastMissingMaps) {} default void onDrivingOptionsBuildError() {} @@ -333,23 +333,45 @@ public class RoutingController { setState(State.NONE); setBuildState(BuildState.NONE); - prepare(getStartPoint(), getEndPoint()); + prepare(getStartPoint(), getEndPoint(), false); } public void prepare(@Nullable MapObject startPoint, @Nullable MapObject endPoint) + { + prepare(startPoint, endPoint, false); + } + + public void prepare(@Nullable MapObject startPoint, @Nullable MapObject endPoint, boolean fromApi) { Logger.d(TAG, "prepare (" + (endPoint == null ? "route)" : "p2p)")); - initLastRouteType(startPoint, endPoint); + initLastRouteType(startPoint, endPoint, fromApi); prepare(startPoint, endPoint, mLastRouterType); } - private void initLastRouteType(@Nullable MapObject startPoint, @Nullable MapObject endPoint) + private void initLastRouteType(@Nullable MapObject startPoint, @Nullable MapObject endPoint, + boolean fromApi) { + if (shouldForceTransitRoute(fromApi)) + { + mLastRouterType = Framework.ROUTER_TYPE_TRANSIT; + return; + } + if (startPoint != null && endPoint != null) mLastRouterType = Framework.nativeGetBestRouter(startPoint.getLat(), startPoint.getLon(), endPoint.getLat(), endPoint.getLon()); } + private boolean isSubwayEnabled() + { + return mContainer != null && mContainer.isSubwayEnabled(); + } + + private boolean shouldForceTransitRoute(boolean fromApi) + { + return mState == State.NONE && isSubwayEnabled() && !fromApi; + } + public void prepare(final @Nullable MapObject startPoint, final @Nullable MapObject endPoint, @Framework.RouterType int routerType) { @@ -404,17 +426,6 @@ public class RoutingController resetToPlanningStateIfNavigating(); } - public void launchPlanning() - { - build(); - setState(State.PREPARE); - startPlanning(); - if (mContainer != null) - mContainer.updateMenu(); - if (mContainer != null) - mContainer.onResetToPlanningState(); - } - /** * @return False if not navigating, true otherwise */ diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingErrorDialogFragment.java b/android/app/src/main/java/app/organicmaps/routing/RoutingErrorDialogFragment.java index 9feac460a5..961abdca58 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingErrorDialogFragment.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingErrorDialogFragment.java @@ -2,13 +2,10 @@ package app.organicmaps.routing; import android.content.Context; import android.content.DialogInterface; -import android.graphics.Typeface; import android.os.Bundle; -import android.text.TextUtils; import android.util.Pair; import android.view.View; import android.widget.Button; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -38,15 +35,7 @@ public class RoutingErrorDialogFragment extends BaseRoutingErrorDialogFragment ResultCodesHelper.getDialogTitleSubtitle(requireContext(), mResultCode, mMissingMaps.size()); Pair titleMessage = resHolder.getTitleMessage(); - TextView titleView = new TextView(requireContext()); - titleView.setText(titleMessage.first); - titleView.setPadding(65, 32, 32, 16); - titleView.setTextSize(18); - titleView.setMaxLines(4); - titleView.setEllipsize(TextUtils.TruncateAt.END); - titleView.setTypeface(null, Typeface.BOLD); - builder.setCustomTitle(titleView); - + builder.setTitle(titleMessage.first); mMessage = titleMessage.second; builder.setNegativeButton(resHolder.getCancelBtnResId(), null); if (ResultCodesHelper.isDownloadable(mResultCode, mMissingMaps.size())) diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingMapsDownloadFragment.java b/android/app/src/main/java/app/organicmaps/routing/RoutingMapsDownloadFragment.java index 976fe7a9db..8af14f5494 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingMapsDownloadFragment.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingMapsDownloadFragment.java @@ -42,7 +42,8 @@ public class RoutingMapsDownloadFragment extends BaseRoutingErrorDialogFragment mMapsArray[i] = item.id; } - MapManager.startDownload(mMapsArray); + for (String map : mMaps) + MapManager.nativeDownload(map); } private View setupFrame(View frame) diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java index fe46c03661..691681690e 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java @@ -1,14 +1,12 @@ package app.organicmaps.routing; import android.app.Activity; -import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.CompoundButton; import android.widget.RadioGroup; import android.widget.TextView; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.DrawableRes; import androidx.annotation.IdRes; import androidx.annotation.NonNull; @@ -80,7 +78,6 @@ public class RoutingPlanController extends ToolbarController } RoutingPlanController(View root, Activity activity, - ActivityResultLauncher startDrivingOptionsForResult, @NonNull RoutingPlanInplaceController.RoutingPlanListener routingPlanListener, @Nullable RoutingBottomMenuListener listener) { @@ -106,7 +103,7 @@ public class RoutingPlanController extends ToolbarController View btn = mDrivingOptionsBtnContainer.findViewById(R.id.driving_options_btn); mDrivingOptionsImage = mFrame.findViewById(R.id.driving_options_btn_img); - btn.setOnClickListener(v -> DrivingOptionsActivity.start(requireActivity(), startDrivingOptionsForResult)); + btn.setOnClickListener(v -> DrivingOptionsActivity.start(requireActivity())); mDriverOptionsLayoutListener = new SelfTerminatedDrivingOptionsLayoutListener(); mAnimToggle = MwmApplication.from(activity.getApplicationContext()) .getResources().getInteger(R.integer.anim_default); diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java index 4a54c0f3c1..23987e71d2 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java @@ -7,9 +7,9 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import app.organicmaps.Framework; -import app.organicmaps.MwmActivity; import app.organicmaps.R; import app.organicmaps.base.BaseMwmFragment; @@ -21,9 +21,15 @@ public class RoutingPlanFragment extends BaseMwmFragment @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - final MwmActivity activity = (MwmActivity) requireActivity(); + final FragmentActivity activity = requireActivity(); View res = inflater.inflate(R.layout.fragment_routing, container, false); - mPlanController = new RoutingPlanController(res, activity, activity.startDrivingOptionsForResult, activity, activity); + RoutingBottomMenuListener listener = null; + if (activity instanceof RoutingBottomMenuListener) + listener = (RoutingBottomMenuListener) activity; + + RoutingPlanInplaceController.RoutingPlanListener planListener = + (RoutingPlanInplaceController.RoutingPlanListener) activity; + mPlanController = new RoutingPlanController(res, activity, planListener, listener); return res; } diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanInplaceController.java b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanInplaceController.java index d71c973329..a6f8db5c81 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanInplaceController.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanInplaceController.java @@ -2,10 +2,8 @@ package app.organicmaps.routing; import android.animation.Animator; import android.animation.ValueAnimator; -import android.content.Intent; import android.os.Bundle; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -22,11 +20,10 @@ public class RoutingPlanInplaceController extends RoutingPlanController private Animator mAnimator; public RoutingPlanInplaceController(@NonNull MwmActivity activity, - ActivityResultLauncher startDrivingOptionsForResult, @NonNull RoutingPlanListener routingPlanListener, @NonNull RoutingBottomMenuListener listener) { - super(activity.findViewById(R.id.routing_plan_frame), activity, startDrivingOptionsForResult, routingPlanListener, listener); + super(activity.findViewById(R.id.routing_plan_frame), activity, routingPlanListener, listener); mRoutingPlanListener = routingPlanListener; } diff --git a/android/app/src/main/java/app/organicmaps/search/NativeBookmarkSearchListener.java b/android/app/src/main/java/app/organicmaps/sdk/search/BookmarkSearchListener.java similarity index 74% rename from android/app/src/main/java/app/organicmaps/search/NativeBookmarkSearchListener.java rename to android/app/src/main/java/app/organicmaps/sdk/search/BookmarkSearchListener.java index 8db93d4be8..1595e56a4a 100644 --- a/android/app/src/main/java/app/organicmaps/search/NativeBookmarkSearchListener.java +++ b/android/app/src/main/java/app/organicmaps/sdk/search/BookmarkSearchListener.java @@ -1,4 +1,4 @@ -package app.organicmaps.search; +package app.organicmaps.sdk.search; import androidx.annotation.Keep; import androidx.annotation.Nullable; @@ -6,11 +6,11 @@ import androidx.annotation.Nullable; /** * Native search will return results via this interface. */ -public interface NativeBookmarkSearchListener +public interface BookmarkSearchListener { /** * @param bookmarkIds Founded bookmark ids. - * @param timestamp Timestamp of search request. + * @param timestamp Timestamp of search request. */ // Used by JNI. @Keep @@ -19,7 +19,7 @@ public interface NativeBookmarkSearchListener /** * @param bookmarkIds Founded bookmark ids. - * @param timestamp Timestamp of search request. + * @param timestamp Timestamp of search request. */ // Used by JNI. @Keep diff --git a/android/app/src/main/java/app/organicmaps/search/DisplayedCategories.java b/android/app/src/main/java/app/organicmaps/sdk/search/DisplayedCategories.java similarity index 72% rename from android/app/src/main/java/app/organicmaps/search/DisplayedCategories.java rename to android/app/src/main/java/app/organicmaps/sdk/search/DisplayedCategories.java index f9080862ff..2ad6b74656 100644 --- a/android/app/src/main/java/app/organicmaps/search/DisplayedCategories.java +++ b/android/app/src/main/java/app/organicmaps/sdk/search/DisplayedCategories.java @@ -1,8 +1,8 @@ -package app.organicmaps.search; +package app.organicmaps.sdk.search; import androidx.annotation.NonNull; -class DisplayedCategories +public class DisplayedCategories { @NonNull public static String[] getKeys() diff --git a/android/app/src/main/java/app/organicmaps/sdk/search/MapSearchListener.java b/android/app/src/main/java/app/organicmaps/sdk/search/MapSearchListener.java new file mode 100644 index 0000000000..df0bb8aa69 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/sdk/search/MapSearchListener.java @@ -0,0 +1,19 @@ +package app.organicmaps.sdk.search; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +public interface MapSearchListener +{ + // Called from JNI. + @Keep + @SuppressWarnings("unused") + record Result(String countryId, String matchedString) + { + } + + // Called from JNI. + @Keep + @SuppressWarnings("unused") + void onMapSearchResults(@NonNull Result[] results, long timestamp, boolean isLast); +} diff --git a/android/app/src/main/java/app/organicmaps/search/Popularity.java b/android/app/src/main/java/app/organicmaps/sdk/search/Popularity.java similarity index 90% rename from android/app/src/main/java/app/organicmaps/search/Popularity.java rename to android/app/src/main/java/app/organicmaps/sdk/search/Popularity.java index cf2f196c47..0c64bf813e 100644 --- a/android/app/src/main/java/app/organicmaps/search/Popularity.java +++ b/android/app/src/main/java/app/organicmaps/sdk/search/Popularity.java @@ -1,4 +1,4 @@ -package app.organicmaps.search; +package app.organicmaps.sdk.search; import android.os.Parcel; import android.os.Parcelable; @@ -38,12 +38,12 @@ public class Popularity implements Parcelable } @Override - public void writeToParcel(Parcel dest, int flags) + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(this.mType.ordinal()); } - protected Popularity(Parcel in) + protected Popularity(@NonNull Parcel in) { int tmpMPopularity = in.readInt(); this.mType = Type.values()[tmpMPopularity]; diff --git a/android/app/src/main/java/app/organicmaps/search/SearchEngine.java b/android/app/src/main/java/app/organicmaps/sdk/search/SearchEngine.java similarity index 78% rename from android/app/src/main/java/app/organicmaps/search/SearchEngine.java rename to android/app/src/main/java/app/organicmaps/sdk/search/SearchEngine.java index dd899e627b..582e5a2cad 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchEngine.java +++ b/android/app/src/main/java/app/organicmaps/sdk/search/SearchEngine.java @@ -1,4 +1,4 @@ -package app.organicmaps.search; +package app.organicmaps.sdk.search; import android.content.Context; @@ -14,9 +14,9 @@ import app.organicmaps.util.concurrency.UiThread; import java.nio.charset.StandardCharsets; -public enum SearchEngine implements NativeSearchListener, - NativeMapSearchListener, - NativeBookmarkSearchListener +public enum SearchEngine implements SearchListener, + MapSearchListener, + BookmarkSearchListener { INSTANCE; @@ -30,7 +30,7 @@ public enum SearchEngine implements NativeSearchListener, UiThread.run( () -> { - for (NativeSearchListener listener : mListeners) + for (SearchListener listener : mListeners) listener.onResultsUpdate(results, timestamp); }); } @@ -41,18 +41,18 @@ public enum SearchEngine implements NativeSearchListener, UiThread.run( () -> { - for (NativeSearchListener listener : mListeners) + for (SearchListener listener : mListeners) listener.onResultsEnd(timestamp); }); } @Override - public void onMapSearchResults(final NativeMapSearchListener.Result[] results, final long timestamp, final boolean isLast) + public void onMapSearchResults(@NonNull final MapSearchListener.Result[] results, final long timestamp, final boolean isLast) { UiThread.run( () -> { - for (NativeMapSearchListener listener : mMapListeners) + for (MapSearchListener listener : mMapListeners) listener.onMapSearchResults(results, timestamp, isLast); }); } @@ -60,55 +60,53 @@ public enum SearchEngine implements NativeSearchListener, @Override public void onBookmarkSearchResultsUpdate(@Nullable long[] bookmarkIds, long timestamp) { - for (NativeBookmarkSearchListener listener : mBookmarkListeners) + for (BookmarkSearchListener listener : mBookmarkListeners) listener.onBookmarkSearchResultsUpdate(bookmarkIds, timestamp); } @Override public void onBookmarkSearchResultsEnd(@Nullable long[] bookmarkIds, long timestamp) { - for (NativeBookmarkSearchListener listener : mBookmarkListeners) + for (BookmarkSearchListener listener : mBookmarkListeners) listener.onBookmarkSearchResultsEnd(bookmarkIds, timestamp); } - private final ObserverList mListeners = new ObserverList<>(); + private final ObserverList mListeners = new ObserverList<>(); - private final ObserverList mMapListeners = new ObserverList<>(); + private final ObserverList mMapListeners = new ObserverList<>(); - private final ObserverList mBookmarkListeners = new ObserverList<>(); + private final ObserverList mBookmarkListeners = new ObserverList<>(); - public void addListener(NativeSearchListener listener) + public void addListener(SearchListener listener) { mListeners.addObserver(listener); } - public void removeListener(NativeSearchListener listener) + public void removeListener(SearchListener listener) { mListeners.removeObserver(listener); } - public void addMapListener(NativeMapSearchListener listener) + public void addMapListener(MapSearchListener listener) { mMapListeners.addObserver(listener); } - public void removeMapListener(NativeMapSearchListener listener) + public void removeMapListener(MapSearchListener listener) { mMapListeners.removeObserver(listener); } - public void addBookmarkListener(NativeBookmarkSearchListener listener) + public void addBookmarkListener(BookmarkSearchListener listener) { mBookmarkListeners.addObserver(listener); } - public void removeBookmarkListener(NativeBookmarkSearchListener listener) + public void removeBookmarkListener(BookmarkSearchListener listener) { mBookmarkListeners.removeObserver(listener); } - private native void nativeInit(); - /** * * @param context @@ -116,7 +114,7 @@ public enum SearchEngine implements NativeSearchListener, * @return whether search was actually started. */ @MainThread - public boolean search(@NonNull Context context, String query, boolean isCategory, + public boolean search(@NonNull Context context, @NonNull String query, boolean isCategory, long timestamp, boolean hasLocation, double lat, double lon) { return nativeRunSearch(query.getBytes(StandardCharsets.UTF_8), isCategory, @@ -146,7 +144,7 @@ public enum SearchEngine implements NativeSearchListener, } @MainThread - public static void searchMaps(@NonNull Context context, String query, long timestamp) + public static void searchMaps(@NonNull Context context, @NonNull String query, long timestamp) { nativeRunSearchMaps(query.getBytes(StandardCharsets.UTF_8), Language.getKeyboardLocale(context), timestamp); @@ -208,6 +206,8 @@ public enum SearchEngine implements NativeSearchListener, nativeInit(); } + private native void nativeInit(); + /** * @param bytes utf-8 formatted bytes of query. */ diff --git a/android/app/src/main/java/app/organicmaps/search/NativeSearchListener.java b/android/app/src/main/java/app/organicmaps/sdk/search/SearchListener.java similarity index 82% rename from android/app/src/main/java/app/organicmaps/search/NativeSearchListener.java rename to android/app/src/main/java/app/organicmaps/sdk/search/SearchListener.java index f427306828..171ce99753 100644 --- a/android/app/src/main/java/app/organicmaps/search/NativeSearchListener.java +++ b/android/app/src/main/java/app/organicmaps/sdk/search/SearchListener.java @@ -1,4 +1,4 @@ -package app.organicmaps.search; +package app.organicmaps.sdk.search; import androidx.annotation.Keep; import androidx.annotation.NonNull; @@ -6,10 +6,10 @@ import androidx.annotation.NonNull; /** * Native search will return results via this interface. */ -public interface NativeSearchListener +public interface SearchListener { /** - * @param results Search results. + * @param results Search results. * @param timestamp Timestamp of search request. */ // Called by JNI. diff --git a/android/app/src/main/java/app/organicmaps/search/SearchRecents.java b/android/app/src/main/java/app/organicmaps/sdk/search/SearchRecents.java similarity index 94% rename from android/app/src/main/java/app/organicmaps/search/SearchRecents.java rename to android/app/src/main/java/app/organicmaps/sdk/search/SearchRecents.java index 54029e8683..e11e528e76 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchRecents.java +++ b/android/app/src/main/java/app/organicmaps/sdk/search/SearchRecents.java @@ -1,8 +1,7 @@ -package app.organicmaps.search; +package app.organicmaps.sdk.search; import android.content.Context; import android.text.TextUtils; -import android.util.Pair; import androidx.annotation.NonNull; @@ -50,6 +49,8 @@ public final class SearchRecents } private static native void nativeGetList(List result); + private static native void nativeAdd(String locale, String query); + private static native void nativeClear(); } diff --git a/android/app/src/main/java/app/organicmaps/search/SearchResult.java b/android/app/src/main/java/app/organicmaps/sdk/search/SearchResult.java similarity index 97% rename from android/app/src/main/java/app/organicmaps/search/SearchResult.java rename to android/app/src/main/java/app/organicmaps/sdk/search/SearchResult.java index f903188ece..2fdcf6861a 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchResult.java +++ b/android/app/src/main/java/app/organicmaps/sdk/search/SearchResult.java @@ -1,4 +1,4 @@ -package app.organicmaps.search; +package app.organicmaps.sdk.search; import android.content.Context; import android.graphics.Typeface; @@ -32,7 +32,7 @@ public class SearchResult public static final int OPEN_NOW_NO = 2; public static final SearchResult EMPTY = new SearchResult("", "", 0, 0, - new int[] {}, new int[] {}); + new int[]{}, new int[]{}); // Used by JNI. @Keep diff --git a/android/app/src/main/java/app/organicmaps/search/CategoriesAdapter.java b/android/app/src/main/java/app/organicmaps/search/CategoriesAdapter.java index ea8bf7504b..2fecc13de3 100644 --- a/android/app/src/main/java/app/organicmaps/search/CategoriesAdapter.java +++ b/android/app/src/main/java/app/organicmaps/search/CategoriesAdapter.java @@ -18,6 +18,7 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; +import app.organicmaps.sdk.search.DisplayedCategories; import app.organicmaps.util.ThemeUtils; import java.lang.annotation.Retention; @@ -26,9 +27,10 @@ import java.lang.annotation.RetentionPolicy; class CategoriesAdapter extends RecyclerView.Adapter { @Retention(RetentionPolicy.SOURCE) - @IntDef({ TYPE_CATEGORY }) - @interface ViewType {} - private static final int TYPE_CATEGORY = 0; + @IntDef({ ViewType.CATEGORY }) + @interface ViewType { + int CATEGORY = 0; + } @StringRes private int[] mCategoryResIds; @@ -121,15 +123,16 @@ class CategoriesAdapter extends RecyclerView.Adapter startVoiceRecognitionForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> - { - mToolbarController.onVoiceRecognitionResult(activityResult); - }); - private final LocationListener mLocationListener = new LocationListener() { @Override @@ -307,10 +303,8 @@ public class SearchFragment extends BaseMwmFragment SearchEngine.INSTANCE.addListener(this); - SharedPreferences preferences = MwmApplication.prefs(requireContext()); - int lastSelectedTabPosition = preferences.getInt(Config.KEY_PREF_LAST_SEARCHED_TAB, 0); if (SearchRecents.getSize() == 0 && Config.isSearchHistoryEnabled()) - pager.setCurrentItem(lastSelectedTabPosition); + pager.setCurrentItem(TabAdapter.Tab.CATEGORIES.ordinal()); tabAdapter.setTabSelectedListener(tab -> mToolbarController.deactivate()); @@ -535,6 +529,14 @@ public class SearchFragment extends BaseMwmFragment mToolbarController.showProgress(true); } + @Override + @SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630 + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + mToolbarController.onActivityResult(requestCode, resultCode, data); + } + @Override public boolean onBackPressed() { diff --git a/android/app/src/main/java/app/organicmaps/search/SearchHistoryAdapter.java b/android/app/src/main/java/app/organicmaps/search/SearchHistoryAdapter.java index 72c795ae6c..b405b66aeb 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchHistoryAdapter.java +++ b/android/app/src/main/java/app/organicmaps/search/SearchHistoryAdapter.java @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; import app.organicmaps.location.LocationHelper; import app.organicmaps.routing.RoutingController; +import app.organicmaps.sdk.search.SearchRecents; import app.organicmaps.widget.SearchToolbarController; import app.organicmaps.util.Graphics; diff --git a/android/app/src/main/java/app/organicmaps/search/TabAdapter.java b/android/app/src/main/java/app/organicmaps/search/TabAdapter.java index 5de16dfd38..a803e2a582 100644 --- a/android/app/src/main/java/app/organicmaps/search/TabAdapter.java +++ b/android/app/src/main/java/app/organicmaps/search/TabAdapter.java @@ -1,7 +1,6 @@ package app.organicmaps.search; import android.content.Context; -import android.content.SharedPreferences; import android.util.SparseArray; import android.view.View; @@ -12,8 +11,6 @@ import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; import com.google.android.material.tabs.TabLayout; - -import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.util.Graphics; import app.organicmaps.util.Config; @@ -94,9 +91,6 @@ class TabAdapter extends FragmentPagerAdapter @Override public void onTabSelected(TabLayout.Tab tab) { - SharedPreferences.Editor editor = MwmApplication.prefs(mContext).edit(); - editor.putInt(Config.KEY_PREF_LAST_SEARCHED_TAB, tab.getPosition()); - editor.apply(); super.onTabSelected(tab); Graphics.tint(mContext, tab.getIcon(), androidx.appcompat.R.attr.colorAccent); } @@ -157,9 +151,7 @@ class TabAdapter extends FragmentPagerAdapter ViewPager.OnPageChangeListener listener = new PageChangedListener(tabs); mPager.addOnPageChangeListener(listener); tabs.setOnTabSelectedListener(new OnTabSelectedListenerForViewPager(mPager)); - SharedPreferences preferences = MwmApplication.prefs(mPager.getContext()); - int lastSelectedTabPosition = preferences.getInt(Config.KEY_PREF_LAST_SEARCHED_TAB, 0); - listener.onPageSelected(lastSelectedTabPosition); + listener.onPageSelected(0); } void setTabSelectedListener(OnTabSelectedListener listener) diff --git a/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsActivity.java b/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsActivity.java index b98fe46f3f..94b7cd1a94 100644 --- a/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsActivity.java +++ b/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsActivity.java @@ -3,9 +3,10 @@ package app.organicmaps.settings; import android.app.Activity; import android.content.Intent; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; + +import app.organicmaps.MwmActivity; import app.organicmaps.base.BaseMwmFragmentActivity; public class DrivingOptionsActivity extends BaseMwmFragmentActivity @@ -16,9 +17,9 @@ public class DrivingOptionsActivity extends BaseMwmFragmentActivity return DrivingOptionsFragment.class; } - public static void start(@NonNull Activity activity, ActivityResultLauncher startDrivingOptionsForResult) + public static void start(@NonNull Activity activity) { Intent intent = new Intent(activity, DrivingOptionsActivity.class); - startDrivingOptionsForResult.launch(intent); + activity.startActivityForResult(intent, MwmActivity.REQ_CODE_DRIVING_OPTIONS); } } diff --git a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java index b7d8e35f69..72c07ec630 100644 --- a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java @@ -32,7 +32,7 @@ import app.organicmaps.util.SharedPropertiesUtils; import app.organicmaps.util.ThemeSwitcher; import app.organicmaps.util.Utils; import app.organicmaps.util.log.LogsManager; -import app.organicmaps.search.SearchRecents; +import app.organicmaps.sdk.search.SearchRecents; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.util.Locale; diff --git a/android/app/src/main/java/app/organicmaps/settings/VoiceInstructionsSettingsFragment.java b/android/app/src/main/java/app/organicmaps/settings/VoiceInstructionsSettingsFragment.java index 7fd38e86f7..a22f74d630 100644 --- a/android/app/src/main/java/app/organicmaps/settings/VoiceInstructionsSettingsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/VoiceInstructionsSettingsFragment.java @@ -1,6 +1,5 @@ package app.organicmaps.settings; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.res.Configuration; @@ -12,8 +11,6 @@ import android.text.style.ForegroundColorSpan; import android.view.View; import android.widget.Toast; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; @@ -38,6 +35,7 @@ import java.util.Map; public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment { + private static final int REQUEST_INSTALL_DATA = 1; @NonNull @SuppressWarnings("NotNullFieldNotInitialized") @@ -69,14 +67,6 @@ public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment private LanguageData mCurrentLanguage; private String mSelectedLanguage; - private final ActivityResultLauncher startInstallDataIntentForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> { - - if(activityResult.getResultCode() == Activity.RESULT_OK) - { - onInstallDataResult(); - } - }); - private final Preference.OnPreferenceChangeListener mEnabledListener = (preference, newValue) -> { final boolean set = (Boolean) newValue; if (!set) @@ -117,7 +107,8 @@ public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment if (lang.downloaded) setLanguage(lang); else - UiUtils.startActivityForResult(startInstallDataIntentForResult, new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA)); + UiUtils.startActivityForResult(VoiceInstructionsSettingsFragment.this, + new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA), REQUEST_INSTALL_DATA); return false; }; @@ -190,13 +181,21 @@ public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment updateTts(); } - private void onInstallDataResult() + @Override + @SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630 + public void onActivityResult(int requestCode, int resultCode, Intent data) { - updateTts(); + // Do not check resultCode here as it is always RESULT_CANCELED + super.onActivityResult(requestCode, resultCode, data); - LanguageData lang = mLanguages.get(mSelectedLanguage); - if (lang != null && lang.downloaded) - setLanguage(lang); + if (requestCode == REQUEST_INSTALL_DATA) + { + updateTts(); + + LanguageData lang = mLanguages.get(mSelectedLanguage); + if (lang != null && lang.downloaded) + setLanguage(lang); + } } private void enableListeners(boolean enable) diff --git a/android/app/src/main/java/app/organicmaps/util/Config.java b/android/app/src/main/java/app/organicmaps/util/Config.java index 2f9cdbf882..14eccbcead 100644 --- a/android/app/src/main/java/app/organicmaps/util/Config.java +++ b/android/app/src/main/java/app/organicmaps/util/Config.java @@ -7,6 +7,7 @@ import android.os.Build; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import app.organicmaps.BuildConfig; +import app.organicmaps.MwmActivity; import app.organicmaps.MwmApplication; import app.organicmaps.R; @@ -35,8 +36,6 @@ public final class Config private static final String KEY_DONATE_URL = "DonateUrl"; private static final String KEY_PREF_SEARCH_HISTORY = "SearchHistoryEnabled"; - public static final String KEY_PREF_LAST_SEARCHED_TAB = "LastSearchTab"; - /** * The total number of app launches. */ diff --git a/android/app/src/main/java/app/organicmaps/util/Constants.java b/android/app/src/main/java/app/organicmaps/util/Constants.java index b29d23da00..a9d4f75e9a 100644 --- a/android/app/src/main/java/app/organicmaps/util/Constants.java +++ b/android/app/src/main/java/app/organicmaps/util/Constants.java @@ -57,13 +57,5 @@ public final class Constants public static final String XIAOMI = "XIAOMI"; } - public static class ProductsPopupCloseReason - { - public static final String CLOSE = "close"; - public static final String REMIND_LATER = "remind_later"; - public static final String ALREADY_DONATED = "already_donated"; - public static final String SELECT_PRODUCT = "select_product"; - } - private Constants() {} } diff --git a/android/app/src/main/java/app/organicmaps/util/LocationUtils.java b/android/app/src/main/java/app/organicmaps/util/LocationUtils.java index e8c2571514..0631893b7e 100644 --- a/android/app/src/main/java/app/organicmaps/util/LocationUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/LocationUtils.java @@ -118,14 +118,13 @@ public class LocationUtils return ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED; } - /** - * Checks if the app has location permissions granted. - * Returns true if either: - * Only ACCESS_COARSE_LOCATION is granted - * Both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION are granted - */ - public static boolean checkLocationPermission(@NonNull Context context) + public static boolean checkCoarseLocationPermission(@NonNull Context context) { return ContextCompat.checkSelfPermission(context, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED; } + + public static boolean checkLocationPermission(@NonNull Context context) + { + return checkFineLocationPermission(context) || checkCoarseLocationPermission(context); + } } diff --git a/android/app/src/main/java/app/organicmaps/util/StringUtils.java b/android/app/src/main/java/app/organicmaps/util/StringUtils.java index 00129f8105..d82b80a092 100644 --- a/android/app/src/main/java/app/organicmaps/util/StringUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/StringUtils.java @@ -10,7 +10,6 @@ import androidx.annotation.NonNull; import app.organicmaps.MwmApplication; import app.organicmaps.R; -import java.text.NumberFormat; import java.util.Locale; public class StringUtils @@ -25,31 +24,11 @@ public class StringUtils return String.format(Locale.getDefault(), pattern, args); } - /** - * This method returns correct string representation of % (percent number) for different locales - * For example, that's how the result of would look like: - * — formatPercent(0.2319) will return 23.19% (in US locale) - * — formatPercent(0.2319) will return %23.19 (in Turkish locale) - * — formatPercent(0.2319) will return 23,19% (in Latvian locale) - * — formatPercent(1.23) will return 123% - * — formatPercent(0.000145) will return 0.01% - * — formatPercent(0.37) will return 37% - * - * @param fraction a double value, that represents a fraction of a whole - * @return correct string representation of percent for different locales - */ - public static String formatPercent(double fraction) { - NumberFormat percentFormat = NumberFormat.getPercentInstance(); - percentFormat.setMaximumFractionDigits(2); - return percentFormat.format(fraction); - } - public static native boolean nativeIsHtml(String text); public static native boolean nativeContainsNormalized(String str, String substr); public static native String[] nativeFilterContainsNormalized(String[] strings, String substr); - public static native int nativeFormatSpeed(double metersPerSecond); public static native Pair nativeFormatSpeedAndUnits(double metersPerSecond); public static native Distance nativeFormatDistance(double meters); @NonNull diff --git a/android/app/src/main/java/app/organicmaps/util/UiUtils.java b/android/app/src/main/java/app/organicmaps/util/UiUtils.java index aa97078569..3bd2609007 100644 --- a/android/app/src/main/java/app/organicmaps/util/UiUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/UiUtils.java @@ -9,7 +9,6 @@ import android.graphics.Color; import android.graphics.Rect; import android.os.Build; import android.text.TextUtils; -import android.util.DisplayMetrics; import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; @@ -19,8 +18,6 @@ import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.AnyRes; import androidx.annotation.AttrRes; import androidx.annotation.ColorInt; @@ -35,6 +32,7 @@ import androidx.core.graphics.Insets; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsControllerCompat; +import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.MwmApplication; import app.organicmaps.R; @@ -209,15 +207,6 @@ public final class UiUtils return context.getResources().getDimensionPixelSize(id); } - // this method returns the total height of the display (in pixels) including notch and other touchable areas - public static int getDisplayTotalHeight(Context context) - { - DisplayMetrics metrics = new DisplayMetrics(); - WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - windowManager.getDefaultDisplay().getRealMetrics(metrics); - return metrics.heightPixels; - } - public static void updateRedButton(Button button) { button.setTextColor(ThemeUtils.getColor(button.getContext(), button.isEnabled() ? R.attr.redButtonTextColor @@ -373,9 +362,10 @@ public final class UiUtils } } - public static void startActivityForResult(ActivityResultLauncher startForResult, @NonNull Intent intent) + @SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630 + public static void startActivityForResult(@NonNull Fragment fragment, @NonNull Intent intent, int requestCode) { - startForResult.launch(intent); + fragment.startActivityForResult(intent, requestCode); } // utility class diff --git a/android/app/src/main/java/app/organicmaps/util/Utils.java b/android/app/src/main/java/app/organicmaps/util/Utils.java index a13374805e..64a0b14245 100644 --- a/android/app/src/main/java/app/organicmaps/util/Utils.java +++ b/android/app/src/main/java/app/organicmaps/util/Utils.java @@ -271,12 +271,10 @@ public class Utils * @param context the app context * @param uri the URI to open. * @param failMessage string id: message to show in a toast when the system can't find an app to open with. - * @param action (optional) the Intent action to use. If none is provided, defaults to Intent.ACTION_VIEW. */ - public static void openUri(@NonNull Context context, @NonNull Uri uri, Integer failMessage, @NonNull String... action) + public static void openUri(@NonNull Context context, @NonNull Uri uri, Integer failMessage) { - final String act = (action != null && action.length > 0 && action[0] != null) ? action[0] : Intent.ACTION_VIEW; - final Intent intent = new Intent(act); + final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/android/app/src/main/java/app/organicmaps/widget/LanesView.java b/android/app/src/main/java/app/organicmaps/widget/LanesView.java index b1a1a5daa0..3436e032fd 100644 --- a/android/app/src/main/java/app/organicmaps/widget/LanesView.java +++ b/android/app/src/main/java/app/organicmaps/widget/LanesView.java @@ -59,14 +59,14 @@ public class LanesView extends View try (TypedArray data = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LanesView, 0, 0)) { - backgroundColor = getAttrColor(data, R.styleable.LanesView_lanesBackgroundColor, DefaultValues.BACKGROUND_COLOR); - mActiveLaneTintColor = getAttrColor(data, R.styleable.LanesView_lanesActiveLaneTintColor, DefaultValues.ACTIVE_LANE_TINT_COLOR); - mInactiveLaneTintColor = getAttrColor(data, R.styleable.LanesView_lanesInactiveLaneTintColor, DefaultValues.INACTIVE_LANE_TINT_COLOR); - mCornerRadius = (int) Math.max(data.getDimension(R.styleable.LanesView_lanesCornerRadius, DefaultValues.CORNER_RADIUS), 0.0f); + backgroundColor = getAttrColor(data, R.styleable.LanesView_backgroundColor, DefaultValues.BACKGROUND_COLOR); + mActiveLaneTintColor = getAttrColor(data, R.styleable.LanesView_activeLaneTintColor, DefaultValues.ACTIVE_LANE_TINT_COLOR); + mInactiveLaneTintColor = getAttrColor(data, R.styleable.LanesView_inactiveLaneTintColor, DefaultValues.INACTIVE_LANE_TINT_COLOR); + mCornerRadius = (int) Math.max(data.getDimension(R.styleable.LanesView_cornerRadius, DefaultValues.CORNER_RADIUS), 0.0f); if (isInEditMode()) { - final int lanesCount = Math.max(1, data.getInt(R.styleable.LanesView_lanesEditModeLanesCount, DefaultValues.LANES_COUNT)); + final int lanesCount = Math.max(1, data.getInt(R.styleable.LanesView_editModeLanesCount, DefaultValues.LANES_COUNT)); createLanesForEditMode(lanesCount); } } diff --git a/android/app/src/main/java/app/organicmaps/widget/SearchToolbarController.java b/android/app/src/main/java/app/organicmaps/widget/SearchToolbarController.java index 434d714389..c6fc6468ef 100644 --- a/android/app/src/main/java/app/organicmaps/widget/SearchToolbarController.java +++ b/android/app/src/main/java/app/organicmaps/widget/SearchToolbarController.java @@ -10,7 +10,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; -import androidx.activity.result.ActivityResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -23,6 +22,7 @@ import com.google.android.material.textfield.TextInputEditText; public class SearchToolbarController extends ToolbarController implements View.OnClickListener { + private static final int REQUEST_VOICE_RECOGNITION = 0xCA11; @Nullable private final View mToolbarContainer; @NonNull @@ -107,7 +107,7 @@ public class SearchToolbarController extends ToolbarController implements View.O clear(); } - protected void startVoiceRecognition(Intent intent) + protected void startVoiceRecognition(Intent intent, int code) { throw new RuntimeException("To be used startVoiceRecognition() must be implemented by descendant class"); } @@ -130,7 +130,7 @@ public class SearchToolbarController extends ToolbarController implements View.O try { startVoiceRecognition(InputUtils.createIntentForVoiceRecognition( - requireActivity().getString(getVoiceInputPrompt()))); + requireActivity().getString(getVoiceInputPrompt())), REQUEST_VOICE_RECOGNITION); } catch (ActivityNotFoundException e) { @@ -210,18 +210,14 @@ public class SearchToolbarController extends ToolbarController implements View.O UiUtils.showIf(show, mSearchContainer); } - public void onVoiceRecognitionResult(ActivityResult activityResult) + public void onActivityResult(int requestCode, int resultCode, Intent data) { - if(activityResult.getResultCode() == Activity.RESULT_OK) - { - if (activityResult.getData() == null) - { - return; - } - String recognitionResult = InputUtils.getBestRecognitionResult(activityResult.getData()); - if (!TextUtils.isEmpty(recognitionResult)) - setQuery(recognitionResult); - } + if (requestCode == REQUEST_VOICE_RECOGNITION && resultCode == Activity.RESULT_OK) + { + String result = InputUtils.getBestRecognitionResult(data); + if (!TextUtils.isEmpty(result)) + setQuery(result); + } } public void setHint(@StringRes int hint) diff --git a/android/app/src/main/java/app/organicmaps/widget/SpeedLimitView.java b/android/app/src/main/java/app/organicmaps/widget/SpeedLimitView.java index 4e71f55c5d..4cc8b2ff43 100644 --- a/android/app/src/main/java/app/organicmaps/widget/SpeedLimitView.java +++ b/android/app/src/main/java/app/organicmaps/widget/SpeedLimitView.java @@ -8,14 +8,16 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.util.AttributeSet; -import android.view.MotionEvent; +import android.util.Pair; import android.view.View; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import app.organicmaps.R; +import app.organicmaps.util.StringUtils; public class SpeedLimitView extends View { @@ -24,10 +26,6 @@ public class SpeedLimitView extends View @ColorInt int BACKGROUND_COLOR = Color.WHITE; @ColorInt - int BORDER_COLOR = Color.RED; - @ColorInt - int ALERT_COLOR = Color.RED; - @ColorInt int TEXT_COLOR = Color.BLACK; @ColorInt int TEXT_ALERT_COLOR = Color.WHITE; @@ -63,28 +61,29 @@ public class SpeedLimitView extends View private float mBorderRadius; private float mBorderWidth; - private int mSpeedLimit = 0; - @NonNull - private String mSpeedLimitStr = "0"; - private boolean mAlert = false; + private double mSpeedLimitMps; + @Nullable + private String mSpeedLimitStr; + + private double mCurrentSpeed; public SpeedLimitView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); try (TypedArray data = context.getTheme() - .obtainStyledAttributes(attrs, R.styleable.SpeedLimitView, 0, 0)) + .obtainStyledAttributes(attrs, R.styleable.SpeedLimitView, 0, 0)) { - mBackgroundColor = data.getColor(R.styleable.SpeedLimitView_speedLimitBackgroundColor, DefaultValues.BACKGROUND_COLOR); - mBorderColor = data.getColor(R.styleable.SpeedLimitView_speedLimitBorderColor, DefaultValues.BORDER_COLOR); - mAlertColor = data.getColor(R.styleable.SpeedLimitView_speedLimitAlertColor, DefaultValues.ALERT_COLOR); - mTextColor = data.getColor(R.styleable.SpeedLimitView_speedLimitTextColor, DefaultValues.TEXT_COLOR); - mTextAlertColor = data.getColor(R.styleable.SpeedLimitView_speedLimitTextAlertColor, DefaultValues.TEXT_ALERT_COLOR); + mBackgroundColor = data.getColor(R.styleable.SpeedLimitView_BackgroundColor, DefaultValues.BACKGROUND_COLOR); + mBorderColor = data.getColor(R.styleable.SpeedLimitView_borderColor, ContextCompat.getColor(context, R.color.base_red)); + mAlertColor = data.getColor(R.styleable.SpeedLimitView_alertColor, ContextCompat.getColor(context, R.color.base_red)); + mTextColor = data.getColor(R.styleable.SpeedLimitView_textColor, DefaultValues.TEXT_COLOR); + mTextAlertColor = data.getColor(R.styleable.SpeedLimitView_textAlertColor, DefaultValues.TEXT_ALERT_COLOR); if (isInEditMode()) { - mSpeedLimit = data.getInt(R.styleable.SpeedLimitView_speedLimitEditModeSpeedLimit, 60); - mSpeedLimitStr = Integer.toString(mSpeedLimit); - mAlert = data.getBoolean(R.styleable.SpeedLimitView_speedLimitEditModeAlert, false); + mSpeedLimitMps = data.getInt(R.styleable.SpeedLimitView_editModeSpeedLimit, -1); + mSpeedLimitStr = mSpeedLimitMps > 0 ? String.valueOf(((int) mSpeedLimitMps)) : null; + mCurrentSpeed = data.getInt(R.styleable.SpeedLimitView_editModeCurrentSpeed, -1); } } @@ -102,19 +101,29 @@ public class SpeedLimitView extends View mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); } - public void setSpeedLimit(final int speedLimit, boolean alert) + public void setSpeedLimitMps(final double speedLimitMps) { - final boolean speedLimitChanged = mSpeedLimit != speedLimit; + if (mSpeedLimitMps == speedLimitMps) + return; - mSpeedLimit = speedLimit; - mAlert = alert; - - if (speedLimitChanged) + mSpeedLimitMps = speedLimitMps; + if (mSpeedLimitMps <= 0) { - mSpeedLimitStr = Integer.toString(mSpeedLimit); - configureTextSize(); + mSpeedLimitStr = null; + setVisibility(GONE); + return; } + final Pair speedLimitAndUnits = StringUtils.nativeFormatSpeedAndUnits(mSpeedLimitMps); + setVisibility(VISIBLE); + mSpeedLimitStr = speedLimitAndUnits.first; + configureTextSize(); + invalidate(); + } + + public void setCurrentSpeed(final double currentSpeed) + { + mCurrentSpeed = currentSpeed; invalidate(); } @@ -123,15 +132,13 @@ public class SpeedLimitView extends View { super.onDraw(canvas); - final boolean validSpeedLimit = mSpeedLimit > 0; - if (!validSpeedLimit) - return; + final boolean alert = mCurrentSpeed > mSpeedLimitMps && mSpeedLimitMps > 0; final float cx = mWidth / 2; final float cy = mHeight / 2; - drawSign(canvas, cx, cy, mAlert); - drawText(canvas, cx, cy, mAlert); + drawSign(canvas, cx, cy, alert); + drawText(canvas, cx, cy, alert); } private void drawSign(@NonNull Canvas canvas, float cx, float cy, boolean alert) @@ -151,6 +158,9 @@ public class SpeedLimitView extends View private void drawText(@NonNull Canvas canvas, float cx, float cy, boolean alert) { + if (mSpeedLimitStr == null) + return; + if (alert) mTextPaint.setColor(mTextAlertColor); else @@ -162,26 +172,6 @@ public class SpeedLimitView extends View canvas.drawText(mSpeedLimitStr, cx, textY, mTextPaint); } - @Override - public boolean onTouchEvent(@NonNull MotionEvent event) - { - final float cx = mWidth / 2; - final float cy = mHeight / 2; - if (Math.pow(event.getX() - cx, 2) + Math.pow(event.getY() - cy, 2) <= Math.pow(mBackgroundRadius, 2)) - { - performClick(); - return true; - } - return false; - } - - @Override - public boolean performClick() - { - super.performClick(); - return false; - } - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { @@ -201,6 +191,9 @@ public class SpeedLimitView extends View // Apply binary search to determine the optimal text size that fits within the circular boundary. private void configureTextSize() { + if (mSpeedLimitStr == null) + return; + final String text = mSpeedLimitStr; final float textRadius = mBorderRadius - mBorderWidth; final float textMaxSize = 2 * textRadius; diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java index 28cbf2b032..aeb6363b6e 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java @@ -11,7 +11,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import app.organicmaps.Framework; -import app.organicmaps.MwmActivity; import app.organicmaps.R; import app.organicmaps.base.BaseMwmDialogFragment; import app.organicmaps.bookmarks.data.DistanceAndAzimut; @@ -47,7 +46,6 @@ public class DirectionFragment extends BaseMwmDialogFragment { final View root = inflater.inflate(R.layout.fragment_direction, container, false); root.setOnTouchListener((v, event) -> { - root.performClick(); dismiss(); return false; }); @@ -101,7 +99,6 @@ public class DirectionFragment extends BaseMwmDialogFragment super.onResume(); LocationHelper.from(requireContext()).addListener(this); SensorHelper.from(requireContext()).addListener(this); - ((MwmActivity) requireActivity()).hideOrShowUIWithoutClosingPlacePage(true); refreshViews(); } @@ -113,13 +110,6 @@ public class DirectionFragment extends BaseMwmDialogFragment SensorHelper.from(requireContext()).removeListener(this); } - @Override - public void onStop() - { - super.onStop(); - ((MwmActivity) requireActivity()).hideOrShowUIWithoutClosingPlacePage(false); - } - @Override public void onLocationUpdated(@NonNull Location location) { diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java index dd31a18ac1..6dad3f6361 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/EditBookmarkFragment.java @@ -43,9 +43,6 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. public static final String EXTRA_CATEGORY_ID = "CategoryId"; public static final String EXTRA_ID = "BookmarkTrackId"; public static final String EXTRA_BOOKMARK_TYPE = "BookmarkType"; - public static final String STATE_ICON = "icon"; - public static final String STATE_BOOKMARK_CATEGORY = "bookmark_category"; - public static final String STATE_COLOR = "color"; public static final int TYPE_BOOKMARK = 1; public static final int TYPE_TRACK = 2; @@ -145,13 +142,8 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. mIvColor.setOnClickListener(this); //For tracks an bookmarks same category is used so this portion is common for both - if (savedInstanceState != null && savedInstanceState.getParcelable(STATE_BOOKMARK_CATEGORY) != null) - mBookmarkCategory = savedInstanceState.getParcelable(STATE_BOOKMARK_CATEGORY); - else - { - long categoryId = args.getLong(EXTRA_CATEGORY_ID); - mBookmarkCategory = BookmarkManager.INSTANCE.getCategoryById(categoryId); - } + long categoryId = args.getLong(EXTRA_CATEGORY_ID); + mBookmarkCategory = BookmarkManager.INSTANCE.getCategoryById(categoryId); long id = args.getLong(EXTRA_ID); switch (mType) @@ -159,9 +151,7 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. case TYPE_BOOKMARK -> { mBookmark = BookmarkManager.INSTANCE.getBookmarkInfo(id); - if (savedInstanceState != null && savedInstanceState.getParcelable(STATE_ICON) != null) - mIcon = savedInstanceState.getParcelable(STATE_ICON); - else if (mBookmark != null) + if (mBookmark != null) mIcon = mBookmark.getIcon(); refreshBookmark(); } @@ -169,8 +159,6 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. { mTrack = BookmarkManager.INSTANCE.getTrack(id); mColor = mTrack.getColor(); - if (savedInstanceState != null) - mColor = savedInstanceState.getInt(STATE_COLOR, mColor); refreshTrack(); } } @@ -252,15 +240,6 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. dismiss(); } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) - { - super.onSaveInstanceState(outState); - outState.putParcelable(STATE_ICON, mIcon); - outState.putParcelable(STATE_BOOKMARK_CATEGORY, mBookmarkCategory); - outState.putInt(STATE_COLOR, mColor); - } - @Override public void onClick(View v) { diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java index e6489f8a05..343a5bb475 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java @@ -50,7 +50,6 @@ import app.organicmaps.widget.placepage.sections.PlacePageBookmarkFragment; import app.organicmaps.widget.placepage.sections.PlacePageLinksFragment; import app.organicmaps.widget.placepage.sections.PlacePageOpeningHoursFragment; import app.organicmaps.widget.placepage.sections.PlacePagePhoneFragment; -import app.organicmaps.widget.placepage.sections.PlacePageProductsFragment; import app.organicmaps.widget.placepage.sections.PlacePageWikipediaFragment; import com.google.android.material.button.MaterialButton; @@ -70,7 +69,6 @@ public class PlacePageView extends Fragment implements View.OnClickListener, { private static final String PREF_COORDINATES_FORMAT = "coordinates_format"; private static final String BOOKMARK_FRAGMENT_TAG = "BOOKMARK_FRAGMENT_TAG"; - private static final String PRODUCTS_FRAGMENT_TAG = "PRODUCTS_FRAGMENT_TAG"; private static final String WIKIPEDIA_FRAGMENT_TAG = "WIKIPEDIA_FRAGMENT_TAG"; private static final String PHONE_FRAGMENT_TAG = "PHONE_FRAGMENT_TAG"; private static final String OPENING_HOURS_FRAGMENT_TAG = "OPENING_HOURS_FRAGMENT_TAG"; @@ -204,11 +202,9 @@ public class PlacePageView extends Fragment implements View.OnClickListener, mFrame.setOnClickListener((v) -> mPlacePageViewListener.onPlacePageRequestToggleState()); mPreview = mFrame.findViewById(R.id.pp__preview); - mFrame.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { final int oldHeight = oldBottom - oldTop; final int newHeight = bottom - top; - if (oldHeight != newHeight) mPlacePageViewListener.onPlacePageContentChanged(mPreview.getHeight(), newHeight); }); @@ -391,18 +387,6 @@ public class PlacePageView extends Fragment implements View.OnClickListener, updateViewFragment(PlacePageWikipediaFragment.class, WIKIPEDIA_FRAGMENT_TAG, R.id.place_page_wikipedia_fragment, hasWikipediaEntry()); } - private boolean hasProductsEntry() - { - return Framework.nativeShouldShowProducts(); - } - - private void updateProductsView() - { - var hasProductsEntry = hasProductsEntry(); - - updateViewFragment(PlacePageProductsFragment.class, PRODUCTS_FRAGMENT_TAG, R.id.place_page_products_fragment, hasProductsEntry); - } - private void setTextAndColorizeSubtitle() { String text = mMapObject.getSubtitle(); @@ -496,7 +480,6 @@ public class PlacePageView extends Fragment implements View.OnClickListener, } updateLinksView(); updateOpeningHoursView(); - updateProductsView(); updateWikipediaView(); updateBookmarkView(); updatePhoneView(); diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageProductsFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageProductsFragment.java deleted file mode 100644 index 9982f0cdf8..0000000000 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageProductsFragment.java +++ /dev/null @@ -1,107 +0,0 @@ -package app.organicmaps.widget.placepage.sections; - -import static androidx.core.util.ObjectsCompat.requireNonNull; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import java.util.Objects; - -import app.organicmaps.Framework; -import app.organicmaps.R; -import app.organicmaps.products.Product; -import app.organicmaps.products.ProductsConfig; -import app.organicmaps.util.Constants; -import app.organicmaps.util.UiUtils; -import app.organicmaps.util.Utils; - -public class PlacePageProductsFragment extends Fragment -{ - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) - { - return inflater.inflate(R.layout.place_page_products_fragment, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) - { - super.onViewCreated(view, savedInstanceState); - - var config = Framework.nativeGetProductsConfiguration(); - if (config != null && isValidConfig(config)) - { - UiUtils.show(view); - - updateView(view, config); - } else - { - UiUtils.hide(view); - } - } - - private void updateView(@NonNull View view, @NonNull ProductsConfig config) - { - var layoutInflater = LayoutInflater.from(view.getContext()); - - TextView productsPrompt = requireNonNull(view.findViewById(R.id.products_prompt)); - LinearLayout productsButtons = requireNonNull(view.findViewById(R.id.products_buttons)); - View closeButton = requireNonNull(view.findViewById(R.id.products_close)); - View productsRemindLater = requireNonNull(view.findViewById(R.id.products_remind_later)); - View alreadyDonated = requireNonNull(view.findViewById(R.id.products_already_donated)); - - productsPrompt.setText(config.placePagePrompt); - - productsButtons.removeAllViews(); - - for (var product : Objects.requireNonNull(config.products)) - { - var button = (Button) layoutInflater.inflate(R.layout.item_product, productsButtons, false); - button.setText(product.title); - button.setOnClickListener((v) -> { - onProductSelected(product); - }); - - productsButtons.addView(button); - } - - closeButton.setOnClickListener((v) -> { - closeWithReason(view, Constants.ProductsPopupCloseReason.CLOSE); - }); - - productsRemindLater.setOnClickListener((v) -> { - closeWithReason(view, Constants.ProductsPopupCloseReason.REMIND_LATER); - }); - - alreadyDonated.setOnClickListener((v) -> { - closeWithReason(view, Constants.ProductsPopupCloseReason.ALREADY_DONATED); - }); - } - - private void closeWithReason(View view, String reason) - { - Framework.nativeDidCloseProductsPopup(reason); - UiUtils.hide(view); - } - - private void onProductSelected(Product product) - { - Utils.openUrl(requireActivity(), product.link); - Framework.nativeDidSelectProduct(product.title, product.link); - } - - private boolean isValidConfig(@NonNull ProductsConfig config) - { - return config.products != null && config.products.length > 0; - } -} diff --git a/android/app/src/main/res/drawable/ic_add_list.xml b/android/app/src/main/res/drawable/ic_add_list.xml deleted file mode 100644 index a4f9f7a544..0000000000 --- a/android/app/src/main/res/drawable/ic_add_list.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_building.xml b/android/app/src/main/res/drawable/ic_building.xml deleted file mode 100644 index c2424cc1f0..0000000000 --- a/android/app/src/main/res/drawable/ic_building.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_floor.xml b/android/app/src/main/res/drawable/ic_floor.xml deleted file mode 100644 index 4b2a27afb2..0000000000 --- a/android/app/src/main/res/drawable/ic_floor.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_import.xml b/android/app/src/main/res/drawable/ic_import.xml index c0ae388199..a4f9f7a544 100644 --- a/android/app/src/main/res/drawable/ic_import.xml +++ b/android/app/src/main/res/drawable/ic_import.xml @@ -1,9 +1,9 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/android/app/src/main/res/drawable/ic_location_arrow_blue.xml b/android/app/src/main/res/drawable/ic_location_arrow_blue.xml deleted file mode 100644 index 0be38e62d0..0000000000 --- a/android/app/src/main/res/drawable/ic_location_arrow_blue.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_location_crosshair_blue.xml b/android/app/src/main/res/drawable/ic_location_crosshair_blue.xml deleted file mode 100644 index aa23f65a1b..0000000000 --- a/android/app/src/main/res/drawable/ic_location_crosshair_blue.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_manage_route.xml b/android/app/src/main/res/drawable/ic_manage_route.xml deleted file mode 100644 index fd1ba2b93d..0000000000 --- a/android/app/src/main/res/drawable/ic_manage_route.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_location_crosshair.xml b/android/app/src/main/res/drawable/ic_my_location.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_location_crosshair.xml rename to android/app/src/main/res/drawable/ic_my_location.xml diff --git a/android/app/src/main/res/drawable/ic_operating_hours.xml b/android/app/src/main/res/drawable/ic_operating_hours.xml index 2ce8a142ba..682253e6f6 100644 --- a/android/app/src/main/res/drawable/ic_operating_hours.xml +++ b/android/app/src/main/res/drawable/ic_operating_hours.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/android/app/src/main/res/drawable/ic_street_address.xml b/android/app/src/main/res/drawable/ic_street_address.xml deleted file mode 100644 index 85d5766f30..0000000000 --- a/android/app/src/main/res/drawable/ic_street_address.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_track_recording_status.xml b/android/app/src/main/res/drawable/ic_track_recording_status.xml deleted file mode 100644 index 9ca2e31d6f..0000000000 --- a/android/app/src/main/res/drawable/ic_track_recording_status.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/route_point_01.xml b/android/app/src/main/res/drawable/route_point_01.xml deleted file mode 100644 index f61bca62be..0000000000 --- a/android/app/src/main/res/drawable/route_point_01.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_02.xml b/android/app/src/main/res/drawable/route_point_02.xml deleted file mode 100644 index d945fc7573..0000000000 --- a/android/app/src/main/res/drawable/route_point_02.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_03.xml b/android/app/src/main/res/drawable/route_point_03.xml deleted file mode 100644 index 1bd922d612..0000000000 --- a/android/app/src/main/res/drawable/route_point_03.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_04.xml b/android/app/src/main/res/drawable/route_point_04.xml deleted file mode 100644 index 5eb2839fac..0000000000 --- a/android/app/src/main/res/drawable/route_point_04.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_05.xml b/android/app/src/main/res/drawable/route_point_05.xml deleted file mode 100644 index 8c87e652f9..0000000000 --- a/android/app/src/main/res/drawable/route_point_05.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_06.xml b/android/app/src/main/res/drawable/route_point_06.xml deleted file mode 100644 index f42b79c893..0000000000 --- a/android/app/src/main/res/drawable/route_point_06.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_07.xml b/android/app/src/main/res/drawable/route_point_07.xml deleted file mode 100644 index 003e621640..0000000000 --- a/android/app/src/main/res/drawable/route_point_07.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_08.xml b/android/app/src/main/res/drawable/route_point_08.xml deleted file mode 100644 index 0517fce861..0000000000 --- a/android/app/src/main/res/drawable/route_point_08.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_09.xml b/android/app/src/main/res/drawable/route_point_09.xml deleted file mode 100644 index 6cf968bf54..0000000000 --- a/android/app/src/main/res/drawable/route_point_09.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_10.xml b/android/app/src/main/res/drawable/route_point_10.xml deleted file mode 100644 index 9b0dd4ac00..0000000000 --- a/android/app/src/main/res/drawable/route_point_10.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_11.xml b/android/app/src/main/res/drawable/route_point_11.xml deleted file mode 100644 index 011e29d3a1..0000000000 --- a/android/app/src/main/res/drawable/route_point_11.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_12.xml b/android/app/src/main/res/drawable/route_point_12.xml deleted file mode 100644 index dc49d8702a..0000000000 --- a/android/app/src/main/res/drawable/route_point_12.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_13.xml b/android/app/src/main/res/drawable/route_point_13.xml deleted file mode 100644 index 53a78161c6..0000000000 --- a/android/app/src/main/res/drawable/route_point_13.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_14.xml b/android/app/src/main/res/drawable/route_point_14.xml deleted file mode 100644 index 596445aa6b..0000000000 --- a/android/app/src/main/res/drawable/route_point_14.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_15.xml b/android/app/src/main/res/drawable/route_point_15.xml deleted file mode 100644 index 3d4285f9b3..0000000000 --- a/android/app/src/main/res/drawable/route_point_15.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_16.xml b/android/app/src/main/res/drawable/route_point_16.xml deleted file mode 100644 index e98de508d7..0000000000 --- a/android/app/src/main/res/drawable/route_point_16.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_17.xml b/android/app/src/main/res/drawable/route_point_17.xml deleted file mode 100644 index 30836bb004..0000000000 --- a/android/app/src/main/res/drawable/route_point_17.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_18.xml b/android/app/src/main/res/drawable/route_point_18.xml deleted file mode 100644 index ac0edc0523..0000000000 --- a/android/app/src/main/res/drawable/route_point_18.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_19.xml b/android/app/src/main/res/drawable/route_point_19.xml deleted file mode 100644 index d2f55877df..0000000000 --- a/android/app/src/main/res/drawable/route_point_19.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_20.xml b/android/app/src/main/res/drawable/route_point_20.xml deleted file mode 100644 index c9a25476c6..0000000000 --- a/android/app/src/main/res/drawable/route_point_20.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/route_point_finish.png b/android/app/src/main/res/drawable/route_point_finish.png deleted file mode 100644 index 46b0adf6d6..0000000000 Binary files a/android/app/src/main/res/drawable/route_point_finish.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/route_point_start.png b/android/app/src/main/res/drawable/route_point_start.png deleted file mode 100644 index f928ba045d..0000000000 Binary files a/android/app/src/main/res/drawable/route_point_start.png and /dev/null differ diff --git a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_navigation.xml b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_navigation.xml deleted file mode 100644 index 4ed89632e4..0000000000 --- a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_navigation.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_planning.xml b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_planning.xml deleted file mode 100644 index b80f476643..0000000000 --- a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_planning.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_regular.xml b/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_regular.xml deleted file mode 100644 index 1f8f5a0a03..0000000000 --- a/android/app/src/main/res/layout-h400dp-land/map_buttons_layout_regular.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml b/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml index bfd6f149b8..f20833f98b 100644 --- a/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml +++ b/android/app/src/main/res/layout-h400dp/map_buttons_layout_navigation.xml @@ -49,13 +49,6 @@ app:layout_constraintBottom_toTopOf="@+id/btn_bookmarks" app:layout_constraintStart_toStartOf="parent" /> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1503,14 +1679,17 @@ + + + - + - + diff --git a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift index cac8307f61..0f7795f5d3 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift @@ -20,7 +20,7 @@ @unknown default: fatalError() } - let presenter = PlacePagePresenter(view: viewController, headerView: layout.headerViewController) + let presenter = PlacePagePresenter(view: viewController) viewController.setLayout(layout) viewController.interactor = interactor interactor.presenter = presenter @@ -45,7 +45,7 @@ @unknown default: fatalError() } - let presenter = PlacePagePresenter(view: viewController, headerView: layout.headerViewController) + let presenter = PlacePagePresenter(view: viewController) viewController.interactor = interactor interactor.presenter = presenter layout.presenter = presenter diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index bb2e8161b3..a7a657e205 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -295,42 +295,8 @@ extension PlacePageInteractor: PlacePageHeaderViewControllerDelegate { func previewDidPressShare(from sourceView: UIView) { guard let mapViewController else { return } - switch placePageData.objectType { - case .POI, .bookmark: - let shareViewController = ActivityViewController.share(forPlacePage: placePageData) - shareViewController.present(inParentViewController: mapViewController, anchorView: sourceView) - case .track: - presenter?.showShareTrackMenu() - default: - fatalError() - } - } - - func previewDidPressExportTrack(_ type: KmlFileType, from sourceView: UIView) { - guard let trackId = placePageData.trackData?.trackId else { - fatalError("Track data should not be nil during the track export") - } - bookmarksManager.shareTrack(trackId, fileType: type) { [weak self] status, url in - guard let self, let mapViewController else { return } - switch status { - case .success: - guard let url else { fatalError("Invalid sharing url") } - let shareViewController = ActivityViewController.share(for: url, message: self.placePageData.previewData.title!) { _,_,_,_ in - self.bookmarksManager.finishSharing() - } - shareViewController.present(inParentViewController: mapViewController, anchorView: sourceView) - case .emptyCategory: - self.showAlert(withTitle: L("bookmarks_error_title_share_empty"), - message: L("bookmarks_error_message_share_empty")) - case .archiveError, .fileError: - self.showAlert(withTitle: L("dialog_routing_system_error"), - message: L("bookmarks_error_message_share_general")) - } - } - } - - private func showAlert(withTitle title: String, message: String) { - MWMAlertViewController.activeAlert().presentInfoAlert(title, text: message) + let shareViewController = ActivityViewController.share(forPlacePage: placePageData) + shareViewController.present(inParentViewController: mapViewController, anchorView: sourceView) } } diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/IPlacePageLayout.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/IPlacePageLayout.swift index ca8cc89456..8189c97a76 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/IPlacePageLayout.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/IPlacePageLayout.swift @@ -24,7 +24,6 @@ enum PlacePageState { protocol IPlacePageLayout: AnyObject { var presenter: PlacePagePresenterProtocol? { get set } var headerViewControllers: [UIViewController] { get } - var headerViewController: PlacePageHeaderViewController { get } var bodyViewControllers: [UIViewController] { get } var actionBar: ActionBarViewController? { get } var navigationBar: UIViewController? { get } diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift index a49e96e3c1..cc9f44096e 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageCommonLayout.swift @@ -27,7 +27,7 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout { } lazy var headerViewController: PlacePageHeaderViewController = { - PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .flexible) + PlacePageHeaderBuilder.build(data: placePageData.previewData, delegate: interactor, headerType: .flexible) }() lazy var previewViewController: PlacePagePreviewViewController = { @@ -81,7 +81,7 @@ class PlacePageCommonLayout: NSObject, IPlacePageLayout { } () lazy var placePageNavigationViewController: PlacePageHeaderViewController = { - return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .fixed) + return PlacePageHeaderBuilder.build(data: placePageData.previewData, delegate: interactor, headerType: .fixed) } () init(interactor: PlacePageInteractor, storyboard: UIStoryboard, data: PlacePageData) { diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackLayout.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackLayout.swift index 0c6e55c6eb..7faf58fcb4 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackLayout.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackLayout.swift @@ -22,7 +22,7 @@ class PlacePageTrackLayout: IPlacePageLayout { }() lazy var headerViewController: PlacePageHeaderViewController = { - PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .flexible) + PlacePageHeaderBuilder.build(data: placePageData.previewData, delegate: interactor, headerType: .flexible) }() lazy var previewViewController: PlacePagePreviewViewController = { @@ -32,7 +32,7 @@ class PlacePageTrackLayout: IPlacePageLayout { }() lazy var placePageNavigationViewController: PlacePageHeaderViewController = { - return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .fixed) + return PlacePageHeaderBuilder.build(data: placePageData.previewData, delegate: interactor, headerType: .fixed) }() lazy var editTrackViewController: PlacePageEditBookmarkOrTrackViewController = { diff --git a/iphone/Maps/UI/PlacePage/PlacePagePresenter.swift b/iphone/Maps/UI/PlacePage/PlacePagePresenter.swift index 4bc4b63bfa..133e550f83 100644 --- a/iphone/Maps/UI/PlacePage/PlacePagePresenter.swift +++ b/iphone/Maps/UI/PlacePage/PlacePagePresenter.swift @@ -4,16 +4,13 @@ protocol PlacePagePresenterProtocol: AnyObject { func showNextStop() func closeAnimated() func showAlert(_ alert: UIAlertController) - func showShareTrackMenu() } -final class PlacePagePresenter: NSObject { +class PlacePagePresenter: NSObject { private weak var view: PlacePageViewProtocol! - private weak var headerView: PlacePageHeaderViewProtocol! - init(view: PlacePageViewProtocol, headerView: PlacePageHeaderViewProtocol) { + init(view: PlacePageViewProtocol) { self.view = view - self.headerView = headerView } } @@ -39,8 +36,4 @@ extension PlacePagePresenter: PlacePagePresenterProtocol { func showAlert(_ alert: UIAlertController) { view.showAlert(alert) } - - func showShareTrackMenu() { - headerView.showShareTrackMenu() - } } diff --git a/iphone/Maps/UI/Search/MWMSearchContentView.h b/iphone/Maps/UI/Search/MWMSearchContentView.h new file mode 100644 index 0000000000..5a28e7be6e --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchContentView.h @@ -0,0 +1,3 @@ +@interface MWMSearchContentView : SolidTouchView + +@end diff --git a/iphone/Maps/UI/Search/MWMSearchContentView.m b/iphone/Maps/UI/Search/MWMSearchContentView.m new file mode 100644 index 0000000000..6f9cb7c566 --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchContentView.m @@ -0,0 +1,14 @@ +#import "MWMSearchContentView.h" + +@implementation MWMSearchContentView + +- (void)layoutSubviews +{ + [self.subviews enumerateObjectsUsingBlock:^(UIView * view, NSUInteger idx, BOOL * stop) + { + view.frame = self.bounds; + }]; + [super layoutSubviews]; +} + +@end diff --git a/iphone/Maps/UI/Search/MWMSearchManager+Layout.h b/iphone/Maps/UI/Search/MWMSearchManager+Layout.h new file mode 100644 index 0000000000..c39e4c5980 --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchManager+Layout.h @@ -0,0 +1,8 @@ +#import "MWMSearchManager.h" + +@interface MWMSearchManager (Layout) + +- (void)layoutTopViews; +- (void)removeKeyboardObservers; + +@end diff --git a/iphone/Maps/UI/Search/MWMSearchManager+Layout.m b/iphone/Maps/UI/Search/MWMSearchManager+Layout.m new file mode 100644 index 0000000000..2220e61943 --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchManager+Layout.m @@ -0,0 +1,120 @@ +#import +#import "MWMSearchManager+Layout.h" +#import "MapViewController.h" + +static CGFloat const changeModeViewOffsetNormal = -24; +static CGFloat const changeModeViewOffsetKeyboard = -12; + +@interface MWMSearchManager () + +@property(nonatomic) IBOutlet UIView *searchBarView; +@property(nonatomic) IBOutlet UIView *actionBarView; +@property(nonatomic) IBOutlet UIView *contentView; + +@property(nonatomic) NSLayoutConstraint *contentViewTopHidden; +@property(nonatomic) NSLayoutConstraint *contentViewBottomHidden; +@property(nonatomic) NSLayoutConstraint *actionBarViewBottomKeyboard; +@property(nonatomic) NSLayoutConstraint *actionBarViewBottomNormal; + +@property(weak, nonatomic, readonly) UIView *searchViewContainer; + +@end + +@implementation MWMSearchManager (Layout) + +- (void)layoutTopViews { + UIView *searchBarView = self.searchBarView; + UIView *changeModeView = self.actionBarView; + UIView *contentView = self.contentView; + UIView *parentView = self.searchViewContainer; + + searchBarView.translatesAutoresizingMaskIntoConstraints = NO; + changeModeView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + + NSLayoutXAxisAnchor *leadingAnchor = parentView.leadingAnchor; + NSLayoutXAxisAnchor *trailingAnchor = parentView.trailingAnchor; + NSLayoutYAxisAnchor *topAnchor = parentView.safeAreaLayoutGuide.topAnchor; + NSLayoutYAxisAnchor *bottomAnchor = parentView.safeAreaLayoutGuide.bottomAnchor; + + [searchBarView.topAnchor constraintEqualToAnchor:topAnchor].active = YES; + [searchBarView.leadingAnchor constraintEqualToAnchor:leadingAnchor].active = YES; + if (IPAD) + [searchBarView.widthAnchor constraintEqualToConstant:kWidthForiPad].active = YES; + else + [searchBarView.trailingAnchor constraintEqualToAnchor:trailingAnchor].active = YES; + + [changeModeView.centerXAnchor constraintEqualToAnchor:parentView.centerXAnchor].active = YES; + self.actionBarViewBottomNormal = [changeModeView.bottomAnchor constraintEqualToAnchor:bottomAnchor + constant:changeModeViewOffsetNormal]; + self.actionBarViewBottomNormal.priority = UILayoutPriorityDefaultLow + 10; + self.actionBarViewBottomNormal.active = YES; + + self.actionBarViewBottomKeyboard = [changeModeView.bottomAnchor constraintEqualToAnchor:parentView.bottomAnchor + constant:changeModeViewOffsetKeyboard]; + self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultLow; + self.actionBarViewBottomKeyboard.active = YES; + + NSLayoutConstraint *contentViewTop = [contentView.topAnchor constraintEqualToAnchor:searchBarView.bottomAnchor]; + contentViewTop.priority = UILayoutPriorityDefaultLow + 10; + contentViewTop.active = YES; + + NSLayoutConstraint *contentViewBottom = [contentView.bottomAnchor constraintEqualToAnchor:parentView.bottomAnchor]; + contentViewBottom.priority = UILayoutPriorityDefaultLow + 10; + contentViewBottom.active = YES; + + self.contentViewTopHidden = [contentView.topAnchor constraintEqualToAnchor:parentView.bottomAnchor]; + self.contentViewTopHidden.priority = UILayoutPriorityDefaultLow; + self.contentViewTopHidden.active = YES; + + self.contentViewBottomHidden = [contentView.heightAnchor constraintEqualToAnchor:parentView.heightAnchor]; + self.contentViewBottomHidden.priority = UILayoutPriorityDefaultLow; + self.contentViewBottomHidden.active = YES; + + [contentView.leadingAnchor constraintEqualToAnchor:searchBarView.leadingAnchor].active = YES; + [contentView.trailingAnchor constraintEqualToAnchor:searchBarView.trailingAnchor].active = YES; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)removeKeyboardObservers { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - keyboard movements +- (void)keyboardWillShow:(NSNotification *)notification { + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; + CGFloat offset = IPAD ? changeModeViewOffsetNormal : changeModeViewOffsetKeyboard; + if (self.actionBarView.isHidden) { + self.actionBarViewBottomKeyboard.constant = -keyboardSize.height + offset; + self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultHigh; + } else { + [UIView animateWithDuration:kDefaultAnimationDuration + animations:^{ + self.actionBarViewBottomKeyboard.constant = -keyboardSize.height + offset; + self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultHigh; + [self.actionBarView.superview layoutIfNeeded]; + }]; + } +} + +- (void)keyboardWillHide:(NSNotification *)notification { + if (self.actionBarView.isHidden) { + self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultLow; + } else { + [UIView animateWithDuration:kDefaultAnimationDuration + animations:^{ + self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultLow; + [self.actionBarView.superview layoutIfNeeded]; + }]; + } +} + +@end diff --git a/iphone/Maps/UI/Search/MWMSearchManager.h b/iphone/Maps/UI/Search/MWMSearchManager.h new file mode 100644 index 0000000000..8c3309dd73 --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchManager.h @@ -0,0 +1,34 @@ +#import "MWMAlertViewController.h" +#import "MWMSearchManagerObserver.h" +#import "MWMSearchManagerState.h" + +typedef NS_ENUM(NSInteger, MWMSearchManagerRoutingTooltipSearch) { + MWMSearchManagerRoutingTooltipSearchNone, + MWMSearchManagerRoutingTooltipSearchStart, + MWMSearchManagerRoutingTooltipSearchFinish +}; +@class SearchTextField; + +@interface MWMSearchManager : NSObject + +extern const CGFloat kWidthForiPad; + ++ (nonnull MWMSearchManager *)manager NS_SWIFT_NAME(manager()); ++ (void)addObserver:(nonnull id)observer; ++ (void)removeObserver:(nonnull id)observer; + +@property(nullable, weak, nonatomic) IBOutlet SearchTextField *searchTextField; + +@property(nonatomic) MWMSearchManagerState state; +@property(nonatomic) MWMSearchManagerRoutingTooltipSearch routingTooltipSearch; + +@property(nonnull, nonatomic) IBOutletCollection(UIView) NSArray *topViews; + +- (void)searchText:(nonnull NSString *)text forInputLocale:(nullable NSString *)locale withCategory:(BOOL)isCategory; + +#pragma mark - Layout + +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator:(nonnull id)coordinator; + +@end diff --git a/iphone/Maps/UI/Search/MWMSearchManager.mm b/iphone/Maps/UI/Search/MWMSearchManager.mm new file mode 100644 index 0000000000..87a06641ca --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchManager.mm @@ -0,0 +1,536 @@ +#import "MWMSearchManager.h" +#import "MWMFrameworkListener.h" +#import "MWMMapViewControlsManager.h" +#import "MWMNoMapsViewController.h" +#import "MWMRoutePoint+CPP.h" +#import "MWMRouter.h" +#import "MWMSearchManager+Layout.h" +#import "MWMSearchTableViewController.h" +#import "MapViewController.h" +#import "SwiftBridge.h" + +namespace { +typedef NS_ENUM(NSUInteger, MWMSearchManagerActionBarState) { + MWMSearchManagerActionBarStateHidden, + MWMSearchManagerActionBarStateTabBar, + MWMSearchManagerActionBarStateModeFilter +}; + +using Observer = id; +using Observers = NSHashTable; +} // namespace + +const CGFloat kWidthForiPad = 320; + +@interface MWMMapViewControlsManager () + +@property(nonatomic) MWMSearchManager *searchManager; + +@end + +@interface MWMSearchManager () + +@property(weak, nonatomic, readonly) UIViewController *ownerController; +@property(weak, nonatomic, readonly) UIView *searchViewContainer; +@property(weak, nonatomic, readonly) UIView *actionBarContainer; +@property(weak, nonatomic, readonly) MWMMapViewControlsManager *controlsManager; + +@property(nonatomic) IBOutlet SearchBar *searchBarView; +@property(weak, nonatomic) IBOutlet SearchActionBarView *actionBarView; +@property(nonatomic) IBOutlet UIView *contentView; +@property(strong, nonatomic) IBOutlet UIView *tableViewContainer; + +@property(nonatomic) NSLayoutConstraint *contentViewTopHidden; +@property(nonatomic) NSLayoutConstraint *contentViewBottomHidden; +@property(nonatomic) NSLayoutConstraint *actionBarViewBottomKeyboard; +@property(nonatomic) NSLayoutConstraint *actionBarViewBottomNormal; + +@property(nonatomic) UINavigationController *navigationController; +@property(nonatomic) MWMSearchTableViewController *tableViewController; +@property(nonatomic) MWMNoMapsViewController *noMapsController; + +@property(nonatomic) Observers *observers; + +@end + +@implementation MWMSearchManager + ++ (MWMSearchManager *)manager { + return [MWMMapViewControlsManager manager].searchManager; +} +- (nullable instancetype)init { + self = [super init]; + if (self) { + [NSBundle.mainBundle loadNibNamed:@"MWMSearchView" owner:self options:nil]; + self.state = MWMSearchManagerStateHidden; + [MWMSearch addObserver:self]; + _observers = [Observers weakObjectsHashTable]; + } + return self; +} + +- (void)beginSearch { + if (self.state != MWMSearchManagerStateHidden) + self.state = MWMSearchManagerStateTableSearch; +} + +- (void)endSearch { + if (self.state != MWMSearchManagerStateHidden) + self.state = MWMSearchManagerStateDefault; + self.searchTextField.text = @""; + [MWMSearch clear]; +} + +- (void)closeSearch { + [self.searchTextField endEditing:YES]; + [self endSearch]; +} + +#pragma mark - Actions + +- (IBAction)textFieldDidEndEditing:(UITextField *)textField { + if (textField.text.length == 0) + [self endSearch]; +} + +- (IBAction)textFieldTextDidChange:(UITextField *)textField { + NSString *text = textField.text; + if (text.length > 0) { + [self beginSearch]; + [MWMSearch searchQuery:text forInputLocale:textField.textInputMode.primaryLanguage withCategory:NO]; + } else { + [self endSearch]; + } +} + +- (IBAction)cancelButtonPressed { + self.state = MWMSearchManagerStateHidden; +} + +- (IBAction)backButtonPressed { + self.state = MWMSearchManagerStateTableSearch; +} + +#pragma mark - Layout + +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator:(id)coordinator { + [self.navigationController viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; +} + +#pragma mark - UITextFieldDelegate + +- (void)textFieldDidBeginEditing:(UITextField *)textField { + BOOL const isEmpty = (textField.text.length == 0); + self.state = isEmpty ? MWMSearchManagerStateDefault : MWMSearchManagerStateTableSearch; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + textField.text = [[textField.text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet] + stringByAppendingString:@" "]; + [self textFieldTextDidChange:textField]; + [textField resignFirstResponder]; + return YES; +} + +#pragma mark - MWMSearchTabbedViewProtocol + +- (void)searchText:(NSString *)text forInputLocale:(NSString *)locale withCategory:(BOOL)isCategory { + [self beginSearch]; + self.searchTextField.text = text; + NSString *inputLocale = locale ?: self.searchTextField.textInputMode.primaryLanguage; + [MWMSearch searchQuery:text forInputLocale:inputLocale withCategory:isCategory]; +} + +- (void)dismissKeyboard { + [self.searchTextField resignFirstResponder]; +} +- (void)processSearchWithResult:(search::Result const &)result { + if (self.routingTooltipSearch == MWMSearchManagerRoutingTooltipSearchNone) { + [MWMSearch showResult:result]; + } else { + BOOL const isStart = self.routingTooltipSearch == MWMSearchManagerRoutingTooltipSearchStart; + auto point = [[MWMRoutePoint alloc] initWithPoint:result.GetFeatureCenter() + title:@(result.GetString().c_str()) + subtitle:@(result.GetAddress().c_str()) + type:isStart ? MWMRoutePointTypeStart : MWMRoutePointTypeFinish + intermediateIndex:0]; + if (isStart) + [MWMRouter buildFromPoint:point bestRouter:NO]; + else + [MWMRouter buildToPoint:point bestRouter:NO]; + } + if (!IPAD || [MWMNavigationDashboardManager sharedManager].state != MWMNavigationDashboardStateHidden) + self.state = MWMSearchManagerStateResult; +} + +#pragma mark - MWMStorageObserver + +- (void)processCountryEvent:(NSString *)countryId { + using namespace storage; + NodeStatuses nodeStatuses{}; + GetFramework().GetStorage().GetNodeStatuses(countryId.UTF8String, nodeStatuses); + if (nodeStatuses.m_status != NodeStatus::OnDisk) + return; + [self updateTopController]; + if (self.state == MWMSearchManagerStateTableSearch || self.state == MWMSearchManagerStateMapSearch) { + NSString *text = self.searchTextField.text; + if (text.length != 0) + [MWMSearch searchQuery:text forInputLocale:self.searchTextField.textInputMode.primaryLanguage withCategory:NO]; + } +} + +#pragma mark - State changes + +- (void)updateTopController { + UIViewController *selfTopVC = self.topController; + if (!selfTopVC || [selfTopVC isEqual:self.navigationController.topViewController]) + return; + NSMutableArray *viewControllers = [self.navigationController.viewControllers mutableCopy]; + viewControllers[0] = selfTopVC; + [self.navigationController setViewControllers:viewControllers animated:NO]; +} + +- (void)changeToHiddenState { + self.routingTooltipSearch = MWMSearchManagerRoutingTooltipSearchNone; + [self closeSearch]; + + MWMMapViewControlsManager *controlsManager = self.controlsManager; + auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state; + if (navigationManagerState == MWMNavigationDashboardStateHidden) { + controlsManager.menuState = controlsManager.menuRestoreState; + } + [self viewHidden:YES]; +} + +- (void)changeToDefaultState { + MWMMapViewControlsManager *controlsManager = self.controlsManager; + + [self.navigationController popToRootViewControllerAnimated:NO]; + + self.searchBarView.state = SearchBarStateReady; + GetFramework().DeactivateMapSelection(); + [self animateConstraints:^{ + self.contentViewTopHidden.priority = UILayoutPriorityDefaultLow; + self.contentViewBottomHidden.priority = UILayoutPriorityDefaultLow; + }]; + auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state; + if (navigationManagerState == MWMNavigationDashboardStateHidden) { + controlsManager.menuState = controlsManager.menuRestoreState; + } + [self viewHidden:NO]; + self.actionBarState = MWMSearchManagerActionBarStateHidden; + [self.searchTextField becomeFirstResponder]; + [self.searchBarView applyTheme]; +} + +- (void)changeToTableSearchState { + MWMMapViewControlsManager *controlsManager = self.controlsManager; + + [self.navigationController popToRootViewControllerAnimated:NO]; + + self.searchBarView.state = SearchBarStateReady; + GetFramework().DeactivateMapSelection(); + [self updateTableSearchActionBar]; + auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state; + if (navigationManagerState == MWMNavigationDashboardStateHidden) { + controlsManager.menuState = controlsManager.menuRestoreState; + } + [self viewHidden:NO]; + [MWMSearch setSearchOnMap:NO]; + [self.tableViewController reloadData]; + + if (![self.navigationController.viewControllers containsObject:self.tableViewController]) + [self.navigationController pushViewController:self.tableViewController animated:NO]; +} + +- (void)changeToMapSearchState { + [self.navigationController popToRootViewControllerAnimated:NO]; + + self.searchBarView.state = SearchBarStateBack; + self.actionBarState = MWMSearchManagerActionBarStateModeFilter; + if (!IPAD) { + [self animateConstraints:^{ + self.contentViewTopHidden.priority = UILayoutPriorityDefaultHigh; + self.contentViewBottomHidden.priority = UILayoutPriorityDefaultHigh; + }]; + } + auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state; + [self viewHidden:navigationManagerState != MWMNavigationDashboardStateHidden]; + self.controlsManager.menuState = MWMBottomMenuStateHidden; + GetFramework().DeactivateMapSelection(); + [MWMSearch setSearchOnMap:YES]; + [self.tableViewController reloadData]; + + [self.searchTextField resignFirstResponder]; + + if (navigationManagerState == MWMNavigationDashboardStateNavigation) { + self.searchTextField.text = @""; + } +} + +- (void)changeToResultSearchState { + [self.navigationController popToRootViewControllerAnimated:NO]; + + self.searchBarView.state = SearchBarStateBack; + self.actionBarState = MWMSearchManagerActionBarStateModeFilter; + if (!IPAD) { + [self animateConstraints:^{ + self.contentViewTopHidden.priority = UILayoutPriorityDefaultHigh; + self.contentViewBottomHidden.priority = UILayoutPriorityDefaultHigh; + + }]; + } + auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state; + [self viewHidden:navigationManagerState != MWMNavigationDashboardStateHidden]; + [self.tableViewController reloadData]; + + [self.searchTextField resignFirstResponder]; + + if (navigationManagerState == MWMNavigationDashboardStateNavigation) { + self.searchTextField.text = @""; + } +} + +- (void)animateConstraints:(MWMVoidBlock)block { + UIView *parentView = self.searchViewContainer; + [parentView layoutIfNeeded]; + block(); + [UIView animateWithDuration:kDefaultAnimationDuration + animations:^{ + [parentView layoutIfNeeded]; + }]; +} + +#pragma mark - MWMSearchObserver + +- (void)onSearchCompleted { + if (self.state == MWMSearchManagerStateMapSearch || self.state == MWMSearchManagerStateResult) { + self.searchBarView.state = SearchBarStateBack; + } else { + self.searchBarView.state = SearchBarStateReady; + } + + if (self.state != MWMSearchManagerStateTableSearch) + return; + [self.tableViewController onSearchCompleted]; + [self updateTableSearchActionBar]; +} + +- (void)onSearchStarted { + self.searchBarView.state = SearchBarStateSearching; + if (self.state != MWMSearchManagerStateTableSearch) + return; + self.actionBarState = MWMSearchManagerActionBarStateModeFilter; +} + +- (void)onSearchResultsUpdated { + [self.tableViewController reloadData]; +} + +- (void)updateTableSearchActionBar { + if (self.state != MWMSearchManagerStateTableSearch) + return; + [self animateConstraints:^{ + BOOL hideActionBar = NO; + if ([MWMSearch resultsCount] == 0) + hideActionBar = YES; + else if (IPAD) + hideActionBar = YES; + self.actionBarState = + hideActionBar ? MWMSearchManagerActionBarStateHidden : MWMSearchManagerActionBarStateModeFilter; + + self.contentViewTopHidden.priority = UILayoutPriorityDefaultLow; + self.contentViewBottomHidden.priority = UILayoutPriorityDefaultLow; + }]; +} + +#pragma mark - Add/Remove Observers + ++ (void)addObserver:(id)observer { + [[MWMSearchManager manager].observers addObject:observer]; +} + ++ (void)removeObserver:(id)observer { + [[MWMSearchManager manager].observers removeObject:observer]; +} + +#pragma mark - MWMSearchManagerObserver + +- (void)onSearchManagerStateChanged { + for (Observer observer in self.observers) + [observer onSearchManagerStateChanged]; +} + +#pragma mark - Filters + +- (IBAction)changeMode { + switch (self.state) { + case MWMSearchManagerStateTableSearch: + self.state = MWMSearchManagerStateMapSearch; + break; + case MWMSearchManagerStateMapSearch: + self.state = MWMSearchManagerStateTableSearch; + break; + default: + break; + } +} + +#pragma mark - Properties + +- (UINavigationController *)navigationController { + if (!_navigationController) { + _navigationController = [[UINavigationController alloc] init]; + [self.contentView addSubview:_navigationController.view]; + _navigationController.navigationBarHidden = YES; + } + return _navigationController; +} + +- (UIViewController *)topController { + [[MWMStorage sharedStorage] removeObserver:self]; + self.noMapsController = nil; + switch (self.state) { + case MWMSearchManagerStateHidden: + return nil; + case MWMSearchManagerStateDefault: { + if (GetFramework().GetStorage().HaveDownloadedCountries()) { + MWMSearchTabViewController *tabViewController = [MWMSearchTabViewController new]; + tabViewController.delegate = self; + return tabViewController; + } + self.noMapsController = [MWMNoMapsViewController controller]; + [[MWMStorage sharedStorage] addObserver:self]; + return self.noMapsController; + } + case MWMSearchManagerStateTableSearch: + return self.tableViewController; + case MWMSearchManagerStateMapSearch: + return self.tableViewController; + case MWMSearchManagerStateResult: + return self.tableViewController; + } +} + +- (void)searchTabController:(MWMSearchTabViewController *)viewController + didSearch:(NSString *)didSearch + withCategory:(BOOL)isCategory +{ + [self searchText:didSearch forInputLocale:[[AppInfo sharedInfo] languageId] withCategory:isCategory]; +} + +- (MWMSearchTableViewController *)tableViewController { + if (!_tableViewController) + _tableViewController = [[MWMSearchTableViewController alloc] initWithDelegate:self]; + return _tableViewController; +} + +- (void)setState:(MWMSearchManagerState)state { + if (_state == state) + return; + + _state = state; + [self updateTopController]; + switch (state) { + case MWMSearchManagerStateHidden: + [self changeToHiddenState]; + break; + case MWMSearchManagerStateDefault: + [self changeToDefaultState]; + break; + case MWMSearchManagerStateTableSearch: + [self changeToTableSearchState]; + break; + case MWMSearchManagerStateMapSearch: + [self changeToMapSearchState]; + break; + case MWMSearchManagerStateResult: + [self changeToResultSearchState]; + break; + } + [self onSearchManagerStateChanged]; + [self.actionBarView updateForState:state]; + [[MapViewController sharedController] updateStatusBarStyle]; +} + +- (void)viewHidden:(BOOL)hidden { + UIView *searchBarView = self.searchBarView; + UIView *actionBarView = self.actionBarView; + UIView *contentView = self.contentView; + UIView *parentView = self.searchViewContainer; + + if (!hidden) { + if (searchBarView.superview) { + [parentView bringSubviewToFront:searchBarView]; + [parentView bringSubviewToFront:contentView]; + [parentView bringSubviewToFront:actionBarView]; + return; + } + [parentView addSubview:searchBarView]; + [parentView addSubview:contentView]; + [parentView addSubview:actionBarView]; + [self layoutTopViews]; + // Set Search controller default hidden state for iPad before it will be shown. + if (IPAD) { + self.searchViewContainerLeadingConstraint.constant = -kWidthForiPad; + [parentView.superview layoutIfNeeded]; + } + } + [UIView animateWithDuration:kDefaultAnimationDuration + animations:^{ + if (IPAD) { + self.searchViewContainerLeadingConstraint.constant = hidden ? -kWidthForiPad : 0; + [parentView.superview layoutIfNeeded]; + } else { + CGFloat const alpha = hidden ? 0 : 1; + contentView.alpha = alpha; + actionBarView.alpha = alpha; + searchBarView.alpha = alpha; + } + } + completion:^(BOOL finished) { + if (!hidden) + return; + [contentView removeFromSuperview]; + [actionBarView removeFromSuperview]; + [searchBarView removeFromSuperview]; + [self removeKeyboardObservers]; + }]; +} + +- (void)setActionBarState:(MWMSearchManagerActionBarState)actionBarState { + switch (actionBarState) { + case MWMSearchManagerActionBarStateHidden: + self.actionBarView.hidden = YES; + break; + case MWMSearchManagerActionBarStateTabBar: + self.actionBarView.hidden = YES; + break; + case MWMSearchManagerActionBarStateModeFilter: + self.actionBarView.hidden = NO; + break; + } +} + +- (UIViewController *)ownerController { + return [MapViewController sharedController]; +} +- (UIView *)searchViewContainer { + return [MapViewController sharedController].searchViewContainer; +} +- (NSLayoutConstraint *)searchViewContainerLeadingConstraint { + return [MapViewController sharedController].searchViewContainerLeadingConstraint; +} +- (UIView *)actionBarContainer { + return [MapViewController sharedController].controlsView; +} + +- (MWMMapViewControlsManager *)controlsManager { + return [MWMMapViewControlsManager manager]; +} +@end diff --git a/iphone/Maps/UI/Search/MWMSearchManagerObserver.h b/iphone/Maps/UI/Search/MWMSearchManagerObserver.h new file mode 100644 index 0000000000..e1ef8ab95c --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchManagerObserver.h @@ -0,0 +1,5 @@ +@protocol MWMSearchManagerObserver + +- (void)onSearchManagerStateChanged; + +@end diff --git a/iphone/Maps/UI/Search/MWMSearchManagerState.h b/iphone/Maps/UI/Search/MWMSearchManagerState.h new file mode 100644 index 0000000000..8ef6214263 --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchManagerState.h @@ -0,0 +1,8 @@ +typedef NS_ENUM(NSUInteger, MWMSearchManagerState) +{ + MWMSearchManagerStateHidden, + MWMSearchManagerStateDefault, + MWMSearchManagerStateTableSearch, + MWMSearchManagerStateMapSearch, + MWMSearchManagerStateResult +}; diff --git a/iphone/Maps/UI/Search/MWMSearchView.xib b/iphone/Maps/UI/Search/MWMSearchView.xib new file mode 100644 index 0000000000..336dffe9a4 --- /dev/null +++ b/iphone/Maps/UI/Search/MWMSearchView.xib @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Search/SearchActionBarView.swift b/iphone/Maps/UI/Search/SearchActionBarView.swift new file mode 100644 index 0000000000..184304f5ca --- /dev/null +++ b/iphone/Maps/UI/Search/SearchActionBarView.swift @@ -0,0 +1,56 @@ +@objc class SearchActionBarView: SolidTouchView { + @IBOutlet private var mapButton: MWMButton! + @IBOutlet private var listButton: MWMButton! + + @IBOutlet private var stackView: UIStackView! + + override func awakeFromNib() { + super.awakeFromNib() + + hideView(mapButton, isHidden: true, animated: false) + hideView(listButton, isHidden: true, animated: false) + } + + @objc func updateForState(_ state: MWMSearchManagerState) { + hideView(mapButton, isHidden: true, animated: false) + hideView(listButton, isHidden: true, animated: false) + + iPhoneSpecific { + switch state { + case .tableSearch: + hideView(mapButton, isHidden: false, animated: true) + case .mapSearch: + hideView(listButton, isHidden: false, animated: true) + case .default: + break + case .hidden: fallthrough + case .result: fallthrough + @unknown default: + break + } + } + } + + private func hideView(_ view: UIView, isHidden: Bool, animated: Bool) { + view.isHidden = isHidden + if animated { + UIView.animate(withDuration: kDefaultAnimationDuration / 2, + delay: 0, + options: [.beginFromCurrentState], + animations: { + self.layoutIfNeeded() + }, completion: { complete in + if complete { + UIView.animate(withDuration: kDefaultAnimationDuration / 2, + delay: 0, options: [.beginFromCurrentState], + animations: { + view.alpha = isHidden ? 0 : 1 + }, completion: nil) + } + }) + } else { + view.alpha = isHidden ? 0 : 1 + view.isHidden = isHidden + } + } +} diff --git a/iphone/Maps/UI/Search/SearchBar.swift b/iphone/Maps/UI/Search/SearchBar.swift new file mode 100644 index 0000000000..7bc4033471 --- /dev/null +++ b/iphone/Maps/UI/Search/SearchBar.swift @@ -0,0 +1,57 @@ +@objc enum SearchBarState: Int { + case ready + case searching + case back +} + +final class SearchBar: SolidTouchView { + @IBOutlet private var searchIcon: UIImageView! + @IBOutlet private var activityIndicator: UIActivityIndicatorView! + @IBOutlet private var backButton: UIButton! + @IBOutlet private var searchTextField: SearchTextField! + @IBOutlet private var stackView: UIStackView! + + override var visibleAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: .top, iPad: .left) } + + override var placePageAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: [], iPad: .left) } + + override var widgetsAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: [], iPad: .left) } + + override var trafficButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: .top, iPad: .left) } + + override var tabBarAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: [], iPad: .left) } + + override var trackRecordingButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: .top, iPad: .left) } + + @objc var state: SearchBarState = .ready { + didSet { + if state != oldValue { + updateLeftView() + } + } + } + + override func awakeFromNib() { + super.awakeFromNib() + updateLeftView() + searchTextField.leftViewMode = UITextField.ViewMode.always + searchTextField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 32, height: 32)) + searchTextField.applyTheme() + } + + private func updateLeftView() { + searchIcon.isHidden = true + activityIndicator.isHidden = true + backButton.isHidden = true + + switch state { + case .ready: + searchIcon.isHidden = false + case .searching: + activityIndicator.isHidden = false + activityIndicator.startAnimating() + case .back: + backButton.isHidden = false + } + } +} diff --git a/iphone/Maps/UI/Search/SearchOnMap/PlaceholderView.swift b/iphone/Maps/UI/Search/SearchOnMap/PlaceholderView.swift deleted file mode 100644 index 9bf57b97c3..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/PlaceholderView.swift +++ /dev/null @@ -1,122 +0,0 @@ -final class PlaceholderView: UIView { - - private let activityIndicator: UIActivityIndicatorView? - private let titleLabel = UILabel() - private let subtitleLabel = UILabel() - private let stackView = UIStackView() - private var keyboardHeight: CGFloat = 0 - private var centerYConstraint: NSLayoutConstraint! - private var containerModalYTranslation: CGFloat = 0 - private let minOffsetFromTheKeyboardTop: CGFloat = 20 - private let maxOffsetFromTheTop: CGFloat = 100 - - init(title: String? = nil, subtitle: String? = nil, hasActivityIndicator: Bool = false) { - self.activityIndicator = hasActivityIndicator ? UIActivityIndicatorView() : nil - super.init(frame: .zero) - setupView(title: title, subtitle: subtitle) - layoutView() - setupKeyboardObservers() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - private func setupKeyboardObservers() { - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillShow(_:)), - name: UIResponder.keyboardWillShowNotification, - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillHide(_:)), - name: UIResponder.keyboardWillHideNotification, - object: nil) - } - - @objc private func keyboardWillShow(_ notification: Notification) { - if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { - keyboardHeight = keyboardFrame.height - reloadConstraints() - } - } - - @objc private func keyboardWillHide(_ notification: Notification) { - keyboardHeight = 0 - reloadConstraints() - } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass { - reloadConstraints() - } - } - - private func setupView(title: String?, subtitle: String?) { - if let activityIndicator = activityIndicator { - activityIndicator.hidesWhenStopped = true - activityIndicator.startAnimating() - if #available(iOS 13.0, *) { - activityIndicator.style = .medium - } else { - activityIndicator.style = .gray - } - } - - titleLabel.text = title - titleLabel.setFontStyle(.medium16, color: .blackPrimary) - titleLabel.textAlignment = .center - - subtitleLabel.text = subtitle - subtitleLabel.setFontStyle(.regular14, color: .blackSecondary) - subtitleLabel.textAlignment = .center - subtitleLabel.isHidden = subtitle == nil - subtitleLabel.numberOfLines = 2 - - stackView.axis = .vertical - stackView.alignment = .center - stackView.spacing = 8 - } - - private func layoutView() { - if let activityIndicator = activityIndicator { - stackView.addArrangedSubview(activityIndicator) - } - if let title = titleLabel.text, !title.isEmpty { - stackView.addArrangedSubview(titleLabel) - } - if let subtitle = subtitleLabel.text, !subtitle.isEmpty { - stackView.addArrangedSubview(subtitleLabel) - } - - addSubview(stackView) - stackView.translatesAutoresizingMaskIntoConstraints = false - - centerYConstraint = stackView.centerYAnchor.constraint(equalTo: centerYAnchor) - NSLayoutConstraint.activate([ - stackView.centerXAnchor.constraint(equalTo: centerXAnchor), - stackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.8), - centerYConstraint - ]) - } - - private func reloadConstraints() { - let offset = keyboardHeight > 0 ? max(bounds.height / 2 - keyboardHeight, minOffsetFromTheKeyboardTop + stackView.frame.height) : containerModalYTranslation / 2 - let maxOffset = bounds.height / 2 - maxOffsetFromTheTop - centerYConstraint.constant = -min(offset, maxOffset) - layoutIfNeeded() - } -} - -// MARK: - ModallyPresentedViewController -extension PlaceholderView: ModallyPresentedViewController { - func translationYDidUpdate(_ translationY: CGFloat) { - self.containerModalYTranslation = translationY - reloadConstraints() - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/Presentation/MapPassthroughView.swift b/iphone/Maps/UI/Search/SearchOnMap/Presentation/MapPassthroughView.swift deleted file mode 100644 index 7d60beafb6..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/Presentation/MapPassthroughView.swift +++ /dev/null @@ -1,30 +0,0 @@ -/// A transparent view that allows touch events to pass through to the MapViewController's view. -/// -/// This view is used to enable interaction with the underlying map while still maintaining a -/// transparent overlay. It does not block touch events but forwards them to the specified `passingView`. - -final class MapPassthroughView: UIView { - private weak var passingView: UIView? - - init(passingView: UIView) { - self.passingView = passingView - super.init(frame: passingView.bounds) - self.autoresizingMask = [.flexibleWidth, .flexibleHeight] - self.alpha = 0 - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - guard let passingView else { return nil } - let pointInPassthroughView = passingView.convert(point, from: self) - super.hitTest(point, with: event) - if passingView.bounds.contains(pointInPassthroughView) { - return MapViewController.shared()?.view.hitTest(point, with: event) - } - return nil - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/Presentation/ModalScreenPresentationStep.swift b/iphone/Maps/UI/Search/SearchOnMap/Presentation/ModalScreenPresentationStep.swift deleted file mode 100644 index e6196ec034..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/Presentation/ModalScreenPresentationStep.swift +++ /dev/null @@ -1,94 +0,0 @@ -enum ModalScreenPresentationStep { - case fullScreen - case halfScreen - case compact - case hidden -} - -extension ModalScreenPresentationStep { - private enum Constants { - static let iPadWidth: CGFloat = 350 - static let compactHeightOffset: CGFloat = 120 - static let fullScreenHeightFactorPortrait: CGFloat = 0.1 - static let halfScreenHeightFactorPortrait: CGFloat = 0.55 - static let landscapeTopInset: CGFloat = 10 - } - - var upper: ModalScreenPresentationStep { - switch self { - case .fullScreen: - return .fullScreen - case .halfScreen: - return .fullScreen - case .compact: - return .halfScreen - case .hidden: - return .compact - } - } - - var lower: ModalScreenPresentationStep { - switch self { - case .fullScreen: - return .halfScreen - case .halfScreen: - return .compact - case .compact: - return .compact - case .hidden: - return .hidden - } - } - - var first: ModalScreenPresentationStep { - .fullScreen - } - - var last: ModalScreenPresentationStep { - .compact - } - - func frame(for viewController: UIViewController, in containerView: UIView) -> CGRect { - let isIPad = UIDevice.current.userInterfaceIdiom == .pad - let containerSize = containerView.bounds.size - let safeAreaInsets = containerView.safeAreaInsets - var frame = CGRect(origin: .zero, size: containerSize) - - if isIPad { - frame.size.width = Constants.iPadWidth - switch self { - case .hidden: - frame.origin.x = -Constants.iPadWidth - default: - frame.origin.x = .zero - } - return frame - } - - let isPortraitOrientation = viewController.traitCollection.verticalSizeClass == .regular - if isPortraitOrientation { - switch self { - case .fullScreen: - frame.origin.y = containerSize.height * Constants.fullScreenHeightFactorPortrait - case .halfScreen: - frame.origin.y = containerSize.height * Constants.halfScreenHeightFactorPortrait - case .compact: - frame.origin.y = containerSize.height - Constants.compactHeightOffset - case .hidden: - frame.origin.y = containerSize.height - } - } else { - frame.size.width = Constants.iPadWidth - frame.origin.x = safeAreaInsets.left - switch self { - case .fullScreen: - frame.origin.y = Constants.landscapeTopInset - case .halfScreen, .compact: - frame.origin.y = containerSize.height - Constants.compactHeightOffset - case .hidden: - frame.origin.y = containerSize.height - } - } - return frame - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SearchOnMapModalPresentationController.swift b/iphone/Maps/UI/Search/SearchOnMap/Presentation/SearchOnMapModalPresentationController.swift deleted file mode 100644 index c459f8bca3..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SearchOnMapModalPresentationController.swift +++ /dev/null @@ -1,226 +0,0 @@ -protocol ModallyPresentedViewController { - func translationYDidUpdate(_ translationY: CGFloat) -} - -protocol SearchOnMapModalPresentationView: AnyObject { - func setPresentationStep(_ step: ModalScreenPresentationStep) - func close() -} - -final class SearchOnMapModalPresentationController: UIPresentationController { - - private enum StepChangeAnimation { - case slide - case slideAndBounce - } - - private enum Constants { - static let animationDuration: TimeInterval = kDefaultAnimationDuration - static let springDamping: CGFloat = 0.85 - static let springVelocity: CGFloat = 0.2 - static let iPhoneCornerRadius: CGFloat = 10 - static let slowSwipeVelocity: CGFloat = 500 - static let fastSwipeDownVelocity: CGFloat = 4000 - static let fastSwipeUpVelocity: CGFloat = 3000 - static let translationThreshold: CGFloat = 50 - static let panGestureThreshold: CGFloat = 5 - } - - private var initialTranslationY: CGFloat = 0 - private weak var interactor: SearchOnMapInteractor? { - (presentedViewController as? SearchOnMapViewController)?.interactor - } - // TODO: replace with set of steps passed from the outside - private var presentationStep: ModalScreenPresentationStep = .fullScreen - private var internalScrollViewContentOffset: CGFloat = 0 - private var maxAvailableFrameOfPresentedView: CGRect = .zero - - // MARK: - Init - override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) { - super.init(presentedViewController: presentedViewController, presenting: presentingViewController) - - iPhoneSpecific { - let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) - panGestureRecognizer.delegate = self - presentedViewController.view.addGestureRecognizer(panGestureRecognizer) - if let presentedViewController = presentedViewController as? SearchOnMapView { - presentedViewController.scrollViewDelegate = self - } - } - } - - // MARK: - Lifecycle - override func containerViewWillLayoutSubviews() { - super.containerViewWillLayoutSubviews() - presentedView?.frame = frameOfPresentedViewInContainerView - } - - override func presentationTransitionWillBegin() { - guard let containerView else { return } - containerView.backgroundColor = .clear - let passThroughView = MapPassthroughView(passingView: containerView) - containerView.addSubview(passThroughView) - } - - override func presentationTransitionDidEnd(_ completed: Bool) { - translationYDidUpdate(presentedView?.frame.origin.y ?? 0) - } - - override func dismissalTransitionDidEnd(_ completed: Bool) { - super.dismissalTransitionDidEnd(completed) - if completed { - presentedView?.removeFromSuperview() - } - } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - updateMaxAvailableFrameOfPresentedView() - } - - // MARK: - Layout - override var frameOfPresentedViewInContainerView: CGRect { - guard let containerView else { return .zero } - let frame = presentationStep.frame(for: presentedViewController, in: containerView) - updateMaxAvailableFrameOfPresentedView() - return frame - } - - private func updateMaxAvailableFrameOfPresentedView() { - guard let containerView else { return } - maxAvailableFrameOfPresentedView = ModalScreenPresentationStep.fullScreen.frame(for: presentedViewController, in: containerView) - } - - private func updateSideButtonsAvailableArea(_ newY: CGFloat) { - iPhoneSpecific { - guard presentedViewController.traitCollection.verticalSizeClass != .compact else { return } - var sideButtonsAvailableArea = MWMSideButtons.getAvailableArea() - sideButtonsAvailableArea.size.height = newY - sideButtonsAvailableArea.origin.y - MWMSideButtons.updateAvailableArea(sideButtonsAvailableArea) - } - } - - // MARK: - Pan gesture handling - @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { - guard let presentedView, maxAvailableFrameOfPresentedView != .zero else { return } - interactor?.handle(.didStartDraggingSearch) - - let translation = gesture.translation(in: presentedView) - let velocity = gesture.velocity(in: presentedView) - - switch gesture.state { - case .began: - initialTranslationY = presentedView.frame.origin.y - case .changed: - let newY = max(max(initialTranslationY + translation.y, 0), maxAvailableFrameOfPresentedView.origin.y) - presentedView.frame.origin.y = newY - updateSideButtonsAvailableArea(newY) - translationYDidUpdate(newY) - case .ended: - let nextStep: ModalScreenPresentationStep - if velocity.y > Constants.fastSwipeDownVelocity { - interactor?.handle(.closeSearch) - return - } else if velocity.y < -Constants.fastSwipeUpVelocity { - nextStep = .fullScreen // fast swipe up - } else if velocity.y > Constants.slowSwipeVelocity || translation.y > Constants.translationThreshold { - if presentationStep == .compact { - interactor?.handle(.closeSearch) - return - } - nextStep = presentationStep.lower // regular swipe down - } else if velocity.y < -Constants.slowSwipeVelocity || translation.y < -Constants.translationThreshold { - nextStep = presentationStep.upper // regular swipe up - } else { - // TODO: swipe to closest step on the big translation - nextStep = presentationStep - } - let animation: StepChangeAnimation = abs(velocity.y) > Constants.slowSwipeVelocity ? .slideAndBounce : .slide - animateTo(nextStep, animation: animation) - default: - break - } - } - - private func animateTo(_ presentationStep: ModalScreenPresentationStep, animation: StepChangeAnimation = .slide) { - guard let presentedView, let containerView else { return } - self.presentationStep = presentationStep - interactor?.handle(.didUpdatePresentationStep(presentationStep)) - - let updatedFrame = presentationStep.frame(for: presentedViewController, in: containerView) - let targetYTranslation = updatedFrame.origin.y - - switch animation { - case .slide: - UIView.animate(withDuration: Constants.animationDuration, - delay: 0, - options: .curveEaseOut, - animations: { [weak self] in - presentedView.frame = updatedFrame - self?.translationYDidUpdate(targetYTranslation) - self?.updateSideButtonsAvailableArea(targetYTranslation) - }) - case .slideAndBounce: - UIView.animate(withDuration: Constants.animationDuration, - delay: 0, - usingSpringWithDamping: Constants.springDamping, - initialSpringVelocity: Constants.springVelocity, - options: .curveLinear, - animations: { [weak self] in - presentedView.frame = updatedFrame - self?.translationYDidUpdate(targetYTranslation) - self?.updateSideButtonsAvailableArea(targetYTranslation) - }) - } - } -} - -// MARK: - SearchOnMapModalPresentationView -extension SearchOnMapModalPresentationController: SearchOnMapModalPresentationView { - func setPresentationStep(_ step: ModalScreenPresentationStep) { - guard presentationStep != step else { return } - animateTo(step) - } - - func close() { - guard let containerView else { return } - updateSideButtonsAvailableArea(containerView.frame.height) - presentedViewController.dismiss(animated: true) - } -} - -// MARK: - ModallyPresentedViewController -extension SearchOnMapModalPresentationController: ModallyPresentedViewController { - func translationYDidUpdate(_ translationY: CGFloat) { - iPhoneSpecific { - (presentedViewController as? SearchOnMapViewController)?.translationYDidUpdate(translationY) - } - } -} - -// MARK: - UIGestureRecognizerDelegate -extension SearchOnMapModalPresentationController: UIGestureRecognizerDelegate { - func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - true - } - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - // threshold is used to soften transition from the internal scroll zero content offset - internalScrollViewContentOffset < Constants.panGestureThreshold - } -} - -// MARK: - SearchOnMapScrollViewDelegate -extension SearchOnMapModalPresentationController: SearchOnMapScrollViewDelegate { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - guard let presentedView else { return } - let hasReachedTheTop = Int(presentedView.frame.origin.y) > Int(maxAvailableFrameOfPresentedView.origin.y) - let hasZeroContentOffset = internalScrollViewContentOffset == 0 - if hasReachedTheTop && hasZeroContentOffset { - // prevent the internal scroll view scrolling - scrollView.contentOffset.y = internalScrollViewContentOffset - return - } - internalScrollViewContentOffset = scrollView.contentOffset.y - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SearchOnMapModalTransitionManager.swift b/iphone/Maps/UI/Search/SearchOnMap/Presentation/SearchOnMapModalTransitionManager.swift deleted file mode 100644 index 335b7d4d76..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SearchOnMapModalTransitionManager.swift +++ /dev/null @@ -1,21 +0,0 @@ -@objc -final class SearchOnMapModalTransitionManager: NSObject, UIViewControllerTransitioningDelegate { - - weak var presentationController: SearchOnMapModalPresentationView? - - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? { - isIPad ? SideMenuPresentationAnimator() : nil - } - - func animationController(forDismissed dismissed: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? { - isIPad ? SideMenuDismissalAnimator() : nil - } - - func presentationController(forPresented presented: UIViewController, - presenting: UIViewController?, - source: UIViewController) -> UIPresentationController? { - let presentationController = SearchOnMapModalPresentationController(presentedViewController: presented, presenting: presenting) - self.presentationController = presentationController - return presentationController - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SideMenuDismissalAnimator.swift b/iphone/Maps/UI/Search/SearchOnMap/Presentation/SideMenuDismissalAnimator.swift deleted file mode 100644 index 35515734d7..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SideMenuDismissalAnimator.swift +++ /dev/null @@ -1,22 +0,0 @@ -final class SideMenuDismissalAnimator: NSObject, UIViewControllerAnimatedTransitioning { - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return kDefaultAnimationDuration / 2 - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - guard let fromVC = transitionContext.viewController(forKey: .from) else { return } - let initialFrame = transitionContext.initialFrame(for: fromVC) - let targetFrame = initialFrame.offsetBy(dx: -initialFrame.width, dy: 0) - - UIView.animate(withDuration: transitionDuration(using: transitionContext), - delay: .zero, - options: .curveEaseIn, - animations: { - fromVC.view.frame = targetFrame - }, - completion: { - fromVC.view.removeFromSuperview() - transitionContext.completeTransition($0) - }) - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SideMenuPresentationAnimator.swift b/iphone/Maps/UI/Search/SearchOnMap/Presentation/SideMenuPresentationAnimator.swift deleted file mode 100644 index 0bc83210f7..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/Presentation/SideMenuPresentationAnimator.swift +++ /dev/null @@ -1,25 +0,0 @@ -final class SideMenuPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return kDefaultAnimationDuration / 2 - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - guard let toVC = transitionContext.viewController(forKey: .to) else { return } - let containerView = transitionContext.containerView - let finalFrame = transitionContext.finalFrame(for: toVC) - let originFrame = finalFrame.offsetBy(dx: -finalFrame.width, dy: 0) - containerView.addSubview(toVC.view) - toVC.view.frame = originFrame - toVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - - UIView.animate(withDuration: transitionDuration(using: transitionContext), - delay: .zero, - options: .curveEaseOut, - animations: { - toVC.view.frame = finalFrame - }, - completion: { - transitionContext.completeTransition($0) - }) - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapHeaderView.swift b/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapHeaderView.swift deleted file mode 100644 index 1dd937c832..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapHeaderView.swift +++ /dev/null @@ -1,113 +0,0 @@ -protocol SearchOnMapHeaderViewDelegate: UISearchBarDelegate { - func cancelButtonDidTap() -} - -final class SearchOnMapHeaderView: UIView { - weak var delegate: SearchOnMapHeaderViewDelegate? { - didSet { - searchBar.delegate = delegate - } - } - - private enum Constants { - static let grabberHeight: CGFloat = 5 - static let grabberWidth: CGFloat = 36 - static let grabberTopMargin: CGFloat = 5 - static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 8) - } - - private let grabberView = UIView() - private let searchBar = UISearchBar() - private let cancelButton = UIButton() - - override init(frame: CGRect) { - super.init(frame: frame) - setupView() - layoutView() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupView() { - setStyle(.searchHeader) - layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - - setupGrabberView() - setupSearchBar() - setupCancelButton() - } - - private func setupGrabberView() { - grabberView.setStyle(.background) - grabberView.layer.setCorner(radius: Constants.grabberHeight / 2) - iPadSpecific { [weak self] in - self?.grabberView.isHidden = true - } - } - - private func setupSearchBar() { - searchBar.placeholder = L("search") - searchBar.showsCancelButton = false - if #available(iOS 13.0, *) { - searchBar.searchTextField.clearButtonMode = .always - searchBar.returnKeyType = .search - searchBar.searchTextField.enablesReturnKeyAutomatically = true - } - } - - private func setupCancelButton() { - cancelButton.tintColor = .whitePrimaryText() - cancelButton.setStyle(.clearBackground) - cancelButton.setTitle(L("cancel"), for: .normal) - cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) - } - - private func layoutView() { - addSubview(grabberView) - addSubview(searchBar) - addSubview(cancelButton) - - grabberView.translatesAutoresizingMaskIntoConstraints = false - searchBar.translatesAutoresizingMaskIntoConstraints = false - cancelButton.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - grabberView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.grabberTopMargin), - grabberView.centerXAnchor.constraint(equalTo: centerXAnchor), - grabberView.widthAnchor.constraint(equalToConstant: Constants.grabberWidth), - grabberView.heightAnchor.constraint(equalToConstant: Constants.grabberHeight), - - searchBar.topAnchor.constraint(equalTo: grabberView.bottomAnchor), - searchBar.leadingAnchor.constraint(equalTo: leadingAnchor), - searchBar.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: -Constants.cancelButtonInsets.left), - - cancelButton.centerYAnchor.constraint(equalTo: searchBar.centerYAnchor), - cancelButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.cancelButtonInsets.right), - - bottomAnchor.constraint(equalTo: searchBar.bottomAnchor) - ]) - } - - @objc private func cancelButtonTapped() { - delegate?.cancelButtonDidTap() - } - - func setSearchText(_ text: String) { - searchBar.text = text - } - - func setIsSearching(_ isSearching: Bool) { - if isSearching { - searchBar.becomeFirstResponder() - } else if searchBar.isFirstResponder { - searchBar.resignFirstResponder() - } - } - - var searchText: SearchOnMap.SearchText { - SearchOnMap.SearchText(searchBar.text ?? "", locale: searchBar.textInputMode?.primaryLanguage) - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapInteractor.swift b/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapInteractor.swift deleted file mode 100644 index d56e7f9995..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapInteractor.swift +++ /dev/null @@ -1,165 +0,0 @@ -final class SearchOnMapInteractor: NSObject { - - private let presenter: SearchOnMapPresenter - private let searchManager: SearchManager.Type - private let routeManager: MWMRouter.Type - private var isUpdatesDisabled = false - private var showResultsOnMap: Bool = false - - var routingTooltipSearch: SearchOnMapRoutingTooltipSearch = .none - - init(presenter: SearchOnMapPresenter, - searchManager: SearchManager.Type = Search.self, - routeManager: MWMRouter.Type = MWMRouter.self) { - self.presenter = presenter - self.searchManager = searchManager - self.routeManager = routeManager - super.init() - searchManager.add(self) - } - - deinit { - searchManager.remove(self) - } - - func handle(_ event: SearchOnMap.Request) { - let response = resolve(event) - presenter.process(response) - } - - private func resolve(_ event: SearchOnMap.Request) -> SearchOnMap.Response { - switch event { - case .openSearch: - return .showHistoryAndCategory - case .hideSearch: - return .setSearchScreenHidden(true) - case .didStartDraggingSearch: - return .setIsTyping(false) - case .didStartTyping: - return .setIsTyping(true) - case .didType(let searchText): - return processTypedText(searchText) - case .clearButtonDidTap: - return processClearButtonDidTap() - case .didSelectText(let searchText, let isCategory): - return processSelectedText(searchText, isCategory: isCategory) - case .searchButtonDidTap(let searchText): - return processSearchButtonDidTap(searchText) - case .didSelectResult(let result, let searchText): - return processSelectedResult(result, searchText: searchText) - case .didSelectPlaceOnMap: - return isIPad ? .none : .setSearchScreenHidden(true) - case .didDeselectPlaceOnMap: - return deselectPlaceOnMap() - case .didStartDraggingMap: - return .setSearchScreenCompact - case .didUpdatePresentationStep(let step): - return .updatePresentationStep(step) - case .closeSearch: - return closeSearch() - } - } - - private func processClearButtonDidTap() -> SearchOnMap.Response { - isUpdatesDisabled = true - searchManager.clear() - return .clearSearch - } - - private func processSearchButtonDidTap(_ searchText: SearchOnMap.SearchText) -> SearchOnMap.Response { - searchManager.saveQuery(searchText.text, - forInputLocale: searchText.locale) - showResultsOnMap = true - return .showOnTheMap - } - - private func processTypedText(_ searchText: SearchOnMap.SearchText) -> SearchOnMap.Response { - isUpdatesDisabled = false - showResultsOnMap = true - searchManager.searchQuery(searchText.text, - forInputLocale: searchText.locale, - withCategory: false) - return .startSearching - } - - private func processSelectedText(_ searchText: SearchOnMap.SearchText, isCategory: Bool) -> SearchOnMap.Response { - isUpdatesDisabled = false - searchManager.saveQuery(searchText.text, - forInputLocale: searchText.locale) - searchManager.searchQuery(searchText.text, - forInputLocale: searchText.locale, - withCategory: isCategory) - showResultsOnMap = true - return .selectText(searchText.text) - } - - private func processSelectedResult(_ result: SearchResult, searchText: SearchOnMap.SearchText) -> SearchOnMap.Response { - switch result.itemType { - case .regular: - searchManager.saveQuery(searchText.text, - forInputLocale:searchText.locale) - switch routingTooltipSearch { - case .none: - searchManager.showResult(at: result.index) - case .start: - let point = MWMRoutePoint(cgPoint: result.point, - title: result.titleText, - subtitle: result.addressText, - type: .start, - intermediateIndex: 0) - routeManager.build(from: point, bestRouter: false) - case .finish: - let point = MWMRoutePoint(cgPoint: result.point, - title: result.titleText, - subtitle: result.addressText, - type: .finish, - intermediateIndex: 0) - routeManager.build(to: point, bestRouter: false) - @unknown default: - fatalError("Unsupported routingTooltipSearch") - } - return isIPad ? .none : .setSearchScreenHidden(true) - case .suggestion: - searchManager.searchQuery(result.suggestion, - forInputLocale: searchText.locale, - withCategory: result.isPureSuggest) - return .selectText(result.suggestion) - @unknown default: - fatalError("Unsupported result type") - } - } - - private func deselectPlaceOnMap() -> SearchOnMap.Response { - routingTooltipSearch = .none - searchManager.showViewportSearchResultsOnMap() - return .setSearchScreenHidden(false) - } - - private func closeSearch() -> SearchOnMap.Response { - routingTooltipSearch = .none - isUpdatesDisabled = true - showResultsOnMap = false - searchManager.clear() - return .close - } -} - -// MARK: - MWMSearchObserver -extension SearchOnMapInteractor: MWMSearchObserver { - func onSearchCompleted() { - guard !isUpdatesDisabled else { return } - let results = searchManager.getResults() - if showResultsOnMap && !results.isEmpty { - searchManager.showEverywhereSearchResultsOnMap() - showResultsOnMap = false - } - presenter.process(.showResults(SearchOnMap.SearchResults(results), isSearchCompleted: true)) - } - - func onSearchResultsUpdated() { - guard !isUpdatesDisabled else { return } - let results = searchManager.getResults() - guard !results.isEmpty else { return } - presenter.process(.showResults(SearchOnMap.SearchResults(results), isSearchCompleted: false)) - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapManager.swift b/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapManager.swift deleted file mode 100644 index 982e7555c3..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapManager.swift +++ /dev/null @@ -1,96 +0,0 @@ -@objc -enum SearchOnMapState: Int { - case searching - case hidden - case closed -} - -@objc -enum SearchOnMapRoutingTooltipSearch: Int { - case none - case start - case finish -} - -@objc -protocol SearchOnMapManagerObserver: AnyObject { - func searchManager(didChangeState state: SearchOnMapState) -} - -@objcMembers -final class SearchOnMapManager: NSObject { - private let navigationController: UINavigationController - private weak var interactor: SearchOnMapInteractor? - private let observers = ListenerContainer() - - // MARK: - Public properties - weak var viewController: UIViewController? - var isSearching: Bool { viewController != nil } - - init(navigationController: UINavigationController = MapViewController.shared()!.navigationController!) { - self.navigationController = navigationController - } - - // MARK: - Public methods - func startSearching(isRouting: Bool) { - if viewController != nil { - interactor?.handle(.openSearch) - return - } - FrameworkHelper.deactivateMapSelection() - let viewController = buildViewController(isRouting: isRouting) - self.viewController = viewController - self.interactor = viewController.interactor - navigationController.present(viewController, animated: true) - } - - func hide() { - interactor?.handle(.hideSearch) - } - - func close() { - interactor?.handle(.closeSearch) - } - - func setRoutingTooltip(_ tooltip: SearchOnMapRoutingTooltipSearch) { - interactor?.routingTooltipSearch = tooltip - } - - func setPlaceOnMapSelected(_ isSelected: Bool) { - interactor?.handle(isSelected ? .didSelectPlaceOnMap : .didDeselectPlaceOnMap) - } - - func setMapIsDragging() { - interactor?.handle(.didStartDraggingMap) - } - - func searchText(_ text: String, locale: String, isCategory: Bool) { - let searchText = SearchOnMap.SearchText(text, locale: locale) - interactor?.handle(.didSelectText(searchText, isCategory: isCategory)) - } - - func addObserver(_ observer: SearchOnMapManagerObserver) { - observers.addListener(observer) - } - - func removeObserver(_ observer: SearchOnMapManagerObserver) { - observers.removeListener(observer) - } - - // MARK: - Private methods - private func buildViewController(isRouting: Bool) -> SearchOnMapViewController { - let transitioningManager = SearchOnMapModalTransitionManager() - let presenter = SearchOnMapPresenter(transitionManager: transitioningManager, - isRouting: isRouting, - didChangeState: { [weak self] state in - guard let self else { return } - self.observers.forEach { observer in observer.searchManager(didChangeState: state) } - }) - let interactor = SearchOnMapInteractor(presenter: presenter) - let viewController = SearchOnMapViewController(interactor: interactor) - presenter.view = viewController - viewController.modalPresentationStyle = .custom - viewController.transitioningDelegate = transitioningManager - return viewController - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapModels.swift b/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapModels.swift deleted file mode 100644 index 8147e534f3..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapModels.swift +++ /dev/null @@ -1,94 +0,0 @@ -enum SearchOnMap { - struct ViewModel: Equatable { - enum ContentState: Equatable { - case historyAndCategory - case results(SearchResults) - case noResults - case searching - } - - var isTyping: Bool - var skipSuggestions: Bool - var searchingText: String? - var contentState: ContentState - var presentationStep: ModalScreenPresentationStep - } - - struct SearchResults: Equatable { - let results: [SearchResult] - let hasPartialMatch: Bool - let isEmpty: Bool - let count: Int - let suggestionsCount: Int - - init(_ results: [SearchResult]) { - self.results = results - self.hasPartialMatch = !results.allSatisfy { $0.highlightRanges.isEmpty } - self.isEmpty = results.isEmpty - self.count = results.count - self.suggestionsCount = results.filter { $0.itemType == .suggestion }.count - } - } - - struct SearchText { - let text: String - let locale: String - - init(_ text: String, locale: String? = nil) { - self.text = text - self.locale = locale ?? AppInfo.shared().languageId - } - } - - enum Request { - case openSearch - case hideSearch - case closeSearch - case didStartDraggingSearch - case didStartDraggingMap - case didStartTyping - case didType(SearchText) - case didSelectText(SearchText, isCategory: Bool) - case didSelectResult(SearchResult, withSearchText: SearchText) - case searchButtonDidTap(SearchText) - case clearButtonDidTap - case didSelectPlaceOnMap - case didDeselectPlaceOnMap - case didUpdatePresentationStep(ModalScreenPresentationStep) - } - - enum Response: Equatable { - case startSearching - case showOnTheMap - case setIsTyping(Bool) - case showHistoryAndCategory - case showResults(SearchResults, isSearchCompleted: Bool = false) - case selectText(String?) - case clearSearch - case setSearchScreenHidden(Bool) - case setSearchScreenCompact - case updatePresentationStep(ModalScreenPresentationStep) - case close - case none - } -} - -extension SearchOnMap.SearchResults { - static let empty = SearchOnMap.SearchResults([]) - - subscript(index: Int) -> SearchResult { - results[index] - } - - mutating func skipSuggestions() { - self = SearchOnMap.SearchResults(results.filter { $0.itemType != .suggestion }) - } -} - -extension SearchOnMap.ViewModel { - static let initial = SearchOnMap.ViewModel(isTyping: false, - skipSuggestions: false, - searchingText: nil, - contentState: .historyAndCategory, - presentationStep: .fullScreen) -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapPresenter.swift b/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapPresenter.swift deleted file mode 100644 index aa422b027d..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapPresenter.swift +++ /dev/null @@ -1,117 +0,0 @@ -final class SearchOnMapPresenter { - typealias Response = SearchOnMap.Response - typealias ViewModel = SearchOnMap.ViewModel - - weak var view: SearchOnMapView? - weak var presentationView: SearchOnMapModalPresentationView? { transitionManager.presentationController } - - private var searchState: SearchOnMapState = .searching { - didSet { - guard searchState != oldValue else { return } - didChangeState?(searchState) - } - } - - private let transitionManager: SearchOnMapModalTransitionManager - private var viewModel: ViewModel = .initial - private var isRouting: Bool - private var didChangeState: ((SearchOnMapState) -> Void)? - - init(transitionManager: SearchOnMapModalTransitionManager, isRouting: Bool, didChangeState: ((SearchOnMapState) -> Void)?) { - self.transitionManager = transitionManager - self.isRouting = isRouting - self.didChangeState = didChangeState - didChangeState?(searchState) - } - - func process(_ response: SearchOnMap.Response) { - guard response != .none else { return } - - if response == .close { - searchState = .closed - presentationView?.close() - return - } - - let showSearch = response == .setSearchScreenHidden(false) || response == .showHistoryAndCategory - guard viewModel.presentationStep != .hidden || showSearch else { - return - } - - let newViewModel = resolve(action: response, with: viewModel) - if viewModel != newViewModel { - viewModel = newViewModel - view?.render(newViewModel) - searchState = newViewModel.presentationStep.searchState - presentationView?.setPresentationStep(newViewModel.presentationStep) - } - } - - private func resolve(action: Response, with previousViewModel: ViewModel) -> ViewModel { - var viewModel = previousViewModel - viewModel.searchingText = nil // should not be nil only when the text is passed to the search field - - switch action { - case .startSearching: - viewModel.isTyping = true - viewModel.skipSuggestions = false - viewModel.contentState = .searching - case .showOnTheMap: - viewModel.isTyping = false - viewModel.skipSuggestions = true - viewModel.presentationStep = isRouting ? .hidden : .halfScreen - if case .results(var results) = viewModel.contentState, !results.isEmpty { - results.skipSuggestions() - viewModel.contentState = .results(results) - } - case .setIsTyping(let isSearching): - viewModel.isTyping = isSearching - if isSearching { - viewModel.presentationStep = .fullScreen - } - case .showHistoryAndCategory: - viewModel.isTyping = true - viewModel.contentState = .historyAndCategory - viewModel.presentationStep = .fullScreen - case .showResults(var searchResults, let isSearchCompleted): - if (viewModel.skipSuggestions) { - searchResults.skipSuggestions() - } - viewModel.contentState = searchResults.isEmpty && isSearchCompleted ? .noResults : .results(searchResults) - case .selectText(let text): - viewModel.isTyping = false - viewModel.skipSuggestions = false - viewModel.searchingText = text - viewModel.contentState = .searching - viewModel.presentationStep = isRouting ? .hidden : .halfScreen - case .clearSearch: - viewModel.searchingText = "" - viewModel.isTyping = true - viewModel.skipSuggestions = false - viewModel.contentState = .historyAndCategory - viewModel.presentationStep = .fullScreen - case .setSearchScreenHidden(let isHidden): - viewModel.isTyping = false - viewModel.presentationStep = isHidden ? .hidden : (isRouting ? .fullScreen : .halfScreen) - case .setSearchScreenCompact: - viewModel.isTyping = false - viewModel.presentationStep = .compact - case .updatePresentationStep(let step): - viewModel.presentationStep = step - case .close, .none: - break - } - return viewModel - } -} - -private extension ModalScreenPresentationStep { - var searchState: SearchOnMapState { - switch self { - case .fullScreen, .halfScreen, .compact: - return .searching - case .hidden: - return .hidden - } - } -} diff --git a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapViewController.swift b/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapViewController.swift deleted file mode 100644 index 93e45b0d8d..0000000000 --- a/iphone/Maps/UI/Search/SearchOnMap/SearchOnMapViewController.swift +++ /dev/null @@ -1,362 +0,0 @@ -protocol SearchOnMapView: AnyObject { - var scrollViewDelegate: SearchOnMapScrollViewDelegate? { get set } - - func render(_ viewModel: SearchOnMap.ViewModel) -} - -@objc -protocol SearchOnMapScrollViewDelegate: AnyObject { - func scrollViewDidScroll(_ scrollView: UIScrollView) -} - -final class SearchOnMapViewController: UIViewController { - typealias ViewModel = SearchOnMap.ViewModel - typealias ContentState = SearchOnMap.ViewModel.ContentState - typealias SearchText = SearchOnMap.SearchText - - fileprivate enum Constants { - static let categoriesHeight: CGFloat = 100 - static let filtersHeight: CGFloat = 50 - static let keyboardAnimationDuration: CGFloat = 0.3 - static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 8) - static let estimatedRowHeight: CGFloat = 80 - } - - let interactor: SearchOnMapInteractor - weak var scrollViewDelegate: SearchOnMapScrollViewDelegate? - - private var searchResults = SearchOnMap.SearchResults([]) - - // MARK: - UI Elements - private let headerView = SearchOnMapHeaderView() - private let containerView = UIView() - private let resultsTableView = UITableView() - private let historyAndCategoryTabViewController = SearchTabViewController() - // TODO: implement filters - private let filtersCollectionView: UICollectionView = { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .horizontal - return UICollectionView(frame: .zero, collectionViewLayout: layout) - }() - private var searchingActivityView = PlaceholderView(hasActivityIndicator: true) - private var containerModalYTranslation: CGFloat = 0 - private var searchNoResultsView = PlaceholderView(title: L("search_not_found"), - subtitle: L("search_not_found_query")) - - // MARK: - Init - init(interactor: SearchOnMapInteractor) { - self.interactor = interactor - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - // MARK: - Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - layoutViews() - interactor.handle(.openSearch) - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - headerView.setIsSearching(false) - } - - // MARK: - Private methods - private func setupViews() { - view.setStyle(.clearBackground) - setupTapGestureRecognizer() - setupHeaderView() - setupContainerView() - setupResultsTableView() - setupHistoryAndCategoryTabView() - setupResultsTableView() - setupFiltersCollectionView() - } - - private func setupTapGestureRecognizer() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)) - tapGesture.cancelsTouchesInView = false - view.addGestureRecognizer(tapGesture) - } - - private func setupHeaderView() { - headerView.delegate = self - } - - private func setupContainerView() { - containerView.setStyle(.background) - } - - private func setupResultsTableView() { - resultsTableView.setStyle(.background) - resultsTableView.estimatedRowHeight = Constants.estimatedRowHeight - resultsTableView.rowHeight = UITableView.automaticDimension - resultsTableView.registerNib(cellClass: SearchSuggestionCell.self) - resultsTableView.registerNib(cellClass: SearchCommonCell.self) - resultsTableView.dataSource = self - resultsTableView.delegate = self - resultsTableView.keyboardDismissMode = .onDrag - } - - private func setupHistoryAndCategoryTabView() { - historyAndCategoryTabViewController.delegate = self - } - - // TODO: (KK) Implement filters collection viewe - private func setupFiltersCollectionView() { - filtersCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "FilterCell") - filtersCollectionView.dataSource = self - } - - private func layoutViews() { - view.addSubview(headerView) - view.addSubview(containerView) - headerView.translatesAutoresizingMaskIntoConstraints = false - containerView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - headerView.topAnchor.constraint(equalTo: view.topAnchor), - headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - - containerView.topAnchor.constraint(equalTo: headerView.bottomAnchor), - containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) - - layoutResultsView() - layoutHistoryAndCategoryTabView() - layoutSearchNoResultsView() - layoutSearchingView() - } - - private func layoutResultsView() { - containerView.addSubview(resultsTableView) - resultsTableView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - resultsTableView.topAnchor.constraint(equalTo: containerView.topAnchor), - resultsTableView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - resultsTableView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - resultsTableView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) - ]) - } - - private func layoutHistoryAndCategoryTabView() { - containerView.addSubview(historyAndCategoryTabViewController.view) - historyAndCategoryTabViewController.view.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - historyAndCategoryTabViewController.view.topAnchor.constraint(equalTo: containerView.topAnchor), - historyAndCategoryTabViewController.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - historyAndCategoryTabViewController.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - historyAndCategoryTabViewController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) - ]) - } - - private func layoutSearchNoResultsView() { - searchNoResultsView.translatesAutoresizingMaskIntoConstraints = false - containerView.addSubview(searchNoResultsView) - NSLayoutConstraint.activate([ - searchNoResultsView.topAnchor.constraint(equalTo: containerView.topAnchor), - searchNoResultsView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - searchNoResultsView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - searchNoResultsView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) - ]) - } - - private func layoutSearchingView() { - containerView.insertSubview(searchingActivityView, at: 0) - searchingActivityView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - searchingActivityView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - searchingActivityView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - searchingActivityView.topAnchor.constraint(equalTo: containerView.topAnchor), - searchingActivityView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) - ]) - } - - // MARK: - Handle Button Actions - @objc private func handleTapOutside(_ gesture: UITapGestureRecognizer) { - let location = gesture.location(in: view) - if resultsTableView.frame.contains(location) && searchResults.isEmpty { - headerView.setIsSearching(false) - } - } - - // MARK: - Handle State Updates - private func setContent(_ content: ContentState) { - switch content { - case .historyAndCategory: - historyAndCategoryTabViewController.reloadSearchHistory() - case let .results(results): - if searchResults != results { - searchResults = results - resultsTableView.reloadData() - } - case .noResults: - searchResults = .empty - resultsTableView.reloadData() - case .searching: - break - } - showView(viewToShow(for: content)) - } - - private func viewToShow(for content: ContentState) -> UIView { - switch content { - case .historyAndCategory: - return historyAndCategoryTabViewController.view - case .results: - return resultsTableView - case .noResults: - return searchNoResultsView - case .searching: - return searchingActivityView - } - } - - private func showView(_ view: UIView) { - let viewsToHide: [UIView] = [resultsTableView, - historyAndCategoryTabViewController.view, - searchNoResultsView, - searchingActivityView].filter { $0 != view } - UIView.transition(with: containerView, - duration: kDefaultAnimationDuration / 2, - options: [.transitionCrossDissolve, .curveEaseInOut], animations: { - viewsToHide.forEach { viewToHide in - view.isHidden = false - view.alpha = 1 - viewToHide.isHidden = true - viewToHide.alpha = 0 - } - }) - } - - private func setIsSearching(_ isSearching: Bool) { - headerView.setIsSearching(isSearching) - } - - private func replaceSearchText(with text: String) { - headerView.setSearchText(text) - } -} - -// MARK: - Public methods -extension SearchOnMapViewController: SearchOnMapView { - func render(_ viewModel: ViewModel) { - setContent(viewModel.contentState) - setIsSearching(viewModel.isTyping) - if let searchingText = viewModel.searchingText { - replaceSearchText(with: searchingText) - } - } -} - -// MARK: - ModallyPresentedViewController -extension SearchOnMapViewController: ModallyPresentedViewController { - func translationYDidUpdate(_ translationY: CGFloat) { - self.containerModalYTranslation = translationY - resultsTableView.contentInset.bottom = translationY - historyAndCategoryTabViewController.translationYDidUpdate(translationY) - searchNoResultsView.translationYDidUpdate(translationY) - searchingActivityView.translationYDidUpdate(translationY) - } -} - -// MARK: - UITableViewDataSource -extension SearchOnMapViewController: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - searchResults.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let result = searchResults[indexPath.row] - switch result.itemType { - case .regular: - let cell = tableView.dequeueReusableCell(cell: SearchCommonCell.self, indexPath: indexPath) - cell.configure(with: result, isPartialMatching: searchResults.hasPartialMatch) - return cell - case .suggestion: - let cell = tableView.dequeueReusableCell(cell: SearchSuggestionCell.self, indexPath: indexPath) - cell.configure(with: result, isPartialMatching: true) - cell.isLastCell = indexPath.row == searchResults.suggestionsCount - 1 - return cell - @unknown default: - fatalError("Unknown item type") - } - } -} - -// MARK: - UITableViewDelegate -extension SearchOnMapViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let result = searchResults[indexPath.row] - interactor.handle(.didSelectResult(result, withSearchText: headerView.searchText)) - tableView.deselectRow(at: indexPath, animated: true) - } - - func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - interactor.handle(.didStartDraggingSearch) - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - scrollViewDelegate?.scrollViewDidScroll(scrollView) - } -} - -// MARK: - UICollectionViewDataSource -extension SearchOnMapViewController: UICollectionViewDataSource { - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - // TODO: remove search from here - Int(Search.resultsCount()) - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterCell", for: indexPath) - return cell - } -} - -// MARK: - SearchOnMapHeaderViewDelegate -extension SearchOnMapViewController: SearchOnMapHeaderViewDelegate { - func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { - interactor.handle(.didStartTyping) - } - - func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { - guard !searchText.isEmpty else { - interactor.handle(.clearButtonDidTap) - return - } - interactor.handle(.didType(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage))) - } - - func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - guard let searchText = searchBar.text, !searchText.isEmpty else { return } - interactor.handle(.searchButtonDidTap(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage))) - } - - func cancelButtonDidTap() { - interactor.handle(.closeSearch) - } -} - -// MARK: - SearchTabViewControllerDelegate -extension SearchOnMapViewController: SearchTabViewControllerDelegate { - func searchTabController(_ viewController: SearchTabViewController, didSearch text: String, withCategory: Bool) { - interactor.handle(.didSelectText(SearchText(text, locale: nil), isCategory: withCategory)) - } -} - diff --git a/iphone/Maps/UI/Search/SearchTextField.swift b/iphone/Maps/UI/Search/SearchTextField.swift new file mode 100644 index 0000000000..2f623249fd --- /dev/null +++ b/iphone/Maps/UI/Search/SearchTextField.swift @@ -0,0 +1,31 @@ +class SearchTextField: UITextField { + override func drawPlaceholder(in rect: CGRect) { + guard let font = font, let tint = tintColor else { + super.drawPlaceholder(in: rect); + return + } + placeholder?.draw( + in: rect, + withAttributes: [ + NSAttributedString.Key.font: font, + NSAttributedString.Key.foregroundColor: tint + ]) + } + + override func layoutSubviews() { + super.layoutSubviews() + for view in subviews { + if (view is UIButton) { + let button = view as? UIButton + let clearButtonImage: UIImage? + if #available(iOS 13.0, *) { + clearButtonImage = UIImage(named: "ic_clear")?.withRenderingMode(.alwaysTemplate).withTintColor(tintColor) + } else { + clearButtonImage = UIImage(named: "ic_search_clear_14") + } + button?.setImage(clearButtonImage, for: .normal) + button?.tintColor = tintColor + } + } + } +} diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchCell.h b/iphone/Maps/UI/Search/TableView/MWMSearchCell.h index cff502b8a3..5bb9aed7ff 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchCell.h +++ b/iphone/Maps/UI/Search/TableView/MWMSearchCell.h @@ -1,11 +1,9 @@ #import "MWMTableViewCell.h" -@class SearchResult; - -static CGFloat const kSearchCellSeparatorInset = 48; +namespace search { class Result; } @interface MWMSearchCell : MWMTableViewCell -- (void)configureWith:(SearchResult * _Nonnull)result isPartialMatching:(BOOL)isPartialMatching; - +- (void)config:(search::Result const &)result + localizedTypeName:(NSString *)localizedTypeName; @end diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchCell.mm b/iphone/Maps/UI/Search/TableView/MWMSearchCell.mm index e447d19f70..ff74ab3ebd 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchCell.mm +++ b/iphone/Maps/UI/Search/TableView/MWMSearchCell.mm @@ -1,5 +1,6 @@ #import "MWMSearchCell.h" -#import "SearchResult.h" + +#include "search/result.hpp" @interface MWMSearchCell () @@ -9,9 +10,11 @@ @implementation MWMSearchCell -- (void)configureWith:(SearchResult * _Nonnull)result isPartialMatching:(BOOL)isPartialMatching { - NSString * title = result.titleText; - +- (void)config:(search::Result const &)result + localizedTypeName:(NSString *)localizedTypeName +{ + NSString * title = result.GetString().empty() ? localizedTypeName : @(result.GetString().c_str()); + if (title.length == 0) { self.titleLabel.text = @""; @@ -27,17 +30,19 @@ } NSMutableAttributedString * attributedTitle = [[NSMutableAttributedString alloc] initWithString:title]; - NSDictionary * titleAttributes = isPartialMatching ? unselectedTitleAttributes : selectedTitleAttributes; + [attributedTitle addAttributes:unselectedTitleAttributes range:NSMakeRange(0, title.length)]; + size_t const rangesCount = result.GetHighlightRangesCount(); + for (size_t i = 0; i < rangesCount; ++i) + { + std::pair const & range = result.GetHighlightRange(i); - NSArray *highlightRanges = result.highlightRanges; - [attributedTitle addAttributes:titleAttributes range:NSMakeRange(0, title.length)]; - - for (NSValue *rangeValue in highlightRanges) { - NSRange range = [rangeValue rangeValue]; - if (NSMaxRange(range) <= result.titleText.length) { - [attributedTitle addAttributes:selectedTitleAttributes range:range]; - } else { - NSLog(@"Incorrect range: %@ for string: %@", NSStringFromRange(range), result.titleText); + if (range.first + range.second <= title.length) + { + [attributedTitle addAttributes:selectedTitleAttributes range:NSMakeRange(range.first, range.second)]; + } + else + { + LOG(LERROR, ("Incorrect range: ", range, " for string: ", result.GetString())); } } self.titleLabel.attributedText = attributedTitle; diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.h b/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.h index a66d784289..41e2e4b07f 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.h +++ b/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.h @@ -1,10 +1,13 @@ #import "MWMSearchCell.h" -@class SearchResult; +#include "map/everywhere_search_callback.hpp" + +#include "search/result.hpp" -NS_SWIFT_NAME(SearchCommonCell) @interface MWMSearchCommonCell : MWMSearchCell -- (void)configureWith:(SearchResult * _Nonnull)result isPartialMatching:(BOOL)isPartialMatching; +- (void)config:(search::Result const &)result + productInfo:(search::ProductInfo const &)productInfo + localizedTypeName:(NSString *)localizedTypeName; @end diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.mm b/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.mm index 15a60cdb51..bd59169fa8 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.mm +++ b/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.mm @@ -2,7 +2,14 @@ #import "CLLocation+Mercator.h" #import "MWMLocationManager.h" #import "SwiftBridge.h" -#import "SearchResult.h" + +#include "map/place_page_info.hpp" + +#include "geometry/mercator.hpp" + +#include "platform/localization.hpp" +#include "platform/distance.hpp" + @interface MWMSearchCommonCell () @@ -11,41 +18,96 @@ @property(weak, nonatomic) IBOutlet UILabel * locationLabel; @property(weak, nonatomic) IBOutlet UILabel * openLabel; @property(weak, nonatomic) IBOutlet UIView * popularView; -@property(weak, nonatomic) IBOutlet UIImageView * iconImageView; @end @implementation MWMSearchCommonCell -- (void)configureWith:(SearchResult * _Nonnull)result isPartialMatching:(BOOL)isPartialMatching { - [super configureWith:result isPartialMatching:isPartialMatching]; - self.locationLabel.text = result.addressText; +- (void)config:(search::Result const &)result + productInfo:(search::ProductInfo const &)productInfo + localizedTypeName:(NSString *)localizedTypeName +{ + [super config:result localizedTypeName:localizedTypeName]; + + self.locationLabel.text = @(result.GetAddress().c_str()); [self.locationLabel sizeToFit]; - self.infoLabel.text = result.infoText; - self.distanceLabel.text = result.distanceText; + + self.infoLabel.text = @(result.GetFeatureDescription().c_str()); + + CLLocation * lastLocation = [MWMLocationManager lastLocation]; + double distanceInMeters = 0.0; + if (lastLocation) + { + if (result.HasPoint()) + { + distanceInMeters = + mercator::DistanceOnEarth(lastLocation.mercator, result.GetFeatureCenter()); + std::string distanceStr = platform::Distance::CreateFormatted(distanceInMeters).ToString(); + self.distanceLabel.text = @(distanceStr.c_str()); + } + } + + /// @todo Restore "TOP" badge in future, when popularity will be available. + //self.popularView.hidden = result.GetRankingInfo().m_popularity == 0; self.popularView.hidden = YES; - self.openLabel.text = result.openStatusText; - self.openLabel.textColor = result.openStatusColor; - [self.openLabel setHidden:result.openStatusText.length == 0]; - [self setStyleNameAndApply:@"Background"]; - [self.iconImageView setStyleNameAndApply:@"BlueBackground"]; - self.iconImageView.image = [UIImage imageNamed:result.iconImageName]; - self.separatorInset = UIEdgeInsetsMake(0, kSearchCellSeparatorInset, 0, 0); + + switch (result.IsOpenNow()) + { + case osm::Yes: + { + int const minutes = result.GetMinutesUntilClosed(); + if (minutes < 60) // less than 1 hour + { + self.openLabel.textColor = UIColor.systemYellowColor; + NSString *time = [NSString stringWithFormat: @"%d %@", minutes, L(@"minute")]; + self.openLabel.text = [NSString stringWithFormat: L(@"closes_in"), time]; + } + else + { + self.openLabel.textColor = UIColor.systemGreenColor; + self.openLabel.text = L(@"editor_time_open"); + } + self.openLabel.hidden = false; + break; + } + + case osm::No: + { + self.openLabel.textColor = UIColor.systemRedColor; + int const minutes = result.GetMinutesUntilOpen(); + if (minutes < 60) // less than 1 hour + { + NSString *time = [NSString stringWithFormat: @"%d %@", minutes, L(@"minute")]; + self.openLabel.text = [NSString stringWithFormat: L(@"opens_in"), time]; + } + else + { + self.openLabel.text = L(@"closed"); + } + self.openLabel.hidden = false; + break; + } + + case osm::Unknown: + { + self.openLabel.hidden = true; + break; + } + } + + [self setStyleNameAndApply: @"Background"]; } -- (void)layoutSubviews { - [super layoutSubviews]; - [self.iconImageView.layer setCornerRadius:self.iconImageView.height / 2]; -} - -- (NSDictionary *)selectedTitleAttributes { +- (NSDictionary *)selectedTitleAttributes +{ return @{ NSForegroundColorAttributeName : [UIColor blackPrimaryText], NSFontAttributeName : [UIFont bold17] }; } -- (NSDictionary *)unselectedTitleAttributes { +- (NSDictionary *)unselectedTitleAttributes +{ return @{ NSForegroundColorAttributeName : [UIColor blackPrimaryText], NSFontAttributeName : [UIFont regular17] diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.xib b/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.xib index 073542c1e0..e951d0195f 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.xib +++ b/iphone/Maps/UI/Search/TableView/MWMSearchCommonCell.xib @@ -1,41 +1,50 @@ - + - - + - - + + - + + + + + + + + + + + - - - + + - - - - + + - + + + + @@ -130,19 +132,13 @@ - - + - + - - - - - diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.h b/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.h index fd8da7a790..1b586a9407 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.h +++ b/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.h @@ -1,6 +1,5 @@ #import "MWMSearchCell.h" -NS_SWIFT_NAME(SearchSuggestionCell) @interface MWMSearchSuggestionCell : MWMSearchCell @property (nonatomic) BOOL isLastCell; diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.mm b/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.mm index b8c5174702..26851843af 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.mm +++ b/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.mm @@ -3,6 +3,7 @@ @interface MWMSearchSuggestionCell () @property (weak, nonatomic) IBOutlet UIImageView * icon; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint * separatorLeftOffset; @end @@ -30,7 +31,7 @@ - (void)setIsLastCell:(BOOL)isLastCell { _isLastCell = isLastCell; - self.separatorInset = UIEdgeInsetsMake(0, isLastCell ? 0 : kSearchCellSeparatorInset, 0, 0); + self.separatorLeftOffset.constant = isLastCell ? 0.0 : 60.0; } @end diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.xib b/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.xib index e269b2d272..82bd5e4ee7 100644 --- a/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.xib +++ b/iphone/Maps/UI/Search/TableView/MWMSearchSuggestionCell.xib @@ -1,9 +1,9 @@ - + - + @@ -12,34 +12,44 @@ - + - + - - + + + + + + + + + - + + + + - + - - + + @@ -47,6 +57,7 @@ + diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchTableView.h b/iphone/Maps/UI/Search/TableView/MWMSearchTableView.h new file mode 100644 index 0000000000..253cb150c8 --- /dev/null +++ b/iphone/Maps/UI/Search/TableView/MWMSearchTableView.h @@ -0,0 +1,5 @@ +@interface MWMSearchTableView : UIView + +- (void)hideNoResultsView:(BOOL)hide; + +@end diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchTableView.m b/iphone/Maps/UI/Search/TableView/MWMSearchTableView.m new file mode 100644 index 0000000000..40608e3a2a --- /dev/null +++ b/iphone/Maps/UI/Search/TableView/MWMSearchTableView.m @@ -0,0 +1,71 @@ +#import "MWMSearchTableView.h" +#import "MWMKeyboard.h" +#import "MWMSearchNoResults.h" + +@interface MWMSearchTableView () + +@property(weak, nonatomic) IBOutlet NSLayoutConstraint * noResultsBottomOffset; + +@property(weak, nonatomic) IBOutlet UIView * noResultsContainer; +@property(weak, nonatomic) IBOutlet UIView * noResultsWrapper; +@property(nonatomic) MWMSearchNoResults * noResultsView; + +@end + +@implementation MWMSearchTableView + +- (void)awakeFromNib +{ + [super awakeFromNib]; + CALayer * sl = self.layer; + sl.shouldRasterize = YES; + sl.rasterizationScale = UIScreen.mainScreen.scale; + [MWMKeyboard addObserver:self]; +} + +- (void)hideNoResultsView:(BOOL)hide +{ + if (hide) + { + self.noResultsContainer.hidden = YES; + [self.noResultsView removeFromSuperview]; + } + else + { + self.noResultsContainer.hidden = NO; + [self.noResultsWrapper addSubview:self.noResultsView]; + [self onKeyboardAnimation]; + } +} + +#pragma mark - MWMKeyboard + +- (void)onKeyboardAnimation +{ + CGFloat const keyboardHeight = [MWMKeyboard keyboardHeight]; + if (keyboardHeight >= self.height) + return; + + self.noResultsBottomOffset.constant = keyboardHeight; + if (self.superview) + [self layoutIfNeeded]; +} + +- (void)onKeyboardWillAnimate +{ + if (self.superview) + [self layoutIfNeeded]; +} + +- (MWMSearchNoResults *)noResultsView +{ + if (!_noResultsView) + { + _noResultsView = [MWMSearchNoResults viewWithImage:nil + title:L(@"search_not_found") + text:L(@"search_not_found_query")]; + } + return _noResultsView; +} + +@end diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.h b/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.h new file mode 100644 index 0000000000..3003035858 --- /dev/null +++ b/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.h @@ -0,0 +1,31 @@ +#import "MWMSearch.h" +#import "MWMSearchManager.h" +#import "MWMSearchTabbedViewProtocol.h" +#import "MWMViewController.h" + +#include + +@class SearchTextField; +namespace search +{ +class Result; +} // search + +@protocol MWMSearchTableViewProtocol + +@property(nullable, weak, nonatomic) SearchTextField * searchTextField; + +@property(nonatomic) MWMSearchManagerState state; + +- (void)processSearchWithResult:(search::Result const &)result; + +@end + +@interface MWMSearchTableViewController : MWMViewController + +- (nonnull instancetype)init __attribute__((unavailable("init is not available"))); +- (nonnull instancetype)initWithDelegate:(nonnull id)delegate; + +- (void)reloadData; + +@end diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.mm b/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.mm new file mode 100644 index 0000000000..b8ec74203f --- /dev/null +++ b/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.mm @@ -0,0 +1,142 @@ +#import "MWMSearchTableViewController.h" +#import "MWMSearchCommonCell.h" +#import "MWMSearchSuggestionCell.h" +#import "MWMSearchTableView.h" +#import "SwiftBridge.h" + +#include "platform/localization.hpp" + +#include "search/result.hpp" + +namespace { +NSString *GetLocalizedTypeName(search::Result const &result) { + return @(result.GetLocalizedFeatureType().c_str()); +} +} + +@interface MWMSearchTableViewController () + +@property(weak, nonatomic) IBOutlet UITableView *tableView; + +@property(weak, nonatomic) id delegate; + +@end + +@implementation MWMSearchTableViewController + +- (nonnull instancetype)initWithDelegate:(id)delegate { + self = [super init]; + if (self) + _delegate = delegate; + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupTableView]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.tableView.hidden = NO; + self.tableView.insetsContentViewsToSafeArea = YES; + [(MWMSearchTableView *)self.view hideNoResultsView:YES]; + [self reloadData]; +} + +- (void)setupTableView { + UITableView *tableView = self.tableView; + tableView.estimatedRowHeight = 80.; + tableView.rowHeight = UITableViewAutomaticDimension; + [tableView registerNibWithCellClass:[MWMSearchSuggestionCell class]]; + [tableView registerNibWithCellClass:[MWMSearchCommonCell class]]; +} + +- (void)reloadData { + [self.tableView reloadData]; +} +#pragma mark - Layout + +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + [coordinator + animateAlongsideTransition:^(id context) { + [self reloadData]; + } + completion:nil]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [MWMSearch resultsCount]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if ([MWMSearch resultsCount] == 0) { + NSAssert(false, @"Invalid reload with outdated SearchIndex"); + return [tableView dequeueReusableCellWithCellClass:[MWMSearchCommonCell class] indexPath:indexPath]; + } + + auto const row = indexPath.row; + auto const containerIndex = [MWMSearch containerIndexWithRow:row]; + auto const & result = [MWMSearch resultWithContainerIndex:containerIndex]; + + switch ([MWMSearch resultTypeWithRow:row]) + { + case MWMSearchItemTypeRegular: + { + auto cell = static_cast( + [tableView dequeueReusableCellWithCellClass:[MWMSearchCommonCell class] indexPath:indexPath]); + auto const & productInfo = [MWMSearch productInfoWithContainerIndex:containerIndex]; + [cell config:result productInfo:productInfo localizedTypeName:GetLocalizedTypeName(result)]; + return cell; + } + case MWMSearchItemTypeSuggestion: + { + auto cell = static_cast( + [tableView dequeueReusableCellWithCellClass:[MWMSearchSuggestionCell class] indexPath:indexPath]); + [cell config:result localizedTypeName:@""]; + cell.isLastCell = row == [MWMSearch suggestionsCount] - 1; + return cell; + } + } +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + id delegate = self.delegate; + auto const row = indexPath.row; + auto const containerIndex = [MWMSearch containerIndexWithRow:row]; + auto const & result = [MWMSearch resultWithContainerIndex:containerIndex]; + + switch ([MWMSearch resultTypeWithRow:row]) + { + case MWMSearchItemTypeRegular: + { + SearchTextField const * textField = delegate.searchTextField; + [MWMSearch saveQuery:textField.text forInputLocale:textField.textInputMode.primaryLanguage]; + [delegate processSearchWithResult:result]; + break; + } + case MWMSearchItemTypeSuggestion: + { + [delegate searchText:@(result.GetSuggestionString().c_str()) forInputLocale:nil + withCategory:result.GetResultType() == search::Result::Type::PureSuggest]; + break; + } + } +} + +#pragma mark - MWMSearchObserver + +- (void)onSearchCompleted { + [self reloadData]; + BOOL const noResults = [MWMSearch resultsCount] == 0; + self.tableView.hidden = noResults; + [(MWMSearchTableView *)self.view hideNoResultsView:!noResults]; +} + +@end diff --git a/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.xib b/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.xib new file mode 100644 index 0000000000..f9db80e653 --- /dev/null +++ b/iphone/Maps/UI/Search/TableView/MWMSearchTableViewController.xib @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoriesViewController.swift b/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoriesViewController.swift index 745d81a5bb..830b9f5a06 100644 --- a/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoriesViewController.swift +++ b/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoriesViewController.swift @@ -1,4 +1,4 @@ -protocol SearchCategoriesViewControllerDelegate: SearchOnMapScrollViewDelegate { +protocol SearchCategoriesViewControllerDelegate: AnyObject { func categoriesViewController(_ viewController: SearchCategoriesViewController, didSelect category: String) } @@ -12,48 +12,36 @@ final class SearchCategoriesViewController: MWMTableViewController { categories = frameworkHelper.searchCategories() super.init(nibName: nil, bundle: nil) } - - @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() - tableView.setStyle(.background) - tableView.register(cell: SearchCategoryCell.self) + + tableView.registerNib(cellClass: SearchCategoryCell.self) + tableView.separatorStyle = .none tableView.keyboardDismissMode = .onDrag } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return categories.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(cell: SearchCategoryCell.self, indexPath: indexPath) - cell.configure(with: category(at: indexPath)) + cell.update(with: category(at: indexPath)) return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectedCategory = category(at: indexPath) delegate?.categoriesViewController(self, didSelect: selectedCategory) - tableView.deselectRow(at: indexPath, animated: true) } - - override func scrollViewDidScroll(_ scrollView: UIScrollView) { - delegate?.scrollViewDidScroll(scrollView) - } - + func category(at indexPath: IndexPath) -> String { let index = indexPath.row return categories[index] } } - -extension SearchCategoriesViewController: ModallyPresentedViewController { - func translationYDidUpdate(_ translationY: CGFloat) { - guard isViewLoaded else { return } - tableView.contentInset.bottom = translationY + view.safeAreaInsets.bottom - } -} diff --git a/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoryCell.swift b/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoryCell.swift index b9ccf3ae9f..fb96f27922 100644 --- a/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoryCell.swift +++ b/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoryCell.swift @@ -1,25 +1,16 @@ -final class SearchCategoryCell: UITableViewCell { +final class SearchCategoryCell: MWMTableViewCell { + @IBOutlet weak var iconImageView: UIImageView! + @IBOutlet weak var titleLabel: UILabel! - private var categoryName: String = "" - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: .default, reuseIdentifier: reuseIdentifier) - setStyle(.defaultTableViewCell) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(with categoryName: String) { - self.categoryName = categoryName - textLabel?.text = L(categoryName) - imageView?.mwm_name = String(format: "ic_%@", categoryName) + private var category: String = "" + func update(with category: String) { + self.category = category + iconImageView.mwm_name = String(format: "ic_%@", category) + titleLabel.text = L(category) } override func applyTheme() { super.applyTheme() - imageView?.mwm_name = String(format: "ic_%@", categoryName) + iconImageView.mwm_name = String(format: "ic_%@", category) } } diff --git a/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoryCell.xib b/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoryCell.xib new file mode 100644 index 0000000000..fa090e4142 --- /dev/null +++ b/iphone/Maps/UI/Search/Tabs/CategoriesTab/SearchCategoryCell.xib @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryCell.swift b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryCell.swift deleted file mode 100644 index 202edb2fe9..0000000000 --- a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryCell.swift +++ /dev/null @@ -1,34 +0,0 @@ -final class SearchHistoryCell: MWMTableViewCell { - enum Content { - case query(String) - case clear - } - - static private let placeholderImage = UIImage.filled(with: .clear, size: CGSize(width: 28, height: 28)) - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: .default, reuseIdentifier: reuseIdentifier) - setStyle(.defaultTableViewCell) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(for content: Content) { - switch content { - case .query(let query): - textLabel?.text = query - textLabel?.setFontStyleAndApply(.regular17, color: .blackSecondary) - imageView?.image = UIImage(resource: .icSearch) - imageView?.setStyleAndApply(.black) - isSeparatorHidden = false - case .clear: - textLabel?.text = L("clear_search") - textLabel?.setFontStyleAndApply(.regular14, color: .linkBlue) - imageView?.image = Self.placeholderImage - isSeparatorHidden = true - } - } -} diff --git a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryClearCell.xib b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryClearCell.xib new file mode 100644 index 0000000000..c22b6c3ab5 --- /dev/null +++ b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryClearCell.xib @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryQueryCell.swift b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryQueryCell.swift new file mode 100644 index 0000000000..83f7890318 --- /dev/null +++ b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryQueryCell.swift @@ -0,0 +1,7 @@ +final class SearchHistoryQueryCell: MWMTableViewCell { + @IBOutlet weak var queryLabel: UILabel! + + func update(with query: String) { + queryLabel.text = query + } +} diff --git a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryQueryCell.xib b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryQueryCell.xib new file mode 100644 index 0000000000..9873a4db20 --- /dev/null +++ b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryQueryCell.xib @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryViewController.swift b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryViewController.swift index be08ea1c38..3b504ee1d7 100644 --- a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryViewController.swift +++ b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryViewController.swift @@ -1,106 +1,72 @@ -protocol SearchHistoryViewControllerDelegate: SearchOnMapScrollViewDelegate { +protocol SearchHistoryViewControllerDelegate: AnyObject { func searchHistoryViewController(_ viewController: SearchHistoryViewController, didSelect query: String) } final class SearchHistoryViewController: MWMViewController { private weak var delegate: SearchHistoryViewControllerDelegate? - private var lastQueries: [String] = [] + private var lastQueries: [String] private let frameworkHelper: MWMSearchFrameworkHelper - private let emptyHistoryView = PlaceholderView(title: L("search_history_title"), - subtitle: L("search_history_text")) - - private let tableView = UITableView() - - // MARK: - Init + private static let clearCellIdentifier = "SearchHistoryViewController_clearCellIdentifier" + + @IBOutlet private var tableView: UITableView! + @IBOutlet private weak var noResultsViewContainer: UIView! + init(frameworkHelper: MWMSearchFrameworkHelper, delegate: SearchHistoryViewControllerDelegate?) { self.delegate = delegate + self.lastQueries = frameworkHelper.lastSearchQueries() self.frameworkHelper = frameworkHelper super.init(nibName: nil, bundle: nil) } - - @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - // MARK: - Lifecycle + override func viewDidLoad() { super.viewDidLoad() - setupTableView() - setupNoResultsView() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - reload() - } - - // MARK: - Private methods - private func setupTableView() { - tableView.setStyle(.background) - tableView.register(cell: SearchHistoryCell.self) - tableView.keyboardDismissMode = .onDrag - tableView.delegate = self - tableView.dataSource = self - tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 1)) - - view.addSubview(tableView) - tableView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - } - - private func setupNoResultsView() { - view.addSubview(emptyHistoryView) - emptyHistoryView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - emptyHistoryView.topAnchor.constraint(equalTo: view.topAnchor), - emptyHistoryView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - emptyHistoryView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - emptyHistoryView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - } - - private func showEmptyHistoryView(_ isVisible: Bool = true, animated: Bool = true) { - UIView.transition(with: emptyHistoryView, - duration: animated ? kDefaultAnimationDuration : 0, - options: [.transitionCrossDissolve, .curveEaseInOut]) { - self.emptyHistoryView.alpha = isVisible ? 1.0 : 0.0 - self.emptyHistoryView.isHidden = !isVisible + + if frameworkHelper.isSearchHistoryEmpty() { + showNoResultsView() + } else { + tableView.registerNib(cellClass: SearchHistoryQueryCell.self) + let nib = UINib(nibName: "SearchHistoryClearCell", bundle: nil) + tableView.register(nib, forCellReuseIdentifier: SearchHistoryViewController.clearCellIdentifier) } + tableView.keyboardDismissMode = .onDrag } - private func clearSearchHistory() { - frameworkHelper.clearSearchHistory() - reload() + func showNoResultsView() { + guard let noResultsView = MWMSearchNoResults.view(with: nil, + title: L("search_history_title"), + text: L("search_history_text")) else { + assertionFailure() + return + } + noResultsViewContainer.addSubview(noResultsView) + tableView.isHidden = true } - - // MARK: - Public methods - func reload() { - guard isViewLoaded else { return } - lastQueries = frameworkHelper.lastSearchQueries() - showEmptyHistoryView(lastQueries.isEmpty ? true : false) - tableView.reloadData() + + func clearSearchHistory() { + frameworkHelper.clearSearchHistory() + lastQueries = [] } } extension SearchHistoryViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return lastQueries.isEmpty ? 0 : lastQueries.count + 1 + return frameworkHelper.isSearchHistoryEmpty() ? 0 : lastQueries.count + 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(cell: SearchHistoryCell.self, indexPath: indexPath) if indexPath.row == lastQueries.count { - cell.configure(for: .clear) - } else { - cell.configure(for: .query(lastQueries[indexPath.row])) + let cell = tableView.dequeueReusableCell(withIdentifier: SearchHistoryViewController.clearCellIdentifier, + for: indexPath) + return cell } + + let cell = tableView.dequeueReusableCell(cell: SearchHistoryQueryCell.self, indexPath: indexPath) + cell.update(with: lastQueries[indexPath.row]) return cell } } @@ -109,22 +75,15 @@ extension SearchHistoryViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == lastQueries.count { clearSearchHistory() + UIView.animate(withDuration: kDefaultAnimationDuration, + animations: { + tableView.alpha = 0.0 + }) { _ in + self.showNoResultsView() + } } else { let query = lastQueries[indexPath.row] delegate?.searchHistoryViewController(self, didSelect: query) } - tableView.deselectRow(at: indexPath, animated: true) - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - delegate?.scrollViewDidScroll(scrollView) - } -} - -extension SearchHistoryViewController: ModallyPresentedViewController { - func translationYDidUpdate(_ translationY: CGFloat) { - guard isViewLoaded else { return } - tableView.contentInset.bottom = translationY - emptyHistoryView.translationYDidUpdate(translationY) } } diff --git a/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryViewController.xib b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryViewController.xib new file mode 100644 index 0000000000..2d589dfac8 --- /dev/null +++ b/iphone/Maps/UI/Search/Tabs/HistoryTab/SearchHistoryViewController.xib @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Search/Tabs/MWMSearchTabbedViewProtocol.h b/iphone/Maps/UI/Search/Tabs/MWMSearchTabbedViewProtocol.h new file mode 100644 index 0000000000..98147899b9 --- /dev/null +++ b/iphone/Maps/UI/Search/Tabs/MWMSearchTabbedViewProtocol.h @@ -0,0 +1,12 @@ +#import "MWMSearchManagerState.h" + +@protocol MWMSearchTabbedViewProtocol + +@required + +@property(nonatomic) MWMSearchManagerState state; + +- (void)searchText:(NSString *)text forInputLocale:(NSString *)locale withCategory:(BOOL)isCategory; +- (void)dismissKeyboard; + +@end diff --git a/iphone/Maps/UI/Search/Tabs/SearchTabViewController.swift b/iphone/Maps/UI/Search/Tabs/SearchTabViewController.swift index e8ef5bc720..5d60214d05 100644 --- a/iphone/Maps/UI/Search/Tabs/SearchTabViewController.swift +++ b/iphone/Maps/UI/Search/Tabs/SearchTabViewController.swift @@ -1,6 +1,7 @@ +import CoreFoundation @objc(MWMSearchTabViewControllerDelegate) -protocol SearchTabViewControllerDelegate: SearchOnMapScrollViewDelegate { - func searchTabController(_ viewController: SearchTabViewController, didSearch: String, withCategory: Bool) +protocol SearchTabViewControllerDelegate: AnyObject { + func searchTabController(_ viewContoller: SearchTabViewController, didSearch: String, withCategory: Bool) } @objc(MWMSearchTabViewController) @@ -47,22 +48,6 @@ final class SearchTabViewController: TabViewController { super.viewDidDisappear(animated) activeTab = SearchActiveTab.init(rawValue: tabView.selectedIndex ?? 0) ?? .categories } - - func reloadSearchHistory() { - (viewControllers[SearchActiveTab.history.rawValue] as? SearchHistoryViewController)?.reload() - } -} - -extension SearchTabViewController: ModallyPresentedViewController { - func translationYDidUpdate(_ translationY: CGFloat) { - viewControllers.forEach { ($0 as? ModallyPresentedViewController)?.translationYDidUpdate(translationY) } - } -} - -extension SearchTabViewController: SearchOnMapScrollViewDelegate { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - delegate?.scrollViewDidScroll(scrollView) - } } extension SearchTabViewController: SearchCategoriesViewControllerDelegate { diff --git a/iphone/Maps/UI/Storyboard/Main.storyboard b/iphone/Maps/UI/Storyboard/Main.storyboard index c1605e6180..a78cd06513 100644 --- a/iphone/Maps/UI/Storyboard/Main.storyboard +++ b/iphone/Maps/UI/Storyboard/Main.storyboard @@ -1,9 +1,9 @@ - - + + - + @@ -14,26 +14,26 @@ - + - + - + - + @@ -98,8 +98,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -115,11 +171,14 @@ + + + @@ -128,6 +187,8 @@ + + @@ -135,11 +196,13 @@ + + @@ -154,14 +217,18 @@ + + + + @@ -173,6 +240,10 @@ + + + + @@ -181,6 +252,7 @@ + @@ -188,6 +260,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -195,6 +295,9 @@ + + + @@ -239,7 +342,7 @@ - + @@ -259,7 +362,7 @@ - + @@ -275,7 +378,7 @@ - + @@ -301,11 +404,11 @@ - + - + @@ -316,10 +419,10 @@ - + - + @@ -336,7 +439,7 @@ - + @@ -345,7 +448,7 @@ - + @@ -354,7 +457,7 @@ - + @@ -363,7 +466,7 @@ - + @@ -372,13 +475,13 @@ - + - + - + @@ -398,7 +501,7 @@ - + @@ -407,7 +510,7 @@ - + @@ -508,7 +611,7 @@ - + @@ -576,26 +679,26 @@ - + - + - + - + @@ -665,7 +768,7 @@ - + @@ -710,11 +813,11 @@ - + - + @@ -725,7 +828,7 @@ - + @@ -736,7 +839,7 @@ - + @@ -780,11 +883,11 @@ - + - + @@ -796,13 +899,13 @@ - + @@ -845,20 +948,20 @@ - + - + - + - +