diff --git a/.clang-format b/.clang-format index a5b87e0ba..fceb426b3 100644 --- a/.clang-format +++ b/.clang-format @@ -3,7 +3,6 @@ BasedOnStyle: Google AccessModifierOffset: -2 AlignAfterOpenBracket: Align -AlignArrayOfStructures: Right AlignConsecutiveMacros: AcrossEmptyLinesAndComments AlignEscapedNewlines: LeftWithLastLine AlignOperands: AlignAfterOperator @@ -40,8 +39,10 @@ ColumnLimit: 120 ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 4 DerivePointerAlignment: false +EmptyLineBeforeAccessModifier: Always IncludeBlocks: Preserve IndentAccessModifiers: false +IndentCaseLabels: false IndentExternBlock: NoIndent InsertBraces: false InsertNewlineAtEOF: true @@ -49,6 +50,7 @@ LambdaBodyIndentation: OuterScope PackConstructorInitializers: CurrentLine PointerAlignment: Middle RemoveBracesLLVM: true +RemoveSemicolon: true QualifierAlignment: Right SpacesInContainerLiterals: false Standard: Latest diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 000000000..9c47cc33e --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,8 @@ +# Files that should not be formatted. +./3party +# A patched copy of the https://registry.khronos.org/OpenGL/api/GLES3/gl3.h +./android/sdk/src/main/cpp/app/organicmaps/sdk/opengl/gl3stub.h +# Formatting it leads to crashes in runtime. Newer protobuf may fix it. +./libs/indexer/drules_struct.pb.cc +# No need to format this 3party tool. +tools/osmctools/*.c diff --git a/.forgejo/workflows/dco.yml b/.forgejo/workflows/dco.yml index 13e796fcb..fbee4e38e 100644 --- a/.forgejo/workflows/dco.yml +++ b/.forgejo/workflows/dco.yml @@ -1,8 +1,9 @@ -name: dco +name: DCO on: [pull_request] jobs: check: - runs-on: codeberg-tiny + runs-on: ubuntu-latest steps: - - uses: https://github.com/KineticCafe/actions-dco@v1 + - name: Check for Developer Certificate of Origin (DCO) compliance + uses: https://github.com/KineticCafe/actions-dco@fb284c903a7673a3d4b0bdd104479a6f0d46dae7 # v1.3.6 diff --git a/.forgejo/workflows/desktop-file-check.yaml b/.forgejo/workflows/desktop-file-check.yaml index b0f6c0a7e..384f94730 100644 --- a/.forgejo/workflows/desktop-file-check.yaml +++ b/.forgejo/workflows/desktop-file-check.yaml @@ -3,7 +3,7 @@ on: workflow_dispatch: # Manual trigger pull_request: paths: - - qt/res/app.comaps.comaps.desktop + - qt/res/linux/app.comaps.comaps.desktop - .forgejo/workflows/desktop-file-check.yaml # Run check on self change jobs: @@ -16,7 +16,7 @@ jobs: with: fetch-depth: 1 sparse-checkout: | - qt/res/app.comaps.comaps.desktop + qt/res/linux/app.comaps.comaps.desktop - name: Install desktop-file-validate tool shell: bash @@ -27,4 +27,4 @@ jobs: - name: Validate desktop file shell: bash - run: desktop-file-validate qt/res/app.comaps.comaps.desktop && echo "Successfully validated .desktop file" + run: desktop-file-validate qt/res/linux/app.comaps.comaps.desktop && echo "Successfully validated .desktop file" diff --git a/.forgejo/workflows/linux-check.yaml b/.forgejo/workflows/linux-check.yaml index ee76ec45a..f4e978577 100644 --- a/.forgejo/workflows/linux-check.yaml +++ b/.forgejo/workflows/linux-check.yaml @@ -133,6 +133,14 @@ jobs: with: key: ${{ github.workflow }}-unity-${{ matrix.compiler.CC }}-${{ matrix.CMAKE_BUILD_TYPE }} + - name: Configure repository + shell: bash + env: + SKIP_MAP_DOWNLOAD: 1 + SKIP_GENERATE_SYMBOLS: 1 + SKIP_GENERATE_DRULES: 1 + run: ./configure.sh + - name: CMake shell: bash env: diff --git a/.github/workflows/android-check.yaml b/.github/workflows/android-check.yaml index 0e8146026..f87bfea69 100644 --- a/.github/workflows/android-check.yaml +++ b/.github/workflows/android-check.yaml @@ -23,6 +23,10 @@ jobs: shell: bash run: git submodule update --depth 1 --init --recursive --jobs=$(($(nproc) * 20)) + - name: Configure repository + shell: bash + run: SKIP_GENERATE_SYMBOLS=1 ./configure.sh + - name: Lint shell: bash working-directory: android @@ -62,6 +66,7 @@ jobs: libxinerama-dev \ libxcursor-dev \ libxi-dev \ + python3-protobuf \ zlib1g-dev - name: Checkout sources diff --git a/.github/workflows/clang-format.yaml b/.github/workflows/clang-format.yaml deleted file mode 100644 index f74088c8e..000000000 --- a/.github/workflows/clang-format.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: clang-format - -on: - push: - branches: [ master ] - paths: - - 'android/app/src/**.java' - - '.clang-format' - - '.github/workflows/clang-format.yml' - pull_request: - branches: [ master ] - paths: - - 'android/app/src/**.java' - - '.clang-format' - - '.github/workflows/clang-format.yml' - -jobs: - check-formatting: - runs-on: ubuntu-latest - continue-on-error: true # TODO(AB): Remove this line when ready to enforce formatting. - - steps: - - uses: actions/checkout@v4 - - - name: Install clang-format - run: | - sudo apt-get update - sudo apt-get install -y clang-format-19 - clang-format-19 --version - - - name: Check Java formatting - run: | - JAVA_FILES=($(find android/app/src -name '*.java')) - - FORMATTING_ISSUES=$(clang-format-19 --dry-run --Werror $JAVA_FILES 2>&1 || true) - - if [ -n "$FORMATTING_ISSUES" ]; then - echo "$FORMATTING_ISSUES" - echo "" - echo "To fix formatting, please run:" - echo " clang-format -i " - exit 1 - fi diff --git a/.github/workflows/code-style-check.yaml b/.github/workflows/code-style-check.yaml new file mode 100644 index 000000000..409f0a5e8 --- /dev/null +++ b/.github/workflows/code-style-check.yaml @@ -0,0 +1,51 @@ +name: Code style check + +on: + pull_request: + branches: [ master ] + paths: # Should stay in sync with tools/unix/clang-format.sh + - '.github/workflows/code-style-check.yaml' + - 'android/app/src/**.java' + - 'android/sdk/src/**.java' + - 'android/sdk/src/main/cpp/**.[ch]pp' + - 'dev_sandbox/**.[ch]pp' + - 'generator/**.[ch]pp' + - 'iphone/**.[ch]pp' + - 'iphone/**.[hm]' + - 'iphone/**.mm' + - 'libs/**.[ch]pp' + - 'libs/**.[hm]' + - '!libs/indexer/drules_struct.pb.h' + - 'libs/**.mm' + - 'qt/**.[ch]pp' + - 'qt/**.h' + - 'tools/**.[ch]pp' + - '.clang-format' + - '.clang-format-ignore' + +jobs: + code-style-check: + runs-on: ubuntu-latest + steps: + - name: Install clang-format + run: | + sudo apt purge -y clang-format-18 # Remove default old version of clang-format + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + echo 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' | sudo tee /etc/apt/sources.list.d/llvm-toolchain-noble-20.list + sudo apt-get update + sudo apt-get install -y clang-format-20 + sudo update-alternatives --force --install /usr/bin/clang-format clang-format /usr/bin/clang-format-20 10 + sudo update-alternatives --force --install /usr/bin/git-clang-format git-clang-format /usr/bin/git-clang-format-20 10 + clang-format --version + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Check code style + run: tools/unix/clang-format.sh + + - name: Post clang-format comments + if: failure() + uses: reviewdog/action-suggester@v1.21.0 + with: + tool_name: clang-format + fail_level: error diff --git a/.github/workflows/ios-check.yaml b/.github/workflows/ios-check.yaml index b644403c6..295e28632 100644 --- a/.github/workflows/ios-check.yaml +++ b/.github/workflows/ios-check.yaml @@ -30,6 +30,7 @@ jobs: run: | brew install qt \ optipng + pip3 install "protobuf<3.21" --break-system-packages - name: Checkout sources uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index de99b911d..c9f3705b5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,12 +14,10 @@ stxxl.errlog stxxl.log screenlog.0 -data/symbols/*/design/ # symbols png/sdf are now generated at build data/symbols/**/symbols.png data/symbols/**/symbols.sdf -data/colors_design.txt -data/patterns_design.txt + data/bookmarks data/edits.xml data/traffic.xml @@ -28,11 +26,15 @@ data/WorldCoasts.mwm data/world_mwm/* data/*_hash data/drules_proto* -data/classificator.txt -data/types.txt -data/visibility.txt -data/colors.txt -data/patterns.txt +data/classificator.txt* +data/types.txt* +data/visibility.txt* +data/colors.txt* +data/patterns.txt* +# TODO: designer is not used at the moment +# data/symbols/*/design/ +# data/colors_design.txt +# data/patterns_design.txt # Compiled Python *.pyc @@ -51,7 +53,7 @@ omim.sdf *.suo *.aps *.rc -!qt/res/windows.rc +!qt/res/windows/windows.rc *.pdb out/ diff --git a/.gitmodules b/.gitmodules index 320fd8f71..6944e9898 100644 --- a/.gitmodules +++ b/.gitmodules @@ -65,3 +65,7 @@ [submodule "3party/imgui/imgui"] path = 3party/imgui/imgui url = https://github.com/ocornut/imgui +[submodule "3party/glaze"] + path = 3party/glaze + url = https://github.com/stephenberry/glaze + branch = main diff --git a/3party/CMakeLists.txt b/3party/CMakeLists.txt index 5178ae0b5..dd851d7db 100644 --- a/3party/CMakeLists.txt +++ b/3party/CMakeLists.txt @@ -57,6 +57,7 @@ endif() add_subdirectory(agg) add_subdirectory(bsdiff-courgette) +add_subdirectory(glaze) add_subdirectory(minizip) add_subdirectory(open-location-code) add_subdirectory(opening_hours) diff --git a/3party/Vulkan-Headers b/3party/Vulkan-Headers index 595c8d479..f69f0433b 160000 --- a/3party/Vulkan-Headers +++ b/3party/Vulkan-Headers @@ -1 +1 @@ -Subproject commit 595c8d4794410a4e64b98dc58d27c0310d7ea2fd +Subproject commit f69f0433bae0b30598380ef0420b9d2d02dbac4d diff --git a/3party/freetype/CMakeLists.txt b/3party/freetype/CMakeLists.txt index cd8d17ad7..f1e5950b3 100644 --- a/3party/freetype/CMakeLists.txt +++ b/3party/freetype/CMakeLists.txt @@ -4,7 +4,9 @@ set(FT_DISABLE_HARFBUZZ ON) add_subdirectory(freetype) # Fix warning with ONE_PIXEL macro clash. -target_compile_options(freetype PRIVATE -Wno-macro-redefined) +if(NOT MSVC) + target_compile_options(freetype PRIVATE -Wno-macro-redefined) +endif() # Use ft2build.h from the current directory instead of the default. target_include_directories(freetype diff --git a/3party/glaze b/3party/glaze new file mode 160000 index 000000000..5a58d7936 --- /dev/null +++ b/3party/glaze @@ -0,0 +1 @@ +Subproject commit 5a58d7936ece70eeb64cc30aa82e9e83ae9c05bb diff --git a/3party/harfbuzz/CMakeLists.txt b/3party/harfbuzz/CMakeLists.txt index d7041d46b..871e34359 100644 --- a/3party/harfbuzz/CMakeLists.txt +++ b/3party/harfbuzz/CMakeLists.txt @@ -11,14 +11,23 @@ target_include_directories(${PROJECT_NAME} harfbuzz/src ) -# Keep these settigns in sync with xcode/harfbuzz project. -target_compile_options(${PROJECT_NAME} - PRIVATE - -fno-rtti - -fno-exceptions - -fno-threadsafe-statics - $<$,$>:-Wno-format-pedantic> -) +if (MSVC) + target_compile_options(${PROJECT_NAME} + PRIVATE + /GR- + /EHsc- + /Zc:threadSafeInit- + ) +else() + # Keep these settings in sync with xcode/harfbuzz project. + target_compile_options(${PROJECT_NAME} + PRIVATE + -fno-rtti + -fno-exceptions + -fno-threadsafe-statics + $<$,$>:-Wno-format-pedantic> + ) +endif() target_compile_definitions(${PROJECT_NAME} PRIVATE @@ -28,14 +37,20 @@ target_compile_definitions(${PROJECT_NAME} #$<$:HAVE_CORETEXT> HAVE_ATEXIT HAVE_GETPAGESIZE - HAVE_MMAP HAVE_MPROTECT - HAVE_PTHREAD HAVE_SYSCONF - HAVE_SYS_MMAN_H - HAVE_UNISTD_H ) +if (NOT MSVC) + target_compile_definitions(${PROJECT_NAME} + PRIVATE + HAVE_MMAP + HAVE_PTHREAD + HAVE_SYS_MMAN_H + HAVE_UNISTD_H + ) +endif() + target_link_libraries(${PROJECT_NAME} Freetype::Freetype) add_library(harfbuzz::harfbuzz ALIAS harfbuzz) diff --git a/3party/icu/CMakeLists.txt b/3party/icu/CMakeLists.txt index 0258c86dc..7b8df431b 100644 --- a/3party/icu/CMakeLists.txt +++ b/3party/icu/CMakeLists.txt @@ -172,6 +172,7 @@ add_library(icuuc icu/icu4c/source/common/uvector.cpp icu/icu4c/source/common/uvectr32.cpp icu/icu4c/source/common/uvectr64.h + icu/icu4c/source/common/wintz.cpp icu/icu4c/source/common/wintz.h ) diff --git a/3party/protobuf/CMakeLists.txt b/3party/protobuf/CMakeLists.txt index 58e6a1d0b..850776731 100644 --- a/3party/protobuf/CMakeLists.txt +++ b/3party/protobuf/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRC protobuf/src/google/protobuf/message_lite.cc protobuf/src/google/protobuf/repeated_field.cc protobuf/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc + protobuf/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc protobuf/src/google/protobuf/stubs/bytestream.cc protobuf/src/google/protobuf/stubs/common.cc protobuf/src/google/protobuf/stubs/int128.cc diff --git a/3party/succinct/mapper.hpp b/3party/succinct/mapper.hpp index 2b43a7083..8e096c8e0 100644 --- a/3party/succinct/mapper.hpp +++ b/3party/succinct/mapper.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include "mappable_vector.hpp" @@ -283,12 +282,6 @@ namespace succinct { namespace mapper { return mapper.bytes_read(); } - template - size_t map(T& val, boost::iostreams::mapped_file_source const& m, uint64_t flags = 0, const char* friendly_name = "") - { - return map(val, m.data(), flags, friendly_name); - } - template uint64_t size_of(T& val) { diff --git a/3party/succinct/util.hpp b/3party/succinct/util.hpp index 47a4f332b..8121a31a3 100644 --- a/3party/succinct/util.hpp +++ b/3party/succinct/util.hpp @@ -8,7 +8,6 @@ #include #include -#include namespace succinct { namespace util { @@ -205,29 +204,6 @@ namespace succinct { namespace util { std::string m_cur_value; }; - struct mmap_lines - { - typedef buffer_line_iterator iterator; - typedef buffer_line_iterator const_iterator; - - mmap_lines(std::string filename) - : m_map(filename) - {} - - const_iterator begin() const - { - return const_iterator(m_map.data(), m_map.size()); - } - - const_iterator end() const - { - return const_iterator(); - } - - private: - boost::iostreams::mapped_file_source m_map; - }; - struct input_error : std::invalid_argument { input_error(std::string const& what) diff --git a/3party/vulkan_wrapper/vulkan_wrapper.cpp b/3party/vulkan_wrapper/vulkan_wrapper.cpp index 4322a49d9..bd56577fb 100755 --- a/3party/vulkan_wrapper/vulkan_wrapper.cpp +++ b/3party/vulkan_wrapper/vulkan_wrapper.cpp @@ -19,7 +19,18 @@ extern "C" { #endif #include "vulkan_wrapper.h" + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else #include +#endif int InitVulkan(void) { #if defined(__APPLE__) @@ -30,6 +41,9 @@ int InitVulkan(void) { if (!libvulkan) { libvulkan = dlopen("libMoltenVK.dylib", RTLD_NOW | RTLD_LOCAL); } +#elif defined( _WIN32 ) + HMODULE libvulkan = LoadLibraryA("vulkan-1.dll"); + auto dlsym = [](HMODULE h, char const * name) { return GetProcAddress(h, name); }; #else void* libvulkan = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL); if (!libvulkan) { diff --git a/CMakeLists.txt b/CMakeLists.txt index 975286cbd..9ac12fa55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.22.1) project(omim C CXX) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_C_STANDARD 17) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_C_STANDARD 23) # Our code does not rely on gnu extensions. set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_C_EXTENSIONS OFF) @@ -39,18 +39,16 @@ if (APPLE AND NOT ("${CMAKE_SYSTEM_NAME}" STREQUAL Android)) # https://gitlab.kitware.com/cmake/cmake/-/issues/21963 enable_language(OBJC) set(CMAKE_OBJC_EXTENSIONS OFF) - set(CMAKE_OBJC_STANDARD 11) + set(CMAKE_OBJC_STANDARD 23) set(CMAKE_OBJC_FLAGS -fobjc-arc) set(CMAKE_OBJC_VISIBILITY_PRESET hidden) enable_language(OBJCXX) set(CMAKE_OBJCXX_EXTENSIONS OFF) - set(CMAKE_OBJCXX_STANDARD 20) + set(CMAKE_OBJCXX_STANDARD 23) set(CMAKE_OBJCXX_FLAGS -fobjc-arc) set(CMAKE_OBJCXX_VISIBILITY_PRESET hidden) endif() -execute_process(COMMAND "./configure.sh" WORKING_DIRECTORY ${OMIM_ROOT}) - message(STATUS "Using compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") if (CMAKE_UNITY_BUILD) @@ -87,7 +85,7 @@ if (MSVC) add_compile_options(/utf-8) add_link_options(/INCREMENTAL:NO) else() - add_compile_options(-ffast-math) + add_compile_options(-ffast-math $<$:-Wno-psabi>) endif() if (PLATFORM_WIN) @@ -185,7 +183,7 @@ if (NOT PLATFORM_IPHONE AND NOT PLATFORM_ANDROID) endif() # To allow #include "base/file_name.hpp" in all sources. -include_directories(${CMAKE_HOME_DIRECTORY}) +include_directories("${CMAKE_HOME_DIRECTORY}" "${CMAKE_HOME_DIRECTORY}/libs" "${CMAKE_HOME_DIRECTORY}/tools") if (USE_PCH) message(STATUS "Precompiled headers are ON") @@ -197,7 +195,7 @@ if (USE_PCH) endif() # Should be on the root level, not in 3party, so tests can get these dependencies. -if (PLATFORM_LINUX OR PLATFORM_WIN) +if (PLATFORM_LINUX) find_package(ICU COMPONENTS uc i18n data REQUIRED) find_package(Freetype REQUIRED) find_package(harfbuzz REQUIRED) @@ -227,50 +225,22 @@ endif() # Used in qt/ and shaders/ find_package(Python3 REQUIRED COMPONENTS Interpreter) -add_subdirectory(base) -add_subdirectory(coding) -add_subdirectory(descriptions) -add_subdirectory(drape) -add_subdirectory(drape_frontend) -add_subdirectory(editor) -add_subdirectory(ge0) -add_subdirectory(generator/mwm_diff) -add_subdirectory(geometry) -add_subdirectory(indexer) -add_subdirectory(kml) -add_subdirectory(map) -add_subdirectory(cppjansson) -add_subdirectory(platform) -add_subdirectory(routing) -add_subdirectory(routing_common) -add_subdirectory(search) -add_subdirectory(shaders) -add_subdirectory(storage) -add_subdirectory(tracking) -add_subdirectory(traffic) -add_subdirectory(traffxml) -add_subdirectory(transit) +add_subdirectory(libs) if (PLATFORM_DESKTOP) - omim_add_tool_subdirectory(feature_list) + add_subdirectory(dev_sandbox) add_subdirectory(generator) - add_subdirectory(openlr) - add_subdirectory(poly_borders) - omim_add_tool_subdirectory(topography_generator) - add_subdirectory(track_analyzing) - omim_add_tool_subdirectory(track_generator) + add_subdirectory(tools) if (NOT SKIP_QT_GUI) add_subdirectory(qt) - omim_add_tool_subdirectory(skin_generator) endif() if (GENERATOR_TOOL) add_compile_options(-march=native -mtune=native) message(STATUS "target CPU optimizations enabled, produced binaries will NOT work on a different CPU") endif() - add_subdirectory(dev_sandbox) endif() -omim_add_test_subdirectory(qt_tstfrm) +omim_add_test_subdirectory(libs/qt_tstfrm) if (PLATFORM_ANDROID) add_subdirectory(android/sdk/src/main/cpp) diff --git a/README.md b/README.md index bb8cf175c..de158dafd 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,20 @@ @@ -41,8 +41,6 @@ A community-led free & open source maps app based on [OpenStreetMap](https://www.openstreetmap.org), built for transparency, privacy, and not-for-profit values. A fork of Organic Maps, originally based on Maps.ME. -**Available for:** Android, iOS, ARM macOS, and alpha Linux/macOS desktop builds (also usable on Linux phones). -

App Store @@ -77,9 +75,10 @@ A community-led free & open source maps app based on [OpenStreetMap](https://www ## ⚡️ Highlights - **Offline-first**: Navigate without a connection -- **Privacy-respecting**: No tracking, Ads or data collection +- **Privacy-respecting**: No tracking, ads or data collection - **Lightweight**: Battery- and space-efficient - **Simple**: Polished, user-focused interface +- **Cross-platform**: Available for Android, iOS, MacOS, and Linux. - **Community-built**: Free, open source, and collaborative - **Transparent**: Open finances and governance @@ -140,7 +139,7 @@ There is a dedicated [Zulip](https://codeberg.org/comaps/Governance/src/branch/m ## 💸 Funding -CoMaps is free. To stay that way, it relies on your support. +CoMaps is free. To fund development, we rely on your voluntary support ♥️ Donate via [OpenCollective](https://opencollective.com/comaps/donate) or [Liberapay](https://liberapay.com/CoMaps). The project's financial information is completely open and transparent at [our Open Collective](https://opencollective.com/comaps). diff --git a/android/app/build.gradle b/android/app/build.gradle index 23da71be5..a46f3a8c1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -138,8 +138,6 @@ android { disable 'MissingTranslation' // https://github.com/organicmaps/organicmaps/issues/3551 disable 'MissingQuantity', 'UnusedQuantity' - // https://github.com/organicmaps/organicmaps/issues/3550 - disable 'ByteOrderMark' // https://github.com/organicmaps/organicmaps/issues/1077 disable 'CustomSplashScreen' // https://github.com/organicmaps/organicmaps/issues/3610 diff --git a/android/app/src/fdroid/java/app/organicmaps/location b/android/app/src/fdroid/java/app/organicmaps/location new file mode 120000 index 000000000..c3bacf635 --- /dev/null +++ b/android/app/src/fdroid/java/app/organicmaps/location @@ -0,0 +1 @@ +../../../../google/java/app/organicmaps/location \ No newline at end of file diff --git a/android/app/src/fdroid/java/app/organicmaps/sdk/location b/android/app/src/fdroid/java/app/organicmaps/sdk/location deleted file mode 120000 index f41d2255b..000000000 --- a/android/app/src/fdroid/java/app/organicmaps/sdk/location +++ /dev/null @@ -1 +0,0 @@ -../../../../../google/java/app/organicmaps/sdk/location \ No newline at end of file diff --git a/android/app/src/fdroid/play/listings/bn-BD/short-description.txt b/android/app/src/fdroid/play/listings/bn-BD/short-description.txt new file mode 100644 index 000000000..8535fd3f4 --- /dev/null +++ b/android/app/src/fdroid/play/listings/bn-BD/short-description.txt @@ -0,0 +1 @@ +মানচিত্র নেভিগেশন - আপনার যাত্রা সম্পর্কে আরও জানুন - সম্প্রদায় কর্তৃক পরিচালিত diff --git a/android/app/src/fdroid/play/listings/bn-BD/title.txt b/android/app/src/fdroid/play/listings/bn-BD/title.txt new file mode 100644 index 000000000..608379a9f --- /dev/null +++ b/android/app/src/fdroid/play/listings/bn-BD/title.txt @@ -0,0 +1 @@ +কোম্যাপস - অফলাইনে হাইকিং, সাইকেলিং ও ড্রাইভিং diff --git a/android/app/src/fdroid/play/listings/bn/short-description.txt b/android/app/src/fdroid/play/listings/bn/short-description.txt deleted file mode 100644 index aac6a2300..000000000 --- a/android/app/src/fdroid/play/listings/bn/short-description.txt +++ /dev/null @@ -1 +0,0 @@ -সহজ মানচিত্র নেভিগেশন - আপনার যাত্রা সম্পর্কে আরও জানুন - সম্প্রদায় কর্তৃক পরিচালিত diff --git a/android/app/src/fdroid/play/listings/bn/title.txt b/android/app/src/fdroid/play/listings/bn/title.txt deleted file mode 100644 index 6123e8757..000000000 --- a/android/app/src/fdroid/play/listings/bn/title.txt +++ /dev/null @@ -1 +0,0 @@ -কোম্যাপস - অফলাইনে হাইকিং, সাইকেলিং এবং ড্রাইভিং করুন গোপনীয়তা সহ 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 deleted file mode 100644 index b6009257a..000000000 --- a/android/app/src/fdroid/play/listings/cs-CZ/release-notes.txt +++ /dev/null @@ -1,7 +0,0 @@ -• Data OpenStreetMap k 4. 8. -• vylepšené barvy mapy pro vodu, lesy, křoviny, různé vybavení, pěší zóny atd. -• přidány stanice lesní stráže, krytých parkovišť pro jízdní kola, únikových her, úschoven zavazadel, partnerských pošt -• vylepšeny výškové vrstevnice na 20 m pro některé oblíbené turistické oblasti -• podpora více zkratek a aliasů pro vyhledávání -• přidání ikon pro vyhledávání a záložky pro rychlé občerstvení, jízdní kola a dobíjecí stanice -• plynulejší pohyb šipky pro určení polohy 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 6c49d88e9..31760b19a 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,7 +1,8 @@ -• OpenStreetMap-Daten vom 4. August -• Verbesserte Farben für Wasser, Wälder, Gestrüpp, verschiedene Einrichtungen, Fussgängerbereiche etc. -• Besucherstationen, überdachte Fahrradparkplätze, Escaperooms, Gepäckschließfächer, und Postpartner hinzugefügt -• Konturhöhenlinien aktualisiert, bis zu 20m für beliebte Wanderregionen -• Unterstützung für mehr Such-Abkürzungen und Synonyme -• Such- und Lesezeichen-Symbole für Fast Food, Rad- und Lade-Stationen -• Der Positionspfeil bewegt sich gleichmässiger +• OpenStreetMap-Daten vom 22. August +• Neue Geschwindigkeitsanzeige & Details aufgezeichneter Strecken +• Besserer dunkler Modus, neue farbige Labels +• Anzeige wann Orte & Öffnungszeiten überprüft wurden +• Routenplanung berücksichtigt Stoppschilder, Ampeln & bedingte Einschränkungen. +• GPS-Peilung wird statt Kompass bevorzugt & höhere GPS-Frequenz. +• Adressen & Notizen im OSM-Editor unterstützt. +• Mehr Verbesserungen an UI-Elementen & Kartenstil, alle Details in den Codeberg-Versionshinweisen! 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 30680de88..ac838ba47 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,7 +1,9 @@ -• OpenStreetMap data as of August 4 -• improve map colors for water, forests, scrubs, various amenities, pedestrian areas etc. -• add ranger stations, covered bicycle parkings, escape games, luggage lockers, post office partners -• upgrade altitude contour lines to 20m step for some popular hiking regions -• support more search abbreviations and aliases -• add search and bookmark icons for fast food, bicycle and charging stations -• more smooth position arrow movements +• OpenStreetMap data as of August 22 +• New current speed indicator, display track info +• Improved dark map style, added coloured labels +• Display when places & opening hours were last checked +• Routing now considers turns, stop signs, lights & conditional restrictions +• Prefer GPS bearing over compass and increase location poll rate +• Enable adding standalone addresses & notes in the editor +• Various UI element & map style improvements +Check our Codeberg release notes for more changes! 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 9ea20621d..7232299c7 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,7 +1,9 @@ -• Datos de OpenStreetMap a fecha 2025.08.04 -• Mejora de colores del mapa para agua, bosques, matorrales, servicios, zonas peatonales, etc. -• Añadidas estaciones de guardabosques, aparcamientos cubiertos de bicis, juegos de escape, consignas y oficinas de correo -• Nuevas curvas de nivel (20 m) en regiones populares para senderismo -• Más abreviaturas y alias de búsqueda -• Iconos de búsqueda y marcadores para comida rápida, bicicletas y estaciones de recarga -• Más fluidez de la flecha de posición +• Datos de OSM a 22-08-2025 +• Nuevo indicador de velocidad e información de pista +• Modo oscuro mejorado y nombres de POI coloreados +• Indicador de última revisión de lugares y horarios de apertura +• Rutas mejoradas teniendo en cuenta giros, señales de STOP, semáforos y restricciones +• Mayor precisión para determinar tu localización y sentido +• Añade direcciones y notas con el editor +• Otras mejoras en elementos de la interfaz y estilo del mapa +Entra en nuestro Codeberg para más cambios! diff --git a/android/app/src/fdroid/play/listings/eu-ES/full-description.txt b/android/app/src/fdroid/play/listings/eu-ES/full-description.txt index f2a1db5d9..778614bd1 100644 --- a/android/app/src/fdroid/play/listings/eu-ES/full-description.txt +++ b/android/app/src/fdroid/play/listings/eu-ES/full-description.txt @@ -24,7 +24,7 @@ Sartu komunitatean eta lagundu maparik onena aplikatzen • Lineaz kanpoko Wikipedia artikuluak • Metroaren garraio geruza eta jarraibideak • Arrastoen grabazioa -• Laster-markak eta ibilbideak esportatu eta inportatu KML, KMZ, GPX formatuetan +• Markagailuak eta arrastoak esportatu eta inportatu KML, KMZ, GPX formatuetan • Gauean erabiltzeko modu iluna • Hobetu mapako datuak guztiontzat oinarrizko editore integratua erabiliz diff --git a/android/app/src/fdroid/play/listings/fa-IR/title.txt b/android/app/src/fdroid/play/listings/fa-IR/title.txt index 4c116ac10..b9a08c074 100644 --- a/android/app/src/fdroid/play/listings/fa-IR/title.txt +++ b/android/app/src/fdroid/play/listings/fa-IR/title.txt @@ -1 +1 @@ -CoMaps - کوه نوردی، دوچرخه سواری و رانندگی افلاین و خصوصی +CoMaps - کوه‌نوردی، دوچرخه‌سواری و رانندگی آفلاین 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 new file mode 100644 index 000000000..13084e62c --- /dev/null +++ b/android/app/src/fdroid/play/listings/fr-FR/release-notes.txt @@ -0,0 +1,9 @@ +• Données OSM du 22 août +• Nouvel indicateur de vitesse et détails des pistes +• Amélioration du style sombre, ajout de labels colorées +• Affichage de la date de dernière vérification d'un lieu +• Support dans le routage des virages, signalisations et restrictions +• Amélioration de la boussole et de la précision GPS +• Support des adresses et des notes dans l'éditeur +• Diverses améliorations d'UI et du style de la carte +Plus d'informations sur notre Codeberg diff --git a/android/app/src/fdroid/play/listings/hr/title.txt b/android/app/src/fdroid/play/listings/hr/title.txt index 2d3b475da..01d072b91 100644 --- a/android/app/src/fdroid/play/listings/hr/title.txt +++ b/android/app/src/fdroid/play/listings/hr/title.txt @@ -1 +1 @@ -CoMaps - Pješačite, biciklirajte, vozite Offline i privatno +CoMaps - Pješačite, biciklirajte, vozite Offline diff --git a/android/app/src/fdroid/play/listings/hu-HU/full-description.txt b/android/app/src/fdroid/play/listings/hu-HU/full-description.txt new file mode 100644 index 000000000..92ffe381a --- /dev/null +++ b/android/app/src/fdroid/play/listings/hu-HU/full-description.txt @@ -0,0 +1,32 @@ +A CoMaps egy közösség által létrehozott, ingyenes és nyílt forráskódú térképalkalmazás, amely az OpenStreetMap térképadatain alapul, és amelyet az átláthatóság, a magánélet védelme és a nonprofit jelleg iránti elkötelezettség erősít. A CoMaps az Organic Maps villája, amely viszont a Maps.ME villája. + +A projekt előzményeiről és az irányáról a codeberg.org/comaps oldalon olvashatsz. +Csatlakozz ott a közösséghez, és segíts a legjobb térképalkalmazás elkészítésében. +• Használd az alkalmazást és terjeszd a hírét +• Adj visszajelzést és jelentsd a problémákat +• Frissítsd és javítsd a térképadatokat az alkalmazásban vagy az OpenStreetMap weboldalán. + +‣ Offline-fókuszú: Tervezd meg és navigáld külföldi útad mobilszolgáltatás nélkül, keress útpontokat egy távoli túra során stb. Az alkalmazás minden funkcióját úgy terveztük, hogy internetkapcsolat nélkül is működjön. +‣ A magánélet tiszteletben tartása: Az alkalmazás az adatvédelem szem előtt tartásával készült, nem azonosítja az embereket, nem követi nyomon és nem gyűjt személyes adatokat, és reklámmentes. +‣ Takarékosdik az akkumulátorral és a tárhellyel: Nem meríti le az akkumulátort, mint más navigációs alkalmazások. A jól összeállítot kis méretű térképek értékes helyet takarítanak meg a telefonon. +‣ Egyszerű és szép grafikus felület, nagyszerű és könnyen használható funkciók, amelyek egyszerűen működnek. +‣ Ingyenes és a közösség által készített: A hozzád hasonló emberek segítettek az alkalmazás létrehozásában azáltal, hogy hozzáadtak helyeket az OpenStreetMap térképhez, tesztelték és visszajelzést adtak a funkciókról, valamint hozzájárultak a fejlesztői képességeikkel és a pénzükkel. +‣ Nyílt és átlátható döntéshozatal és pénzügyek, nonprofit és teljesen nyílt forráskódú. + +Főbb jellemzők: +• Letölthető részletes térképek olyan helyekkel, amelyek sok kereskedelmi térképen nem állnak rendelkezésre. +• Szabadtéri üzemmód kiemelt túraútvonalakkal, táborhelyekkel, vízforrásokkal, csúcsokkal, szintvonalakkal stb. +• Sétaútvonalak és kerékpárutak +• Érdekes pontok, mint például éttermek, benzinkutak, szállodák, üzletek, látnivalók és még sok minden más +• Keresés név, cím vagy az érdekes pontok kategóriája alapján +• Navigáció hangutasításokkal gyalogláshoz, kerékpározáshoz vagy vezetéshez +• Kedvenc helyeid könyvjelzővel láthatod el egyetlen érintéssel +• Wikipedia szócikkek internetkapcsolat nélkül +• Metró tranzit réteg és irányok +• Útvonal mentése +• Könyvjelzők és nyomvonalak exportálása és importálása KML, KMZ, GPX formátumokban +• Sötét üzemmód az éjszakai használathoz +• Térképadatok javítása mindenki számára egy alapvető beépített szerkesztő segítségével + +Itt van a szabadság +Fedezd fel az utadat, navigálj a világban úgy, hogy a magánélet és a közösség kerüljön előtérbe! diff --git a/android/app/src/fdroid/play/listings/ia/short-description.txt b/android/app/src/fdroid/play/listings/ia/short-description.txt deleted file mode 100644 index df5517d58..000000000 --- a/android/app/src/fdroid/play/listings/ia/short-description.txt +++ /dev/null @@ -1 +0,0 @@ -Navigation facile del mappa – Discoperi tu viage – Alimentate per le communitate diff --git a/android/app/src/fdroid/play/listings/it-IT/full-description.txt b/android/app/src/fdroid/play/listings/it-IT/full-description.txt index 780d8ab24..9bcdb7ae9 100644 --- a/android/app/src/fdroid/play/listings/it-IT/full-description.txt +++ b/android/app/src/fdroid/play/listings/it-IT/full-description.txt @@ -1,4 +1,4 @@ -Una app sviluppata dalla comunità, gratuita e open-source, basata su OpenStreetMap e sull'impegno alla trasparenza, al rispetto della Privacy senza scopo di lucro. CoMaps è uno spin-off di Organic Maps, che a sua volta deriva da Maps.ME. +Una app realizzata dalla community gratuita e open-source, basata su OpenStreetMap e sull'impegno alla trasparenza, al rispetto della Privacy e senza scopo di lucro. CoMaps è uno spin-off di Organic Maps, che a sua volta deriva da Maps.ME. Leggi delle ragioni del progetto e della sua direzione futura su codeberg.org/comaps. Unisciti alla nostra comunità e aiutaci a creare la migliore app di mappe. @@ -15,7 +15,7 @@ Unisciti alla nostra comunità e aiutaci a creare la migliore app di mappe. Funzionalità principali: • Scarica mappe dettagliate di luoghi che non sono disponibili su Google Maps -• Modalità Outdoor con percorsi escursionistici, campeggi, sorgenti d'acqua, picchi, dislivelli ed altro evidenziati +• Modalità Outdoor con percorsi escursionistici, campeggi, sorgenti d'acqua, picchi, dislivelli ed altri punti d'interesse evidenziati • Strade pedonali e piste ciclabili • Punti d'interesse come ristoranti, stazioni di benzina, hotel, negozi, luoghi turistici e molto altro • Cerca per nome, indirizzo o categoria 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 deleted file mode 100644 index a2a553b3a..000000000 --- a/android/app/src/fdroid/play/listings/it-IT/release-notes.txt +++ /dev/null @@ -1,7 +0,0 @@ -• Dati di OpenStreetMap aggiornati al 4 Agosto -• Migliorati i colori per acqua, foreste, servizi etc -• Aggiunte le stazioni delle guardie forestali, i parcheggi coperti per bici, gli escape games e altri servizi -• Aggiornato l'intervallo delle isolinee a 20 m per le zone escursionistiche più popolari -• Aggiunto il supporto per un maggior numero di alias -• Aggiunte le icone per i fast food, i punti di ricarica e le biciclette -• Resi più fluidi i movimenti della freccia di posizione diff --git a/android/app/src/fdroid/play/listings/it-IT/short-description.txt b/android/app/src/fdroid/play/listings/it-IT/short-description.txt index c368e5606..ac9d7e2f7 100644 --- a/android/app/src/fdroid/play/listings/it-IT/short-description.txt +++ b/android/app/src/fdroid/play/listings/it-IT/short-description.txt @@ -1 +1 @@ -Navigazione semplice - Immergiti nella tua avventura - Sviluppato dalla comunità +Navigazione semplice - Immergiti nella tua avventura - Realizzato dalla comunità diff --git a/android/app/src/fdroid/play/listings/nl-NL/title.txt b/android/app/src/fdroid/play/listings/nl-NL/title.txt index 43c1a8662..ca1d19158 100644 --- a/android/app/src/fdroid/play/listings/nl-NL/title.txt +++ b/android/app/src/fdroid/play/listings/nl-NL/title.txt @@ -1 +1 @@ -CoMaps - Wandel, fiets, rijdt offline met privacy +CoMaps - Wandel, fiets, rijd offline met privacy diff --git a/android/app/src/fdroid/play/listings/no-NO/short-description.txt b/android/app/src/fdroid/play/listings/no-NO/short-description.txt new file mode 100644 index 000000000..8bee04c06 --- /dev/null +++ b/android/app/src/fdroid/play/listings/no-NO/short-description.txt @@ -0,0 +1 @@ +Lett kart navigasjon - Opplev mere på din reise - Drevet av felleskapet diff --git a/android/app/src/fdroid/play/listings/no-NO/title.txt b/android/app/src/fdroid/play/listings/no-NO/title.txt new file mode 100644 index 000000000..60a12f1b1 --- /dev/null +++ b/android/app/src/fdroid/play/listings/no-NO/title.txt @@ -0,0 +1 @@ +CoMaps - Gå tur, sykkel, kjør - med personvern 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 d9ed20642..a5b703092 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,7 +1,8 @@ -• Dados OSM de 4/08 -• Melhoria nas cores para água, florestas, matagais, serviços, áreas de pedestres, etc. -• Adição de guarda-florestais, estacionamentos cobertos para bicicletas, jogos de fuga, armários para bagagem e parceiros postais -• Melhoria na precisão de curvas de nível para 20 m em algumas regiões populares -• Suporte a mais abreviações para busca -• Adição de ícones de pesquisa e favoritos para fast food, bicicletas e estações de recarga -• Movimentos mais suaves para seta de posição +• Dados OSM de 22/08 +• Novo indicador de velocidade e tela de informações de trilha +• Melhor estilo de mapa escuro, rótulos coloridos adicionados +• Exibição da última atualização de locais e horários de funcionamento +• O roteamento agora considera conversões, PARE, semáforos, e restrições condicionais +• Preferência por orientação GPS em vez de bússola e aumento da frequência de localização +• Editor: adição de endereços e notas independentes +• Melhorias em elementos da interface e estilo de mapa diff --git a/android/app/src/fdroid/play/listings/pt/full-description.txt b/android/app/src/fdroid/play/listings/pt/full-description.txt deleted file mode 100644 index 4e1476fbf..000000000 --- a/android/app/src/fdroid/play/listings/pt/full-description.txt +++ /dev/null @@ -1,32 +0,0 @@ -Um aplicativo de mapas gratuito e de código aberto, liderado pela comunidade, baseado em dados do OpenStreetMap e reforçado pelo compromisso com a transparência, privacidade e sem fins lucrativos. O CoMaps é um fork/spin-off do Organic Maps, que por sua vez é um fork do Maps.ME. - -Leia mais sobre os motivos do projeto e sua direção em codeberg.org/comaps. -Junte-se à comunidade e ajude a criar o melhor aplicativo de mapas. -• Use o aplicativo e divulgue-o. -• Envie feedback e relate problemas. -• Atualize os dados do mapa no aplicativo ou no site do OpenStreetMap. - -‣ Foco offline: Planeje e navegue em sua viagem ao exterior sem a necessidade de sinal de celular, pesquise pontos de referência durante uma caminhada distante, etc. Todas as funções do aplicativo foram projetadas para funcionar offline. -‣ Respeitando a privacidade: O aplicativo foi projetado com a privacidade em mente - não identifica pessoas, não rastreia e não coleta informações pessoais. Sem anúncios. -‣ Simples e sofisticado: recursos essenciais e fáceis de usar que simplesmente funcionam. -‣ Economiza bateria e espaço: Não consome muita bateria como outros aplicativos de navegação. Mapas compactos economizam espaço precioso no seu celular. -‣ Gratuito e desenvolvido pela comunidade: Pessoas como você ajudaram a desenvolver o aplicativo adicionando lugares ao OpenStreetMap, testando e dando feedback sobre os recursos e contribuindo com suas habilidades de desenvolvimento e dinheiro. -‣ Tomada de decisões e finanças abertas e transparentes, sem fins lucrativos e totalmente de código aberto. - -Principais recursos: -• Mapas detalhados para download com locais não disponíveis no Google Maps -• Modo ao ar livre com trilhas em destaque, acampamentos, fontes de água, picos, curvas de nível, etc. -• Trilhas para caminhada e ciclovias -• Pontos de interesse como restaurantes, postos de gasolina, hotéis, lojas, pontos turísticos e muito mais -• Pesquise por nome, endereço ou por categoria de ponto de interesse -• Navegação com anúncios de voz para caminhadas, ciclismo ou direção -• Marque seus lugares favoritos com um único toque -• Artigos offline da Wikipédia -• Camada e direções de transporte público do metrô -• Gravação de trilhas -• Exporte e importe favoritos e trilhas nos formatos KML, KMZ e GPX -• Um modo escuro para usar à noite -• Aprimore os dados do mapa para todos usando um editor básico integrado - -A Liberdade Chegou -Descubra sua jornada, navegue pelo mundo com privacidade e comunidade em primeiro lugar! diff --git a/android/app/src/fdroid/play/listings/pt/short-description.txt b/android/app/src/fdroid/play/listings/pt/short-description.txt deleted file mode 100644 index fb5a97658..000000000 --- a/android/app/src/fdroid/play/listings/pt/short-description.txt +++ /dev/null @@ -1 +0,0 @@ -Navegação fácil nos mapas - Descubra mais sobre o seu percurso - Feito por todos diff --git a/android/app/src/fdroid/play/listings/pt/title.txt b/android/app/src/fdroid/play/listings/pt/title.txt deleted file mode 100644 index e7d14d499..000000000 --- a/android/app/src/fdroid/play/listings/pt/title.txt +++ /dev/null @@ -1 +0,0 @@ -CoMaps - Mapas e Navegação - Offline e Privada diff --git a/android/app/src/fdroid/play/listings/ro/title.txt b/android/app/src/fdroid/play/listings/ro/title.txt index 920d58402..7d5b5ad93 100644 --- a/android/app/src/fdroid/play/listings/ro/title.txt +++ b/android/app/src/fdroid/play/listings/ro/title.txt @@ -1 +1 @@ -CoMaps - Drumeții, Ciclism, Condus Offline în confidențialitate +CoMaps - călătorește offline cu confidențialitate 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 75b7bbda7..4450553c9 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,7 +1,9 @@ -• Карты OpenStreetMap от 4 августа -• Улучшен цвет воды, леса, кустарников, различных объектов инфраструктуры, пешеходных зон и т.д. -• Добавлены лесничества, крытые велопарковки, квесты, камеры хранения -• Для некоторых популярных туристических регионов добавлены линии высот 20м -• Поддержка дополнительных поисковых сокращений и синонимов -• Добавлены иконки меток и результатов поиска для фастфуда, велопарковок и зарядных станций -• Более плавное движение стрелки местоположения +• Данные 22 августа +• Новый спидометр,информация о маршруте +• Улучшен тёмный стиль,цветные метки +• Время последней проверки часов работы +• Построении маршрута учитывает повороты, знак «Стоп»,светофоры и ограничения +• Предпочтение отдается GPS-координатам,а не компасу,увеличена частота опроса местоположения +• Можно добавлять отдельные адреса и заметки в редакторе +• Улучшены различные элементы пользовательского интерфейса и стиль карты +Ознакомьтесь с примечания к релизу про изменения! diff --git a/android/app/src/fdroid/play/listings/sr/full-description.txt b/android/app/src/fdroid/play/listings/sr/full-description.txt index 2f0d58a85..4b26f789a 100644 --- a/android/app/src/fdroid/play/listings/sr/full-description.txt +++ b/android/app/src/fdroid/play/listings/sr/full-description.txt @@ -1,32 +1,32 @@ -Бесплатна апликација отвореног кода коју је обављала заједница и заснива се на ОpenStreetMap-у подацима, усмерени транспарентност, приватност и непрофитни. CoMaps је Апликације за органске карте виљушка, које је пак форк Maps.ME. +Бесплатна апликација отвореног кода коју одржава заједница, која се заснива на ОpenStreetMap подацима. Непрофитна, транспарентна и цени вашу приватност. CoMaps је настала од апликације Organic Maps, које је настала од апликације Maps.ME. Прочитајте о разлозима пројекта и његовог правца на codeberg.org/comaps Придружите се отвореној заједници и помозите да направимо најбољу апликацију за мапе • Користите апликацију и проширите глас о томе • Дајте повратне информације и пријавите проблеме -• Ажурирајте податке на мапи у апликацији или на веб локацији OpenStreetMap +• Ажурирајте податке на мапи у апликацији или на сајту OpenStreetMap -‣ фокусирано на офлајн Вар : Планирајте и управљајте путовањем у иностранству без потребе за мобилним услугама, тражите путне тачке док је на даљинском покрету, итд. Све апликације су дизајниране за рад ван мреже. -‣ Поштовање приватности : Апликација је осмишљена задржавања у уму приватност - не идентификује људе, не прати или прикупља личне податке. Нема реклама. -‣ Једноставно и елегантно : Апликација је тривијална за употребу и једноставно функционише. -‣ Чува вашу батерију и простор : не конзумира батерију као остале навигационе апликације. Компактне картице сачувајте драгоцени простор на вашем телефону. -‣ Отворено и направио је заједницу : Људи попут вас је помогли да додају апликацију додавањем локација на OpenStreetMap-у, тестирању и давање повратних информација о апликацији и доприносе вашим развојним вештинама и новцу. -‣ Отворено и транспарентно доношење одлука и употреба финансија, непрофитна и потпуно отворена извора. +‣ Фокусирано на офлајн рад: Планирајте и управљајте путовањем у иностранству без потребе за мобилним интернетом, тражите успутне тачке док сте на забаченом планинском путу, итд. Све функционалности су дизајниране за рад без интернета. +‣ Поштовање приватности: Апликација је осмишљена са приватношћу корисника на уму - не идентификује људе, не прати или прикупља личне податке. Нема реклама. +‣ Једноставно и елегантно: Апликација је лака за употребу и једноставно функционише. +‣ Чува вашу батерију и простор: Не троши батерију као остале апликације за навигацију. Компактне мапе чувају драгоцени простор на вашем телефону. +‣ Отворено и направљено од стране заједнице: Људи попут вас су допринели развоју апликације додавањем локација на OpenStreetMap, тестирањем, давањем повратних информација о апликацији и помогли су својим програмерским вештинама и донацијама. +‣ Отворено и транспарентно доношење одлука и употреба донација, непрофитна и потпуно отвореног кода. Главне карактеристике : -• Преузмите детаљне мапе са локацијама које нису доступне са Гоогле мапама -• Улазнице за излете са истакнутим планинарским стазама, камповима, изворима воде, планинске врхове, контурне линије итд. +• Преузмимање детаљне мапе са локацијама које нису доступне са Google мапама +• Режим за излете са истакнутим планинарским стазама, камповима, изворима воде, планинским врховима, изохипсама итд. • Стазе за планинарење и бицикле -• Тачке интереса као што су ресторани, бензинске пумпе, хотели, продавнице, погледа и још много тога -• Претражите име или адресу или по категоријама камате -• навигација са гласовним најавама за ходање, бициклизам или аутомобил -• Означите своја омиљена места једним додиром +• Тачке интереса као што су ресторани, бензинске пумпе, хотели, продавнице, знаменитости и још много тога +• Претрага по називу, адреси или категоријама +• Навигација са гласовним инструкцијама за ходање, вожњу бицикла или аутомобила +• Означивање омиљених места једним додиром • Офлајн чланци Википедије -• транзитни слој подземне пруге са навигацијом -• Снимање ГПС трагова -• Ознака и увоз и увоз у KML, KMZ, GPX формате +• Транзитни слој подземне железнице са навигацијом +• Снимање ГПС путања +• Увоз и увоз маркера и путања у KML, KMZ, GPX формате • Тамни режим за употребу током ноћи -• Побољшајте карту за све кориснике, користећи основни уредни уредник мапе +• Побољшајте карту за све кориснике, користећи основни едитор за мапе Слобода је овде -Откријте своје путовање, крећете се са свијетом са приватношћу и заједницом на првом месту! +Откријте своје путовање, крећете се са светом са приватношћу и заједницом на првом месту! diff --git a/android/app/src/fdroid/play/listings/sr/release-notes.txt b/android/app/src/fdroid/play/listings/sr/release-notes.txt deleted file mode 100644 index 1ff7167c5..000000000 --- a/android/app/src/fdroid/play/listings/sr/release-notes.txt +++ /dev/null @@ -1,7 +0,0 @@ -• подаци из OpenStreetMap-а од 4. августа -• побољшане боје на мапи за воду, шуме, жбуње, разне објекте, пешачке зоне итд. -• додате станице ренџера, наткривена паркинг места за бицикле, escape room-ови, ормарићи за пртљаг -• унапређене изохипсе на кораке од 20 м за популарне планинарске регионе -• подршка за више скраћеница и алтернативних назива у претрази -• додате иконе за претрагу и обележавање за брзу храну, бицикле и станице за пуњење -• равномерније кретање стрелице која приказује позицију \ No newline at end of file diff --git a/android/app/src/google/java/app/organicmaps/sdk/location/GoogleFusedLocationProvider.java b/android/app/src/google/java/app/organicmaps/location/GoogleFusedLocationProvider.java similarity index 98% rename from android/app/src/google/java/app/organicmaps/sdk/location/GoogleFusedLocationProvider.java rename to android/app/src/google/java/app/organicmaps/location/GoogleFusedLocationProvider.java index 22182ceb8..fdcf24964 100644 --- a/android/app/src/google/java/app/organicmaps/sdk/location/GoogleFusedLocationProvider.java +++ b/android/app/src/google/java/app/organicmaps/location/GoogleFusedLocationProvider.java @@ -1,4 +1,4 @@ -package app.organicmaps.sdk.location; +package app.organicmaps.location; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; @@ -10,6 +10,7 @@ import android.location.Location; import android.os.Looper; import androidx.annotation.NonNull; import androidx.annotation.RequiresPermission; +import app.organicmaps.sdk.location.BaseLocationProvider; import app.organicmaps.sdk.util.LocationUtils; import app.organicmaps.sdk.util.log.Logger; import com.google.android.gms.common.api.ApiException; diff --git a/android/app/src/google/java/app/organicmaps/sdk/location/LocationProviderFactory.java b/android/app/src/google/java/app/organicmaps/location/LocationProviderFactoryImpl.java similarity index 56% rename from android/app/src/google/java/app/organicmaps/sdk/location/LocationProviderFactory.java rename to android/app/src/google/java/app/organicmaps/location/LocationProviderFactoryImpl.java index eea7c1207..f5afff6dd 100644 --- a/android/app/src/google/java/app/organicmaps/sdk/location/LocationProviderFactory.java +++ b/android/app/src/google/java/app/organicmaps/location/LocationProviderFactoryImpl.java @@ -1,23 +1,25 @@ -package app.organicmaps.sdk.location; +package app.organicmaps.location; import android.content.Context; import androidx.annotation.NonNull; +import app.organicmaps.sdk.location.AndroidNativeProvider; +import app.organicmaps.sdk.location.BaseLocationProvider; +import app.organicmaps.sdk.location.LocationProviderFactory; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.log.Logger; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; -public class LocationProviderFactory +public class LocationProviderFactoryImpl implements LocationProviderFactory { - private static final String TAG = LocationProviderFactory.class.getSimpleName(); + private static final String TAG = LocationProviderFactoryImpl.class.getSimpleName(); - public static boolean isGoogleLocationAvailable(@NonNull Context context) + public boolean isGoogleLocationAvailable(@NonNull Context context) { return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS; } - public static BaseLocationProvider getProvider(@NonNull Context context, - @NonNull BaseLocationProvider.Listener listener) + public BaseLocationProvider getProvider(@NonNull Context context, @NonNull BaseLocationProvider.Listener listener) { if (isGoogleLocationAvailable(context) && Config.useGoogleServices()) { diff --git a/android/app/src/huawei/java/app/organicmaps/location b/android/app/src/huawei/java/app/organicmaps/location new file mode 120000 index 000000000..c3bacf635 --- /dev/null +++ b/android/app/src/huawei/java/app/organicmaps/location @@ -0,0 +1 @@ +../../../../google/java/app/organicmaps/location \ No newline at end of file diff --git a/android/app/src/huawei/java/app/organicmaps/sdk/location b/android/app/src/huawei/java/app/organicmaps/sdk/location deleted file mode 120000 index f41d2255b..000000000 --- a/android/app/src/huawei/java/app/organicmaps/sdk/location +++ /dev/null @@ -1 +0,0 @@ -../../../../../google/java/app/organicmaps/sdk/location \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8453ff612..dc9ed266e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ values = new ArrayList<>(); for (ElevationInfo.Point point : info.getPoints()) - values.add(new Entry((float) point.getDistance(), point.getAltitude())); + values.add(new Entry((float) point.getDistance(), point.getAltitude(), point)); - LineDataSet set = new LineDataSet(values, "Elevation_profile_points"); + LineDataSet set = new LineDataSet(values, ELEVATION_PROFILE_POINTS); set.setMode(LineDataSet.Mode.CUBIC_BEZIER); set.setCubicIntensity(CUBIC_INTENSITY); set.setDrawFilled(true); @@ -173,8 +168,8 @@ public class ChartController implements OnChartValueSelectedListener, mChart.setData(data); mChart.animateX(CHART_ANIMATION_DURATION); - mMinAltitude.setText(Framework.nativeFormatAltitude(info.getMinAltitude())); - mMaxAltitude.setText(Framework.nativeFormatAltitude(info.getMaxAltitude())); + mMinAltitude.setText(Framework.nativeFormatAltitude(stats.getMinElevation())); + mMaxAltitude.setText(Framework.nativeFormatAltitude(stats.getMaxElevation())); highlightActivePointManually(); } @@ -192,7 +187,9 @@ public class ChartController implements OnChartValueSelectedListener, if (mTrackId == Utils.INVALID_ID) return; - BookmarkManager.INSTANCE.setElevationActivePoint(mTrackId, e.getX()); + if (mInformSelectedActivePointToCore) + BookmarkManager.INSTANCE.setElevationActivePoint(mTrackId, e.getX(), (ElevationInfo.Point) e.getData()); + mInformSelectedActivePointToCore = true; } @NonNull @@ -211,7 +208,6 @@ public class ChartController implements OnChartValueSelectedListener, highlightChartCurrentLocation(); } - @Override public void onCurrentPositionChanged() { if (mTrackId == Utils.INVALID_ID) @@ -222,7 +218,6 @@ public class ChartController implements OnChartValueSelectedListener, highlightActivePointManually(); } - @Override public void onElevationActivePointChanged() { if (mTrackId == Utils.INVALID_ID) @@ -234,6 +229,7 @@ public class ChartController implements OnChartValueSelectedListener, private void highlightActivePointManually() { Highlight highlight = getActivePoint(); + mInformSelectedActivePointToCore = false; mChart.highlightValue(highlight, true); } diff --git a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java index afbb787d5..4feaf55b1 100644 --- a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java +++ b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java @@ -27,6 +27,7 @@ import androidx.annotation.StringRes; import androidx.annotation.StyleRes; import androidx.core.view.ViewCompat; import app.organicmaps.base.BaseMwmFragmentActivity; +import app.organicmaps.downloader.MapManagerHelper; import app.organicmaps.intent.Factory; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.downloader.CountryItem; @@ -35,7 +36,7 @@ import app.organicmaps.sdk.location.LocationListener; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.ConnectionState; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import com.google.android.material.button.MaterialButton; @@ -116,10 +117,10 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity private final app.organicmaps.sdk.DownloadResourcesLegacyActivity.Listener mResourcesDownloadListener = new app.organicmaps.sdk.DownloadResourcesLegacyActivity.Listener() { @Override - public void onProgress(final int percent) + public void onProgress(final int bytesDownloaded) { if (!isFinishing()) - mProgress.setProgressCompat(percent, true); + mProgress.setProgressCompat(bytesDownloaded, true); } @Override @@ -150,14 +151,14 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity switch (item.newStatus) { - case CountryItem.STATUS_DONE: - mAreResourcesDownloaded = true; - showMap(); - return; + case CountryItem.STATUS_DONE: + mAreResourcesDownloaded = true; + showMap(); + return; - case CountryItem.STATUS_FAILED: - MapManager.showError(DownloadResourcesLegacyActivity.this, item, null); - return; + case CountryItem.STATUS_FAILED: + MapManagerHelper.showError(DownloadResourcesLegacyActivity.this, item, null); + return; } } } @@ -252,7 +253,8 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity setDownloadMessage(bytes); mProgress.setMax(bytes); - mProgress.setProgressCompat(0, true); + // Start progress at 1% according to M3 guidelines + mProgress.setProgressCompat(bytes/100, true); } else finishFilesDownload(bytes); @@ -370,10 +372,11 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity String fileSizeString = StringUtils.getFileSizeString(this, item.totalSize); mTvMessage.setText(getString(R.string.downloading_country_can_proceed, item.name, fileSizeString)); mProgress.setMax((int) item.totalSize); - mProgress.setProgressCompat(0, true); + // Start progress at 1% according to M3 guidelines + mProgress.setProgressCompat((int) (item.totalSize/100), true); mCountryDownloadListenerSlot = MapManager.nativeSubscribe(mCountryDownloadListener); - MapManager.startDownload(mCurrentCountry); + MapManagerHelper.startDownload(mCurrentCountry); setAction(PROCEED_TO_MAP); } else diff --git a/android/app/src/main/java/app/organicmaps/MapFragment.java b/android/app/src/main/java/app/organicmaps/MapFragment.java index df0feaae6..7bfa884d5 100644 --- a/android/app/src/main/java/app/organicmaps/MapFragment.java +++ b/android/app/src/main/java/app/organicmaps/MapFragment.java @@ -23,6 +23,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; public class MapFragment extends BaseMwmFragment implements View.OnTouchListener, SurfaceHolder.Callback { private static final String TAG = MapFragment.class.getSimpleName(); + + @NonNull private final Map mMap = new Map(DisplayType.Device); public void updateCompassOffset(int offsetX, int offsetY) @@ -84,6 +86,8 @@ public class MapFragment extends BaseMwmFragment implements View.OnTouchListener { Logger.d(TAG); super.onAttach(context); + + mMap.setLocationHelper(MwmApplication.from(requireContext()).getLocationHelper()); mMap.setMapRenderingListener((MapRenderingListener) context); mMap.setCallbackUnsupported(this::reportUnsupported); } @@ -159,24 +163,24 @@ public class MapFragment extends BaseMwmFragment implements View.OnTouchListener int pointerIndex = event.getActionIndex(); switch (action) { - case MotionEvent.ACTION_POINTER_UP -> action = Map.NATIVE_ACTION_UP; - case MotionEvent.ACTION_UP -> - { - action = Map.NATIVE_ACTION_UP; - pointerIndex = 0; - } - case MotionEvent.ACTION_POINTER_DOWN -> action = Map.NATIVE_ACTION_DOWN; - case MotionEvent.ACTION_DOWN -> - { - action = Map.NATIVE_ACTION_DOWN; - pointerIndex = 0; - } - case MotionEvent.ACTION_MOVE -> - { - action = Map.NATIVE_ACTION_MOVE; - pointerIndex = Map.INVALID_POINTER_MASK; - } - case MotionEvent.ACTION_CANCEL -> action = Map.NATIVE_ACTION_CANCEL; + case MotionEvent.ACTION_POINTER_UP -> action = Map.NATIVE_ACTION_UP; + case MotionEvent.ACTION_UP -> + { + action = Map.NATIVE_ACTION_UP; + pointerIndex = 0; + } + case MotionEvent.ACTION_POINTER_DOWN -> action = Map.NATIVE_ACTION_DOWN; + case MotionEvent.ACTION_DOWN -> + { + action = Map.NATIVE_ACTION_DOWN; + pointerIndex = 0; + } + case MotionEvent.ACTION_MOVE -> + { + action = Map.NATIVE_ACTION_MOVE; + pointerIndex = Map.INVALID_POINTER_MASK; + } + case MotionEvent.ACTION_CANCEL -> action = Map.NATIVE_ACTION_CANCEL; } Map.onTouch(action, event, pointerIndex); return true; diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index 47a97b0a7..27c07690f 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -12,13 +12,13 @@ import static app.organicmaps.sdk.location.LocationState.FOLLOW; import static app.organicmaps.sdk.location.LocationState.FOLLOW_AND_ROTATE; import static app.organicmaps.sdk.location.LocationState.LOCATION_TAG; import static app.organicmaps.sdk.util.PowerManagment.POWER_MANAGEMENT_TAG; +import static app.organicmaps.sdk.util.Utils.dimen; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; import android.app.PendingIntent; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -28,7 +28,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; -import android.text.method.LinkMovementMethod; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -66,7 +65,6 @@ import app.organicmaps.downloader.OnmapDownloader; import app.organicmaps.editor.EditorActivity; import app.organicmaps.editor.EditorHostFragment; import app.organicmaps.editor.FeatureCategoryActivity; -import app.organicmaps.editor.OsmLoginActivity; import app.organicmaps.editor.ReportFragment; import app.organicmaps.help.HelpActivity; import app.organicmaps.intent.Factory; @@ -82,7 +80,6 @@ import app.organicmaps.routing.ManageRouteBottomSheet; import app.organicmaps.routing.NavigationController; import app.organicmaps.routing.NavigationService; import app.organicmaps.routing.RoutingBottomMenuListener; -import app.organicmaps.routing.RoutingController; import app.organicmaps.routing.RoutingErrorDialogFragment; import app.organicmaps.routing.RoutingPlanFragment; import app.organicmaps.routing.RoutingPlanInplaceController; @@ -100,7 +97,6 @@ import app.organicmaps.sdk.display.DisplayType; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.downloader.UpdateInfo; import app.organicmaps.sdk.editor.Editor; -import app.organicmaps.sdk.editor.OsmOAuth; import app.organicmaps.sdk.location.LocationHelper; import app.organicmaps.sdk.location.LocationListener; import app.organicmaps.sdk.location.LocationState; @@ -108,6 +104,7 @@ import app.organicmaps.sdk.location.SensorListener; import app.organicmaps.sdk.location.TrackRecorder; import app.organicmaps.sdk.maplayer.isolines.IsolinesState; import app.organicmaps.sdk.routing.RouteMarkType; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.routing.RoutingOptions; import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.sdk.settings.RoadType; @@ -115,8 +112,6 @@ import app.organicmaps.sdk.settings.UnitLocale; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.LocationUtils; import app.organicmaps.sdk.util.PowerManagment; -import app.organicmaps.sdk.util.ThemeSwitcher; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.sdk.widget.placepage.PlacePageData; import app.organicmaps.search.FloatingSearchToolbarController; @@ -125,7 +120,9 @@ import app.organicmaps.search.SearchFragment; import app.organicmaps.settings.DrivingOptionsActivity; import app.organicmaps.settings.SettingsActivity; import app.organicmaps.util.SharingUtils; +import app.organicmaps.util.ThemeSwitcher; import app.organicmaps.util.ThemeUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; import app.organicmaps.util.bottomsheet.MenuBottomSheetItem; @@ -290,7 +287,6 @@ public class MwmActivity extends BaseMwmFragmentActivity } processIntent(); - migrateOAuthCredentials(); } /** @@ -345,36 +341,6 @@ public class MwmActivity extends BaseMwmFragmentActivity } } - private void migrateOAuthCredentials() - { - if (OsmOAuth.containsOAuth1Credentials()) - { - // Remove old OAuth v1 secrets - OsmOAuth.clearOAuth1Credentials(); - - // Notify user to re-login - dismissAlertDialog(); - final DialogInterface.OnClickListener navigateToLoginHandler = - (dialog, which) -> startActivity(new Intent(MwmActivity.this, OsmLoginActivity.class)); - - final int marginBase = getResources().getDimensionPixelSize(R.dimen.margin_base); - final float textSize = getResources().getDimension(R.dimen.line_spacing_extra_1); - final TextView text = new TextView(this); - text.setText(getText(R.string.alert_reauth_message)); - text.setPadding(marginBase, marginBase, marginBase, marginBase); - text.setTextSize(textSize); - text.setMovementMethod(LinkMovementMethod.getInstance()); - - mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) - .setTitle(R.string.login_osm) - .setView(text) - .setPositiveButton(R.string.login, navigateToLoginHandler) - .setNegativeButton(R.string.cancel, null) - .setOnDismissListener(dialog -> mAlertDialog = null) - .show(); - } - } - private static void checkMeasurementSystem() { UnitLocale.initializeCurrentUnits(); @@ -492,12 +458,10 @@ public class MwmActivity extends BaseMwmFragmentActivity @StyleRes protected int getThemeResourceId(@NonNull String theme) { - Context context = getApplicationContext(); - - if (ThemeUtils.isDefaultTheme(context, theme)) + if (Config.UiTheme.isDefault(theme)) return R.style.MwmTheme_MainActivity; - if (ThemeUtils.isNightTheme(context, theme)) + if (Config.UiTheme.isNight(theme)) return R.style.MwmTheme_Night_MainActivity; return super.getThemeResourceId(theme); @@ -619,7 +583,7 @@ public class MwmActivity extends BaseMwmFragmentActivity private void refreshLightStatusBar() { - UiUtils.setLightStatusBar(this, !(ThemeUtils.isNightTheme(this) || RoutingController.get().isPlanning() + UiUtils.setLightStatusBar(this, !(ThemeUtils.isNightTheme() || RoutingController.get().isPlanning() || ChoosePositionMode.get() != ChoosePositionMode.None)); } @@ -629,7 +593,7 @@ public class MwmActivity extends BaseMwmFragmentActivity UiUtils.setViewInsetsPaddingBottom(mPointChooser, windowInsets); UiUtils.setViewInsetsPaddingNoBottom(mPointChooserToolbar, windowInsets); final int trackRecorderOffset = - TrackRecorder.nativeIsTrackRecordingEnabled() ? UiUtils.dimen(this, R.dimen.map_button_size) : 0; + TrackRecorder.nativeIsTrackRecordingEnabled() ? 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 @@ -683,29 +647,29 @@ public class MwmActivity extends BaseMwmFragmentActivity mPointChooser.findViewById(R.id.done).setOnClickListener(v -> { switch (ChoosePositionMode.get()) { - case Api: - final Intent apiResult = new Intent(); - final double[] center = Framework.nativeGetScreenRectCenter(); - apiResult.putExtra(Const.EXTRA_POINT_LAT, center[0]); - apiResult.putExtra(Const.EXTRA_POINT_LON, center[1]); - apiResult.putExtra(Const.EXTRA_ZOOM_LEVEL, Framework.nativeGetDrawScale()); - setResult(Activity.RESULT_OK, apiResult); - finish(); - break; - case Editor: - if (Framework.nativeIsDownloadedMapAtScreenCenter()) - startActivity(new Intent(MwmActivity.this, FeatureCategoryActivity.class)); - else - { - dismissAlertDialog(); - mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) - .setTitle(R.string.message_invalid_feature_position) - .setPositiveButton(R.string.ok, null) - .setOnDismissListener(dialog -> mAlertDialog = null) - .show(); - } - break; - case None: throw new IllegalStateException("Unexpected Framework.nativeGetChoosePositionMode()"); + case Api: + final Intent apiResult = new Intent(); + final double[] center = Framework.nativeGetScreenRectCenter(); + apiResult.putExtra(Const.EXTRA_POINT_LAT, center[0]); + apiResult.putExtra(Const.EXTRA_POINT_LON, center[1]); + apiResult.putExtra(Const.EXTRA_ZOOM_LEVEL, Framework.nativeGetDrawScale()); + setResult(Activity.RESULT_OK, apiResult); + finish(); + break; + case Editor: + if (Framework.nativeIsDownloadedMapAtScreenCenter()) + startActivity(new Intent(MwmActivity.this, FeatureCategoryActivity.class)); + else + { + dismissAlertDialog(); + mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.message_invalid_feature_position) + .setPositiveButton(R.string.ok, null) + .setOnDismissListener(dialog -> mAlertDialog = null) + .show(); + } + break; + case None: throw new IllegalStateException("Unexpected Framework.nativeGetChoosePositionMode()"); } closePositionChooser(); }); @@ -772,9 +736,9 @@ public class MwmActivity extends BaseMwmFragmentActivity final View mapView = mMapFragment.getView(); if (mapView != null) { - int width = mapView.getWidth(); - int height = mapView.getHeight(); - Framework.nativeSetVisibleRect(0, 0, width, height); + int width = mapView.getWidth(); + int height = mapView.getHeight(); + Framework.nativeSetVisibleRect(0, 0, width, height); } } UiUtils.show(mPointChooser); @@ -972,24 +936,24 @@ public class MwmActivity extends BaseMwmFragmentActivity { switch (button) { - case zoomIn -> Map.zoomIn(); - case zoomOut -> Map.zoomOut(); - case myPosition -> - { - Logger.i(LOCATION_TAG, "The location button pressed"); - // Calls onMyPositionModeChanged(mode + 1). - LocationState.nativeSwitchToNextMode(); - } - case toggleMapLayer -> toggleMapLayerBottomSheet(); - case bookmarks -> showBookmarks(); - case search -> showSearch(""); - case menu -> - { - closeFloatingPanels(); - showBottomSheet(MAIN_MENU_ID); - } - case help -> showHelp(); - case trackRecordingStatus -> showTrackSaveDialog(); + case zoomIn -> Map.zoomIn(); + case zoomOut -> Map.zoomOut(); + case myPosition -> + { + Logger.i(LOCATION_TAG, "The location button pressed"); + // Calls onMyPositionModeChanged(mode + 1). + LocationState.nativeSwitchToNextMode(); + } + case toggleMapLayer -> toggleMapLayerBottomSheet(); + case bookmarks -> showBookmarks(); + case search -> showSearch(""); + case menu -> + { + closeFloatingPanels(); + showBottomSheet(MAIN_MENU_ID); + } + case help -> showHelp(); + case trackRecordingStatus -> showTrackSaveDialog(); } } @@ -1223,21 +1187,22 @@ public class MwmActivity extends BaseMwmFragmentActivity private void onIsolinesStateChanged(@NonNull IsolinesState type) { - if (type != IsolinesState.EXPIREDDATA) + if (type == IsolinesState.NODATA) { - type.activate(this, findViewById(R.id.coordinator), findViewById(R.id.menu_frame)); - return; + Toast.makeText(this, R.string.isolines_location_error_dialog, Toast.LENGTH_SHORT).show(); } - dismissAlertDialog(); - mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) - .setTitle(R.string.downloader_update_maps) - .setMessage(R.string.isolines_activation_error_dialog) - .setPositiveButton(R.string.ok, - (dialog, which) -> startActivity(new Intent(this, DownloaderActivity.class))) - .setNegativeButton(R.string.cancel, null) - .setOnDismissListener(dialog -> mAlertDialog = null) - .show(); + if (type == IsolinesState.EXPIREDDATA) + { + mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.downloader_update_maps) + .setMessage(R.string.isolines_activation_error_dialog) + .setPositiveButton( + R.string.ok, (dialog, which) -> startActivity(new Intent(this, DownloaderActivity.class))) + .setNegativeButton(R.string.cancel, null) + .setOnDismissListener(dialog -> mAlertDialog = null) + .show(); + } } @Override @@ -1682,17 +1647,17 @@ public class MwmActivity extends BaseMwmFragmentActivity 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); + offsetY += 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); + final boolean isSmallScreen = UiUtils.getDisplayTotalHeight(this) < dimen(this, R.dimen.dp_400); if (!isSmallScreen || TrackRecorder.nativeIsTrackRecordingEnabled()) - offsetX += UiUtils.dimen(this, R.dimen.map_button_size); + offsetX += dimen(this, R.dimen.map_button_size); } else if (isTrackRecordingEnabled) - offsetY += UiUtils.dimen(this, R.dimen.map_button_size); + offsetY += dimen(this, R.dimen.map_button_size); } updateCompassOffset(offsetY, offsetX); } @@ -2428,18 +2393,18 @@ public class MwmActivity extends BaseMwmFragmentActivity { switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_DOWN: Map.zoomOut(); return true; - case KeyEvent.KEYCODE_DPAD_UP: Map.zoomIn(); return true; - case KeyEvent.KEYCODE_ESCAPE: - final Intent currIntent = getIntent(); - final String backUrl = Framework.nativeGetParsedBackUrl(); - if (TextUtils.isEmpty(backUrl) || (currIntent != null && Factory.isStartedForApiResult(currIntent))) - { - finish(); - return true; - } - return super.onKeyUp(keyCode, event); - default: return super.onKeyUp(keyCode, event); + case KeyEvent.KEYCODE_DPAD_DOWN: Map.zoomOut(); return true; + case KeyEvent.KEYCODE_DPAD_UP: Map.zoomIn(); return true; + case KeyEvent.KEYCODE_ESCAPE: + final Intent currIntent = getIntent(); + final String backUrl = Framework.nativeGetParsedBackUrl(); + if (TextUtils.isEmpty(backUrl) || (currIntent != null && Factory.isStartedForApiResult(currIntent))) + { + finish(); + return true; + } + return super.onKeyUp(keyCode, event); + default: return super.onKeyUp(keyCode, event); } } @@ -2491,7 +2456,7 @@ public class MwmActivity extends BaseMwmFragmentActivity if (mCurrentWindowInsets != null) { final int offset = mCurrentWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top; - updateCompassOffset(offset + UiUtils.dimen(this, R.dimen.map_button_size)); + updateCompassOffset(offset + dimen(this, R.dimen.map_button_size)); } Toast.makeText(this, R.string.track_recording, Toast.LENGTH_SHORT).show(); TrackRecordingService.startForegroundService(getApplicationContext()); @@ -2584,7 +2549,7 @@ public class MwmActivity extends BaseMwmFragmentActivity items.add(new MenuBottomSheetItem(R.string.download_maps, R.drawable.ic_download, getDownloadMapsCounter(), this::onDownloadMapsOptionSelected)); - mDonatesUrl = Config.getDonateUrl(getApplicationContext()); + mDonatesUrl = Utils.getDonateUrl(getApplicationContext()); if (!mDonatesUrl.isEmpty()) items.add(new MenuBottomSheetItem(R.string.donate, R.drawable.ic_donate, this::onDonateOptionSelected)); diff --git a/android/app/src/main/java/app/organicmaps/MwmApplication.java b/android/app/src/main/java/app/organicmaps/MwmApplication.java index 8aa21dac2..5104468dc 100644 --- a/android/app/src/main/java/app/organicmaps/MwmApplication.java +++ b/android/app/src/main/java/app/organicmaps/MwmApplication.java @@ -14,11 +14,12 @@ import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; +import androidx.preference.PreferenceManager; import app.organicmaps.background.OsmUploadWork; import app.organicmaps.downloader.DownloaderNotifier; +import app.organicmaps.location.LocationProviderFactoryImpl; import app.organicmaps.location.TrackRecordingService; import app.organicmaps.routing.NavigationService; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Map; import app.organicmaps.sdk.OrganicMaps; import app.organicmaps.sdk.display.DisplayManager; @@ -28,9 +29,11 @@ import app.organicmaps.sdk.location.SensorHelper; import app.organicmaps.sdk.location.TrackRecorder; import app.organicmaps.sdk.maplayer.isolines.IsolinesManager; import app.organicmaps.sdk.maplayer.subway.SubwayManager; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.ConnectionState; import app.organicmaps.sdk.util.log.Logger; +import app.organicmaps.util.ThemeSwitcher; import app.organicmaps.util.Utils; import java.io.IOException; import java.lang.ref.WeakReference; @@ -40,6 +43,9 @@ public class MwmApplication extends Application implements Application.ActivityL @NonNull private static final String TAG = MwmApplication.class.getSimpleName(); + @NonNull + private final LocationProviderFactoryImpl mLocationProviderFactory = new LocationProviderFactoryImpl(); + @SuppressWarnings("NotNullFieldNotInitialized") @NonNull private OrganicMaps mOrganicMaps; @@ -98,6 +104,12 @@ public class MwmApplication extends Application implements Application.ActivityL return mOrganicMaps; } + @NonNull + public LocationProviderFactoryImpl getLocationProviderFactory() + { + return mLocationProviderFactory; + } + @NonNull public static MwmApplication from(@NonNull Context context) { @@ -118,7 +130,10 @@ public class MwmApplication extends Application implements Application.ActivityL sInstance = this; - mOrganicMaps = new OrganicMaps(getApplicationContext()); + PreferenceManager.setDefaultValues(this, R.xml.prefs_main, false); + mOrganicMaps = new OrganicMaps(getApplicationContext(), BuildConfig.FLAVOR, BuildConfig.APPLICATION_ID, + BuildConfig.VERSION_CODE, BuildConfig.VERSION_NAME, + BuildConfig.FILE_PROVIDER_AUTHORITY, mLocationProviderFactory); ConnectionState.INSTANCE.initialize(this); @@ -133,6 +148,8 @@ public class MwmApplication extends Application implements Application.ActivityL public boolean initOrganicMaps(@NonNull Runnable onComplete) throws IOException { return mOrganicMaps.init(() -> { + ThemeSwitcher.INSTANCE.initialize(this); + ThemeSwitcher.INSTANCE.restart(false); ProcessLifecycleOwner.get().getLifecycle().addObserver(mProcessLifecycleObserver); onComplete.run(); }); diff --git a/android/app/src/main/java/app/organicmaps/PanelAnimator.java b/android/app/src/main/java/app/organicmaps/PanelAnimator.java index d040d5861..5c67c9a6b 100644 --- a/android/app/src/main/java/app/organicmaps/PanelAnimator.java +++ b/android/app/src/main/java/app/organicmaps/PanelAnimator.java @@ -9,7 +9,8 @@ import androidx.annotation.IntegerRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.Utils; +import app.organicmaps.util.UiUtils; import org.chromium.base.ObserverList; class PanelAnimator @@ -26,7 +27,7 @@ class PanelAnimator PanelAnimator(MwmActivity activity) { mActivity = activity; - mWidth = UiUtils.dimen(activity.getApplicationContext(), R.dimen.panel_width); + mWidth = Utils.dimen(activity.getApplicationContext(), R.dimen.panel_width); mPanel = mActivity.findViewById(R.id.fragment_container); mDuration = mActivity.getResources().getInteger(R.integer.anim_panel); } diff --git a/android/app/src/main/java/app/organicmaps/SplashActivity.java b/android/app/src/main/java/app/organicmaps/SplashActivity.java index 620f6d500..2771538ff 100644 --- a/android/app/src/main/java/app/organicmaps/SplashActivity.java +++ b/android/app/src/main/java/app/organicmaps/SplashActivity.java @@ -60,11 +60,10 @@ public class SplashActivity extends AppCompatActivity { super.onCreate(savedInstanceState); - final Context context = getApplicationContext(); - final String theme = Config.getCurrentUiTheme(context); - if (ThemeUtils.isDefaultTheme(context, theme)) + final String theme = Config.UiTheme.getCurrent(); + if (Config.UiTheme.isDefault(theme)) setTheme(R.style.MwmTheme_Splash); - else if (ThemeUtils.isNightTheme(context, theme)) + else if (Config.UiTheme.isNight(theme)) setTheme(R.style.MwmTheme_Night_Splash); else throw new IllegalArgumentException("Attempt to apply unsupported theme: " + theme); diff --git a/android/app/src/main/java/app/organicmaps/WebContainerDelegate.java b/android/app/src/main/java/app/organicmaps/WebContainerDelegate.java index a0b3bbd2f..671aeb97e 100644 --- a/android/app/src/main/java/app/organicmaps/WebContainerDelegate.java +++ b/android/app/src/main/java/app/organicmaps/WebContainerDelegate.java @@ -9,7 +9,7 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; import app.organicmaps.base.OnBackPressListener; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; public abstract class WebContainerDelegate implements OnBackPressListener { diff --git a/android/app/src/main/java/app/organicmaps/backup/BackupUtils.java b/android/app/src/main/java/app/organicmaps/backup/BackupUtils.java index 91e814e81..5da04b826 100644 --- a/android/app/src/main/java/app/organicmaps/backup/BackupUtils.java +++ b/android/app/src/main/java/app/organicmaps/backup/BackupUtils.java @@ -1,6 +1,7 @@ package app.organicmaps.backup; import static app.organicmaps.sdk.util.StorageUtils.isFolderWritable; +import static app.organicmaps.sdk.util.Utils.dimen; import static app.organicmaps.settings.BackupSettingsFragment.MAX_BACKUPS_DEFAULT_COUNT; import static app.organicmaps.settings.BackupSettingsFragment.MAX_BACKUPS_KEY; @@ -15,8 +16,8 @@ import android.text.style.AbsoluteSizeSpan; import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.sdk.util.log.Logger; +import app.organicmaps.util.UiUtils; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -50,14 +51,14 @@ public class BackupUtils String volumeName; if ("primary".equalsIgnoreCase(volumeId)) - volumeName = context.getString(R.string.maps_storage_shared); + volumeName = context.getString(app.organicmaps.sdk.R.string.maps_storage_shared); else - volumeName = context.getString(R.string.maps_storage_removable); + volumeName = context.getString(app.organicmaps.sdk.R.string.maps_storage_removable); SpannableStringBuilder sb = new SpannableStringBuilder(); - sb.append(volumeName + ": \n", new AbsoluteSizeSpan(UiUtils.dimen(context, R.dimen.text_size_body_3)), + sb.append(volumeName + ": \n", new AbsoluteSizeSpan(dimen(context, R.dimen.text_size_body_3)), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - sb.append("/" + subPath, new AbsoluteSizeSpan(UiUtils.dimen(context, R.dimen.text_size_body_4)), + sb.append("/" + subPath, new AbsoluteSizeSpan(dimen(context, R.dimen.text_size_body_4)), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return sb; } diff --git a/android/app/src/main/java/app/organicmaps/backup/LocalBackupManager.java b/android/app/src/main/java/app/organicmaps/backup/LocalBackupManager.java index f1a8cad85..e31287b54 100644 --- a/android/app/src/main/java/app/organicmaps/backup/LocalBackupManager.java +++ b/android/app/src/main/java/app/organicmaps/backup/LocalBackupManager.java @@ -62,39 +62,39 @@ public class LocalBackupManager implements BookmarkManager.BookmarksSharingListe ErrorCode errorCode = null; switch (result.getCode()) { - case BookmarkSharingResult.SUCCESS -> - { - if (!saveBackup(result)) - { - Logger.e(TAG, "Failed to save backup. See system log above"); - errorCode = ErrorCode.FILE_ERROR; - } - else - { - Logger.i(TAG, "Backup was created and saved successfully"); - } - } - case BookmarkSharingResult.EMPTY_CATEGORY -> - { - errorCode = ErrorCode.EMPTY_CATEGORY; - Logger.e(TAG, "Failed to create backup. Category is empty"); - } - case BookmarkSharingResult.ARCHIVE_ERROR -> - { - errorCode = ErrorCode.ARCHIVE_ERROR; - Logger.e(TAG, "Failed to create archive of bookmarks"); - } - case BookmarkSharingResult.FILE_ERROR -> + case BookmarkSharingResult.SUCCESS -> + { + if (!saveBackup(result)) { + Logger.e(TAG, "Failed to save backup. See system log above"); errorCode = ErrorCode.FILE_ERROR; - Logger.e(TAG, "Failed create file for archive"); } - default -> + else { - errorCode = ErrorCode.UNSUPPORTED; - Logger.e(TAG, "Failed to create backup. Unknown error"); + Logger.i(TAG, "Backup was created and saved successfully"); } } + case BookmarkSharingResult.EMPTY_CATEGORY -> + { + errorCode = ErrorCode.EMPTY_CATEGORY; + Logger.e(TAG, "Failed to create backup. Category is empty"); + } + case BookmarkSharingResult.ARCHIVE_ERROR -> + { + errorCode = ErrorCode.ARCHIVE_ERROR; + Logger.e(TAG, "Failed to create archive of bookmarks"); + } + case BookmarkSharingResult.FILE_ERROR -> + { + errorCode = ErrorCode.FILE_ERROR; + Logger.e(TAG, "Failed create file for archive"); + } + default -> + { + errorCode = ErrorCode.UNSUPPORTED; + Logger.e(TAG, "Failed to create backup. Unknown error"); + } + } ErrorCode finalErrorCode = errorCode; UiThread.run(() -> { diff --git a/android/app/src/main/java/app/organicmaps/base/BaseMwmDialogFragment.java b/android/app/src/main/java/app/organicmaps/base/BaseMwmDialogFragment.java index 0cd943754..1f513dac2 100644 --- a/android/app/src/main/java/app/organicmaps/base/BaseMwmDialogFragment.java +++ b/android/app/src/main/java/app/organicmaps/base/BaseMwmDialogFragment.java @@ -15,7 +15,7 @@ public class BaseMwmDialogFragment extends DialogFragment @StyleRes protected final int getFullscreenTheme() { - return ThemeUtils.isNightTheme(requireContext()) ? getFullscreenDarkTheme() : getFullscreenLightTheme(); + return ThemeUtils.isNightTheme() ? getFullscreenDarkTheme() : getFullscreenLightTheme(); } protected int getStyle() diff --git a/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java b/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java index 4b72a84f0..518373f8f 100644 --- a/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java +++ b/android/app/src/main/java/app/organicmaps/base/BaseMwmFragmentActivity.java @@ -1,7 +1,6 @@ package app.organicmaps.base; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.media.AudioManager; @@ -24,7 +23,6 @@ import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.concurrency.UiThread; import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.util.RtlUtils; -import app.organicmaps.util.ThemeUtils; import com.google.android.material.appbar.MaterialToolbar; import java.util.Objects; @@ -40,12 +38,10 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity @StyleRes protected int getThemeResourceId(@NonNull String theme) { - Context context = getApplicationContext(); - - if (ThemeUtils.isDefaultTheme(context, theme)) + if (Config.UiTheme.isDefault(theme)) return R.style.MwmTheme; - if (ThemeUtils.isNightTheme(context, theme)) + if (Config.UiTheme.isNight(theme)) return R.style.MwmTheme_Night; throw new IllegalArgumentException("Attempt to apply unsupported theme: " + theme); @@ -62,7 +58,7 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity protected final void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mThemeName = Config.getCurrentUiTheme(getApplicationContext()); + mThemeName = Config.UiTheme.getCurrent(); setTheme(getThemeResourceId(mThemeName)); EdgeToEdge.enable(this, SystemBarStyle.dark(Color.TRANSPARENT)); RtlUtils.manageRtl(this); @@ -122,7 +118,7 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity public void onPostResume() { super.onPostResume(); - if (!mThemeName.equals(Config.getCurrentUiTheme(getApplicationContext()))) + if (!mThemeName.equals(Config.UiTheme.getCurrent())) { // Workaround described in https://code.google.com/p/android/issues/detail?id=93731 UiThread.runLater(this::recreate); diff --git a/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java b/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java index 18d821455..cce25376b 100644 --- a/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java +++ b/android/app/src/main/java/app/organicmaps/base/BaseMwmRecyclerFragment.java @@ -14,7 +14,7 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils.ScrollableContentInsetsListener; import app.organicmaps.widget.PlaceholderView; diff --git a/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java b/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java index 11d56fe76..34ddb9e1e 100644 --- a/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java +++ b/android/app/src/main/java/app/organicmaps/base/BaseToolbarActivity.java @@ -10,7 +10,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import com.google.android.material.appbar.MaterialToolbar; diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesActivity.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesActivity.java index ea54ae664..a3f8c28f2 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesActivity.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesActivity.java @@ -42,7 +42,7 @@ public class BookmarkCategoriesActivity extends BaseToolbarActivity @StyleRes public int getThemeResourceId(@NonNull String theme) { - return ThemeUtils.getWindowBgThemeResourceId(getApplicationContext(), theme); + return ThemeUtils.getWindowBgThemeResourceId(theme); } @Override 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 cd9fa68db..559277dde 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesAdapter.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCategoriesAdapter.java @@ -66,47 +66,47 @@ public class BookmarkCategoriesAdapter extends BaseBookmarkCategoryAdapter - { - View header = inflater.inflate(R.layout.item_bookmark_group_list_header, parent, false); - return new HeaderViewHolder(header); - } - case TYPE_CATEGORY_ITEM -> - { - View view = inflater.inflate(R.layout.item_bookmark_category, parent, false); - final CategoryViewHolder holder = new CategoryViewHolder(view); - view.setOnClickListener(new CategoryItemClickListener(holder)); - view.setOnLongClickListener(new LongClickListener(holder)); - return holder; - } - case TYPE_ACTION_ADD -> - { - View item = inflater.inflate(R.layout.item_bookmark_button, parent, false); - item.setOnClickListener(v -> { - if (mCategoryListCallback != null) - mCategoryListCallback.onAddButtonClick(); - }); - return new Holders.GeneralViewHolder(item); - } - case TYPE_ACTION_IMPORT -> - { - View item = inflater.inflate(R.layout.item_bookmark_button, parent, false); - item.setOnClickListener(v -> { - if (mCategoryListCallback != null) - mCategoryListCallback.onImportButtonClick(); - }); - return new Holders.GeneralViewHolder(item); - } - case TYPE_ACTION_EXPORT_ALL_AS_KMZ -> - { - View item = inflater.inflate(R.layout.item_bookmark_button, parent, false); - item.setOnClickListener(v -> { - if (mCategoryListCallback != null) - mCategoryListCallback.onExportButtonClick(); - }); - return new Holders.GeneralViewHolder(item); - } - default -> throw new AssertionError("Invalid item type: " + viewType); + case TYPE_ACTION_HEADER -> + { + View header = inflater.inflate(R.layout.item_bookmark_group_list_header, parent, false); + return new HeaderViewHolder(header); + } + case TYPE_CATEGORY_ITEM -> + { + View view = inflater.inflate(R.layout.item_bookmark_category, parent, false); + final CategoryViewHolder holder = new CategoryViewHolder(view); + view.setOnClickListener(new CategoryItemClickListener(holder)); + view.setOnLongClickListener(new LongClickListener(holder)); + return holder; + } + case TYPE_ACTION_ADD -> + { + View item = inflater.inflate(R.layout.item_bookmark_button, parent, false); + item.setOnClickListener(v -> { + if (mCategoryListCallback != null) + mCategoryListCallback.onAddButtonClick(); + }); + return new Holders.GeneralViewHolder(item); + } + case TYPE_ACTION_IMPORT -> + { + View item = inflater.inflate(R.layout.item_bookmark_button, parent, false); + item.setOnClickListener(v -> { + if (mCategoryListCallback != null) + mCategoryListCallback.onImportButtonClick(); + }); + return new Holders.GeneralViewHolder(item); + } + case TYPE_ACTION_EXPORT_ALL_AS_KMZ -> + { + View item = inflater.inflate(R.layout.item_bookmark_button, parent, false); + item.setOnClickListener(v -> { + if (mCategoryListCallback != null) + mCategoryListCallback.onExportButtonClick(); + }); + return new Holders.GeneralViewHolder(item); + } + default -> throw new AssertionError("Invalid item type: " + viewType); } } @@ -116,44 +116,44 @@ public class BookmarkCategoriesAdapter extends BaseBookmarkCategoryAdapter - { - HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder; - headerViewHolder.setAction(mMassOperationAction, BookmarkManager.INSTANCE.areAllCategoriesInvisible()); - headerViewHolder.getText().setText(R.string.bookmark_lists); - } - case TYPE_CATEGORY_ITEM -> - { - final BookmarkCategory category = getCategoryByPosition(toCategoryPosition(position)); - CategoryViewHolder categoryHolder = (CategoryViewHolder) holder; - categoryHolder.setEntity(category); - categoryHolder.setName(category.getName()); - categoryHolder.setSize(); - categoryHolder.setVisibilityState(category.isVisible()); - ToggleVisibilityClickListener visibilityListener = new ToggleVisibilityClickListener(categoryHolder); - categoryHolder.setVisibilityListener(visibilityListener); - CategoryItemMoreClickListener moreClickListener = new CategoryItemMoreClickListener(categoryHolder); - categoryHolder.setMoreButtonClickListener(moreClickListener); - } - case TYPE_ACTION_ADD -> - { - Holders.GeneralViewHolder generalViewHolder = (Holders.GeneralViewHolder) holder; - generalViewHolder.getImage().setImageResource(R.drawable.ic_add_list); - generalViewHolder.getText().setText(R.string.bookmarks_create_new_group); - } - case TYPE_ACTION_IMPORT -> - { - Holders.GeneralViewHolder generalViewHolder = (Holders.GeneralViewHolder) holder; - generalViewHolder.getImage().setImageResource(R.drawable.ic_import); - generalViewHolder.getText().setText(R.string.bookmarks_import); - } - case TYPE_ACTION_EXPORT_ALL_AS_KMZ -> - { - Holders.GeneralViewHolder generalViewHolder = (Holders.GeneralViewHolder) holder; - generalViewHolder.getImage().setImageResource(R.drawable.ic_export); - generalViewHolder.getText().setText(R.string.bookmarks_export); - } - default -> throw new AssertionError("Invalid item type: " + type); + case TYPE_ACTION_HEADER -> + { + HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder; + headerViewHolder.setAction(mMassOperationAction, BookmarkManager.INSTANCE.areAllCategoriesInvisible()); + headerViewHolder.getText().setText(R.string.bookmark_lists); + } + case TYPE_CATEGORY_ITEM -> + { + final BookmarkCategory category = getCategoryByPosition(toCategoryPosition(position)); + CategoryViewHolder categoryHolder = (CategoryViewHolder) holder; + categoryHolder.setEntity(category); + categoryHolder.setName(category.getName()); + categoryHolder.setSize(); + categoryHolder.setVisibilityState(category.isVisible()); + ToggleVisibilityClickListener visibilityListener = new ToggleVisibilityClickListener(categoryHolder); + categoryHolder.setVisibilityListener(visibilityListener); + CategoryItemMoreClickListener moreClickListener = new CategoryItemMoreClickListener(categoryHolder); + categoryHolder.setMoreButtonClickListener(moreClickListener); + } + case TYPE_ACTION_ADD -> + { + Holders.GeneralViewHolder generalViewHolder = (Holders.GeneralViewHolder) holder; + generalViewHolder.getImage().setImageResource(R.drawable.ic_add_list); + generalViewHolder.getText().setText(R.string.bookmarks_create_new_group); + } + case TYPE_ACTION_IMPORT -> + { + Holders.GeneralViewHolder generalViewHolder = (Holders.GeneralViewHolder) holder; + generalViewHolder.getImage().setImageResource(R.drawable.ic_import); + generalViewHolder.getText().setText(R.string.bookmarks_import); + } + case TYPE_ACTION_EXPORT_ALL_AS_KMZ -> + { + Holders.GeneralViewHolder generalViewHolder = (Holders.GeneralViewHolder) holder; + generalViewHolder.getImage().setImageResource(R.drawable.ic_export); + generalViewHolder.getText().setText(R.string.bookmarks_export); + } + default -> throw new AssertionError("Invalid item type: " + type); } } diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCollectionAdapter.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCollectionAdapter.java index f33d48898..1ba7be546 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCollectionAdapter.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkCollectionAdapter.java @@ -11,7 +11,7 @@ import app.organicmaps.R; import app.organicmaps.adapter.OnItemClickListener; import app.organicmaps.sdk.bookmarks.data.BookmarkCategory; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; 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 c0451773e..8dd111fac 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListActivity.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListActivity.java @@ -41,7 +41,7 @@ public class BookmarkListActivity extends BaseToolbarActivity @StyleRes public int getThemeResourceId(@NonNull String theme) { - return ThemeUtils.getCardBgThemeResourceId(getApplicationContext(), theme); + return ThemeUtils.getCardBgThemeResourceId(theme); } @Override diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListAdapter.java b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListAdapter.java index 76a73c529..31b49aad7 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListAdapter.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarkListAdapter.java @@ -442,37 +442,37 @@ public class BookmarkListAdapter extends RecyclerView.Adapter onMoreButtonClicked(text, moreBtn)); - moreBtn.setOnClickListener(v -> onMoreButtonClicked(text, moreBtn)); - title.setOnClickListener(v -> onMoreButtonClicked(text, moreBtn)); - break; + case TYPE_TRACK: + Holders.TrackViewHolder trackHolder = + new Holders.TrackViewHolder(inflater.inflate(R.layout.item_track, parent, false)); + trackHolder.setOnClickListener(mClickListener); + trackHolder.setOnLongClickListener(mLongClickListener); + trackHolder.setTrackIconClickListener(mIconClickListener); + trackHolder.setMoreButtonClickListener(mMoreClickListener); + holder = trackHolder; + break; + case TYPE_BOOKMARK: + Holders.BookmarkViewHolder bookmarkHolder = + new Holders.BookmarkViewHolder(inflater.inflate(R.layout.item_bookmark, parent, false)); + bookmarkHolder.setOnClickListener(mClickListener); + bookmarkHolder.setOnLongClickListener(mLongClickListener); + holder = bookmarkHolder; + break; + case TYPE_SECTION: + MaterialTextView tv = (MaterialTextView) inflater.inflate(R.layout.item_category_title, parent, false); + holder = new Holders.SectionViewHolder(tv); + break; + case TYPE_DESC: + View desc = inflater.inflate(R.layout.item_category_description, parent, false); + MaterialTextView moreBtn = desc.findViewById(R.id.more_btn); + MaterialTextView text = desc.findViewById(R.id.text); + MaterialTextView title = desc.findViewById(R.id.title); + setMoreButtonVisibility(text, moreBtn); + holder = new Holders.DescriptionViewHolder(desc, mSectionsDataSource.getCategory()); + text.setOnClickListener(v -> onMoreButtonClicked(text, moreBtn)); + moreBtn.setOnClickListener(v -> onMoreButtonClicked(text, moreBtn)); + title.setOnClickListener(v -> onMoreButtonClicked(text, moreBtn)); + break; } if (holder == null) 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 32d2152f8..2122f9b8d 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksListFragment.java @@ -33,15 +33,15 @@ import app.organicmaps.sdk.bookmarks.data.BookmarkInfo; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.BookmarkSharingResult; import app.organicmaps.sdk.bookmarks.data.CategoryDataSource; -import app.organicmaps.sdk.bookmarks.data.Icon; import app.organicmaps.sdk.bookmarks.data.KmlFileType; +import app.organicmaps.sdk.bookmarks.data.PredefinedColors; import app.organicmaps.sdk.bookmarks.data.SortedBlock; import app.organicmaps.sdk.bookmarks.data.Track; import app.organicmaps.sdk.search.BookmarkSearchListener; import app.organicmaps.sdk.search.SearchEngine; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.Graphics; import app.organicmaps.util.SharingUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; @@ -551,12 +551,12 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment - { - return; - } - case BookmarkListAdapter.TYPE_BOOKMARK -> onBookmarkClicked(position, intent, adapter); - case BookmarkListAdapter.TYPE_TRACK -> onTrackClicked(position, intent, adapter); + case BookmarkListAdapter.TYPE_SECTION, BookmarkListAdapter.TYPE_DESC -> + { + return; + } + case BookmarkListAdapter.TYPE_BOOKMARK -> onBookmarkClicked(position, intent, adapter); + case BookmarkListAdapter.TYPE_TRACK -> onTrackClicked(position, intent, adapter); } intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -592,7 +592,7 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment { int from = mTrack.getColor(); - int to = BookmarkManager.ICONS.get(colorPos).argb(); + int to = PredefinedColors.getColor(colorPos); if (from == to) return; BookmarkManager.INSTANCE.changeTrackColor(mTrack.getTrackId(), to); @@ -621,22 +621,22 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment - { - return getBookmarkMenuItems(); - } - case TRACK_MENU_ID -> - { - final Track track = (Track) getBookmarkListAdapter().getItem(mSelectedPosition); - return getTrackMenuItems(track); - } - case OPTIONS_MENU_ID -> - { - return getOptionsMenuItems(); - } + case BOOKMARKS_MENU_ID -> + { + return getBookmarkMenuItems(); + } + case TRACK_MENU_ID -> + { + final Track track = (Track) getBookmarkListAdapter().getItem(mSelectedPosition); + return getTrackMenuItems(track); + } + case OPTIONS_MENU_ID -> + { + return getOptionsMenuItems(); + } } return null; } 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 33ae76970..423ad9f40 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksSharingHelper.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/BookmarksSharingHelper.java @@ -57,27 +57,27 @@ public enum BookmarksSharingHelper switch (result.getCode()) { - case BookmarkSharingResult.SUCCESS -> - SharingUtils.shareBookmarkFile(context, launcher, result.getSharingPath(), result.getMimeType()); - case BookmarkSharingResult.EMPTY_CATEGORY -> - new MaterialAlertDialogBuilder(context, R.style.MwmTheme_AlertDialog) - .setTitle(R.string.bookmarks_error_title_share_empty) - .setMessage(R.string.bookmarks_error_message_share_empty) - .setPositiveButton(R.string.ok, null) - .show(); - case BookmarkSharingResult.ARCHIVE_ERROR, BookmarkSharingResult.FILE_ERROR -> - { - new MaterialAlertDialogBuilder(context, R.style.MwmTheme_AlertDialog) - .setTitle(R.string.dialog_routing_system_error) - .setMessage(R.string.bookmarks_error_message_share_general) - .setPositiveButton(R.string.ok, null) - .show(); - List names = new ArrayList<>(); - for (long categoryId : result.getCategoriesIds()) - names.add(BookmarkManager.INSTANCE.getCategoryById(categoryId).getName()); - Logger.e(TAG, "Failed to share bookmark categories " + names + ", error code: " + result.getCode()); - } - default -> throw new AssertionError("Unsupported bookmark sharing code: " + result.getCode()); + case BookmarkSharingResult.SUCCESS -> + SharingUtils.shareBookmarkFile(context, launcher, result.getSharingPath(), result.getMimeType()); + case BookmarkSharingResult.EMPTY_CATEGORY -> + new MaterialAlertDialogBuilder(context, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.bookmarks_error_title_share_empty) + .setMessage(R.string.bookmarks_error_message_share_empty) + .setPositiveButton(R.string.ok, null) + .show(); + case BookmarkSharingResult.ARCHIVE_ERROR, BookmarkSharingResult.FILE_ERROR -> + { + new MaterialAlertDialogBuilder(context, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.dialog_routing_system_error) + .setMessage(R.string.bookmarks_error_message_share_general) + .setPositiveButton(R.string.ok, null) + .show(); + List names = new ArrayList<>(); + for (long categoryId : result.getCategoriesIds()) + names.add(BookmarkManager.INSTANCE.getCategoryById(categoryId).getName()); + Logger.e(TAG, "Failed to share bookmark categories " + names + ", error code: " + result.getCode()); + } + default -> throw new AssertionError("Unsupported bookmark sharing code: " + result.getCode()); } } diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/ChooseBookmarksSortingTypeFragment.java b/android/app/src/main/java/app/organicmaps/bookmarks/ChooseBookmarksSortingTypeFragment.java index 16c520d59..bc145e1e4 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/ChooseBookmarksSortingTypeFragment.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/ChooseBookmarksSortingTypeFragment.java @@ -13,7 +13,7 @@ import androidx.fragment.app.FragmentManager; import app.organicmaps.R; import app.organicmaps.base.BaseMwmDialogFragment; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; public class ChooseBookmarksSortingTypeFragment extends BaseMwmDialogFragment implements RadioGroup.OnCheckedChangeListener @@ -64,10 +64,10 @@ public class ChooseBookmarksSortingTypeFragment { switch (sortingType) { - case BookmarkManager.SORT_BY_TYPE: return R.id.sort_by_type; - case BookmarkManager.SORT_BY_DISTANCE: return R.id.sort_by_distance; - case BookmarkManager.SORT_BY_TIME: return R.id.sort_by_time; - case BookmarkManager.SORT_BY_NAME: return R.id.sort_by_name; + case BookmarkManager.SORT_BY_TYPE: return R.id.sort_by_type; + case BookmarkManager.SORT_BY_DISTANCE: return R.id.sort_by_distance; + case BookmarkManager.SORT_BY_TIME: return R.id.sort_by_time; + case BookmarkManager.SORT_BY_NAME: return R.id.sort_by_name; } } return R.id.sort_by_default; diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/IconsAdapter.java b/android/app/src/main/java/app/organicmaps/bookmarks/ColorsAdapter.java similarity index 60% rename from android/app/src/main/java/app/organicmaps/bookmarks/IconsAdapter.java rename to android/app/src/main/java/app/organicmaps/bookmarks/ColorsAdapter.java index 58c16343a..42dc03ec5 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/IconsAdapter.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/ColorsAdapter.java @@ -6,19 +6,26 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import androidx.annotation.DrawableRes; import app.organicmaps.R; -import app.organicmaps.sdk.bookmarks.data.Icon; +import app.organicmaps.sdk.bookmarks.data.PredefinedColors; import app.organicmaps.util.Graphics; import com.google.android.material.imageview.ShapeableImageView; import java.util.List; +import java.util.Objects; -public class IconsAdapter extends ArrayAdapter +public class ColorsAdapter extends ArrayAdapter { + @PredefinedColors.Color private int mCheckedIconColor; - public IconsAdapter(Context context, List list) + @DrawableRes + private final int mIconResId; + + public ColorsAdapter(Context context, List list, @DrawableRes int iconResId) { super(context, 0, 0, list); + mIconResId = iconResId; } @Override @@ -35,19 +42,19 @@ public class IconsAdapter extends ArrayAdapter else holder = (SpinnerViewHolder) convertView.getTag(); - final Icon icon = getItem(position); + @PredefinedColors.Color + final int color = Objects.requireNonNull(getItem(position)); Drawable circle; - if (icon.getColor() == mCheckedIconColor) + if (color == mCheckedIconColor) { - circle = Graphics.drawCircleAndImage(getItem(position).argb(), R.dimen.track_circle_size, - app.organicmaps.sdk.R.drawable.ic_bookmark_none, R.dimen.bookmark_icon_size, - getContext()); + circle = Graphics.drawCircleAndImage(PredefinedColors.getColor(mCheckedIconColor), R.dimen.track_circle_size, + mIconResId, R.dimen.bookmark_icon_size, getContext()); } else { - circle = - Graphics.drawCircle(getItem(position).argb(), R.dimen.select_color_circle_size, getContext().getResources()); + circle = Graphics.drawCircle(PredefinedColors.getColor(color), R.dimen.select_color_circle_size, + getContext().getResources()); } holder.icon.setImageDrawable(circle); return convertView; diff --git a/android/app/src/main/java/app/organicmaps/bookmarks/Holders.java b/android/app/src/main/java/app/organicmaps/bookmarks/Holders.java index 16b494b1a..749c71e78 100644 --- a/android/app/src/main/java/app/organicmaps/bookmarks/Holders.java +++ b/android/app/src/main/java/app/organicmaps/bookmarks/Holders.java @@ -18,8 +18,8 @@ import app.organicmaps.sdk.bookmarks.data.BookmarkInfo; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.IconClickListener; import app.organicmaps.sdk.bookmarks.data.Track; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.Graphics; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.widget.recycler.RecyclerClickListener; import app.organicmaps.widget.recycler.RecyclerLongClickListener; diff --git a/android/app/src/main/java/app/organicmaps/car/CarAppSession.java b/android/app/src/main/java/app/organicmaps/car/CarAppSession.java index e4eb30450..594869b8f 100644 --- a/android/app/src/main/java/app/organicmaps/car/CarAppSession.java +++ b/android/app/src/main/java/app/organicmaps/car/CarAppSession.java @@ -26,7 +26,6 @@ import app.organicmaps.car.util.CurrentCountryChangedListener; import app.organicmaps.car.util.IntentUtils; import app.organicmaps.car.util.ThemeUtils; import app.organicmaps.car.util.UserActionRequired; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.PlacePageActivationListener; import app.organicmaps.sdk.bookmarks.data.MapObject; @@ -34,6 +33,7 @@ import app.organicmaps.sdk.display.DisplayChangedListener; import app.organicmaps.sdk.display.DisplayManager; import app.organicmaps.sdk.display.DisplayType; import app.organicmaps.sdk.location.LocationState; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.LocationUtils; import app.organicmaps.sdk.util.log.Logger; diff --git a/android/app/src/main/java/app/organicmaps/car/SurfaceRenderer.java b/android/app/src/main/java/app/organicmaps/car/SurfaceRenderer.java index 9cb01e40d..810dff898 100644 --- a/android/app/src/main/java/app/organicmaps/car/SurfaceRenderer.java +++ b/android/app/src/main/java/app/organicmaps/car/SurfaceRenderer.java @@ -27,7 +27,10 @@ public class SurfaceRenderer implements DefaultLifecycleObserver, SurfaceCallbac { private static final String TAG = SurfaceRenderer.class.getSimpleName(); + @NonNull private final CarContext mCarContext; + + @NonNull private final Map mMap = new Map(Car); @NonNull @@ -56,6 +59,7 @@ public class SurfaceRenderer implements DefaultLifecycleObserver, SurfaceCallbac mSurface.release(); mSurface = surfaceContainer.getSurface(); + mMap.setLocationHelper(MwmApplication.from(mCarContext).getLocationHelper()); mMap.onSurfaceCreated(mCarContext, mSurface, new Rect(0, 0, surfaceContainer.getWidth(), surfaceContainer.getHeight()), surfaceContainer.getDpi()); diff --git a/android/app/src/main/java/app/organicmaps/car/screens/NavigationScreen.java b/android/app/src/main/java/app/organicmaps/car/screens/NavigationScreen.java index 0430a77b8..968b2d86f 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/NavigationScreen.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/NavigationScreen.java @@ -28,11 +28,11 @@ import app.organicmaps.car.util.RoutingUtils; import app.organicmaps.car.util.ThemeUtils; import app.organicmaps.car.util.UiHelpers; import app.organicmaps.routing.NavigationService; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.location.LocationHelper; import app.organicmaps.sdk.location.LocationListener; import app.organicmaps.sdk.routing.JunctionInfo; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.routing.RoutingInfo; import app.organicmaps.sdk.sound.TtsPlayer; import app.organicmaps.sdk.util.LocationUtils; @@ -73,8 +73,7 @@ public class NavigationScreen extends BaseMapScreen implements RoutingController public Template onGetTemplate() { final NavigationTemplate.Builder builder = new NavigationTemplate.Builder(); - builder.setBackgroundColor(ThemeUtils.isNightMode(getCarContext()) ? Colors.NAVIGATION_TEMPLATE_BACKGROUND_NIGHT - : Colors.NAVIGATION_TEMPLATE_BACKGROUND_DAY); + builder.setBackgroundColor(Colors.NAVIGATION_TEMPLATE_BACKGROUND); builder.setActionStrip(createActionStrip()); builder.setMapActionStrip(UiHelpers.createMapActionStrip(getCarContext(), getSurfaceRenderer())); diff --git a/android/app/src/main/java/app/organicmaps/car/screens/PlaceScreen.java b/android/app/src/main/java/app/organicmaps/car/screens/PlaceScreen.java index ee04d535e..96489d783 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/PlaceScreen.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/PlaceScreen.java @@ -35,11 +35,11 @@ import app.organicmaps.car.util.OnBackPressedCallback; import app.organicmaps.car.util.RoutingHelpers; import app.organicmaps.car.util.UiHelpers; import app.organicmaps.routing.ResultCodesHelper; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.Router; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.bookmarks.data.Metadata; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.routing.RoutingInfo; import app.organicmaps.sdk.util.Config; import java.util.Objects; 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 deed7766a..117e6fedc 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 @@ -12,6 +12,7 @@ import androidx.lifecycle.LifecycleOwner; import app.organicmaps.R; import app.organicmaps.car.screens.ErrorScreen; import app.organicmaps.car.screens.base.BaseScreen; +import app.organicmaps.downloader.MapManagerHelper; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.util.StringUtils; @@ -169,7 +170,7 @@ class DownloaderScreen extends BaseScreen mIsDownloadFailed = true; final ErrorScreen.Builder builder = new ErrorScreen.Builder(getCarContext()) .setTitle(R.string.country_status_download_failed) - .setErrorMessage(MapManager.getErrorCodeStrRes(data.errorCode)) + .setErrorMessage(MapManagerHelper.getErrorCodeStrRes(data.errorCode)) .setPositiveButton(R.string.downloader_retry, null); if (!mIsCancelActionDisabled) builder.setNegativeButton(R.string.cancel, this::finish); 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 d673c84ed..6deeb1a67 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 @@ -62,29 +62,29 @@ public final class SearchUiHelpers CarColor color = Colors.DEFAULT; switch (searchResult.description.openNow) { - case SearchResult.OPEN_NOW_YES: - if (searchResult.description.minutesUntilClosed < 60) // less than 1 hour - { - final String time = searchResult.description.minutesUntilClosed + " " + carContext.getString(R.string.minute); - text = carContext.getString(R.string.closes_in, time); - color = Colors.OPENING_HOURS_CLOSES_SOON; - } - else - { - text = carContext.getString(R.string.editor_time_open); - color = Colors.OPENING_HOURS_OPEN; - } - break; - case SearchResult.OPEN_NOW_NO: - if (searchResult.description.minutesUntilOpen < 60) // less than 1 hour - { - final String time = searchResult.description.minutesUntilOpen + " " + carContext.getString(R.string.minute); - text = carContext.getString(R.string.opens_in, time); - } - else - text = carContext.getString(R.string.closed); - color = Colors.OPENING_HOURS_CLOSED; - break; + case SearchResult.OPEN_NOW_YES: + if (searchResult.description.minutesUntilClosed < 60) // less than 1 hour + { + final String time = searchResult.description.minutesUntilClosed + " " + carContext.getString(R.string.minute); + text = carContext.getString(R.string.closes_in, time); + color = Colors.OPENING_HOURS_CLOSES_SOON; + } + else + { + text = carContext.getString(R.string.editor_time_open); + color = Colors.OPENING_HOURS_OPEN; + } + break; + case SearchResult.OPEN_NOW_NO: + if (searchResult.description.minutesUntilOpen < 60) // less than 1 hour + { + final String time = searchResult.description.minutesUntilOpen + " " + carContext.getString(R.string.minute); + text = carContext.getString(R.string.opens_in, time); + } + else + text = carContext.getString(R.string.closed); + color = Colors.OPENING_HOURS_CLOSED; + break; } result.append(text); diff --git a/android/app/src/main/java/app/organicmaps/car/util/Colors.java b/android/app/src/main/java/app/organicmaps/car/util/Colors.java index b4b9deca9..50d98cfd4 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/Colors.java +++ b/android/app/src/main/java/app/organicmaps/car/util/Colors.java @@ -11,8 +11,7 @@ public final class Colors public static final CarColor OPENING_HOURS_CLOSES_SOON = CarColor.YELLOW; public static final CarColor OPENING_HOURS_CLOSED = CarColor.RED; public static final CarColor START_NAVIGATION = CarColor.GREEN; - public static final CarColor NAVIGATION_TEMPLATE_BACKGROUND_DAY = CarColor.GREEN; - public static final CarColor NAVIGATION_TEMPLATE_BACKGROUND_NIGHT = CarColor.DEFAULT; + public static final CarColor NAVIGATION_TEMPLATE_BACKGROUND = CarColor.GREEN; public static final CarColor BUTTON_ACCEPT = CarColor.GREEN; private Colors() {} diff --git a/android/app/src/main/java/app/organicmaps/car/util/CurrentCountryChangedListener.java b/android/app/src/main/java/app/organicmaps/car/util/CurrentCountryChangedListener.java index 802bbf8bc..ea1a9f1f1 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/CurrentCountryChangedListener.java +++ b/android/app/src/main/java/app/organicmaps/car/util/CurrentCountryChangedListener.java @@ -7,9 +7,9 @@ import androidx.car.app.CarContext; import androidx.car.app.ScreenManager; import app.organicmaps.car.screens.download.DownloadMapsScreen; import app.organicmaps.car.screens.download.DownloadMapsScreenBuilder; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; +import app.organicmaps.sdk.routing.RoutingController; public class CurrentCountryChangedListener implements MapManager.CurrentCountryChangedListener { diff --git a/android/app/src/main/java/app/organicmaps/car/util/IntentUtils.java b/android/app/src/main/java/app/organicmaps/car/util/IntentUtils.java index 776e95e63..ab37fbd72 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/IntentUtils.java +++ b/android/app/src/main/java/app/organicmaps/car/util/IntentUtils.java @@ -15,13 +15,13 @@ import app.organicmaps.car.CarAppService; import app.organicmaps.car.SurfaceRenderer; import app.organicmaps.car.screens.NavigationScreen; import app.organicmaps.car.screens.search.SearchScreen; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.Map; import app.organicmaps.sdk.api.ParsedSearchRequest; import app.organicmaps.sdk.api.RequestType; import app.organicmaps.sdk.display.DisplayManager; import app.organicmaps.sdk.display.DisplayType; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.log.Logger; public final class IntentUtils @@ -65,36 +65,36 @@ public final class IntentUtils final ScreenManager screenManager = carContext.getCarService(ScreenManager.class); switch (Framework.nativeParseAndSetApiUrl(uri.toString())) { - case RequestType.INCORRECT: return; - case RequestType.MAP: - screenManager.popToRoot(); - Map.executeMapApiRequest(); - return; - case RequestType.SEARCH: - screenManager.popToRoot(); - final ParsedSearchRequest request = Framework.nativeGetParsedSearchRequest(); - final double[] latlon = Framework.nativeGetParsedCenterLatLon(); - if (latlon != null) - { - Framework.nativeStopLocationFollow(); - Framework.nativeSetViewportCenter(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); - // We need to update viewport for search api manually because of drape engine - // will not notify subscribers when search activity is shown. - if (!request.mIsSearchOnMap) - Framework.nativeSetSearchViewport(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); - } - final SearchScreen.Builder builder = new SearchScreen.Builder(carContext, surfaceRenderer); - builder.setQuery(request.mQuery); - if (request.mLocale != null) - builder.setLocale(request.mLocale); + case RequestType.INCORRECT: return; + case RequestType.MAP: + screenManager.popToRoot(); + Map.executeMapApiRequest(); + return; + case RequestType.SEARCH: + screenManager.popToRoot(); + final ParsedSearchRequest request = Framework.nativeGetParsedSearchRequest(); + final double[] latlon = Framework.nativeGetParsedCenterLatLon(); + if (latlon != null) + { + Framework.nativeStopLocationFollow(); + Framework.nativeSetViewportCenter(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); + // We need to update viewport for search api manually because of drape engine + // will not notify subscribers when search activity is shown. + if (!request.mIsSearchOnMap) + Framework.nativeSetSearchViewport(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); + } + final SearchScreen.Builder builder = new SearchScreen.Builder(carContext, surfaceRenderer); + builder.setQuery(request.mQuery); + if (request.mLocale != null) + builder.setLocale(request.mLocale); - screenManager.popToRoot(); - screenManager.push(builder.build()); - return; - case RequestType.ROUTE: Logger.e(TAG, "Route API is not supported by Android Auto: " + uri); return; - case RequestType.CROSSHAIR: Logger.e(TAG, "Crosshair API is not supported by Android Auto: " + uri); return; - case RequestType.MENU: Logger.e(TAG, "Menu API is not supported by Android Auto: " + uri); return; - case RequestType.SETTINGS: Logger.e(TAG, "Settings API is not supported by Android Auto: " + uri); + screenManager.popToRoot(); + screenManager.push(builder.build()); + return; + case RequestType.ROUTE: Logger.e(TAG, "Route API is not supported by Android Auto: " + uri); return; + case RequestType.CROSSHAIR: Logger.e(TAG, "Crosshair API is not supported by Android Auto: " + uri); return; + case RequestType.MENU: Logger.e(TAG, "Menu API is not supported by Android Auto: " + uri); return; + case RequestType.SETTINGS: Logger.e(TAG, "Settings API is not supported by Android Auto: " + uri); } } diff --git a/android/app/src/main/java/app/organicmaps/car/util/ThemeUtils.java b/android/app/src/main/java/app/organicmaps/car/util/ThemeUtils.java index 20b688d83..5ade7fda6 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/ThemeUtils.java +++ b/android/app/src/main/java/app/organicmaps/car/util/ThemeUtils.java @@ -8,21 +8,22 @@ import androidx.annotation.StringRes; import androidx.annotation.UiThread; import androidx.car.app.CarContext; import app.organicmaps.R; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.MapStyle; +import app.organicmaps.sdk.routing.RoutingController; +import app.organicmaps.sdk.util.Config; public final class ThemeUtils { public enum ThemeMode { - AUTO(R.string.auto, R.string.theme_auto), - LIGHT(R.string.off, R.string.theme_default), - NIGHT(R.string.on, R.string.theme_night); + AUTO(R.string.auto, Config.UiTheme.AUTO), + LIGHT(R.string.off, Config.UiTheme.DEFAULT), + NIGHT(R.string.on, Config.UiTheme.NIGHT); - ThemeMode(@StringRes int titleId, @StringRes int prefsKeyId) + ThemeMode(@StringRes int titleId, @NonNull String config) { mTitleId = titleId; - mPrefsKeyId = prefsKeyId; + mConfig = config; } @StringRes @@ -31,16 +32,16 @@ public final class ThemeUtils return mTitleId; } - @StringRes - public int getPrefsKeyId() + @NonNull + public String getConfig() { - return mPrefsKeyId; + return mConfig; } @StringRes private final int mTitleId; - @StringRes - private final int mPrefsKeyId; + @NonNull + private final String mConfig; } private static final String ANDROID_AUTO_PREFERENCES_FILE_KEY = "ANDROID_AUTO_PREFERENCES_FILE_KEY"; @@ -79,23 +80,20 @@ public final class ThemeUtils @UiThread public static void setThemeMode(@NonNull CarContext context, @NonNull ThemeMode themeMode) { - getSharedPreferences(context).edit().putString(THEME_KEY, context.getString(themeMode.getPrefsKeyId())).commit(); + getSharedPreferences(context).edit().putString(THEME_KEY, themeMode.getConfig()).commit(); update(context, themeMode); } @NonNull public static ThemeMode getThemeMode(@NonNull CarContext context) { - final String autoTheme = context.getString(R.string.theme_auto); - final String lightTheme = context.getString(R.string.theme_default); - final String nightTheme = context.getString(R.string.theme_night); - final String themeMode = getSharedPreferences(context).getString(THEME_KEY, autoTheme); + final String themeMode = getSharedPreferences(context).getString(THEME_KEY, ThemeMode.AUTO.getConfig()); - if (themeMode.equals(autoTheme)) + if (themeMode.equals(ThemeMode.AUTO.getConfig())) return ThemeMode.AUTO; - else if (themeMode.equals(lightTheme)) + else if (themeMode.equals(ThemeMode.LIGHT.getConfig())) return ThemeMode.LIGHT; - else if (themeMode.equals(nightTheme)) + else if (themeMode.equals(ThemeMode.NIGHT.getConfig())) return ThemeMode.NIGHT; else throw new IllegalArgumentException("Unsupported value"); diff --git a/android/app/src/main/java/app/organicmaps/car/util/UiHelpers.java b/android/app/src/main/java/app/organicmaps/car/util/UiHelpers.java index 063041606..ada151fcb 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/UiHelpers.java +++ b/android/app/src/main/java/app/organicmaps/car/util/UiHelpers.java @@ -171,20 +171,20 @@ public final class UiHelpers int drawableRes; switch (locationMode) { - case LocationState.PENDING_POSITION, LocationState.NOT_FOLLOW_NO_POSITION -> - drawableRes = R.drawable.ic_location_off; - case LocationState.NOT_FOLLOW -> drawableRes = R.drawable.ic_not_follow; - case LocationState.FOLLOW -> - { - drawableRes = R.drawable.ic_follow; - tintColor = Colors.LOCATION_TINT; - } - case LocationState.FOLLOW_AND_ROTATE -> - { - drawableRes = R.drawable.ic_follow_and_rotate; - tintColor = Colors.LOCATION_TINT; - } - default -> throw new IllegalArgumentException("Invalid button mode: " + locationMode); + case LocationState.PENDING_POSITION, LocationState.NOT_FOLLOW_NO_POSITION -> + drawableRes = R.drawable.ic_location_off; + case LocationState.NOT_FOLLOW -> drawableRes = R.drawable.ic_not_follow; + case LocationState.FOLLOW -> + { + drawableRes = R.drawable.ic_follow; + tintColor = Colors.LOCATION_TINT; + } + case LocationState.FOLLOW_AND_ROTATE -> + { + drawableRes = R.drawable.ic_follow_and_rotate; + tintColor = Colors.LOCATION_TINT; + } + default -> throw new IllegalArgumentException("Invalid button mode: " + locationMode); } final CarIcon icon = 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 90c95e401..732f15058 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/BottomPanel.java +++ b/android/app/src/main/java/app/organicmaps/downloader/BottomPanel.java @@ -15,7 +15,7 @@ import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.downloader.UpdateInfo; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.button.MaterialButton; import com.google.android.material.floatingactionbutton.FloatingActionButton; @@ -29,7 +29,7 @@ class BottomPanel @Override public void onClick(View v) { - MapManager.warn3gAndDownload(mFragment.requireActivity(), mFragment.getCurrentRoot(), null); + MapManagerHelper.warn3gAndDownload(mFragment.requireActivity(), mFragment.getCurrentRoot(), null); } }; @@ -38,7 +38,10 @@ class BottomPanel public void onClick(View v) { final String country = mFragment.getCurrentRoot(); - MapManager.warnOn3gUpdate(mFragment.requireActivity(), country, () -> MapManager.startUpdate(country)); + MapManagerHelper.warnOn3gUpdate(mFragment.requireActivity(), country, () -> { + DownloaderService.startForegroundService(); + MapManagerHelper.startUpdate(country); + }); } }; @@ -55,7 +58,7 @@ class BottomPanel @Override public void onClick(View v) { - MapManager.warn3gAndRetry(mFragment.requireActivity(), mFragment.getCurrentRoot(), null); + MapManagerHelper.warn3gAndRetry(mFragment.requireActivity(), mFragment.getCurrentRoot(), null); } }; @@ -115,15 +118,15 @@ class BottomPanel { switch (status) { - case STATUS_UPDATABLE -> - { - UpdateInfo info = MapManager.nativeGetUpdateInfo(root); - setUpdateAllState(info); - } // Special case for "Countries" node when no maps currently downloaded. - case STATUS_DOWNLOADABLE, STATUS_DONE, STATUS_PARTLY -> show = false; - case STATUS_PROGRESS, STATUS_APPLYING, STATUS_ENQUEUED -> setCancelState(); - case STATUS_FAILED -> setRetryFailedStates(); - default -> throw new IllegalArgumentException("Inappropriate status for \"" + root + "\": " + status); + case STATUS_UPDATABLE -> + { + UpdateInfo info = MapManager.nativeGetUpdateInfo(root); + setUpdateAllState(info); + } // Special case for "Countries" node when no maps currently downloaded. + case STATUS_DOWNLOADABLE, STATUS_DONE, STATUS_PARTLY -> show = false; + case STATUS_PROGRESS, STATUS_APPLYING, STATUS_ENQUEUED -> setCancelState(); + case STATUS_FAILED -> setRetryFailedStates(); + default -> throw new IllegalArgumentException("Inappropriate status for \"" + root + "\": " + status); } } else @@ -133,15 +136,15 @@ class BottomPanel { switch (status) { - case STATUS_UPDATABLE -> - { - UpdateInfo info = MapManager.nativeGetUpdateInfo(root); - setUpdateAllState(info); - } - case STATUS_DONE -> show = false; - case STATUS_PROGRESS, STATUS_APPLYING, STATUS_ENQUEUED -> setCancelState(); - case STATUS_FAILED -> setRetryFailedStates(); - default -> setDownloadAllState(); + case STATUS_UPDATABLE -> + { + UpdateInfo info = MapManager.nativeGetUpdateInfo(root); + setUpdateAllState(info); + } + case STATUS_DONE -> show = false; + case STATUS_PROGRESS, STATUS_APPLYING, STATUS_ENQUEUED -> setCancelState(); + case STATUS_FAILED -> setRetryFailedStates(); + default -> setDownloadAllState(); } } } 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 7a9b0155e..9c0c7c7e0 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/CountrySuggestFragment.java +++ b/android/app/src/main/java/app/organicmaps/downloader/CountrySuggestFragment.java @@ -16,7 +16,7 @@ import app.organicmaps.base.BaseMwmFragmentActivity; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.widget.WheelProgressView; import com.google.android.material.button.MaterialButton; import com.google.android.material.textview.MaterialTextView; @@ -70,9 +70,9 @@ public class CountrySuggestFragment extends BaseMwmFragment implements View.OnCl switch (item.newStatus) { - case CountryItem.STATUS_FAILED: updateViews(); return; + case CountryItem.STATUS_FAILED: updateViews(); return; - case CountryItem.STATUS_DONE: exitFragment(); return; + case CountryItem.STATUS_DONE: exitFragment(); return; } break; @@ -198,7 +198,8 @@ public class CountrySuggestFragment extends BaseMwmFragment implements View.OnCl final int id = v.getId(); if (id == R.id.btn__download_map) { - MapManager.warn3gAndDownload(requireActivity(), mCurrentCountry.id, () -> mDownloadingCountry = mCurrentCountry); + MapManagerHelper.warn3gAndDownload(requireActivity(), mCurrentCountry.id, + () -> mDownloadingCountry = mCurrentCountry); } else if (id == R.id.btn__select_map) { 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 56060f667..e96b86338 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderAdapter.java +++ b/android/app/src/main/java/app/organicmaps/downloader/DownloaderAdapter.java @@ -20,11 +20,11 @@ import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.MwmActivity; import app.organicmaps.MwmApplication; import app.organicmaps.R; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; import app.organicmaps.util.bottomsheet.MenuBottomSheetItem; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -93,7 +93,7 @@ class DownloaderAdapter extends RecyclerView.Adapter MapManager.startUpdate(item.id)); + MapManagerHelper.warnOn3gUpdate(adapter.mActivity, item.id, () -> MapManagerHelper.startUpdate(item.id)); } private void onExploreActionSelected(CountryItem item, DownloaderAdapter adapter) @@ -206,7 +206,7 @@ class DownloaderAdapter extends RecyclerView.Adapter items = new ArrayList<>(); switch (mSelectedItem.status) { - case CountryItem.STATUS_DOWNLOADABLE: items.add(getDownloadMenuItem()); break; + case CountryItem.STATUS_DOWNLOADABLE: items.add(getDownloadMenuItem()); break; - case CountryItem.STATUS_UPDATABLE: - items.add(getUpdateMenuItem()); - // Fallthrough + case CountryItem.STATUS_UPDATABLE: + items.add(getUpdateMenuItem()); + // Fallthrough - case CountryItem.STATUS_DONE: - if (!mSelectedItem.isExpandable()) - items.add(getExploreMenuItem()); + case CountryItem.STATUS_DONE: + if (!mSelectedItem.isExpandable()) + items.add(getExploreMenuItem()); + appendDeleteMenuItem(items); + break; + + case CountryItem.STATUS_FAILED: + items.add(getCancelMenuItem()); + + if (mSelectedItem.present) + { appendDeleteMenuItem(items); - break; + items.add(getExploreMenuItem()); + } + break; - case CountryItem.STATUS_FAILED: - items.add(getCancelMenuItem()); + case CountryItem.STATUS_PROGRESS: + case CountryItem.STATUS_APPLYING: + case CountryItem.STATUS_ENQUEUED: + items.add(getCancelMenuItem()); - if (mSelectedItem.present) - { - appendDeleteMenuItem(items); - items.add(getExploreMenuItem()); - } - break; + if (mSelectedItem.present) + items.add(getExploreMenuItem()); + break; - case CountryItem.STATUS_PROGRESS: - case CountryItem.STATUS_APPLYING: - case CountryItem.STATUS_ENQUEUED: - items.add(getCancelMenuItem()); - - if (mSelectedItem.present) - items.add(getExploreMenuItem()); - break; - - case CountryItem.STATUS_PARTLY: - items.add(getDownloadMenuItem()); - appendDeleteMenuItem(items); - break; + case CountryItem.STATUS_PARTLY: + items.add(getDownloadMenuItem()); + appendDeleteMenuItem(items); + break; } return items; } @@ -372,20 +372,23 @@ class DownloaderAdapter extends RecyclerView.Adapter + case CountryItem.STATUS_DONE, CountryItem.STATUS_PROGRESS, CountryItem.STATUS_APPLYING, + CountryItem.STATUS_ENQUEUED -> + processLongClick(); + case CountryItem.STATUS_DOWNLOADABLE, CountryItem.STATUS_PARTLY -> + { + if (clickOnStatus) + onDownloadActionSelected(mItem, DownloaderAdapter.this); + else processLongClick(); - case CountryItem.STATUS_DOWNLOADABLE, CountryItem.STATUS_PARTLY -> - { - if (clickOnStatus) - onDownloadActionSelected(mItem, DownloaderAdapter.this); - else - processLongClick(); - } - case CountryItem.STATUS_FAILED -> MapManager.warn3gAndRetry(mActivity, mItem.id, null); - case CountryItem.STATUS_UPDATABLE -> - MapManager.warnOn3gUpdate(mActivity, mItem.id, () -> MapManager.startUpdate(mItem.id)); - default -> throw new IllegalArgumentException("Inappropriate item status: " + mItem.status); + } + case CountryItem.STATUS_FAILED -> + { + MapManagerHelper.warn3gAndRetry(mActivity, mItem.id, null); + } + case CountryItem.STATUS_UPDATABLE -> + MapManagerHelper.warnOn3gUpdate(mActivity, mItem.id, () -> MapManagerHelper.startUpdate(mItem.id)); + default -> throw new IllegalArgumentException("Inappropriate item status: " + mItem.status); } } @@ -535,33 +538,33 @@ class DownloaderAdapter extends RecyclerView.Adapter + case CountryItem.CATEGORY_NEAR_ME -> + { + if (ci.category != prev) { - if (ci.category != prev) - { - headerId = CountryItem.CATEGORY_NEAR_ME; - mItemsAndHeader.add(new GenericItem(mActivity.getString(R.string.downloader_near_me_subtitle))); - prev = ci.category; - } - } - case CountryItem.CATEGORY_DOWNLOADED -> - { - if (ci.category != prev) - { - headerId = CountryItem.CATEGORY_DOWNLOADED; - mItemsAndHeader.add(new GenericItem(mActivity.getString(R.string.downloader_downloaded_subtitle))); - prev = ci.category; - } - } - default -> - { - int prevHeader = headerId; - headerId = CountryItem.CATEGORY_AVAILABLE + ci.name.charAt(0); - if (headerId != prevHeader) - mItemsAndHeader.add(new GenericItem(StringUtils.toUpperCase(ci.name.substring(0, 1)))); + headerId = CountryItem.CATEGORY_NEAR_ME; + mItemsAndHeader.add(new GenericItem(mActivity.getString(R.string.downloader_near_me_subtitle))); prev = ci.category; } } + case CountryItem.CATEGORY_DOWNLOADED -> + { + if (ci.category != prev) + { + headerId = CountryItem.CATEGORY_DOWNLOADED; + mItemsAndHeader.add(new GenericItem(mActivity.getString(R.string.downloader_downloaded_subtitle))); + prev = ci.category; + } + } + default -> + { + int prevHeader = headerId; + headerId = CountryItem.CATEGORY_AVAILABLE + ci.name.charAt(0); + if (headerId != prevHeader) + mItemsAndHeader.add(new GenericItem(StringUtils.toUpperCase(ci.name.substring(0, 1)))); + prev = ci.category; + } + } ci.headerId = headerId; } mItemsAndHeader.add(new GenericItem(ci)); 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 688d0ada6..8e52cc25a 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderFragment.java +++ b/android/app/src/main/java/app/organicmaps/downloader/DownloaderFragment.java @@ -24,8 +24,8 @@ import app.organicmaps.widget.PlaceholderView; import java.util.ArrayList; import java.util.List; -public class DownloaderFragment extends BaseMwmRecyclerFragment - implements MenuBottomSheetFragment.MenuBottomSheetInterface +public class DownloaderFragment + extends BaseMwmRecyclerFragment implements MenuBottomSheetFragment.MenuBottomSheetInterface { private DownloaderToolbarController mToolbarController; @@ -153,7 +153,8 @@ public class DownloaderFragment extends BaseMwmRecyclerFragment= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(this, POST_NOTIFICATIONS) != PERMISSION_GRANTED) @@ -106,8 +106,7 @@ public class DownloaderService extends Service implements MapManager.StorageCall return; } - // TODO: How to calculate progress? - mNotifier.notifyProgress(); + mNotifier.notifyProgress(countryId, (int) bytesTotal, (int) bytesDownloaded); } @Override diff --git a/android/app/src/main/java/app/organicmaps/downloader/DownloaderStatusIcon.java b/android/app/src/main/java/app/organicmaps/downloader/DownloaderStatusIcon.java index 4fabcca82..709b15624 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderStatusIcon.java +++ b/android/app/src/main/java/app/organicmaps/downloader/DownloaderStatusIcon.java @@ -6,8 +6,8 @@ import androidx.annotation.AttrRes; import androidx.annotation.DrawableRes; import app.organicmaps.R; import app.organicmaps.sdk.downloader.CountryItem; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.ThemeUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.widget.WheelProgressView; import com.google.android.material.imageview.ShapeableImageView; 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 e6b593b92..2ad0a7a29 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/DownloaderToolbarController.java +++ b/android/app/src/main/java/app/organicmaps/downloader/DownloaderToolbarController.java @@ -5,7 +5,7 @@ import android.content.Intent; import android.text.TextUtils; import android.view.View; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.widget.SearchToolbarController; class DownloaderToolbarController extends SearchToolbarController diff --git a/android/app/src/main/java/app/organicmaps/downloader/MapManagerHelper.java b/android/app/src/main/java/app/organicmaps/downloader/MapManagerHelper.java new file mode 100644 index 000000000..66fcb2314 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/downloader/MapManagerHelper.java @@ -0,0 +1,223 @@ +package app.organicmaps.downloader; + +import android.app.Activity; +import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.core.util.Consumer; +import app.organicmaps.R; +import app.organicmaps.sdk.downloader.CountryItem; +import app.organicmaps.sdk.downloader.ExpandRetryConfirmationListener; +import app.organicmaps.sdk.downloader.MapManager; +import app.organicmaps.sdk.util.ConnectionState; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import java.lang.ref.WeakReference; + +public class MapManagerHelper +{ + private static WeakReference sCurrentErrorDialog; + + @StringRes + public static int getErrorCodeStrRes(final int errorCode) + { + return switch (errorCode) + { + case CountryItem.ERROR_NO_INTERNET -> R.string.common_check_internet_connection_dialog; + case CountryItem.ERROR_OOM -> R.string.downloader_no_space_title; + default -> throw new IllegalArgumentException("Given error can not be displayed: " + errorCode); + }; + } + + public static void showError(final Activity activity, final MapManager.StorageCallbackData errorData, + @Nullable final Consumer dialogClickListener) + { + if (!MapManager.nativeIsAutoretryFailed()) + return; + + showErrorDialog(activity, errorData, dialogClickListener); + } + + public static void showErrorDialog(final Activity activity, final MapManager.StorageCallbackData errorData, + @Nullable final Consumer dialogClickListener) + { + if (sCurrentErrorDialog != null) + { + AlertDialog dlg = sCurrentErrorDialog.get(); + if (dlg != null && dlg.isShowing()) + return; + } + + final AlertDialog dlg = new MaterialAlertDialogBuilder(activity, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.country_status_download_failed) + .setMessage(getErrorCodeStrRes(errorData.errorCode)) + .setNegativeButton(R.string.cancel, + (dialog, which) -> { + sCurrentErrorDialog = null; + if (dialogClickListener != null) + dialogClickListener.accept(false); + }) + .setPositiveButton(R.string.downloader_retry, + (dialog, which) -> { + ExpandRetryConfirmationListener listener = + new ExpandRetryConfirmationListener(dialogClickListener); + warn3gAndRetry(activity, errorData.countryId, listener); + }) + .create(); + dlg.setCanceledOnTouchOutside(false); + dlg.show(); + sCurrentErrorDialog = new WeakReference<>(dlg); + } + + private static void notifyNoSpaceInternal(Activity activity) + { + new MaterialAlertDialogBuilder(activity, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.downloader_no_space_title) + .setMessage(R.string.downloader_no_space_message) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + /** + * @return true if there is no space to update the given {@code root}, so the alert dialog will be shown. + */ + private static boolean notifyNoSpaceToUpdate(Activity activity, String root) + { + if (MapManager.nativeHasSpaceToUpdate(root)) + return false; + + notifyNoSpaceInternal(activity); + return true; + } + + /** + * @return true if there is no space to download the given {@code root}, so the alert dialog will be shown. + */ + private static boolean notifyNoSpace(Activity activity, String root) + { + if (MapManager.nativeHasSpaceToDownloadCountry(root)) + return false; + + notifyNoSpaceInternal(activity); + return true; + } + + /** + * @return true if there is no space to download {@code size} bytes, so the alert dialog will be shown. + */ + private static boolean notifyNoSpace(Activity activity, long size) + { + if (MapManager.nativeHasSpaceToDownloadAmount(size)) + return false; + + notifyNoSpaceInternal(activity); + return true; + } + + private static boolean warnOn3gInternal(Activity activity, @NonNull final Runnable onAcceptListener) + { + if (MapManager.nativeIsDownloadOn3gEnabled() || !ConnectionState.INSTANCE.isMobileConnected()) + { + onAcceptListener.run(); + return false; + } + + new MaterialAlertDialogBuilder(activity, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.download_over_mobile_header) + .setMessage(R.string.download_over_mobile_message) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok, + (dlg, which) -> { + MapManager.nativeEnableDownloadOn3g(); + onAcceptListener.run(); + }) + .show(); + + return true; + } + + public static boolean warnOn3gUpdate(Activity activity, @Nullable String countryId, + @NonNull final Runnable onAcceptListener) + { + // noinspection SimplifiableIfStatement + if (TextUtils.isEmpty(countryId) || !notifyNoSpaceToUpdate(activity, countryId)) + return warnOn3gInternal(activity, onAcceptListener); + + return true; + } + + public static boolean warnOn3g(Activity activity, @Nullable String countryId, + @NonNull final Runnable onAcceptListener) + { + // noinspection SimplifiableIfStatement + if (TextUtils.isEmpty(countryId) || !notifyNoSpace(activity, countryId)) + return warnOn3gInternal(activity, onAcceptListener); + + return true; + } + + public static boolean warnOn3g(Activity activity, long size, @NonNull Runnable onAcceptListener) + { + return !notifyNoSpace(activity, size) && warnOn3gInternal(activity, onAcceptListener); + } + + public static boolean warn3gAndDownload(Activity activity, final String countryId, + @Nullable final Runnable onAcceptListener) + { + return warnOn3g(activity, countryId, () -> { + if (onAcceptListener != null) + onAcceptListener.run(); + startDownload(countryId); + }); + } + + public static boolean warn3gAndRetry(Activity activity, final String countryId, + @Nullable final Runnable onAcceptListener) + { + return warnOn3g(activity, countryId, () -> { + if (onAcceptListener != null) + onAcceptListener.run(); + retryDownload(countryId); + }); + } + + /** + * Enqueues failed items under given {@code root} node in downloader. + */ + public static void retryDownload(@NonNull String countryId) + { + DownloaderService.startForegroundService(); + MapManager.retryDownload(countryId); + } + + /** + * Enqueues given {@code root} node with its children in downloader. + */ + public static void startUpdate(@NonNull String root) + { + DownloaderService.startForegroundService(); + MapManager.startUpdate(root); + } + + /** + * Enqueues the given list of nodes and its children in downloader. + */ + public static void startDownload(String... countries) + { + DownloaderService.startForegroundService(); + for (var countryId : countries) + { + MapManager.startDownload(countryId); + } + } + + /** + * Enqueues given {@code root} node and its children in downloader. + */ + public static void startDownload(@NonNull String countryId) + { + DownloaderService.startForegroundService(); + MapManager.startDownload(countryId); + } +} 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 3f09d5bdc..a08f20e71 100644 --- a/android/app/src/main/java/app/organicmaps/downloader/OnmapDownloader.java +++ b/android/app/src/main/java/app/organicmaps/downloader/OnmapDownloader.java @@ -9,13 +9,13 @@ import androidx.core.view.ViewCompat; import app.organicmaps.MwmActivity; import app.organicmaps.MwmApplication; import app.organicmaps.R; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.ConnectionState; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import app.organicmaps.widget.WheelProgressView; import com.google.android.material.button.MaterialButton; @@ -52,7 +52,7 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener continue; if (item.newStatus == CountryItem.STATUS_FAILED) - MapManager.showError(mActivity, item, null); + MapManagerHelper.showError(mActivity, item, null); if (mCurrentCountry.id.equals(item.countryId)) { @@ -163,7 +163,7 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener if (TextUtils.equals(mCurrentCountry.id, country) && MapManager.nativeHasSpaceToDownloadCountry(country)) { - MapManager.startDownload(mCurrentCountry.id); + MapManagerHelper.startDownload(mCurrentCountry.id); } } } @@ -199,18 +199,18 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener setAutodownloadLocked(true); }); mButton.setOnClickListener( - v -> MapManager.warnOn3g(mActivity, mCurrentCountry == null ? null : mCurrentCountry.id, () -> { + v -> MapManagerHelper.warnOn3g(mActivity, mCurrentCountry == null ? null : mCurrentCountry.id, () -> { if (mCurrentCountry == null) return; boolean retry = (mCurrentCountry.status == CountryItem.STATUS_FAILED); if (retry) { - MapManager.retryDownload(mCurrentCountry.id); + MapManagerHelper.retryDownload(mCurrentCountry.id); } else { - MapManager.startDownload(mCurrentCountry.id); + MapManagerHelper.startDownload(mCurrentCountry.id); mActivity.requestPostNotificationsPermission(); } })); diff --git a/android/app/src/main/java/app/organicmaps/editor/AdvancedTimetableFragment.java b/android/app/src/main/java/app/organicmaps/editor/AdvancedTimetableFragment.java index 9a795fafd..2043ab2f9 100644 --- a/android/app/src/main/java/app/organicmaps/editor/AdvancedTimetableFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/AdvancedTimetableFragment.java @@ -14,9 +14,9 @@ import app.organicmaps.R; import app.organicmaps.base.BaseMwmFragment; import app.organicmaps.sdk.editor.OpeningHours; import app.organicmaps.sdk.util.Constants; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.Graphics; import app.organicmaps.util.InputUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textview.MaterialTextView; @@ -87,8 +87,8 @@ public class AdvancedTimetableFragment extends BaseMwmFragment implements View.O private void setExampleDrawables(@DrawableRes int left, @DrawableRes int right) { mExamplesTitle.setCompoundDrawablesRelativeWithIntrinsicBounds( - Graphics.tint(requireActivity(), left, androidx.appcompat.R.attr.colorAccent), null, - Graphics.tint(requireActivity(), right, androidx.appcompat.R.attr.colorAccent), null); + Graphics.tint(requireActivity(), left, com.google.android.material.R.attr.colorSecondary), null, + Graphics.tint(requireActivity(), right, com.google.android.material.R.attr.colorSecondary), null); } @Override 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 2f9b02caa..5fdad226d 100644 --- a/android/app/src/main/java/app/organicmaps/editor/EditorFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/EditorFragment.java @@ -20,19 +20,19 @@ import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; import app.organicmaps.base.BaseMwmFragment; import app.organicmaps.dialog.EditTextDialogFragment; +import app.organicmaps.editor.data.TimeFormatUtils; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.bookmarks.data.Metadata; import app.organicmaps.sdk.editor.Editor; import app.organicmaps.sdk.editor.OpeningHours; import app.organicmaps.sdk.editor.data.LocalizedName; import app.organicmaps.sdk.editor.data.LocalizedStreet; -import app.organicmaps.editor.data.TimeFormatUtils; import app.organicmaps.sdk.editor.data.Timetable; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.sdk.util.Utils; import app.organicmaps.util.Graphics; import app.organicmaps.util.InputUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.textfield.TextInputEditText; @@ -236,6 +236,11 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe return true; } + boolean saveEdits() + { + return setEdits() && beforeSavingValidation(); + } + @NonNull protected String getDescription() { @@ -282,6 +287,19 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe return validateNames(); } + private boolean beforeSavingValidation() + { + // Validation to make sure address features have a house number + if (!Editor.nativeCheckHouseNumberWhenIsAddress()) + { + mHouseNumber.requestFocus(); + UiUtils.setInputError(mInputHouseNumber, R.string.error_enter_correct_house_number); + InputUtils.showKeyboard(mHouseNumber); + return false; + } + return true; + } + private boolean validateNames() { for (int pos = 0; pos < mNamesAdapter.getItemCount(); pos++) @@ -623,11 +641,11 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe switch (Editor.nativeGetMapObjectStatus()) { - case Editor.CREATED -> mReset.setText(R.string.editor_remove_place_button); - case Editor.MODIFIED -> mReset.setText(R.string.editor_reset_edits_button); - case Editor.UNTOUCHED -> mReset.setText(R.string.editor_place_doesnt_exist); - case Editor.DELETED -> throw new IllegalStateException("Can't delete already deleted feature."); - case Editor.OBSOLETE -> throw new IllegalStateException("Obsolete objects cannot be reverted."); + case Editor.CREATED -> mReset.setText(R.string.editor_remove_place_button); + case Editor.MODIFIED -> mReset.setText(R.string.editor_reset_edits_button); + case Editor.UNTOUCHED -> mReset.setText(R.string.editor_place_doesnt_exist); + case Editor.DELETED -> throw new IllegalStateException("Can't delete already deleted feature."); + case Editor.OBSOLETE -> throw new IllegalStateException("Obsolete objects cannot be reverted."); } } @@ -641,11 +659,11 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe switch (Editor.nativeGetMapObjectStatus()) { - case Editor.CREATED -> rollback(Editor.CREATED); - case Editor.MODIFIED -> rollback(Editor.MODIFIED); - case Editor.UNTOUCHED -> placeDoesntExist(); - case Editor.DELETED -> throw new IllegalStateException("Can't delete already deleted feature."); - case Editor.OBSOLETE -> throw new IllegalStateException("Obsolete objects cannot be reverted."); + case Editor.CREATED -> rollback(Editor.CREATED); + case Editor.MODIFIED -> rollback(Editor.MODIFIED); + case Editor.UNTOUCHED -> placeDoesntExist(); + case Editor.DELETED -> throw new IllegalStateException("Can't delete already deleted feature."); + case Editor.OBSOLETE -> throw new IllegalStateException("Obsolete objects cannot be reverted."); } } 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 3a7cab0c3..72ffc99b0 100644 --- a/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/EditorHostFragment.java @@ -1,6 +1,5 @@ package app.organicmaps.editor; -import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; @@ -25,7 +24,7 @@ import app.organicmaps.sdk.editor.data.Language; import app.organicmaps.sdk.editor.data.LocalizedName; import app.organicmaps.sdk.editor.data.LocalizedStreet; import app.organicmaps.sdk.editor.data.NamesDataSource; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import app.organicmaps.widget.SearchToolbarController; @@ -173,8 +172,8 @@ public class EditorHostFragment { switch (mMode) { - case OPENING_HOURS, STREET, CUISINE, LANGUAGE, PHONE, SELF_SERVICE -> editMapObject(); - default -> Utils.navigateToParent(requireActivity()); + case OPENING_HOURS, STREET, CUISINE, LANGUAGE, PHONE, SELF_SERVICE -> editMapObject(); + default -> Utils.navigateToParent(requireActivity()); } return true; } @@ -276,6 +275,11 @@ public class EditorHostFragment return ((EditorFragment) getChildFragmentManager().findFragmentByTag(EditorFragment.class.getName())).setEdits(); } + private boolean saveEdits() + { + return ((EditorFragment) getChildFragmentManager().findFragmentByTag(EditorFragment.class.getName())).saveEdits(); + } + @Override public void onClick(View v) { @@ -283,57 +287,57 @@ public class EditorHostFragment { switch (mMode) { - case OPENING_HOURS -> - { - final String timetables = ((TimetableContainerFragment) getChildFragmentManager().findFragmentByTag( - TimetableContainerFragment.class.getName())) - .getTimetable(); - Editor.nativeSetOpeningHours(timetables); - editMapObject(); - } - case STREET -> - setStreet(((StreetFragment) getChildFragmentManager().findFragmentByTag(StreetFragment.class.getName())) - .getStreet()); - case CUISINE -> - { - String[] cuisines = - ((CuisineFragment) getChildFragmentManager().findFragmentByTag(CuisineFragment.class.getName())) - .getCuisines(); - Editor.nativeSetSelectedCuisines(cuisines); - editMapObject(); - } - case SELF_SERVICE -> - setSelection( - Metadata.MetadataType.FMD_SELF_SERVICE, - ((SelfServiceFragment) getChildFragmentManager().findFragmentByTag(SelfServiceFragment.class.getName())) - .getSelection()); - case LANGUAGE -> editMapObject(); - case MAP_OBJECT -> - { - if (!setEdits()) - return; + case OPENING_HOURS -> + { + final String timetables = ((TimetableContainerFragment) getChildFragmentManager().findFragmentByTag( + TimetableContainerFragment.class.getName())) + .getTimetable(); + Editor.nativeSetOpeningHours(timetables); + editMapObject(); + } + case STREET -> + setStreet( + ((StreetFragment) getChildFragmentManager().findFragmentByTag(StreetFragment.class.getName())).getStreet()); + case CUISINE -> + { + String[] cuisines = + ((CuisineFragment) getChildFragmentManager().findFragmentByTag(CuisineFragment.class.getName())) + .getCuisines(); + Editor.nativeSetSelectedCuisines(cuisines); + editMapObject(); + } + case SELF_SERVICE -> + setSelection( + Metadata.MetadataType.FMD_SELF_SERVICE, + ((SelfServiceFragment) getChildFragmentManager().findFragmentByTag(SelfServiceFragment.class.getName())) + .getSelection()); + case LANGUAGE -> editMapObject(); + case MAP_OBJECT -> + { + if (!saveEdits()) + return; - // Save object edits - if (!MwmApplication.prefs(requireContext()).contains(NOOB_ALERT_SHOWN)) - { - showNoobDialog(); - } - else - { - saveNote(); - saveMapObjectEdits(); - } - } - case PHONE -> + // Save object edits + if (!MwmApplication.prefs(requireContext()).contains(NOOB_ALERT_SHOWN)) { - final String phone = - ((PhoneFragment) getChildFragmentManager().findFragmentByTag(PhoneFragment.class.getName())).getPhone(); - if (Editor.nativeIsPhoneValid(phone)) - { - Editor.nativeSetPhone(phone); - editMapObject(); - } + showNoobDialog(); } + else + { + saveNote(); + saveMapObjectEdits(); + } + } + case PHONE -> + { + final String phone = + ((PhoneFragment) getChildFragmentManager().findFragmentByTag(PhoneFragment.class.getName())).getPhone(); + if (Editor.nativeIsPhoneValid(phone)) + { + Editor.nativeSetPhone(phone); + editMapObject(); + } + } } } } diff --git a/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java b/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java index ad4eeb008..d6d3f2867 100644 --- a/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java +++ b/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryAdapter.java @@ -9,7 +9,9 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; import app.organicmaps.sdk.editor.data.FeatureCategory; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.StringUtils; +import app.organicmaps.util.UiUtils; +import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textview.MaterialTextView; public class FeatureCategoryAdapter extends RecyclerView.Adapter @@ -21,6 +23,12 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter - { - return new FeatureViewHolder( - LayoutInflater.from(parent.getContext()).inflate(R.layout.item_feature_category, parent, false)); - } - case TYPE_FOOTER -> - { - return new FooterViewHolder( - LayoutInflater.from(parent.getContext()).inflate(R.layout.item_feature_category_footer, parent, false)); - } - default -> throw new IllegalArgumentException("Unsupported"); + case TYPE_CATEGORY -> + { + return new FeatureViewHolder(inflater.inflate(R.layout.item_feature_category, parent, false)); + } + case TYPE_FOOTER -> + { + return new FooterViewHolder(inflater.inflate(R.layout.item_feature_category_footer, parent, false), + (FooterListener) mFragment); + } + default -> + { + throw new IllegalArgumentException("Unsupported viewType: " + viewType); + } } } @@ -70,6 +82,10 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter listener.onSendNoteClicked()); + mNoteEditText.addTextChangedListener(new StringUtils.SimpleTextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) + { + final String str = s.toString(); + listener.onNoteTextChanged(str); + mSendNoteButton.setEnabled(!str.trim().isEmpty()); + } + }); + } + public void bind(String pendingNoteText) + { + if (!mNoteEditText.getText().toString().equals(pendingNoteText)) + { + mNoteEditText.setText(pendingNoteText); + if (pendingNoteText != null) + mNoteEditText.setSelection(pendingNoteText.length()); + } + mSendNoteButton.setEnabled(pendingNoteText != null && !pendingNoteText.trim().isEmpty()); } } diff --git a/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryFragment.java b/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryFragment.java index e7f222916..e52abfc43 100644 --- a/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/FeatureCategoryFragment.java @@ -2,28 +2,39 @@ package app.organicmaps.editor; import static app.organicmaps.sdk.util.Utils.getLocalizedFeatureType; +import android.content.Intent; import android.os.Bundle; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.base.BaseMwmRecyclerFragment; +import app.organicmaps.dialog.EditTextDialogFragment; +import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.editor.Editor; +import app.organicmaps.sdk.editor.OsmOAuth; import app.organicmaps.sdk.editor.data.FeatureCategory; import app.organicmaps.sdk.util.Language; import app.organicmaps.util.Utils; import app.organicmaps.widget.SearchToolbarController; import app.organicmaps.widget.ToolbarController; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.util.Arrays; import java.util.Comparator; -public class FeatureCategoryFragment extends BaseMwmRecyclerFragment +public class FeatureCategoryFragment + extends BaseMwmRecyclerFragment implements FeatureCategoryAdapter.FooterListener { private FeatureCategory mSelectedCategory; protected ToolbarController mToolbarController; + private static final String NOTE_CONFIRMATION_SHOWN = "NoteConfirmationAlertWasShown"; + private static String mPendingNoteText = ""; public interface FeatureCategoryListener { @@ -104,4 +115,71 @@ public class FeatureCategoryFragment extends BaseMwmRecyclerFragment { + MwmApplication.prefs(requireContext().getApplicationContext()) + .edit() + .putBoolean(NOTE_CONFIRMATION_SHOWN, true) + .apply(); + Editor.nativeCreateStandaloneNote(lat, lon, noteText); + mPendingNoteText = ""; + Toast.makeText(requireContext(), R.string.osm_note_toast, Toast.LENGTH_SHORT).show(); + + if (!OsmOAuth.isAuthorized()) + { + final Intent intent = new Intent(requireActivity(), OsmLoginActivity.class); + startActivity(intent); + } + + requireActivity().finish(); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } } diff --git a/android/app/src/main/java/app/organicmaps/editor/HoursMinutesPickerFragment.java b/android/app/src/main/java/app/organicmaps/editor/HoursMinutesPickerFragment.java index 7bad3f64d..20a70d7f4 100644 --- a/android/app/src/main/java/app/organicmaps/editor/HoursMinutesPickerFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/HoursMinutesPickerFragment.java @@ -79,8 +79,8 @@ public class HoursMinutesPickerFragment extends BaseMwmDialogFragment mTabs.getTabAt(mSelectedTab).select(); @StyleRes - final int theme = ThemeUtils.isNightTheme(requireContext()) ? R.style.MwmMain_DialogFragment_TimePicker_Night - : R.style.MwmMain_DialogFragment_TimePicker; + final int theme = ThemeUtils.isNightTheme() ? R.style.MwmMain_DialogFragment_TimePicker_Night + : R.style.MwmMain_DialogFragment_TimePicker; final AlertDialog dialog = new MaterialAlertDialogBuilder(requireActivity(), theme) .setView(root) .setNegativeButton(R.string.cancel, null) @@ -141,15 +141,15 @@ public class HoursMinutesPickerFragment extends BaseMwmDialogFragment MaterialTextView tabView = (MaterialTextView) inflater.inflate(R.layout.tab_timepicker, mTabs, false); tabView.setText(getResources().getString(R.string.editor_time_from)); final ColorStateList textColor = AppCompatResources.getColorStateList( - requireContext(), ThemeUtils.isNightTheme(requireContext()) ? R.color.accent_color_selector_night - : R.color.accent_color_selector); + requireContext(), + ThemeUtils.isNightTheme() ? R.color.accent_color_selector_night : R.color.accent_color_selector); tabView.setTextColor(textColor); mTabs.addTab(mTabs.newTab().setCustomView(tabView), true); tabView = (MaterialTextView) inflater.inflate(R.layout.tab_timepicker, mTabs, false); tabView.setText(getResources().getString(R.string.editor_time_to)); tabView.setTextColor(textColor); mTabs.addTab(mTabs.newTab().setCustomView(tabView), true); - mTabs.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + mTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { diff --git a/android/app/src/main/java/app/organicmaps/editor/MultilanguageAdapter.java b/android/app/src/main/java/app/organicmaps/editor/MultilanguageAdapter.java index c4f226af0..88cc34522 100644 --- a/android/app/src/main/java/app/organicmaps/editor/MultilanguageAdapter.java +++ b/android/app/src/main/java/app/organicmaps/editor/MultilanguageAdapter.java @@ -10,7 +10,7 @@ import app.organicmaps.sdk.editor.Editor; import app.organicmaps.sdk.editor.data.Language; import app.organicmaps.sdk.editor.data.LocalizedName; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; diff --git a/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java b/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java index f99bfa254..782f149e6 100644 --- a/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java @@ -15,10 +15,11 @@ import app.organicmaps.R; import app.organicmaps.base.BaseMwmToolbarFragment; import app.organicmaps.sdk.editor.OsmOAuth; import app.organicmaps.sdk.util.Constants; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.DateUtils; import app.organicmaps.sdk.util.concurrency.ThreadPool; import app.organicmaps.sdk.util.concurrency.UiThread; import app.organicmaps.util.InputUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils.ScrollableContentInsetsListener; import com.google.android.material.button.MaterialButton; diff --git a/android/app/src/main/java/app/organicmaps/editor/PhoneListAdapter.java b/android/app/src/main/java/app/organicmaps/editor/PhoneListAdapter.java index b4e22b1c2..cab90f3b4 100644 --- a/android/app/src/main/java/app/organicmaps/editor/PhoneListAdapter.java +++ b/android/app/src/main/java/app/organicmaps/editor/PhoneListAdapter.java @@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; import app.organicmaps.sdk.editor.Editor; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; diff --git a/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java b/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java index 13d38d882..4a952124c 100644 --- a/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java @@ -11,14 +11,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.fragment.app.FragmentManager; import app.organicmaps.R; import app.organicmaps.base.BaseMwmToolbarFragment; import app.organicmaps.sdk.editor.OsmOAuth; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.NetworkPolicy; import app.organicmaps.sdk.util.concurrency.ThreadPool; import app.organicmaps.sdk.util.concurrency.UiThread; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils; +import app.organicmaps.widget.StackedButtonDialogFragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.textview.MaterialTextView; @@ -26,6 +29,24 @@ import java.text.NumberFormat; public class ProfileFragment extends BaseMwmToolbarFragment { + @NonNull + private static final NetworkPolicy.DialogPresenter mDialogPresenter = new NetworkPolicy.DialogPresenter() { + @Override + public void showDialogIfNeeded(@NonNull FragmentManager fragmentManager, + @NonNull NetworkPolicy.NetworkPolicyListener listener, @NonNull NetworkPolicy policy, + boolean isToday) + { + StackedButtonDialogFragment.showDialogIfNeeded(fragmentManager, listener, policy, isToday); + } + + @Override + public void showDialog(@NonNull FragmentManager fragmentManager, + @NonNull NetworkPolicy.NetworkPolicyListener listener) + { + StackedButtonDialogFragment.showDialog(fragmentManager, listener); + } + }; + private View mUserInfoBlock; private MaterialTextView mEditsSent; private MaterialTextView mProfileName; @@ -84,7 +105,7 @@ public class ProfileFragment extends BaseMwmToolbarFragment UiUtils.show(mProfileInfoLoading); UiUtils.hide(mUserInfoBlock); } - final int profileEditCount = OsmOAuth.getOsmChangesetsCount(getParentFragmentManager()); + final int profileEditCount = OsmOAuth.getOsmChangesetsCount(mDialogPresenter, getParentFragmentManager()); final String profileUsername = OsmOAuth.getUsername(); final Bitmap profilePicture = OsmOAuth.getProfilePicture(); diff --git a/android/app/src/main/java/app/organicmaps/editor/ReportFragment.java b/android/app/src/main/java/app/organicmaps/editor/ReportFragment.java index a041ea44d..4a8b1f5b6 100644 --- a/android/app/src/main/java/app/organicmaps/editor/ReportFragment.java +++ b/android/app/src/main/java/app/organicmaps/editor/ReportFragment.java @@ -11,7 +11,7 @@ import androidx.core.view.ViewCompat; import app.organicmaps.R; import app.organicmaps.base.BaseMwmToolbarFragment; import app.organicmaps.sdk.editor.Editor; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.WindowInsetUtils.ScrollableContentInsetsListener; import com.google.android.material.textfield.TextInputEditText; diff --git a/android/app/src/main/java/app/organicmaps/editor/SimpleTimetableAdapter.java b/android/app/src/main/java/app/organicmaps/editor/SimpleTimetableAdapter.java index 0b2d20002..1b3497073 100644 --- a/android/app/src/main/java/app/organicmaps/editor/SimpleTimetableAdapter.java +++ b/android/app/src/main/java/app/organicmaps/editor/SimpleTimetableAdapter.java @@ -13,12 +13,13 @@ import androidx.appcompat.widget.SwitchCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; +import app.organicmaps.editor.data.TimeFormatUtils; import app.organicmaps.sdk.editor.OpeningHours; import app.organicmaps.sdk.editor.data.HoursMinutes; -import app.organicmaps.editor.data.TimeFormatUtils; import app.organicmaps.sdk.editor.data.Timespan; import app.organicmaps.sdk.editor.data.Timetable; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.Utils; +import app.organicmaps.util.UiUtils; import com.google.android.material.button.MaterialButton; import com.google.android.material.checkbox.MaterialCheckBox; import com.google.android.material.textview.MaterialTextView; @@ -224,7 +225,7 @@ class SimpleTimetableAdapter extends RecyclerView.Adapter setMode(Mode.ADVANCED, filledTimetables); - case ADVANCED -> setMode(Mode.SIMPLE, filledTimetables); + case SIMPLE -> setMode(Mode.ADVANCED, filledTimetables); + case ADVANCED -> setMode(Mode.SIMPLE, filledTimetables); } } diff --git a/android/app/src/main/java/app/organicmaps/help/FaqFragment.java b/android/app/src/main/java/app/organicmaps/help/FaqFragment.java index 94585eaca..5532020a1 100644 --- a/android/app/src/main/java/app/organicmaps/help/FaqFragment.java +++ b/android/app/src/main/java/app/organicmaps/help/FaqFragment.java @@ -42,8 +42,8 @@ public class FaqFragment extends BaseMwmFragment { switch (which) { - case 0 -> sendGeneralFeedback(); - case 1 -> reportBug(); + case 0 -> sendGeneralFeedback(); + case 1 -> reportBug(); } } }; diff --git a/android/app/src/main/java/app/organicmaps/help/HelpFragment.java b/android/app/src/main/java/app/organicmaps/help/HelpFragment.java index d40673623..c66075277 100644 --- a/android/app/src/main/java/app/organicmaps/help/HelpFragment.java +++ b/android/app/src/main/java/app/organicmaps/help/HelpFragment.java @@ -24,6 +24,8 @@ import app.organicmaps.util.Graphics; import app.organicmaps.util.SharingUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils.ScrollableContentInsetsListener; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.textview.MaterialTextView; public class HelpFragment extends BaseMwmFragment implements View.OnClickListener { @@ -41,15 +43,15 @@ public class HelpFragment extends BaseMwmFragment implements View.OnClickListene @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - mDonateUrl = Config.getDonateUrl(requireContext()); + mDonateUrl = Utils.getDonateUrl(requireContext()); View root = inflater.inflate(R.layout.about, container, false); - ((TextView) root.findViewById(R.id.version)).setText(BuildConfig.VERSION_NAME); + ((MaterialTextView) root.findViewById(R.id.version)).setText(BuildConfig.VERSION_NAME); final boolean isLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; final String dataVersion = DateUtils.getShortDateFormatter().format(Framework.getDataVersion()); - final TextView osmPresentationView = root.findViewById(R.id.osm_presentation); + final MaterialTextView osmPresentationView = root.findViewById(R.id.osm_presentation); if (osmPresentationView != null) { osmPresentationView.setText(getString(R.string.osm_presentation, dataVersion)); @@ -71,13 +73,13 @@ public class HelpFragment extends BaseMwmFragment implements View.OnClickListene setupItem(R.id.report, isLandscape, root); setupItem(R.id.copyright, false, root); - final TextView supportUsView = root.findViewById(R.id.support_us); + final MaterialTextView supportUsView = root.findViewById(R.id.support_us); if (BuildConfig.FLAVOR.equals("google") && !TextUtils.isEmpty(mDonateUrl)) supportUsView.setVisibility(View.GONE); else setupItem(R.id.support_us, true, root); - final TextView donateView = root.findViewById(R.id.donate); + final MaterialButton donateView = root.findViewById(R.id.donate); if (TextUtils.isEmpty(mDonateUrl)) donateView.setVisibility(View.GONE); else 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 e28aa5aad..da6c76b20 100644 --- a/android/app/src/main/java/app/organicmaps/intent/Factory.java +++ b/android/app/src/main/java/app/organicmaps/intent/Factory.java @@ -10,7 +10,6 @@ import androidx.core.content.IntentCompat; import app.organicmaps.MwmActivity; import app.organicmaps.MwmApplication; import app.organicmaps.editor.OsmLoginActivity; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.Map; import app.organicmaps.sdk.api.ParsedRoutingData; @@ -20,6 +19,7 @@ import app.organicmaps.sdk.api.RoutePoint; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.FeatureId; import app.organicmaps.sdk.bookmarks.data.MapObject; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.sdk.util.StorageUtils; import app.organicmaps.sdk.util.concurrency.ThreadPool; @@ -78,67 +78,67 @@ public class Factory switch (Framework.nativeParseAndSetApiUrl(uri.toString())) { - case RequestType.INCORRECT: return false; + case RequestType.INCORRECT: return false; - case RequestType.MAP: - SearchEngine.INSTANCE.cancelInteractiveSearch(); - Map.executeMapApiRequest(); - return true; + case RequestType.MAP: + SearchEngine.INSTANCE.cancelInteractiveSearch(); + Map.executeMapApiRequest(); + return true; - case RequestType.ROUTE: - SearchEngine.INSTANCE.cancelInteractiveSearch(); - final ParsedRoutingData data = Framework.nativeGetParsedRoutingData(); - RoutingController.get().setRouterType(data.mRouterType); - final RoutePoint from = data.mPoints[0]; - final RoutePoint to = data.mPoints[1]; - 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)); - return true; - case RequestType.SEARCH: + case RequestType.ROUTE: + SearchEngine.INSTANCE.cancelInteractiveSearch(); + final ParsedRoutingData data = Framework.nativeGetParsedRoutingData(); + RoutingController.get().setRouterType(data.mRouterType); + final RoutePoint from = data.mPoints[0]; + final RoutePoint to = data.mPoints[1]; + 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)); + return true; + case RequestType.SEARCH: + { + SearchEngine.INSTANCE.cancelInteractiveSearch(); + final ParsedSearchRequest request = Framework.nativeGetParsedSearchRequest(); + final double[] latlon = Framework.nativeGetParsedCenterLatLon(); + if (latlon != null) { - SearchEngine.INSTANCE.cancelInteractiveSearch(); - final ParsedSearchRequest request = Framework.nativeGetParsedSearchRequest(); - final double[] latlon = Framework.nativeGetParsedCenterLatLon(); - if (latlon != null) - { - Framework.nativeStopLocationFollow(); - Framework.nativeSetViewportCenter(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); - // We need to update viewport for search api manually because of drape engine - // will not notify subscribers when search activity is shown. - if (!request.mIsSearchOnMap) - Framework.nativeSetSearchViewport(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); - } - SearchActivity.start(target, request.mQuery, request.mLocale, request.mIsSearchOnMap); - return true; + Framework.nativeStopLocationFollow(); + Framework.nativeSetViewportCenter(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); + // We need to update viewport for search api manually because of drape engine + // will not notify subscribers when search activity is shown. + if (!request.mIsSearchOnMap) + Framework.nativeSetSearchViewport(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); } - case RequestType.CROSSHAIR: + SearchActivity.start(target, request.mQuery, request.mLocale, request.mIsSearchOnMap); + return true; + } + case RequestType.CROSSHAIR: + { + SearchEngine.INSTANCE.cancelInteractiveSearch(); + target.showPositionChooserForAPI(Framework.nativeGetParsedAppName()); + + final double[] latlon = Framework.nativeGetParsedCenterLatLon(); + if (latlon != null) { - SearchEngine.INSTANCE.cancelInteractiveSearch(); - target.showPositionChooserForAPI(Framework.nativeGetParsedAppName()); - - final double[] latlon = Framework.nativeGetParsedCenterLatLon(); - if (latlon != null) - { - Framework.nativeStopLocationFollow(); - Framework.nativeSetViewportCenter(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); - } - - return true; - } - case RequestType.OAUTH2: - { - SearchEngine.INSTANCE.cancelInteractiveSearch(); - - final String oauth2code = Framework.nativeGetParsedOAuth2Code(); - OsmLoginActivity.OAuth2Callback(target, oauth2code); - - return true; + Framework.nativeStopLocationFollow(); + Framework.nativeSetViewportCenter(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM); } - // Menu and Settings url types should be implemented to support deeplinking. - case RequestType.MENU: - case RequestType.SETTINGS: + return true; + } + case RequestType.OAUTH2: + { + SearchEngine.INSTANCE.cancelInteractiveSearch(); + + final String oauth2code = Framework.nativeGetParsedOAuth2Code(); + OsmLoginActivity.OAuth2Callback(target, oauth2code); + + return true; + } + + // Menu and Settings url types should be implemented to support deeplinking. + case RequestType.MENU: + case RequestType.SETTINGS: } return false; diff --git a/android/app/src/main/java/app/organicmaps/maplayer/LayerBottomSheetItem.java b/android/app/src/main/java/app/organicmaps/maplayer/LayerBottomSheetItem.java index 4cdd7fdca..a8b06d26c 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/LayerBottomSheetItem.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/LayerBottomSheetItem.java @@ -42,26 +42,26 @@ public class LayerBottomSheetItem int buttonTextResource = R.string.layers_title; switch (mode) { - case OUTDOORS: - disabledResource = R.attr.outdoorsMenuDisabled; - enabledResource = R.attr.outdoorsMenuEnabled; - buttonTextResource = R.string.button_layer_outdoor; - break; - case SUBWAY: - disabledResource = R.attr.subwayMenuDisabled; - enabledResource = R.attr.subwayMenuEnabled; - buttonTextResource = R.string.subway; - break; - case ISOLINES: - disabledResource = R.attr.isoLinesMenuDisabled; - enabledResource = R.attr.isoLinesMenuEnabled; - buttonTextResource = R.string.button_layer_isolines; - break; - case TRAFFIC: - disabledResource = R.attr.trafficMenuDisabled; - enabledResource = R.attr.trafficMenuEnabled; - buttonTextResource = R.string.button_layer_traffic; - break; + case OUTDOORS: + disabledResource = R.attr.outdoorsMenuDisabled; + enabledResource = R.attr.outdoorsMenuEnabled; + buttonTextResource = R.string.button_layer_outdoor; + break; + case SUBWAY: + disabledResource = R.attr.subwayMenuDisabled; + enabledResource = R.attr.subwayMenuEnabled; + buttonTextResource = R.string.subway; + break; + case ISOLINES: + disabledResource = R.attr.isoLinesMenuDisabled; + enabledResource = R.attr.isoLinesMenuEnabled; + buttonTextResource = R.string.button_layer_isolines; + break; + case TRAFFIC: + disabledResource = R.attr.trafficMenuDisabled; + enabledResource = R.attr.trafficMenuEnabled; + buttonTextResource = R.string.button_layer_traffic; + break; } int disabled = ThemeUtils.getResource(mContext, disabledResource); int enabled = ThemeUtils.getResource(mContext, enabledResource); diff --git a/android/app/src/main/java/app/organicmaps/maplayer/LayersAdapter.java b/android/app/src/main/java/app/organicmaps/maplayer/LayersAdapter.java index 2d5e20ea8..fc8021cc7 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/LayersAdapter.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/LayersAdapter.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; import app.organicmaps.sdk.util.SharedPropertiesUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import java.util.List; public class LayersAdapter extends RecyclerView.Adapter 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 cdfe67050..02ed48520 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/MapButtonsController.java @@ -16,6 +16,7 @@ import android.view.ViewTreeObserver; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.OptIn; +import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; @@ -26,7 +27,6 @@ import app.organicmaps.MwmActivity; import app.organicmaps.R; import app.organicmaps.leftbutton.LeftButton; import app.organicmaps.leftbutton.LeftToggleButton; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.downloader.UpdateInfo; @@ -34,9 +34,11 @@ import app.organicmaps.sdk.location.TrackRecorder; import app.organicmaps.sdk.maplayer.isolines.IsolinesManager; import app.organicmaps.sdk.maplayer.subway.SubwayManager; import app.organicmaps.sdk.maplayer.traffic.TrafficManager; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.Config; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.ThemeUtils; +import app.organicmaps.util.UiUtils; +import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.widget.menu.MyPositionButton; import app.organicmaps.widget.placepage.PlacePageViewModel; @@ -213,7 +215,7 @@ public class MapButtonsController extends Fragment // helpButton.setImageResource(R.drawable.ic_launcher); // } // // Keep this button colorful in normal theme. - // if (!ThemeUtils.isNightTheme(requireContext())) + // if (!ThemeUtils.isNightTheme()) // helpButton.getDrawable().setTintList(null); } else if (leftButtonView != null) @@ -230,21 +232,21 @@ public class MapButtonsController extends Fragment return; switch (button) { - case zoom: UiUtils.showIf(show && Config.showZoomButtons(), buttonView); break; - case toggleMapLayer: - if (mToggleMapLayerButton != null) - UiUtils.showIf(show && !isInNavigationMode(), mToggleMapLayerButton); - break; - case myPosition: - if (mNavMyPosition != null) - mNavMyPosition.showButton(show); - break; - case search: mSearchWheel.show(show); - case bookmarks: - case menu: UiUtils.showIf(show, buttonView); break; - case trackRecordingStatus: - UiUtils.showIf(show, buttonView); - animateIconBlinking(show, (FloatingActionButton) buttonView); + case zoom: UiUtils.showIf(show && Config.showZoomButtons(), buttonView); break; + case toggleMapLayer: + if (mToggleMapLayerButton != null) + UiUtils.showIf(show && !isInNavigationMode(), mToggleMapLayerButton); + break; + case myPosition: + if (mNavMyPosition != null) + mNavMyPosition.showButton(show); + break; + case search: mSearchWheel.show(show); + case bookmarks: + case menu: UiUtils.showIf(show, buttonView); break; + case trackRecordingStatus: + UiUtils.showIf(show, buttonView); + animateIconBlinking(show, (FloatingActionButton) buttonView); } } @@ -295,7 +297,7 @@ public class MapButtonsController extends Fragment mBadgeDrawable.setMaxCharacterCount(0); mBadgeDrawable.setHorizontalOffset(verticalOffset); mBadgeDrawable.setVerticalOffset(dpToPx(9, context)); - mBadgeDrawable.setBackgroundColor(getResources().getColor(R.color.base_accent)); + mBadgeDrawable.setBackgroundColor(ContextCompat.getColor(context, R.color.base_accent)); mBadgeDrawable.setVisible(enable); BadgeUtils.attachBadgeDrawable(mBadgeDrawable, menuButton); } 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 1d05d8e58..80bc75e93 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/SearchWheel.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/SearchWheel.java @@ -14,11 +14,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import app.organicmaps.R; -import app.organicmaps.routing.RoutingController; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.search.SearchEngine; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.sdk.util.concurrency.UiThread; import app.organicmaps.util.Graphics; +import app.organicmaps.util.UiUtils; public class SearchWheel implements View.OnClickListener { @@ -215,7 +215,7 @@ public class SearchWheel implements View.OnClickListener final SearchOption searchOption = mMapButtonsViewModel.getSearchOption().getValue(); mSearchButton.setImageDrawable(Graphics.tint( mSearchButton.getContext(), searchOption == null ? R.drawable.ic_routing_search_off : searchOption.mDrawableOff, - androidx.appcompat.R.attr.colorAccent)); + com.google.android.material.R.attr.colorSecondary)); } @Override diff --git a/android/app/src/main/java/app/organicmaps/maplayer/ToggleMapLayerFragment.java b/android/app/src/main/java/app/organicmaps/maplayer/ToggleMapLayerFragment.java index 60e774e80..d54fb634b 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/ToggleMapLayerFragment.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/ToggleMapLayerFragment.java @@ -14,6 +14,7 @@ import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.sdk.maplayer.Mode; import app.organicmaps.sdk.util.SharedPropertiesUtils; +import app.organicmaps.util.ThemeSwitcher; import app.organicmaps.util.Utils; import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; import app.organicmaps.widget.recycler.SpanningLinearLayoutManager; @@ -72,6 +73,9 @@ public class ToggleMapLayerFragment extends Fragment Context context = v.getContext(); SharedPropertiesUtils.setLayerMarkerShownForLayerMode(mode); mode.setEnabled(context, !mode.isEnabled(context)); + // TODO: dirty hack :( + if (mode == Mode.OUTDOORS) + ThemeSwitcher.INSTANCE.restart(true); mAdapter.notifyDataSetChanged(); mMapButtonsController.updateLayerButton(); if (MwmApplication.from(context).getIsolinesManager().shouldShowNotification()) diff --git a/android/app/src/main/java/app/organicmaps/maplayer/traffic/widget/TrafficButton.java b/android/app/src/main/java/app/organicmaps/maplayer/traffic/widget/TrafficButton.java index f2bda4035..ccf986ab1 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/traffic/widget/TrafficButton.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/traffic/widget/TrafficButton.java @@ -10,8 +10,8 @@ import android.widget.RelativeLayout; import androidx.annotation.NonNull; import androidx.core.content.res.ResourcesCompat; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.ThemeUtils; +import app.organicmaps.util.UiUtils; import java.util.Objects; @SuppressWarnings("unused") @@ -43,22 +43,20 @@ public class TrafficButton void turnOff() { stopWaitingAnimation(); - mButton.setImageResource(ThemeUtils.isNightTheme(mButton.getContext()) ? R.drawable.ic_traffic_on_night - : R.drawable.ic_traffic_on); + mButton.setImageResource(ThemeUtils.isNightTheme() ? R.drawable.ic_traffic_on_night : R.drawable.ic_traffic_on); } void turnOn() { stopWaitingAnimation(); - mButton.setImageResource(ThemeUtils.isNightTheme(mButton.getContext()) ? R.drawable.ic_traffic_on_night - : R.drawable.ic_traffic_on); + mButton.setImageResource(ThemeUtils.isNightTheme() ? R.drawable.ic_traffic_on_night : R.drawable.ic_traffic_on); } void markAsOutdated() { stopWaitingAnimation(); - mButton.setImageResource(ThemeUtils.isNightTheme(mButton.getContext()) ? R.drawable.ic_traffic_outdated_night - : R.drawable.ic_traffic_outdated); + mButton.setImageResource(ThemeUtils.isNightTheme() ? R.drawable.ic_traffic_outdated_night + : R.drawable.ic_traffic_outdated); } void startWaitingAnimation() diff --git a/android/app/src/main/java/app/organicmaps/routing/BaseRoutingErrorDialogFragment.java b/android/app/src/main/java/app/organicmaps/routing/BaseRoutingErrorDialogFragment.java index 1628b7bbf..2dce832a6 100644 --- a/android/app/src/main/java/app/organicmaps/routing/BaseRoutingErrorDialogFragment.java +++ b/android/app/src/main/java/app/organicmaps/routing/BaseRoutingErrorDialogFragment.java @@ -11,12 +11,15 @@ import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; import app.organicmaps.R; import app.organicmaps.adapter.DisabledChildSimpleExpandableListAdapter; import app.organicmaps.base.BaseMwmDialogFragment; import app.organicmaps.sdk.downloader.CountryItem; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.Utils; +import app.organicmaps.util.UiUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textview.MaterialTextView; import java.util.ArrayList; @@ -121,11 +124,11 @@ abstract class BaseRoutingErrorDialogFragment extends BaseMwmDialogFragment } listView.setAdapter(buildAdapter()); - listView.setChildDivider(new ColorDrawable(getResources().getColor(android.R.color.transparent))); + listView.setChildDivider(new ColorDrawable(ContextCompat.getColor(requireContext(), android.R.color.transparent))); UiUtils.waitLayout(listView, () -> { final int width = listView.getWidth(); - final int indicatorWidth = UiUtils.dimen(requireContext(), R.dimen.margin_quadruple); + final int indicatorWidth = Utils.dimen(requireContext(), R.dimen.margin_quadruple); listView.setIndicatorBounds(width - indicatorWidth, width); listView.setIndicatorBoundsRelative(width - indicatorWidth, width); }); diff --git a/android/app/src/main/java/app/organicmaps/routing/ManageRouteAdapter.java b/android/app/src/main/java/app/organicmaps/routing/ManageRouteAdapter.java index bf7f32769..0404c544f 100644 --- a/android/app/src/main/java/app/organicmaps/routing/ManageRouteAdapter.java +++ b/android/app/src/main/java/app/organicmaps/routing/ManageRouteAdapter.java @@ -11,14 +11,14 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.appcompat.content.res.AppCompatResources; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.imageview.ShapeableImageView; -import com.google.android.material.textview.MaterialTextView; import app.organicmaps.R; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.routing.RouteMarkData; import app.organicmaps.sdk.routing.RouteMarkType; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; +import com.google.android.material.imageview.ShapeableImageView; +import com.google.android.material.textview.MaterialTextView; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -63,26 +63,26 @@ public class ManageRouteAdapter extends RecyclerView.Adapter { + MapManagerHelper.warnOn3g(requireActivity(), size, () -> { final FragmentManager manager = requireActivity().getSupportFragmentManager(); RoutingMapsDownloadFragment downloader = RoutingMapsDownloadFragment.create(manager.getFragmentFactory(), getAppContextOrThrow(), mMapsArray); 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 6be818816..fd8a225f2 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingMapsDownloadFragment.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingMapsDownloadFragment.java @@ -9,9 +9,11 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentFactory; import app.organicmaps.R; +import app.organicmaps.downloader.MapManagerHelper; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.routing.RoutingController; +import app.organicmaps.util.UiUtils; import app.organicmaps.widget.WheelProgressView; import java.util.HashSet; import java.util.List; @@ -39,7 +41,7 @@ public class RoutingMapsDownloadFragment extends BaseRoutingErrorDialogFragment mMapsArray[i] = item.id; } - MapManager.startDownload(mMapsArray); + MapManagerHelper.startDownload(mMapsArray); } 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 c75af51d8..926bb34ae 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanController.java @@ -17,11 +17,12 @@ import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.Router; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.routing.RoutingInfo; import app.organicmaps.sdk.routing.RoutingOptions; import app.organicmaps.sdk.routing.TransitRouteInfo; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.settings.DrivingOptionsActivity; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import app.organicmaps.widget.RoutingToolbarButton; import app.organicmaps.widget.ToolbarController; @@ -140,7 +141,8 @@ public class RoutingPlanController extends ToolbarController setupRouterButton(R.id.vehicle, R.drawable.ic_car, this::onVehicleModeSelected); setupRouterButton(R.id.pedestrian, R.drawable.ic_pedestrian, this::onPedestrianModeSelected); // setupRouterButton(R.id.taxi, R.drawable.ic_taxi, this::onTaxiModeSelected); - setupRouterButton(R.id.transit, R.drawable.ic_route_planning_metro_40px, this::onTransitModeSelected); + setupRouterButton(R.id.transit, app.organicmaps.sdk.R.drawable.ic_route_planning_metro_40px, + this::onTransitModeSelected); setupRouterButton(R.id.bicycle, R.drawable.ic_bike, this::onBicycleModeSelected); setupRouterButton(R.id.ruler, app.organicmaps.sdk.R.drawable.ic_ruler_route, this::onRulerModeSelected); } 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 5080c6383..32e7b13de 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java @@ -10,6 +10,7 @@ import app.organicmaps.MwmActivity; import app.organicmaps.R; import app.organicmaps.base.BaseMwmFragment; import app.organicmaps.sdk.Router; +import app.organicmaps.sdk.routing.RoutingController; public class RoutingPlanFragment extends BaseMwmFragment { 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 06e766475..7528ed618 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanInplaceController.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanInplaceController.java @@ -9,7 +9,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import app.organicmaps.MwmActivity; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; public class RoutingPlanInplaceController extends RoutingPlanController { diff --git a/android/app/src/main/java/app/organicmaps/routing/TransitStepView.java b/android/app/src/main/java/app/organicmaps/routing/TransitStepView.java index ed6ed4944..352ed3d0c 100644 --- a/android/app/src/main/java/app/organicmaps/routing/TransitStepView.java +++ b/android/app/src/main/java/app/organicmaps/routing/TransitStepView.java @@ -90,7 +90,7 @@ public class TransitStepView extends View implements MultilineLayoutManager.Sque { mDrawable = null; mText = info.getDistance() + " " + info.getDistanceUnits(); - mTextPaint.setColor(ThemeUtils.isDefaultTheme(getContext()) ? Color.BLACK : Color.WHITE); + mTextPaint.setColor(ThemeUtils.isDefaultTheme() ? Color.BLACK : Color.WHITE); } else { diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Icon.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Icon.java deleted file mode 100644 index ffc3e9252..000000000 --- a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Icon.java +++ /dev/null @@ -1,184 +0,0 @@ -package app.organicmaps.sdk.bookmarks.data; - -import android.os.Parcel; -import android.os.Parcelable; -import androidx.annotation.DrawableRes; -import androidx.annotation.IntDef; -import app.organicmaps.sdk.R; -import com.google.common.base.Objects; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -public class Icon implements Parcelable -{ - @Retention(RetentionPolicy.SOURCE) - @IntDef({PREDEFINED_COLOR_NONE, PREDEFINED_COLOR_RED, PREDEFINED_COLOR_BLUE, PREDEFINED_COLOR_PURPLE, - PREDEFINED_COLOR_YELLOW, PREDEFINED_COLOR_PINK, PREDEFINED_COLOR_BROWN, PREDEFINED_COLOR_GREEN, - PREDEFINED_COLOR_ORANGE, PREDEFINED_COLOR_DEEPPURPLE, PREDEFINED_COLOR_LIGHTBLUE, PREDEFINED_COLOR_CYAN, - PREDEFINED_COLOR_TEAL, PREDEFINED_COLOR_LIME, PREDEFINED_COLOR_DEEPORANGE, PREDEFINED_COLOR_GRAY, - PREDEFINED_COLOR_BLUEGRAY}) - @interface PredefinedColor - {} - - static final int PREDEFINED_COLOR_NONE = 0; - static final int PREDEFINED_COLOR_RED = 1; - static final int PREDEFINED_COLOR_BLUE = 2; - static final int PREDEFINED_COLOR_PURPLE = 3; - static final int PREDEFINED_COLOR_YELLOW = 4; - static final int PREDEFINED_COLOR_PINK = 5; - static final int PREDEFINED_COLOR_BROWN = 6; - static final int PREDEFINED_COLOR_GREEN = 7; - static final int PREDEFINED_COLOR_ORANGE = 8; - static final int PREDEFINED_COLOR_DEEPPURPLE = 9; - static final int PREDEFINED_COLOR_LIGHTBLUE = 10; - static final int PREDEFINED_COLOR_CYAN = 11; - static final int PREDEFINED_COLOR_TEAL = 12; - static final int PREDEFINED_COLOR_LIME = 13; - static final int PREDEFINED_COLOR_DEEPORANGE = 14; - static final int PREDEFINED_COLOR_GRAY = 15; - static final int PREDEFINED_COLOR_BLUEGRAY = 16; - - private static int shift(int v, int bitCount) - { - return v << bitCount; - } - private static int toARGB(int r, int g, int b) - { - return shift(255, 24) + shift(r, 16) + shift(g, 8) + b; - } - - /// @note Important! Should be synced with kml/types.hpp/PredefinedColor - /// @todo Values can be taken from Core. - private static final int[] ARGB_COLORS = {toARGB(229, 27, 35), // none - toARGB(229, 27, 35), // red - toARGB(0, 110, 199), // blue - toARGB(156, 39, 176), // purple - toARGB(255, 200, 0), // yellow - toARGB(255, 65, 130), // pink - toARGB(121, 85, 72), // brown - toARGB(56, 142, 60), // green - toARGB(255, 160, 0), // orange - toARGB(102, 57, 191), // deeppurple - toARGB(36, 156, 242), // lightblue - toARGB(20, 190, 205), // cyan - toARGB(0, 165, 140), // teal - toARGB(147, 191, 57), // lime - toARGB(240, 100, 50), // deeporange - toARGB(115, 115, 115), // gray - toARGB(89, 115, 128)}; // bluegray - - static final int BOOKMARK_ICON_TYPE_NONE = 0; - - /// @note Important! Should be synced with kml/types.hpp/BookmarkIcon - /// @todo Can make better: take name-by-type from Core and make a concat: "R.drawable.ic_bookmark_" + name. - // First icon should be "none" <-> BOOKMARK_ICON_TYPE_NONE. - @DrawableRes - private static final int[] TYPE_ICONS = { - R.drawable.ic_bookmark_none, R.drawable.ic_bookmark_hotel, R.drawable.ic_bookmark_animals, - R.drawable.ic_bookmark_buddhism, R.drawable.ic_bookmark_building, R.drawable.ic_bookmark_christianity, - R.drawable.ic_bookmark_entertainment, R.drawable.ic_bookmark_money, R.drawable.ic_bookmark_food, - R.drawable.ic_bookmark_gas, R.drawable.ic_bookmark_judaism, R.drawable.ic_bookmark_medicine, - R.drawable.ic_bookmark_mountain, R.drawable.ic_bookmark_museum, R.drawable.ic_bookmark_islam, - R.drawable.ic_bookmark_park, R.drawable.ic_bookmark_parking, R.drawable.ic_bookmark_shop, - R.drawable.ic_bookmark_sights, R.drawable.ic_bookmark_swim, R.drawable.ic_bookmark_water, - R.drawable.ic_bookmark_bar, R.drawable.ic_bookmark_transport, R.drawable.ic_bookmark_viewpoint, - R.drawable.ic_bookmark_sport, - R.drawable.ic_bookmark_none, // pub - R.drawable.ic_bookmark_none, // art - R.drawable.ic_bookmark_none, // bank - R.drawable.ic_bookmark_none, // cafe - R.drawable.ic_bookmark_none, // pharmacy - R.drawable.ic_bookmark_none, // stadium - R.drawable.ic_bookmark_none, // theatre - R.drawable.ic_bookmark_none, // information - R.drawable.ic_bookmark_none, // ChargingStation - R.drawable.ic_bookmark_none, // BicycleParking - R.drawable.ic_bookmark_none, // BicycleParkingCovered - R.drawable.ic_bookmark_none, // BicycleRental - R.drawable.ic_bookmark_none // FastFood - }; - - @PredefinedColor - private final int mColor; - private final int mType; - - public Icon(@PredefinedColor int color, int type) - { - mColor = color; - mType = type; - } - - @Override - public int describeContents() - { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) - { - dest.writeInt(mColor); - dest.writeInt(mType); - } - - private Icon(Parcel in) - { - mColor = in.readInt(); - mType = in.readInt(); - } - - @PredefinedColor - public int getColor() - { - return mColor; - } - - public int argb() - { - return ARGB_COLORS[mColor]; - } - - public static int getColorPosition(int color) - { - for (int index = 1; index < ARGB_COLORS.length; index++) - { - if (ARGB_COLORS[index] == color) - return index; - } - return -1; - } - - @DrawableRes - public int getResId() - { - return TYPE_ICONS[mType]; - } - - @Override - public boolean equals(Object o) - { - if (this == o) - return true; - if (o instanceof Icon comparedIcon) - return mColor == comparedIcon.mColor && mType == comparedIcon.mType; - return false; - } - - @Override - public int hashCode() - { - return Objects.hashCode(mColor, mType); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { - public Icon createFromParcel(Parcel in) - { - return new Icon(in); - } - - public Icon[] newArray(int size) - { - return new Icon[size]; - } - }; -} diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Track.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Track.java deleted file mode 100644 index 1e713ad20..000000000 --- a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Track.java +++ /dev/null @@ -1,55 +0,0 @@ -package app.organicmaps.sdk.bookmarks.data; - -import androidx.annotation.Keep; -import app.organicmaps.sdk.util.Distance; - -// Called from JNI. -@Keep -@SuppressWarnings("unused") -public class Track -{ - private final long mTrackId; - private final long mCategoryId; - private final String mName; - private final Distance mLength; - private final int mColor; - - Track(long trackId, long categoryId, String name, Distance length, int color) - { - mTrackId = trackId; - mCategoryId = categoryId; - mName = name; - mLength = length; - mColor = color; - } - - public String getName() - { - return mName; - } - - public Distance getLength() - { - return mLength; - } - - public int getColor() - { - return mColor; - } - - public long getTrackId() - { - return mTrackId; - } - - public long getCategoryId() - { - return mCategoryId; - } - - public String getTrackDescription() - { - return BookmarkManager.INSTANCE.getTrackDescription(mTrackId); - } -} diff --git a/android/app/src/main/java/app/organicmaps/sdk/maplayer/isolines/IsolinesState.java b/android/app/src/main/java/app/organicmaps/sdk/maplayer/isolines/IsolinesState.java deleted file mode 100644 index 37cdfe8b6..000000000 --- a/android/app/src/main/java/app/organicmaps/sdk/maplayer/isolines/IsolinesState.java +++ /dev/null @@ -1,40 +0,0 @@ -package app.organicmaps.sdk.maplayer.isolines; - -import android.content.Context; -import android.view.View; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import app.organicmaps.R; -import app.organicmaps.util.Utils; - -public enum IsolinesState -{ - DISABLED, - ENABLED, - EXPIREDDATA { - @Override - public void activate(@NonNull Context context, @Nullable View view, @Nullable View viewAbove) - { - if (view != null) - Utils.showSnackbar(context, view, viewAbove, R.string.isolines_activation_error_dialog); - else - Toast.makeText(context, R.string.isolines_activation_error_dialog, Toast.LENGTH_SHORT).show(); - } - }, - NODATA { - @Override - public void activate(@NonNull Context context, @Nullable View view, @Nullable View viewAbove) - { - if (view != null) - Utils.showSnackbar(context, view, viewAbove, R.string.isolines_location_error_dialog); - else - Toast.makeText(context, R.string.isolines_location_error_dialog, Toast.LENGTH_SHORT).show(); - } - }; - - public void activate(@NonNull Context context, @Nullable View viewAbove, @Nullable View view) - { - /* Do nothing by default */ - } -} diff --git a/android/app/src/main/java/app/organicmaps/sdk/util/DateUtils.java b/android/app/src/main/java/app/organicmaps/sdk/util/DateUtils.java deleted file mode 100644 index 028c9015f..000000000 --- a/android/app/src/main/java/app/organicmaps/sdk/util/DateUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package app.organicmaps.sdk.util; - -import android.content.Context; -import androidx.annotation.Keep; -import androidx.annotation.NonNull; -import java.text.DateFormat; -import java.util.Locale; - -public final class DateUtils -{ - private DateUtils() {} - - @NonNull - public static DateFormat getShortDateFormatter() - { - return DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()); - } - - // Called from JNI. - @Keep - @SuppressWarnings("unused") - public static boolean is24HourFormat(@NonNull Context context) - { - return android.text.format.DateFormat.is24HourFormat(context); - } -} 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 fe509cacb..658d6d53d 100644 --- a/android/app/src/main/java/app/organicmaps/search/CategoriesAdapter.java +++ b/android/app/src/main/java/app/organicmaps/search/CategoriesAdapter.java @@ -89,7 +89,7 @@ class CategoriesAdapter extends RecyclerView.Adapter { @@ -149,39 +149,39 @@ class SearchAdapter extends RecyclerView.Adapter + case SearchResult.OPEN_NOW_YES -> + { + if (result.description.minutesUntilClosed < 60) // less than 1 hour { - if (result.description.minutesUntilClosed < 60) // less than 1 hour - { - final String time = result.description.minutesUntilClosed + " " + resources.getString(R.string.minute); - final String string = resources.getString(R.string.closes_in, time); + final String time = result.description.minutesUntilClosed + " " + resources.getString(R.string.minute); + final String string = resources.getString(R.string.closes_in, time); - UiUtils.setTextAndShow(mOpen, string); - mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_yellow)); - } - else - { - UiUtils.setTextAndShow(mOpen, resources.getString(R.string.editor_time_open)); - mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_green)); - } + UiUtils.setTextAndShow(mOpen, string); + mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_yellow)); } - case SearchResult.OPEN_NOW_NO -> + else { - if (result.description.minutesUntilOpen < 60) // less than 1 hour - { - final String time = result.description.minutesUntilOpen + " " + resources.getString(R.string.minute); - final String string = resources.getString(R.string.opens_in, time); - - UiUtils.setTextAndShow(mOpen, string); - mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_red)); - } - else - { - UiUtils.setTextAndShow(mOpen, resources.getString(R.string.closed)); - mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_red)); - } + UiUtils.setTextAndShow(mOpen, resources.getString(R.string.editor_time_open)); + mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_green)); } - default -> UiUtils.hide(mOpen); + } + case SearchResult.OPEN_NOW_NO -> + { + if (result.description.minutesUntilOpen < 60) // less than 1 hour + { + final String time = result.description.minutesUntilOpen + " " + resources.getString(R.string.minute); + final String string = resources.getString(R.string.opens_in, time); + + UiUtils.setTextAndShow(mOpen, string); + mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_red)); + } + else + { + UiUtils.setTextAndShow(mOpen, resources.getString(R.string.closed)); + mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_red)); + } + } + default -> UiUtils.hide(mOpen); } } diff --git a/android/app/src/main/java/app/organicmaps/search/SearchFragment.java b/android/app/src/main/java/app/organicmaps/search/SearchFragment.java index 766ce2d8d..945547c76 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchFragment.java +++ b/android/app/src/main/java/app/organicmaps/search/SearchFragment.java @@ -26,12 +26,12 @@ import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.base.BaseMwmFragment; import app.organicmaps.downloader.CountrySuggestFragment; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.bookmarks.data.FeatureId; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.location.LocationListener; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.sdk.search.SearchListener; import app.organicmaps.sdk.search.SearchRecents; @@ -39,7 +39,7 @@ import app.organicmaps.sdk.search.SearchResult; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.Language; import app.organicmaps.sdk.util.SharedPropertiesUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.widget.PlaceholderView; 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 df906f695..506b2757a 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchHistoryAdapter.java +++ b/android/app/src/main/java/app/organicmaps/search/SearchHistoryAdapter.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.MwmApplication; import app.organicmaps.R; -import app.organicmaps.routing.RoutingController; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.search.SearchRecents; import app.organicmaps.util.Graphics; import app.organicmaps.widget.SearchToolbarController; @@ -49,32 +49,32 @@ class SearchHistoryAdapter extends RecyclerView.Adapter mSearchToolbarController.setQuery(res.mText.getText())); - break; + case TYPE_ITEM: + res = new ViewHolder( + LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_search_recent, viewGroup, false)); + res.mText.setOnClickListener(v -> mSearchToolbarController.setQuery(res.mText.getText())); + break; - case TYPE_CLEAR: - res = new ViewHolder( - LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_search_clear_history, viewGroup, false)); - res.mText.setOnClickListener(v -> { - SearchRecents.clear(); - notifyDataSetChanged(); - }); - break; + case TYPE_CLEAR: + res = new ViewHolder( + LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_search_clear_history, viewGroup, false)); + res.mText.setOnClickListener(v -> { + SearchRecents.clear(); + notifyDataSetChanged(); + }); + break; - case TYPE_MY_POSITION: - res = new ViewHolder( - LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_search_my_position, viewGroup, false)); - res.mText.setOnClickListener(v -> { - RoutingController.get().onPoiSelected( - MwmApplication.from(viewGroup.getContext()).getLocationHelper().getMyPosition()); - mSearchToolbarController.onUpClick(); - }); - break; + case TYPE_MY_POSITION: + res = new ViewHolder( + LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_search_my_position, viewGroup, false)); + res.mText.setOnClickListener(v -> { + RoutingController.get().onPoiSelected( + MwmApplication.from(viewGroup.getContext()).getLocationHelper().getMyPosition()); + mSearchToolbarController.onUpClick(); + }); + break; - default: throw new IllegalArgumentException("Unsupported ViewHolder type given"); + default: throw new IllegalArgumentException("Unsupported ViewHolder type given"); } Graphics.tint(res.mText); diff --git a/android/app/src/main/java/app/organicmaps/search/SearchHistoryFragment.java b/android/app/src/main/java/app/organicmaps/search/SearchHistoryFragment.java index 82d3c29b1..b4b6b9512 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchHistoryFragment.java +++ b/android/app/src/main/java/app/organicmaps/search/SearchHistoryFragment.java @@ -10,8 +10,8 @@ import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.base.BaseMwmRecyclerFragment; -import app.organicmaps.routing.RoutingController; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.routing.RoutingController; +import app.organicmaps.util.UiUtils; import app.organicmaps.widget.PlaceholderView; import app.organicmaps.widget.SearchToolbarController; 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 470b77d32..314fad9c9 100644 --- a/android/app/src/main/java/app/organicmaps/search/TabAdapter.java +++ b/android/app/src/main/java/app/organicmaps/search/TabAdapter.java @@ -92,7 +92,7 @@ class TabAdapter extends FragmentPagerAdapter 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); + Graphics.tint(mContext, tab.getIcon(), com.google.android.material.R.attr.colorSecondary); } @Override diff --git a/android/app/src/main/java/app/organicmaps/settings/BaseXmlSettingsFragment.java b/android/app/src/main/java/app/organicmaps/settings/BaseXmlSettingsFragment.java index 0b8eec6bf..ddc63a6ea 100644 --- a/android/app/src/main/java/app/organicmaps/settings/BaseXmlSettingsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/BaseXmlSettingsFragment.java @@ -47,7 +47,7 @@ abstract class BaseXmlSettingsFragment extends PreferenceFragmentCompat super.onViewCreated(view, savedInstanceState); int color; - if (ThemeUtils.isDefaultTheme(requireContext())) + if (ThemeUtils.isDefaultTheme()) color = ContextCompat.getColor(requireContext(), R.color.bg_cards); else color = ContextCompat.getColor(requireContext(), R.color.bg_cards_night); diff --git a/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsFragment.java b/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsFragment.java index 69d3a53e0..18349133b 100644 --- a/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/DrivingOptionsFragment.java @@ -11,7 +11,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.SwitchCompat; import app.organicmaps.R; import app.organicmaps.base.BaseMwmToolbarFragment; -import app.organicmaps.routing.RoutingController; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.routing.RoutingOptions; import app.organicmaps.sdk.settings.RoadType; import java.util.ArrayList; 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 0e75ecb04..b6c5d4cc3 100644 --- a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java @@ -31,7 +31,6 @@ import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.editor.OsmOAuth; import app.organicmaps.sdk.editor.data.Language; import app.organicmaps.sdk.location.LocationHelper; -import app.organicmaps.sdk.location.LocationProviderFactory; import app.organicmaps.sdk.routing.RoutingOptions; import app.organicmaps.sdk.search.SearchRecents; import app.organicmaps.sdk.settings.MapLanguageCode; @@ -41,8 +40,8 @@ import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.NetworkPolicy; import app.organicmaps.sdk.util.PowerManagment; import app.organicmaps.sdk.util.SharedPropertiesUtils; -import app.organicmaps.sdk.util.ThemeSwitcher; import app.organicmaps.sdk.util.log.LogsManager; +import app.organicmaps.util.ThemeSwitcher; import app.organicmaps.util.Utils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -448,7 +447,9 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La if (pref == null) return; - if (!LocationProviderFactory.isGoogleLocationAvailable(requireActivity().getApplicationContext())) + if (!MwmApplication.from(requireContext()) + .getLocationProviderFactory() + .isGoogleLocationAvailable(requireActivity().getApplicationContext())) removePreference(getString(R.string.pref_privacy), pref); else { @@ -580,19 +581,19 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La private void initMapStylePrefsCallbacks() { final ListPreference pref = getPreference(getString(R.string.pref_map_style)); - - String curTheme = Config.getUiThemeSettings(requireContext()); - pref.setValue(curTheme); + pref.setEntryValues(new CharSequence[] {Config.UiTheme.DEFAULT, Config.UiTheme.NIGHT, Config.UiTheme.AUTO, + Config.UiTheme.NAV_AUTO}); + pref.setValue(Config.UiTheme.getUiThemeSettings()); pref.setSummary(pref.getEntry()); pref.setOnPreferenceChangeListener((preference, newValue) -> { final String themeName = (String) newValue; - if (!Config.setUiThemeSettings(requireContext(), themeName)) + if (!Config.UiTheme.setUiThemeSettings(themeName)) return true; ThemeSwitcher.INSTANCE.restart(false); - ThemeMode mode = ThemeMode.getInstance(requireContext().getApplicationContext(), themeName); - CharSequence summary = pref.getEntries()[mode.ordinal()]; + final ThemeMode mode = ThemeMode.getInstance(themeName); + final CharSequence summary = pref.getEntries()[mode.ordinal()]; pref.setSummary(summary); return true; }); @@ -691,24 +692,25 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La enum ThemeMode { - DEFAULT(R.string.theme_default), - NIGHT(R.string.theme_night), - AUTO(R.string.theme_auto), - NAV_AUTO(R.string.theme_nav_auto); + DEFAULT(Config.UiTheme.DEFAULT), + NIGHT(Config.UiTheme.NIGHT), + AUTO(Config.UiTheme.AUTO), + NAV_AUTO(Config.UiTheme.NAV_AUTO); - private final int mModeStringId; + @NonNull + private final String mMode; - ThemeMode(@StringRes int modeStringId) + ThemeMode(@NonNull String mode) { - mModeStringId = modeStringId; + mMode = mode; } @NonNull - public static ThemeMode getInstance(@NonNull Context context, @NonNull String src) + public static ThemeMode getInstance(@NonNull String src) { for (ThemeMode each : values()) { - if (context.getResources().getString(each.mModeStringId).equals(src)) + if (each.mMode.equals(src)) return each; } return AUTO; diff --git a/android/app/src/main/java/app/organicmaps/settings/StoragePathAdapter.java b/android/app/src/main/java/app/organicmaps/settings/StoragePathAdapter.java index 13dca24dc..f15c48722 100644 --- a/android/app/src/main/java/app/organicmaps/settings/StoragePathAdapter.java +++ b/android/app/src/main/java/app/organicmaps/settings/StoragePathAdapter.java @@ -11,7 +11,9 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckedTextView; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.settings.StorageItem; +import app.organicmaps.sdk.settings.StoragePathManager; +import app.organicmaps.sdk.util.Utils; import app.organicmaps.util.ThemeUtils; class StoragePathAdapter extends BaseAdapter @@ -63,14 +65,14 @@ class StoragePathAdapter extends BaseAdapter SpannableStringBuilder sb = new SpannableStringBuilder(item.mLabel + "\n" + size); sb.setSpan(new ForegroundColorSpan(ThemeUtils.getColor(mActivity, android.R.attr.textColorSecondary)), sb.length() - size.length(), sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - sb.setSpan(new AbsoluteSizeSpan(UiUtils.dimen(mActivity, R.dimen.text_size_body_3)), sb.length() - size.length(), + sb.setSpan(new AbsoluteSizeSpan(Utils.dimen(mActivity, R.dimen.text_size_body_3)), sb.length() - size.length(), sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); final String path = item.mPath + (item.mIsReadonly ? " (read-only)" : ""); sb.append("\n").append(path); sb.setSpan(new ForegroundColorSpan(ThemeUtils.getColor(mActivity, android.R.attr.textColorSecondary)), sb.length() - path.length(), sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - sb.setSpan(new AbsoluteSizeSpan(UiUtils.dimen(mActivity, R.dimen.text_size_body_4)), sb.length() - path.length(), + sb.setSpan(new AbsoluteSizeSpan(Utils.dimen(mActivity, R.dimen.text_size_body_4)), sb.length() - path.length(), sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); checkedView.setText(sb); diff --git a/android/app/src/main/java/app/organicmaps/settings/StoragePathFragment.java b/android/app/src/main/java/app/organicmaps/settings/StoragePathFragment.java index 88001de91..e435632c9 100644 --- a/android/app/src/main/java/app/organicmaps/settings/StoragePathFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/StoragePathFragment.java @@ -12,6 +12,8 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import app.organicmaps.R; import app.organicmaps.sdk.Framework; +import app.organicmaps.sdk.settings.StorageItem; +import app.organicmaps.sdk.settings.StoragePathManager; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.StorageUtils; import app.organicmaps.sdk.util.concurrency.ThreadPool; 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 1c198c69c..2a9292153 100644 --- a/android/app/src/main/java/app/organicmaps/settings/VoiceInstructionsSettingsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/VoiceInstructionsSettingsFragment.java @@ -26,7 +26,7 @@ import app.organicmaps.sdk.settings.SpeedCameraMode; import app.organicmaps.sdk.sound.LanguageData; import app.organicmaps.sdk.sound.TtsPlayer; import app.organicmaps.sdk.util.Config; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import java.util.Arrays; import java.util.Collections; @@ -326,10 +326,10 @@ public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment final String ttsLinkText = getString(R.string.prefs_languages_information_off_link); final Spannable link = new SpannableString(ttsLinkText + "↗"); // Set link color. - link.setSpan( - new ForegroundColorSpan(ContextCompat.getColor( - requireContext(), UiUtils.getStyledResourceId(requireContext(), androidx.appcompat.R.attr.colorAccent))), - 0, ttsLinkText.length(), 0); + link.setSpan(new ForegroundColorSpan(ContextCompat.getColor( + requireContext(), + UiUtils.getStyledResourceId(requireContext(), com.google.android.material.R.attr.colorSecondary))), + 0, ttsLinkText.length(), 0); ttsLangInfoLink.setSummary(link); final String ttsInfoUrl = requireActivity().getString(R.string.tts_info_link); diff --git a/android/app/src/main/java/app/organicmaps/util/SharingUtils.java b/android/app/src/main/java/app/organicmaps/util/SharingUtils.java index ec0056330..623027d80 100644 --- a/android/app/src/main/java/app/organicmaps/util/SharingUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/SharingUtils.java @@ -22,7 +22,6 @@ import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.bookmarks.data.BookmarkInfo; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.util.StorageUtils; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.sdk.util.log.Logger; import java.io.IOException; import java.util.ArrayList; diff --git a/android/app/src/main/java/app/organicmaps/sdk/util/ThemeSwitcher.java b/android/app/src/main/java/app/organicmaps/util/ThemeSwitcher.java similarity index 80% rename from android/app/src/main/java/app/organicmaps/sdk/util/ThemeSwitcher.java rename to android/app/src/main/java/app/organicmaps/util/ThemeSwitcher.java index 0acd89688..c82c0d5ed 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/util/ThemeSwitcher.java +++ b/android/app/src/main/java/app/organicmaps/util/ThemeSwitcher.java @@ -1,4 +1,4 @@ -package app.organicmaps.sdk.util; +package app.organicmaps.util; import android.app.Activity; import android.app.UiModeManager; @@ -8,14 +8,12 @@ import android.os.Build; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatDelegate; import app.organicmaps.MwmApplication; -import app.organicmaps.R; import app.organicmaps.downloader.DownloaderStatusIcon; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.MapStyle; -import app.organicmaps.sdk.display.DisplayManager; +import app.organicmaps.sdk.routing.RoutingController; +import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.concurrency.UiThread; -import app.organicmaps.util.ThemeUtils; import java.util.Calendar; public enum ThemeSwitcher @@ -29,17 +27,23 @@ public enum ThemeSwitcher @Override public void run() { - boolean isNavAuto = RoutingController.get().isNavigating() && ThemeUtils.isNavAutoTheme(mContext); + boolean navAuto = RoutingController.get().isNavigating() && ThemeUtils.isNavAutoTheme(); // Cancel old checker UiThread.cancelDelayedTasks(mAutoThemeChecker); - if (isNavAuto || ThemeUtils.isAutoTheme(mContext)) + String theme; + if (navAuto || ThemeUtils.isAutoTheme()) { UiThread.runLater(mAutoThemeChecker, CHECK_INTERVAL_MS); - setThemeAndMapStyle(calcAutoTheme()); + theme = calcAutoTheme(); } - else // Only reached when an auto mode should be light - setThemeAndMapStyle(mContext.getResources().getString(R.string.theme_default)); + else + { + // Happens when exiting the Navigation mode. Should restore the light. + theme = Config.UiTheme.DEFAULT; + } + + setThemeAndMapStyle(theme); } }; @@ -65,8 +69,8 @@ public enum ThemeSwitcher public void restart(boolean isRendererActive) { mRendererActive = isRendererActive; - String theme = Config.getUiThemeSettings(mContext); - if (ThemeUtils.isAutoTheme(mContext, theme) || ThemeUtils.isNavAutoTheme(mContext, theme)) + String theme = Config.UiTheme.getUiThemeSettings(); + if (ThemeUtils.isAutoTheme() || ThemeUtils.isNavAutoTheme()) { mAutoThemeChecker.run(); return; @@ -79,10 +83,10 @@ public enum ThemeSwitcher private void setThemeAndMapStyle(@NonNull String theme) { UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); - String oldTheme = Config.getCurrentUiTheme(mContext); + String oldTheme = Config.UiTheme.getCurrent(); MapStyle style; - if (ThemeUtils.isNightTheme(mContext, theme)) + if (ThemeUtils.isNightTheme()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES); @@ -113,7 +117,7 @@ public enum ThemeSwitcher if (!theme.equals(oldTheme)) { - Config.setCurrentUiTheme(mContext, theme); + Config.UiTheme.setCurrent(theme); DownloaderStatusIcon.clearCache(); final Activity a = MwmApplication.from(mContext).getTopActivity(); @@ -150,11 +154,10 @@ public enum ThemeSwitcher * * @return theme_light/dark string */ + @NonNull private String calcAutoTheme() { - String defaultTheme = mContext.getResources().getString(R.string.theme_default); - String nightTheme = mContext.getResources().getString(R.string.theme_night); - Location last = MwmApplication.from(mContext).getLocationHelper().getSavedLocation(); + final Location last = MwmApplication.from(mContext).getLocationHelper().getSavedLocation(); boolean day; if (last != null) @@ -168,6 +171,6 @@ public enum ThemeSwitcher day = (currentHour < 18 && currentHour > 6); } - return (day ? defaultTheme : nightTheme); + return (day ? Config.UiTheme.DEFAULT : Config.UiTheme.NIGHT); } } diff --git a/android/app/src/main/java/app/organicmaps/util/ThemeUtils.java b/android/app/src/main/java/app/organicmaps/util/ThemeUtils.java index bd866ca0c..8f44e5654 100644 --- a/android/app/src/main/java/app/organicmaps/util/ThemeUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/ThemeUtils.java @@ -16,7 +16,8 @@ public final class ThemeUtils private ThemeUtils() {} - public static @ColorInt int getColor(@NonNull Context context, @AttrRes int attr) + @ColorInt + public static int getColor(@NonNull Context context, @AttrRes int attr) { if (!context.getTheme().resolveAttribute(attr, VALUE_BUFFER, true)) throw new IllegalArgumentException("Failed to resolve color theme attribute"); @@ -44,76 +45,45 @@ public final class ThemeUtils return VALUE_BUFFER.resourceId; } - public static boolean isDefaultTheme(@NonNull Context context) + public static boolean isDefaultTheme() { - return isDefaultTheme(context, Config.getCurrentUiTheme(context)); + return Config.UiTheme.isDefault(Config.UiTheme.getCurrent()); } - public static boolean isDefaultTheme(@NonNull Context context, String theme) + public static boolean isNightTheme() { - String defaultTheme = context.getString(R.string.theme_default); - return defaultTheme.equals(theme); + return Config.UiTheme.isNight(Config.UiTheme.getCurrent()); } - public static boolean isNightTheme(@NonNull Context context) + public static boolean isAutoTheme() { - return isNightTheme(context, Config.getCurrentUiTheme(context)); + return Config.UiTheme.isAuto(Config.UiTheme.getUiThemeSettings()); } - public static boolean isNightTheme(@NonNull Context context, String theme) + public static boolean isNavAutoTheme() { - String nightTheme = context.getString(R.string.theme_night); - return nightTheme.equals(theme); - } - - public static boolean isAutoTheme(@NonNull Context context) - { - return isAutoTheme(context, Config.getUiThemeSettings(context)); - } - - public static boolean isAutoTheme(@NonNull Context context, String theme) - { - String autoTheme = context.getString(R.string.theme_auto); - return autoTheme.equals(theme); - } - - public static boolean isNavAutoTheme(@NonNull Context context) - { - return isNavAutoTheme(context, Config.getUiThemeSettings(context)); - } - - public static boolean isNavAutoTheme(@NonNull Context context, String theme) - { - String navAutoTheme = context.getString(R.string.theme_nav_auto); - return navAutoTheme.equals(theme); - } - - public static boolean isValidTheme(@NonNull Context context, String theme) - { - String defaultTheme = context.getString(R.string.theme_default); - String nightTheme = context.getString(R.string.theme_night); - return (defaultTheme.equals(theme) || nightTheme.equals(theme)); + return Config.UiTheme.isNavAuto(Config.UiTheme.getUiThemeSettings()); } @StyleRes - public static int getCardBgThemeResourceId(@NonNull Context context, @NonNull String theme) + public static int getCardBgThemeResourceId(@NonNull String theme) { - if (isDefaultTheme(context, theme)) + if (Config.UiTheme.isDefault(theme)) return R.style.MwmTheme_CardBg; - if (isNightTheme(context, theme)) + if (Config.UiTheme.isNight(theme)) return R.style.MwmTheme_Night_CardBg; throw new IllegalArgumentException("Attempt to apply unsupported theme: " + theme); } @StyleRes - public static int getWindowBgThemeResourceId(@NonNull Context context, @NonNull String theme) + public static int getWindowBgThemeResourceId(@NonNull String theme) { - if (isDefaultTheme(context, theme)) + if (Config.UiTheme.isDefault(theme)) return R.style.MwmTheme_WindowBg; - if (isNightTheme(context, theme)) + if (Config.UiTheme.isNight(theme)) return R.style.MwmTheme_Night_WindowBg; throw new IllegalArgumentException("Attempt to apply unsupported theme: " + theme); diff --git a/android/app/src/main/java/app/organicmaps/sdk/util/UiUtils.java b/android/app/src/main/java/app/organicmaps/util/UiUtils.java similarity index 94% rename from android/app/src/main/java/app/organicmaps/sdk/util/UiUtils.java rename to android/app/src/main/java/app/organicmaps/util/UiUtils.java index a38891899..762973c37 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/util/UiUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/UiUtils.java @@ -1,4 +1,4 @@ -package app.organicmaps.sdk.util; +package app.organicmaps.util; import android.animation.Animator; import android.app.Activity; @@ -16,13 +16,11 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; -import android.widget.Button; import android.widget.TextView; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.AnyRes; import androidx.annotation.AttrRes; import androidx.annotation.ColorInt; -import androidx.annotation.DimenRes; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.StringRes; @@ -34,8 +32,6 @@ import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; -import app.organicmaps.util.ThemeUtils; -import app.organicmaps.util.WindowInsetUtils; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.textfield.TextInputLayout; import java.util.Objects; @@ -203,16 +199,6 @@ public final class UiUtils ThemeUtils.getResource(toolbar.getContext(), androidx.appcompat.R.attr.homeAsUpIndicator)); } - public static boolean isTablet(@NonNull Context context) - { - return context.getResources().getBoolean(R.bool.tabletLayout); - } - - public static int dimen(@NonNull Context context, @DimenRes int id) - { - 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) { @@ -221,13 +207,6 @@ public final class UiUtils windowManager.getDefaultDisplay().getRealMetrics(metrics); return metrics.heightPixels; } - - public static void updateRedButton(Button button) - { - button.setTextColor(ThemeUtils.getColor( - button.getContext(), button.isEnabled() ? R.attr.redButtonTextColor : R.attr.redButtonTextColorDisabled)); - } - public static void setInputError(@NonNull TextInputLayout layout, @StringRes int error) { setInputError(layout, error == 0 ? null : layout.getContext().getString(error)); 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 5c6614da1..ea3dd9ec1 100644 --- a/android/app/src/main/java/app/organicmaps/util/Utils.java +++ b/android/app/src/main/java/app/organicmaps/util/Utils.java @@ -1,5 +1,6 @@ package app.organicmaps.util; +import static app.organicmaps.sdk.util.Utils.dimen; import static app.organicmaps.sdk.util.Utils.isIntentSupported; import android.app.Activity; @@ -7,7 +8,6 @@ import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; @@ -39,14 +39,17 @@ import app.organicmaps.BuildConfig; import app.organicmaps.MwmActivity; import app.organicmaps.MwmApplication; import app.organicmaps.R; +import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.Constants; import app.organicmaps.sdk.util.Distance; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.StringUtils; import app.organicmaps.sdk.util.concurrency.UiThread; import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.sdk.util.log.LogsManager; import com.google.android.material.snackbar.Snackbar; import java.lang.ref.WeakReference; +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; @Keep public class Utils @@ -200,17 +203,33 @@ public class Utils if (TextUtils.isEmpty(url)) return; - final Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = isHttpOrHttpsScheme(url) ? Uri.parse(url) : new Uri.Builder().scheme("http").appendEncodedPath(url).build(); + + Utils.openUri(context, uri, R.string.browser_not_available); + } + + /** + * Attempts to open a URI in another app via the system app chooser. + * @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. + */ + public static void openUri(@NonNull Context context, @NonNull Uri uri, @Nullable Integer failMessage) + { + final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_BROWSABLE); + try { context.startActivity(intent); } catch (ActivityNotFoundException e) { - Toast.makeText(context, context.getString(R.string.browser_not_available), Toast.LENGTH_LONG).show(); + if (failMessage != null) + Toast.makeText(context, context.getString(failMessage), Toast.LENGTH_LONG).show(); Logger.e(TAG, "ActivityNotFoundException", e); } catch (AndroidRuntimeException e) @@ -221,28 +240,6 @@ public class Utils } } - /** - * Attempts to open a URI in another app via the system app chooser. - * @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) - { - final String act = (action != null && action.length > 0 && action[0] != null) ? action[0] : Intent.ACTION_VIEW; - final Intent intent = new Intent(act); - intent.setData(uri); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // https://developer.android.com/guide/components/intents-common - // check that an app exists to open with, otherwise it'll crash - if (intent.resolveActivity(context.getPackageManager()) != null) - context.startActivity(intent); - else - Toast.makeText(context, failMessage, Toast.LENGTH_SHORT).show(); - } - private static boolean isHttpOrHttpsScheme(@NonNull String url) { return url.startsWith("http://") || url.startsWith("https://"); @@ -259,7 +256,7 @@ public class Utils subject = activity.getString(R.string.project_name) + " Bug Report" + (TextUtils.isEmpty(subject) ? "" : ": " + subject); LogsManager.INSTANCE.zipLogs( - new SupportInfoWithLogsCallback(launcher, activity, subject, body, Constants.Email.SUPPORT)); + new SupportInfoWithLogsCallback(launcher, activity, subject, body, BuildConfig.SUPPORT_MAIL)); } // TODO: Don't send logs with general feedback, send system information only (version, device name, connectivity, @@ -268,7 +265,7 @@ public class Utils @NonNull Activity activity) { LogsManager.INSTANCE.zipLogs(new SupportInfoWithLogsCallback( - launcher, activity, activity.getString(R.string.project_name) + " Feedback", "", Constants.Email.SUPPORT)); + launcher, activity, activity.getString(R.string.project_name) + " Feedback", "", BuildConfig.SUPPORT_MAIL)); } public static void navigateToParent(@NonNull Activity activity) @@ -283,9 +280,9 @@ public class Utils String dimension, String unitText) { final SpannableStringBuilder res = new SpannableStringBuilder(dimension).append("\u00A0").append(unitText); - res.setSpan(new AbsoluteSizeSpan(UiUtils.dimen(context, size), false), 0, dimension.length(), + res.setSpan(new AbsoluteSizeSpan(dimen(context, size), false), 0, dimension.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - res.setSpan(new AbsoluteSizeSpan(UiUtils.dimen(context, units), false), dimension.length(), res.length(), + res.setSpan(new AbsoluteSizeSpan(dimen(context, units), false), dimension.length(), res.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return res; } @@ -294,9 +291,9 @@ public class Utils public static Spannable formatDistance(Context context, @NonNull Distance distance) { final SpannableStringBuilder res = new SpannableStringBuilder(distance.toString(context)); - res.setSpan(new AbsoluteSizeSpan(UiUtils.dimen(context, R.dimen.text_size_nav_number), false), 0, + res.setSpan(new AbsoluteSizeSpan(dimen(context, R.dimen.text_size_nav_number), false), 0, distance.mDistanceStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - res.setSpan(new AbsoluteSizeSpan(UiUtils.dimen(context, R.dimen.text_size_nav_dimension), false), + res.setSpan(new AbsoluteSizeSpan(dimen(context, R.dimen.text_size_nav_dimension), false), distance.mDistanceStr.length(), res.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return res; } @@ -460,4 +457,41 @@ public class Utils return getPackageInfoOld(manager, packageName, flags); return manager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags)); } + + @NonNull + public static CharSequence formatRoutingTime(Context context, int seconds, @DimenRes int unitsSize) + { + return formatRoutingTime(context, seconds, unitsSize, R.dimen.text_size_routing_number); + } + + @NonNull + public static CharSequence formatRoutingTime(Context context, int seconds, @DimenRes int unitsSize, + @DimenRes int textSize) + { + long minutes = TimeUnit.SECONDS.toMinutes(seconds) % 60; + long hours = TimeUnit.SECONDS.toHours(seconds); + String min = context.getString(R.string.minute); + String hour = context.getString(R.string.hour); + SpannableStringBuilder displayedH = Utils.formatTime(context, textSize, unitsSize, String.valueOf(hours), hour); + SpannableStringBuilder displayedM = Utils.formatTime(context, textSize, unitsSize, String.valueOf(minutes), min); + return hours == 0 ? displayedM : TextUtils.concat(displayedH + "\u00A0", displayedM); + } + + @NonNull + public static String formatArrivalTime(int seconds) + { + final LocalTime time = LocalTime.now().plusSeconds(seconds); + return StringUtils.formatUsingUsLocale("%d:%02d", time.getHour(), time.getMinute()); + } + + @NonNull + public static String getDonateUrl(@NonNull Context context) + { + final String url = Config.getDonateUrl(); + // Enable donations by default if not Google or Huawei. Replace comaps.app/donate/ with localized page. + if ((url.isEmpty() && !BuildConfig.FLAVOR.equals("google") && !BuildConfig.FLAVOR.equals("huawei")) + || url.endsWith("comaps.app/donate/")) + return context.getString(R.string.app_site_url) + "donate/"; + return url; + } } diff --git a/android/app/src/main/java/app/organicmaps/util/WindowInsetUtils.java b/android/app/src/main/java/app/organicmaps/util/WindowInsetUtils.java index 8655ead24..7870a0dcd 100644 --- a/android/app/src/main/java/app/organicmaps/util/WindowInsetUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/WindowInsetUtils.java @@ -8,7 +8,7 @@ import androidx.core.graphics.Insets; import androidx.core.view.OnApplyWindowInsetsListener; import androidx.core.view.WindowInsetsCompat; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.Utils; public final class WindowInsetUtils { @@ -79,7 +79,7 @@ public final class WindowInsetUtils if (mFloatingActionButton != null) { - int spacing = UiUtils.dimen(v.getContext(), R.dimen.margin_base); + int spacing = Utils.dimen(v.getContext(), R.dimen.margin_base); int buttonMarginBottom = insets.bottom + spacing; ViewGroup.MarginLayoutParams buttonLayoutParams = diff --git a/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java b/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java index 7b38d5be0..aadc44847 100644 --- a/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java +++ b/android/app/src/main/java/app/organicmaps/util/bottomsheet/MenuBottomSheetFragment.java @@ -15,7 +15,7 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +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; diff --git a/android/app/src/main/java/app/organicmaps/widget/BaseSignView.java b/android/app/src/main/java/app/organicmaps/widget/BaseSignView.java new file mode 100644 index 000000000..e3b8d3bf8 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/widget/BaseSignView.java @@ -0,0 +1,172 @@ +package app.organicmaps.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public abstract class BaseSignView extends View +{ + private float mBorderWidthRatio = 0.1f; + protected void setBorderWidthRatio(float ratio) { + mBorderWidthRatio = ratio; + } + + private float mBorderInsetRatio = 0f; + protected void setBorderInsetRatio(float ratio) { + mBorderInsetRatio = ratio; + } + + // colors + protected int mBackgroundColor; + protected int mBorderColor; + protected int mAlertColor; + protected int mTextColor; + protected int mTextAlertColor; + + // paints + protected final Paint mBackgroundPaint; + protected final Paint mBorderPaint; + protected final Paint mTextPaint; + + // geometry + protected float mWidth; + protected float mHeight; + protected float mRadius; + protected float mBorderWidth; + protected float mBorderRadius; + + public BaseSignView(Context ctx, @Nullable AttributeSet attrs) + { + super(ctx, attrs); + mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBorderPaint.setStyle(Paint.Style.STROKE); + mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setTextAlign(Paint.Align.CENTER); + mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + } + + protected void setColors(int backgroundColor, + int borderColor, + int alertColor, + int textColor, + int textAlertColor) + { + mBackgroundColor = backgroundColor; + mBorderColor = borderColor; + mAlertColor = alertColor; + mTextColor = textColor; + mTextAlertColor = textAlertColor; + + mBackgroundPaint.setColor(mBackgroundColor); + mBorderPaint.setColor(mBorderColor); + mTextPaint.setColor(mTextColor); + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + super.onSizeChanged(width, height, oldWidth, oldHeight); + final float paddingX = getPaddingLeft() + getPaddingRight(); + final float paddingY = getPaddingTop() + getPaddingBottom(); + mWidth = width - paddingX; + mHeight = height - paddingY; + mRadius = Math.min(mWidth, mHeight) / 2f; + mBorderWidth = mRadius * mBorderWidthRatio; + // subtract half the stroke PLUS the extra inset + final float gap = mRadius * mBorderInsetRatio; + mBorderRadius = mRadius - (mBorderWidth / 2f) - gap; + configureTextSize(); + } + + @Override + protected void onDraw(@NonNull Canvas canvas) + { + super.onDraw(canvas); + final String str = getValueString(); + if (str == null) return; + + final float cx = mWidth / 2f; + final float cy = mHeight / 2f; + + // background & border + boolean alert = isAlert(); + mBackgroundPaint.setColor(alert ? mAlertColor : mBackgroundColor); + canvas.drawCircle(cx, cy, mRadius, mBackgroundPaint); + if (!alert) + { + mBorderPaint.setStrokeWidth(mBorderWidth); + mBorderPaint.setColor(mBorderColor); + canvas.drawCircle(cx, cy, mBorderRadius, mBorderPaint); + } + + // text + mTextPaint.setColor(alert ? mTextAlertColor : mTextColor); + drawValueString(canvas, cx, cy, str); + } + + @Override + public boolean onTouchEvent(@NonNull MotionEvent e) + { + final float cx = mWidth / 2f, cy = mHeight / 2f; + final float dx = e.getX() - cx, dy = e.getY() - cy; + if ((dx * dx) + (dy * dy) <= (mRadius * mRadius)) + { + performClick(); + return true; + } + return false; + } + + @Override + public boolean performClick() + { + super.performClick(); + return false; + } + + private void drawValueString(Canvas c, float cx, float cy, String str) + { + Rect b = new Rect(); + mTextPaint.getTextBounds(str, 0, str.length(), b); + final float y = cy - b.exactCenterY(); + c.drawText(str, cx, y, mTextPaint); + } + + void configureTextSize() + { + String text = getValueString(); + if (text == null) return; + final float textRadius = mBorderRadius - mBorderWidth; + final float maxTextSize = 2f * textRadius; + final float maxTextSize2 = maxTextSize * maxTextSize; + float lo = 0f, hi = maxTextSize, sz = maxTextSize; + Rect b = new Rect(); + while (lo <= hi) + { + sz = (lo + hi) / 2f; + mTextPaint.setTextSize(sz); + mTextPaint.getTextBounds(text, 0, text.length(), b); + float area = b.width()*b.width() + b.height()*b.height(); + if (area <= maxTextSize2) + lo = sz + 1f; + else + hi = sz - 1f; + } + mTextPaint.setTextSize(Math.max(1f, sz)); + } + + /** child must return the string to draw, or null if nothing */ + @Nullable + protected abstract String getValueString(); + + /** child decides if this is in “alert” state */ + protected abstract boolean isAlert(); +} diff --git a/android/app/src/main/java/app/organicmaps/widget/CurrentSpeedView.java b/android/app/src/main/java/app/organicmaps/widget/CurrentSpeedView.java new file mode 100644 index 000000000..c414b371a --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/widget/CurrentSpeedView.java @@ -0,0 +1,77 @@ +package app.organicmaps.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.Pair; + +import androidx.annotation.Nullable; + +import app.organicmaps.R; +import app.organicmaps.sdk.util.StringUtils; + +public class CurrentSpeedView extends BaseSignView +{ + private double mSpeedMps = -1.0; + private String mSpeedStr = "--"; + + public CurrentSpeedView(Context ctx, @Nullable AttributeSet attrs) + { + super(ctx, attrs); + + setBorderWidthRatio(0.1f); + setBorderInsetRatio(0.05f); + + try (TypedArray a = ctx.getTheme() + .obtainStyledAttributes(attrs, R.styleable.CurrentSpeedView /* reuse same attrs or define new */ , 0, 0)) + { + int bg = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBackgroundColor, DefaultValues.BACKGROUND_COLOR); + int bd = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBorderColor, DefaultValues.BORDER_COLOR); + int tc = a.getColor(R.styleable.CurrentSpeedView_currentSpeedTextColor, DefaultValues.TEXT_COLOR); + setColors(bg, bd, 0, tc, 0); + + if (isInEditMode()) + { + mSpeedMps = a.getInt(R.styleable.CurrentSpeedView_currentSpeedEditModeCurrentSpeed, 50); + mSpeedStr = Integer.toString((int)mSpeedMps); + } + } + } + + public void setCurrentSpeed(double mps) + { + mSpeedMps = mps; + if (mps < 0) + { + mSpeedStr = "--"; + } + else + { + Pair su = StringUtils.nativeFormatSpeedAndUnits(mps); + mSpeedStr = su.first; + } + requestLayout(); + configureTextSize(); + invalidate(); + } + + @Nullable + @Override + protected String getValueString() + { + return mSpeedStr; + } + + @Override + protected boolean isAlert() + { + return false; + } + + private interface DefaultValues + { + int BACKGROUND_COLOR = 0xFFFFFFFF; + int BORDER_COLOR = 0xFF000000; + int TEXT_COLOR = 0xFF000000; + } +} diff --git a/android/app/src/main/java/app/organicmaps/widget/PlaceholderView.java b/android/app/src/main/java/app/organicmaps/widget/PlaceholderView.java index 9dd3a09ba..0655bec5a 100644 --- a/android/app/src/main/java/app/organicmaps/widget/PlaceholderView.java +++ b/android/app/src/main/java/app/organicmaps/widget/PlaceholderView.java @@ -14,7 +14,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; public class PlaceholderView extends LinearLayout { diff --git a/android/app/src/main/java/app/organicmaps/widget/RoutingToolbarButton.java b/android/app/src/main/java/app/organicmaps/widget/RoutingToolbarButton.java index c98423a74..e51e84105 100644 --- a/android/app/src/main/java/app/organicmaps/widget/RoutingToolbarButton.java +++ b/android/app/src/main/java/app/organicmaps/widget/RoutingToolbarButton.java @@ -35,10 +35,9 @@ public class RoutingToolbarButton extends AppCompatRadioButton private void initView() { - setBackgroundResource(ThemeUtils.isNightTheme(getContext()) ? R.drawable.routing_toolbar_button_night - : R.drawable.routing_toolbar_button); - setButtonTintList(ThemeUtils.isNightTheme(getContext()) ? R.color.routing_toolbar_icon_tint_night - : R.color.routing_toolbar_icon_tint); + final boolean isNightTheme = ThemeUtils.isNightTheme(); + setBackgroundResource(isNightTheme ? R.drawable.routing_toolbar_button_night : R.drawable.routing_toolbar_button); + setButtonTintList(isNightTheme ? R.color.routing_toolbar_icon_tint_night : R.color.routing_toolbar_icon_tint); } public void progress() 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 12e17bc92..ea57be923 100644 --- a/android/app/src/main/java/app/organicmaps/widget/SearchToolbarController.java +++ b/android/app/src/main/java/app/organicmaps/widget/SearchToolbarController.java @@ -16,8 +16,8 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import app.organicmaps.R; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.InputUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.textfield.TextInputEditText; public class SearchToolbarController extends ToolbarController implements View.OnClickListener @@ -240,7 +240,8 @@ public class SearchToolbarController extends ToolbarController implements View.O } @NonNull - public OnBackPressedCallback getBackPressedCallback() { + public OnBackPressedCallback getBackPressedCallback() + { return mBackPressedCallback; } } 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 12c4f4819..a819da109 100644 --- a/android/app/src/main/java/app/organicmaps/widget/SpeedLimitView.java +++ b/android/app/src/main/java/app/organicmaps/widget/SpeedLimitView.java @@ -3,225 +3,134 @@ package app.organicmaps.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; + import androidx.annotation.Nullable; + import app.organicmaps.R; -public class SpeedLimitView extends View +public class SpeedLimitView extends BaseSignView { - private interface DefaultValues + private int mSpeedLimit = -1; + private boolean mAlert = false; + private String mSpeedStr = "-1"; + private final int unlimitedBorderColor; + private final int unlimitedStripeColor; + + public SpeedLimitView(Context ctx, @Nullable AttributeSet attrs) { - @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; + super(ctx, attrs); - float BORDER_WIDTH_RATIO = 0.1f; - } + setBorderWidthRatio(0.2f); + setBorderInsetRatio(0.05f); - @ColorInt - private final int mBackgroundColor; - - @ColorInt - private final int mBorderColor; - - @ColorInt - private final int mAlertColor; - - @ColorInt - private final int mTextColor; - - @ColorInt - private final int mTextAlertColor; - - @NonNull - private final Paint mSignBackgroundPaint; - @NonNull - private final Paint mSignBorderPaint; - @NonNull - private final Paint mTextPaint; - - private float mWidth; - private float mHeight; - private float mBackgroundRadius; - private float mBorderRadius; - private float mBorderWidth; - - private int mSpeedLimit = 0; - @NonNull - private String mSpeedLimitStr = "0"; - private boolean mAlert = false; - - public SpeedLimitView(Context context, @Nullable AttributeSet attrs) - { - super(context, attrs); - - try (TypedArray data = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SpeedLimitView, 0, 0)) + try (TypedArray styleAttrs = ctx.getTheme().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); + final int bgColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBackgroundColor, DefaultValues.BACKGROUND_COLOR); + final int borderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBorderColor, DefaultValues.BORDER_COLOR); + final int alertColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitAlertColor, DefaultValues.ALERT_COLOR); + final int textColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextColor, DefaultValues.TEXT_COLOR); + final int txtAlertColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextAlertColor, DefaultValues.TEXT_ALERT_COLOR); + setColors(bgColor, borderColor, alertColor, textColor, txtAlertColor); + + unlimitedBorderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedBorderColor, DefaultValues.UNLIMITED_BORDER_COLOR); + unlimitedStripeColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedStripeColor, DefaultValues.UNLIMITED_STRIPE_COLOR); + if (isInEditMode()) { - mSpeedLimit = data.getInt(R.styleable.SpeedLimitView_speedLimitEditModeSpeedLimit, 60); - mSpeedLimitStr = Integer.toString(mSpeedLimit); - mAlert = data.getBoolean(R.styleable.SpeedLimitView_speedLimitEditModeAlert, false); + mSpeedLimit = styleAttrs.getInt(R.styleable.SpeedLimitView_speedLimitEditModeSpeedLimit, 60); + mAlert = styleAttrs.getBoolean(R.styleable.SpeedLimitView_speedLimitEditModeAlert, false); + mSpeedStr = Integer.toString(mSpeedLimit); } } - - mSignBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mSignBackgroundPaint.setColor(mBackgroundColor); - - mSignBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mSignBorderPaint.setColor(mBorderColor); - mSignBorderPaint.setStrokeWidth(mBorderWidth); - mSignBorderPaint.setStyle(Paint.Style.STROKE); - - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTextPaint.setColor(mTextColor); - mTextPaint.setTextAlign(Paint.Align.CENTER); - mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); } - public void setSpeedLimit(final int speedLimit, boolean alert) + public void setSpeedLimit(int limit, boolean alert) { - final boolean speedLimitChanged = mSpeedLimit != speedLimit; - - mSpeedLimit = speedLimit; - mAlert = alert; - - if (speedLimitChanged) + if (mSpeedLimit != limit) { - mSpeedLimitStr = Integer.toString(mSpeedLimit); - configureTextSize(); + mSpeedLimit = limit; + mSpeedStr = Integer.toString(limit); + requestLayout(); } - + mAlert = alert; + configureTextSize(); invalidate(); } + @Nullable @Override - protected void onDraw(@NonNull Canvas canvas) + protected String getValueString() { - super.onDraw(canvas); - - final boolean validSpeedLimit = mSpeedLimit > 0; - if (!validSpeedLimit) - return; - - final float cx = mWidth / 2; - final float cy = mHeight / 2; - - drawSign(canvas, cx, cy, mAlert); - drawText(canvas, cx, cy, mAlert); + return (mSpeedLimit > 0 ? mSpeedStr : null); } - private void drawSign(@NonNull Canvas canvas, float cx, float cy, boolean alert) + @Override + protected boolean isAlert() { - if (alert) - mSignBackgroundPaint.setColor(mAlertColor); + return mAlert; + } + + @Override + protected void onDraw(Canvas canvas) + { + final float cx = mWidth/2f, cy = mHeight/2f; + + if (mSpeedLimit == 0) // 0 means unlimited speed (maxspeed=none) + { + // background + mBackgroundPaint.setColor(mBackgroundColor); + canvas.drawCircle(cx, cy, mRadius, mBackgroundPaint); + + // black border + mBorderPaint.setColor(unlimitedBorderColor); + mBorderPaint.setStrokeWidth(mBorderWidth); + canvas.drawCircle(cx, cy, mBorderRadius, mBorderPaint); + + // draw 5 diagonal stripes + drawUnlimitedStripes(canvas, cx, cy); + } else - mSignBackgroundPaint.setColor(mBackgroundColor); - - canvas.drawCircle(cx, cy, mBackgroundRadius, mSignBackgroundPaint); - if (!alert) { - mSignBorderPaint.setStrokeWidth(mBorderWidth); - canvas.drawCircle(cx, cy, mBorderRadius, mSignBorderPaint); + // delegate to BaseSignView’s onDraw + super.onDraw(canvas); } } - private void drawText(@NonNull Canvas canvas, float cx, float cy, boolean alert) + private void drawUnlimitedStripes(Canvas c, float cx, float cy) { - if (alert) - mTextPaint.setColor(mTextAlertColor); - else - mTextPaint.setColor(mTextColor); + final Paint stripe = new Paint(Paint.ANTI_ALIAS_FLAG); + stripe.setColor(unlimitedStripeColor); + stripe.setStrokeWidth(mBorderWidth * 0.4f); - final Rect textBounds = new Rect(); - mTextPaint.getTextBounds(mSpeedLimitStr, 0, mSpeedLimitStr.length(), textBounds); - final float textY = cy - textBounds.exactCenterY(); - canvas.drawText(mSpeedLimitStr, cx, textY, mTextPaint); - } + final float radius = mRadius * 0.8f; // Shorten to 80% of full radius + final float diag = (float) (1/Math.sqrt(2)); // 45 degrees + final float dx = -diag, dy = +diag; + final float px = -dy, py = +dx; // Perpendicular + final float step = radius * 0.15f; // Spacing - @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)) + for (int i = -2; i <= 2; i++) { - performClick(); - return true; + final float ox = px * step * i; + final float oy = py * step * i; + final float sx = cx + dx * radius + ox; + final float sy = cy + dy * radius + oy; + final float ex = cx - dx * radius + ox; + final float ey = cy - dy * radius + oy; + c.drawLine(sx, sy, ex, ey, stripe); } - return false; } - @Override - public boolean performClick() + + private interface DefaultValues { - super.performClick(); - return false; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) - { - super.onSizeChanged(w, h, oldw, oldh); - - final float paddingX = (float) (getPaddingLeft() + getPaddingRight()); - final float paddingY = (float) (getPaddingTop() + getPaddingBottom()); - - mWidth = (float) w - paddingX; - mHeight = (float) h - paddingY; - mBackgroundRadius = Math.min(mWidth, mHeight) / 2; - mBorderWidth = mBackgroundRadius * 2 * DefaultValues.BORDER_WIDTH_RATIO; - mBorderRadius = mBackgroundRadius - mBorderWidth / 2; - configureTextSize(); - } - - // Apply binary search to determine the optimal text size that fits within the circular boundary. - private void configureTextSize() - { - final String text = mSpeedLimitStr; - final float textRadius = mBorderRadius - mBorderWidth; - final float textMaxSize = 2 * textRadius; - final float textMaxSizeSquared = (float) Math.pow(textMaxSize, 2); - - float lowerBound = 0; - float upperBound = textMaxSize; - float textSize = textMaxSize; - final Rect textBounds = new Rect(); - - while (lowerBound <= upperBound) - { - textSize = (lowerBound + upperBound) / 2; - mTextPaint.setTextSize(textSize); - mTextPaint.getTextBounds(text, 0, text.length(), textBounds); - - if (Math.pow(textBounds.width(), 2) + Math.pow(textBounds.height(), 2) <= textMaxSizeSquared) - lowerBound = textSize + 1; - else - upperBound = textSize - 1; - } - - mTextPaint.setTextSize(Math.max(1, textSize)); + int BACKGROUND_COLOR = 0xFFFFFFFF; + int BORDER_COLOR = 0xFFFF0000; + int ALERT_COLOR = 0xFFFF0000; + int TEXT_COLOR = 0xFF000000; + int TEXT_ALERT_COLOR = 0xFFFFFFFF; + int UNLIMITED_BORDER_COLOR = 0xFF000000; + int UNLIMITED_STRIPE_COLOR = 0xFF000000; } } diff --git a/android/app/src/main/java/app/organicmaps/widget/StackedButtonDialogFragment.java b/android/app/src/main/java/app/organicmaps/widget/StackedButtonDialogFragment.java index 544d154c4..0c8f46870 100644 --- a/android/app/src/main/java/app/organicmaps/widget/StackedButtonDialogFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/StackedButtonDialogFragment.java @@ -13,6 +13,8 @@ import app.organicmaps.sdk.util.NetworkPolicy; public class StackedButtonDialogFragment extends DialogFragment { + private static final String TAG_NETWORK_POLICY = "network_policy"; + @Nullable private NetworkPolicy.NetworkPolicyListener mListener; @@ -43,7 +45,7 @@ public class StackedButtonDialogFragment extends DialogFragment { Config.setUseMobileDataSettings(type); if (mListener != null) - mListener.onResult(NetworkPolicy.newInstance(canUse)); + mListener.onResult(new NetworkPolicy(canUse)); } @Override @@ -58,4 +60,29 @@ public class StackedButtonDialogFragment extends DialogFragment { mListener = listener; } + + public static void showDialogIfNeeded(@NonNull FragmentManager fragmentManager, + @NonNull NetworkPolicy.NetworkPolicyListener listener, + @NonNull NetworkPolicy policy, boolean isToday) + { + if (isToday) + { + listener.onResult(policy); + return; + } + showDialog(fragmentManager, listener); + } + + public static void showDialog(@NonNull FragmentManager fragmentManager, + @NonNull NetworkPolicy.NetworkPolicyListener listener) + { + StackedButtonDialogFragment dialog = + (StackedButtonDialogFragment) fragmentManager.findFragmentByTag(TAG_NETWORK_POLICY); + if (dialog != null) + dialog.dismiss(); + + dialog = new StackedButtonDialogFragment(); + dialog.setListener(listener); + dialog.show(fragmentManager, TAG_NETWORK_POLICY); + } } diff --git a/android/app/src/main/java/app/organicmaps/widget/StackedButtonsDialog.java b/android/app/src/main/java/app/organicmaps/widget/StackedButtonsDialog.java index f8fc1db04..945bba16e 100644 --- a/android/app/src/main/java/app/organicmaps/widget/StackedButtonsDialog.java +++ b/android/app/src/main/java/app/organicmaps/widget/StackedButtonsDialog.java @@ -10,7 +10,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatDialog; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; public class StackedButtonsDialog extends AppCompatDialog implements View.OnClickListener { diff --git a/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java b/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java index 4dd6de14a..e59c9b9f6 100644 --- a/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java +++ b/android/app/src/main/java/app/organicmaps/widget/ToolbarController.java @@ -10,7 +10,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.view.ViewCompat; import app.organicmaps.R; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils; import com.google.android.material.appbar.MaterialToolbar; diff --git a/android/app/src/main/java/app/organicmaps/widget/menu/MainMenu.java b/android/app/src/main/java/app/organicmaps/widget/menu/MainMenu.java index e5c978820..90e15433b 100644 --- a/android/app/src/main/java/app/organicmaps/widget/menu/MainMenu.java +++ b/android/app/src/main/java/app/organicmaps/widget/menu/MainMenu.java @@ -1,7 +1,7 @@ package app.organicmaps.widget.menu; import android.view.View; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; public class MainMenu { diff --git a/android/app/src/main/java/app/organicmaps/widget/menu/MyPositionButton.java b/android/app/src/main/java/app/organicmaps/widget/menu/MyPositionButton.java index 875ed86a1..56ace762f 100644 --- a/android/app/src/main/java/app/organicmaps/widget/menu/MyPositionButton.java +++ b/android/app/src/main/java/app/organicmaps/widget/menu/MyPositionButton.java @@ -18,8 +18,8 @@ import androidx.core.widget.ImageViewCompat; import app.organicmaps.R; import app.organicmaps.sdk.Map; import app.organicmaps.sdk.location.LocationState; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.ThemeUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.floatingactionbutton.FloatingActionButton; public class MyPositionButton @@ -52,7 +52,7 @@ public class MyPositionButton if (mode == LocationState.FOLLOW || mode == LocationState.FOLLOW_AND_ROTATE || mode == LocationState.PENDING_POSITION) { - colorAttr = androidx.appcompat.R.attr.colorAccent; + colorAttr = com.google.android.material.R.attr.colorSecondary; if (mode == LocationState.PENDING_POSITION) sizeDimen = R.dimen.map_button_size; else diff --git a/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java b/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java index e25f922db..b503cf812 100644 --- a/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java +++ b/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java @@ -1,22 +1,18 @@ package app.organicmaps.widget.menu; -import android.location.Location; -import android.util.Pair; import android.view.View; -import android.widget.Button; -import android.widget.ImageView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.sdk.routing.RoutingInfo; import app.organicmaps.sdk.sound.TtsPlayer; -import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.DateUtils; import app.organicmaps.util.Graphics; -import app.organicmaps.util.ThemeUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.textview.MaterialTextView; import java.time.LocalTime; @@ -29,15 +25,13 @@ public class NavMenu private final View mBottomSheetBackground; private final View mHeaderFrame; - private final ImageView mTts; - private final View mSpeedViewContainer; - private final MaterialTextView mSpeedValue; - private final MaterialTextView mSpeedUnits; + private final ShapeableImageView mTts; + private final MaterialTextView mEtaValue; + private final MaterialTextView mEtaAmPm; private final MaterialTextView mTimeHourValue; private final MaterialTextView mTimeHourUnits; private final MaterialTextView mTimeMinuteValue; private final MaterialTextView mTimeMinuteUnits; - private final MaterialTextView mTimeEstimate; private final MaterialTextView mDistanceValue; private final MaterialTextView mDistanceUnits; private final LinearProgressIndicator mRouteProgress; @@ -92,26 +86,23 @@ public class NavMenu }); // Bottom frame - mSpeedViewContainer = bottomFrame.findViewById(R.id.speed_view_container); - mSpeedValue = bottomFrame.findViewById(R.id.speed_value); - mSpeedUnits = bottomFrame.findViewById(R.id.speed_dimen); + mEtaValue = bottomFrame.findViewById(R.id.eta_value); + mEtaAmPm = bottomFrame.findViewById(R.id.eta_am_pm); mTimeHourValue = bottomFrame.findViewById(R.id.time_hour_value); mTimeHourUnits = bottomFrame.findViewById(R.id.time_hour_dimen); mTimeMinuteValue = bottomFrame.findViewById(R.id.time_minute_value); mTimeMinuteUnits = bottomFrame.findViewById(R.id.time_minute_dimen); - mTimeEstimate = bottomFrame.findViewById(R.id.time_estimate); mDistanceValue = bottomFrame.findViewById(R.id.distance_value); mDistanceUnits = bottomFrame.findViewById(R.id.distance_dimen); mRouteProgress = bottomFrame.findViewById(R.id.navigation_progress); // Bottom frame buttons - ImageView mSettings = bottomFrame.findViewById(R.id.settings); + ShapeableImageView mSettings = bottomFrame.findViewById(R.id.settings); mSettings.setOnClickListener(v -> onSettingsClicked()); mTts = bottomFrame.findViewById(R.id.tts_volume); mTts.setOnClickListener(v -> onTtsClicked()); - Button stop = bottomFrame.findViewById(R.id.stop); + MaterialButton stop = bottomFrame.findViewById(R.id.stop); stop.setOnClickListener(v -> onStopClicked()); - UiUtils.updateRedButton(stop); } private void onStopClicked() @@ -126,6 +117,8 @@ public class NavMenu private void onTtsClicked() { + if (!TtsPlayer.isReady()) + Toast.makeText(mActivity, R.string.pref_tts_no_system_tts_short, Toast.LENGTH_SHORT).show(); TtsPlayer.setEnabled(!TtsPlayer.isEnabled()); refreshTts(); } @@ -166,9 +159,9 @@ public class NavMenu public void refreshTts() { - mTts.setImageDrawable(TtsPlayer.isEnabled() - ? Graphics.tint(mActivity, R.drawable.ic_voice_on, androidx.appcompat.R.attr.colorAccent) - : Graphics.tint(mActivity, R.drawable.ic_voice_off)); + mTts.setImageDrawable(TtsPlayer.isEnabled() ? Graphics.tint(mActivity, R.drawable.ic_voice_on, + com.google.android.material.R.attr.colorSecondary) + : Graphics.tint(mActivity, R.drawable.ic_voice_off)); } private void updateTime(int seconds) @@ -196,42 +189,40 @@ public class NavMenu private void updateTimeEstimate(int seconds) { - final String format = - android.text.format.DateFormat.is24HourFormat(mTimeMinuteValue.getContext()) ? "HH:mm" : "h:mm a"; + // Calculate ETA from current local time and remaining seconds. final LocalTime localTime = LocalTime.now().plusSeconds(seconds); - mTimeEstimate.setText(localTime.format(DateTimeFormatter.ofPattern(format))); - } - private void updateSpeedView(@NonNull RoutingInfo info) - { - final Location last = MwmApplication.from(mActivity).getLocationHelper().getSavedLocation(); - if (last == null) - return; + // String to set the format of the ETA value (24h or AM/PM). + final String etaValueFormat; - Pair speedAndUnits = StringUtils.nativeFormatSpeedAndUnits(last.getSpeed()); - mSpeedValue.setText(speedAndUnits.first); + // Text of the AM/PM view. + final String etaAmPmText; - if (info.speedLimitMps > 0.0 && last.getSpeed() > info.speedLimitMps) + if (DateUtils.is24HourFormat(mTimeMinuteValue.getContext())) { - if (info.isSpeedCamLimitExceeded()) - mSpeedValue.setTextColor(ContextCompat.getColor(mActivity, R.color.white_primary)); - else - mSpeedValue.setTextColor(ContextCompat.getColor(mActivity, R.color.base_red)); + // 24 hours time format. + etaValueFormat = "HH:mm"; + etaAmPmText = ""; } else - mSpeedValue.setTextColor(ThemeUtils.getColor(mActivity, android.R.attr.textColorPrimary)); + { + // AM/PM time format. + etaValueFormat = "h:mm"; + etaAmPmText = localTime.format(DateTimeFormatter.ofPattern("a")); + } - mSpeedUnits.setText(speedAndUnits.second); - mSpeedViewContainer.setActivated(info.isSpeedCamLimitExceeded()); + mEtaValue.setText(localTime.format(DateTimeFormatter.ofPattern(etaValueFormat))); + mEtaAmPm.setText(etaAmPmText); } public void update(@NonNull RoutingInfo info) { - updateSpeedView(info); updateTime(info.totalTimeInSeconds); mDistanceValue.setText(info.distToTarget.mDistanceStr); mDistanceUnits.setText(info.distToTarget.getUnitsStr(mActivity.getApplicationContext())); - mRouteProgress.setProgressCompat((int) info.completionPercent, true); + // Start progress at 1% according to M3 guidelines + final int completionPercent = (info.completionPercent < 1) ? 1 : (int) info.completionPercent; + mRouteProgress.setProgressCompat(completionPercent, true); } public interface NavMenuListener diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java b/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java index d3542f370..0543be12b 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java @@ -1,7 +1,7 @@ package app.organicmaps.widget.placepage; import androidx.annotation.NonNull; -import app.organicmaps.sdk.Framework; +import app.organicmaps.sdk.util.StringUtils; import com.github.mikephil.charting.charts.BarLineChartBase; import com.github.mikephil.charting.formatter.DefaultValueFormatter; @@ -20,6 +20,6 @@ public class AxisValueFormatter extends DefaultValueFormatter @Override public String getFormattedValue(float value) { - return Framework.nativeFormatAltitude(value); + return StringUtils.nativeFormatDistance(value).toString(mChart.getContext()); } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/BookmarkColorDialogFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/BookmarkColorDialogFragment.java index a1a966b63..2994a98ad 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/BookmarkColorDialogFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/BookmarkColorDialogFragment.java @@ -6,24 +6,29 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.widget.GridView; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import app.organicmaps.R; import app.organicmaps.base.BaseMwmDialogFragment; -import app.organicmaps.bookmarks.IconsAdapter; -import app.organicmaps.sdk.bookmarks.data.BookmarkManager; -import app.organicmaps.sdk.bookmarks.data.Icon; +import app.organicmaps.bookmarks.ColorsAdapter; +import app.organicmaps.sdk.bookmarks.data.PredefinedColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.util.List; public class BookmarkColorDialogFragment extends BaseMwmDialogFragment { - public static final String ICON_TYPE = "ExtraIconType"; + public static final String ICON_COLOR = "ExtraIconColor"; + public static final String ICON_RES = "ExtraIconRes"; + @PredefinedColors.Color private int mIconColor; + @DrawableRes + private int mIconResId = app.organicmaps.sdk.R.drawable.ic_bookmark_none; + public interface OnBookmarkColorChangeListener { - void onBookmarkColorSet(int colorPos); + void onBookmarkColorSet(@PredefinedColors.Color int color); } private OnBookmarkColorChangeListener mColorSetListener; @@ -35,7 +40,12 @@ public class BookmarkColorDialogFragment extends BaseMwmDialogFragment public Dialog onCreateDialog(Bundle savedInstanceState) { if (getArguments() != null) - mIconColor = getArguments().getInt(ICON_TYPE); + { + if (getArguments().containsKey(ICON_COLOR)) + mIconColor = getArguments().getInt(ICON_COLOR); + if (getArguments().containsKey(ICON_RES)) + mIconResId = getArguments().getInt(ICON_RES); + } return new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog) .setView(buildView()) @@ -49,19 +59,20 @@ public class BookmarkColorDialogFragment extends BaseMwmDialogFragment mColorSetListener = listener; } + @NonNull private View buildView() { - final List icons = BookmarkManager.ICONS; - final IconsAdapter adapter = new IconsAdapter(requireActivity(), icons); + final List colors = PredefinedColors.getAllPredefinedColors(); + final ColorsAdapter adapter = new ColorsAdapter(requireActivity(), colors, mIconResId); adapter.chooseItem(mIconColor); @SuppressLint("InflateParams") final GridView gView = (GridView) LayoutInflater.from(requireActivity()).inflate(R.layout.fragment_color_grid, null); gView.setAdapter(adapter); - gView.setOnItemClickListener((arg0, who, pos, id) -> { + gView.setOnItemClickListener((parent, view, pos, id) -> { if (mColorSetListener != null) - mColorSetListener.onBookmarkColorSet(pos); + mColorSetListener.onBookmarkColorSet(adapter.getItem(pos)); dismiss(); }); 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 2062281ee..258701ea2 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 @@ -17,7 +17,7 @@ import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.location.LocationListener; import app.organicmaps.sdk.location.SensorListener; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.widget.ArrowView; import com.google.android.material.textview.MaterialTextView; 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 2bcfee1ee..f8bb3302e 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 @@ -24,10 +24,11 @@ import app.organicmaps.sdk.bookmarks.data.BookmarkCategory; import app.organicmaps.sdk.bookmarks.data.BookmarkInfo; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.Icon; +import app.organicmaps.sdk.bookmarks.data.PredefinedColors; import app.organicmaps.sdk.bookmarks.data.Track; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.Graphics; import app.organicmaps.util.InputUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.imageview.ShapeableImageView; @@ -155,23 +156,23 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. switch (mType) { - 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) - mIcon = mBookmark.getIcon(); - refreshBookmark(); - } - case TYPE_TRACK -> - { - mTrack = BookmarkManager.INSTANCE.getTrack(id); - mColor = mTrack.getColor(); - if (savedInstanceState != null) - mColor = savedInstanceState.getInt(STATE_COLOR, mColor); - refreshTrack(); - } + 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) + mIcon = mBookmark.getIcon(); + refreshBookmark(); + } + case TYPE_TRACK -> + { + mTrack = BookmarkManager.INSTANCE.getTrack(id); + mColor = mTrack.getColor(); + if (savedInstanceState != null) + mColor = savedInstanceState.getInt(STATE_COLOR, mColor); + refreshTrack(); + } } initToolbar(view); } @@ -182,7 +183,8 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. super.onStart(); // Focus name and show keyboard for "Unknown Place" bookmarks - if (mBookmark != null && mBookmark.getName().equals(getString(R.string.core_placepage_unknown_place))) + if (mBookmark != null + && mBookmark.getName().equals(getString(app.organicmaps.sdk.R.string.core_placepage_unknown_place))) { mEtName.requestFocus(); mEtName.selectAll(); @@ -209,16 +211,16 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. final ShapeableImageView imageView = toolbar.findViewById(R.id.save); switch (mType) { - case TYPE_BOOKMARK -> - { - imageView.setOnClickListener(v -> saveBookmark()); - toolbar.setTitle(R.string.placepage_edit_bookmark_button); - } - case TYPE_TRACK -> - { - imageView.setOnClickListener(v -> saveTrack()); - toolbar.setTitle(R.string.edit_track); - } + case TYPE_BOOKMARK -> + { + imageView.setOnClickListener(v -> saveBookmark()); + toolbar.setTitle(R.string.placepage_edit_bookmark_button); + } + case TYPE_TRACK -> + { + imageView.setOnClickListener(v -> saveTrack()); + toolbar.setTitle(R.string.edit_track); + } } UiUtils.showHomeUpButton(toolbar); toolbar.setNavigationOnClickListener(v -> dismiss()); @@ -304,9 +306,12 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. final Bundle args = new Bundle(); if (mTrack != null) - args.putInt(BookmarkColorDialogFragment.ICON_TYPE, mTrack.getColor()); + args.putInt(BookmarkColorDialogFragment.ICON_COLOR, PredefinedColors.getPredefinedColorIndex(mColor)); else - args.putInt(BookmarkColorDialogFragment.ICON_TYPE, mIcon.getColor()); + { + args.putInt(BookmarkColorDialogFragment.ICON_COLOR, mIcon.getColor()); + args.putInt(BookmarkColorDialogFragment.ICON_RES, mIcon.getResId()); + } final FragmentManager manager = getChildFragmentManager(); String className = BookmarkColorDialogFragment.class.getName(); final FragmentFactory factory = manager.getFragmentFactory(); @@ -315,24 +320,23 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. dialogFragment.setArguments(args); switch (mType) { - case TYPE_BOOKMARK -> - dialogFragment.setOnColorSetListener(colorPos -> { - final Icon newIcon = BookmarkManager.ICONS.get(colorPos); - if (mIcon.getColor() == newIcon.getColor()) - return; + case TYPE_BOOKMARK -> + dialogFragment.setOnColorSetListener(colorPos -> { + if (mIcon != null && mIcon.getColor() == colorPos) + return; - mIcon = newIcon; - refreshColorMarker(); - }); - case TYPE_TRACK -> - dialogFragment.setOnColorSetListener(colorPos -> { - int from = mTrack.getColor(); - int to = BookmarkManager.ICONS.get(colorPos).argb(); - if (from == to) - return; - mColor = to; - refreshTrackColor(); - }); + mIcon = new Icon(colorPos, mIcon.getType()); + refreshColorMarker(); + }); + case TYPE_TRACK -> + dialogFragment.setOnColorSetListener(colorPos -> { + int from = mTrack.getColor(); + int to = PredefinedColors.getColor(colorPos); + if (from == to) + return; + mColor = to; + refreshTrackColor(); + }); } dialogFragment.show(requireActivity().getSupportFragmentManager(), null); @@ -342,9 +346,8 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View. { if (mIcon != null) { - Drawable circle = - Graphics.drawCircleAndImage(mIcon.argb(), R.dimen.track_circle_size, app.organicmaps.sdk.R.drawable.ic_bookmark_none, - R.dimen.bookmark_icon_size, requireContext()); + final Drawable circle = Graphics.drawCircleAndImage(mIcon.argb(), R.dimen.track_circle_size, mIcon.getResId(), + R.dimen.bookmark_icon_size, requireContext()); mIvColor.setImageDrawable(circle); } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java b/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java index f0f4442cb..fb25738b7 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java @@ -1,7 +1,6 @@ package app.organicmaps.widget.placepage; import android.content.Context; -import android.os.Bundle; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; @@ -10,20 +9,21 @@ import androidx.annotation.Nullable; import androidx.core.widget.NestedScrollView; import app.organicmaps.ChartController; import app.organicmaps.R; -import app.organicmaps.routing.RoutingController; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.bookmarks.data.ElevationInfo; -import app.organicmaps.sdk.util.UiUtils; -import app.organicmaps.sdk.widget.placepage.PlacePageData; +import app.organicmaps.sdk.bookmarks.data.Track; +import app.organicmaps.sdk.bookmarks.data.TrackStatistics; +import app.organicmaps.util.UiUtils; +import app.organicmaps.util.Utils; import java.util.Objects; -@SuppressWarnings("unused") // https://github.com/organicmaps/organicmaps/issues/2829 public class ElevationProfileViewRenderer implements PlacePageStateListener { // Must be correspond to map/elevation_info.hpp constants. private static final int MAX_DIFFICULTY_LEVEL = 3; private static final int UNKNOWN_DIFFICULTY = 0; - + @NonNull + private final View[] mDifficultyLevels = new View[MAX_DIFFICULTY_LEVEL]; @SuppressWarnings("NullableProblems") @NonNull private NestedScrollView mScrollView; @@ -45,8 +45,6 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener @SuppressWarnings("NullableProblems") @NonNull private TextView mTime; - @NonNull - private final View[] mDifficultyLevels = new View[MAX_DIFFICULTY_LEVEL]; @SuppressWarnings("NullableProblems") @NonNull private ChartController mChartController; @@ -58,22 +56,24 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener @SuppressWarnings("NullableProblems") @NonNull private View mTimeContainer; + private View mTitleContainer; - public void render(@NonNull PlacePageData data) + public void render(@NonNull Track track) { final Context context = mAscent.getContext(); + TrackStatistics stats = track.getTrackStatistics(); - mElevationInfo = (ElevationInfo) data; - mChartController.setData(mElevationInfo); - mTitle.setText(mElevationInfo.getName()); + mElevationInfo = track.getElevationInfo(); + mChartController.setData(track); + UiUtils.hide(mTitleContainer); + mTitle.setText(track.getName()); setDifficulty(mElevationInfo.getDifficulty()); - mAscent.setText(formatDistance(context, mElevationInfo.getAscent())); - mDescent.setText(formatDistance(context, mElevationInfo.getDescent())); - mMaxAltitude.setText(formatDistance(context, mElevationInfo.getMaxAltitude())); - mMinAltitude.setText(formatDistance(context, mElevationInfo.getMinAltitude())); - UiUtils.hideIf(mElevationInfo.getDuration() == 0, mTimeContainer); - mTime.setText(RoutingController.formatRoutingTime(mTitle.getContext(), (int) mElevationInfo.getDuration(), - R.dimen.text_size_body_2)); + mAscent.setText(formatDistance(context, (int) stats.getAscent())); + mDescent.setText(formatDistance(context, (int) stats.getDescent())); + mMaxAltitude.setText(formatDistance(context, stats.getMaxElevation())); + mMinAltitude.setText(formatDistance(context, stats.getMinElevation())); + UiUtils.hide(mTimeContainer); + mTime.setText(Utils.formatRoutingTime(mAscent.getContext(), (int) stats.getDuration(), R.dimen.text_size_body_2)); } @NonNull @@ -89,6 +89,7 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener mChartController.initialize(view); mScrollView = (NestedScrollView) view; mTitle = view.findViewById(R.id.title); + mTitleContainer = view.findViewById(R.id.title_container); mAscent = view.findViewById(R.id.ascent); mDescent = view.findViewById(R.id.descent); mMaxAltitude = view.findViewById(R.id.max_altitude); @@ -123,21 +124,13 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener mDifficultyLevels[i].setEnabled(true); } - public void onSave(@NonNull Bundle outState) + public void onChartElevationActivePointChanged() { - // outState.putParcelable(PlacePageUtils.EXTRA_PLACE_PAGE_DATA, mElevationInfo); + mChartController.onElevationActivePointChanged(); } - public void onRestore(@NonNull Bundle inState) + public void onChartCurrentPositionChanged() { - // mElevationInfo = BundleCompat.getParcelable(inState, PlacePageUtils.EXTRA_PLACE_PAGE_DATA, - // ElevationInfo.class); if (mElevationInfo != null) - // render(mElevationInfo); - } - - public void onHide() - { - mScrollView.scrollTo(0, 0); - mChartController.onHide(); + mChartController.onCurrentPositionChanged(); } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java index a58ab35b1..d31e70030 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java @@ -37,9 +37,6 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker private TextView mAltitudeView; @SuppressWarnings("NullableProblems") @NonNull - private TextView mDistanceTextView; - @SuppressWarnings("NullableProblems") - @NonNull private TextView mDistanceValueView; @SuppressWarnings("NullableProblems") @NonNull @@ -93,7 +90,6 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker mTextContentContainer = findViewById(R.id.floating_text_container); mFloatingTriangle = findViewById(R.id.floating_triangle); mImage = findViewById(R.id.image); - mDistanceTextView = findViewById(R.id.distance_text); mAltitudeView = findViewById(R.id.altitude); mDistanceValueView = findViewById(R.id.distance_value); } @@ -125,7 +121,7 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker public void updateOffsets(@NonNull Entry entry, @NonNull Highlight highlight) { updateVertical(entry); - final float halfImg = Math.abs(mImage.getWidth()) / 2f; + final float halfImg = mImage.getResources().getDimensionPixelSize(R.dimen.elevation_profile_marker_width) / 2f; boolean isLeftToRightDirection = isInvertedOrder(highlight); mOffset = isLeftToRightDirection ? -getWidth() + halfImg : -halfImg; updateHorizontal(highlight); @@ -184,7 +180,6 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker private void updatePointValues(@NonNull Entry entry) { - mDistanceTextView.setText(R.string.elevation_profile_distance); mDistanceValueView.setText( StringUtils.nativeFormatDistance(entry.getX()).toString(mDistanceValueView.getContext())); mAltitudeView.setText(Framework.nativeFormatAltitude(entry.getY())); diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java index 5f3978a0a..8d4898b3c 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java @@ -34,6 +34,11 @@ public class PlacePageButtonFactory titleId = R.string.delete; yield R.drawable.ic_bookmarks_on; } + case TRACK_DELETE -> + { + titleId = R.string.delete; + yield R.drawable.ic_delete; + } case ROUTE_FROM -> { titleId = R.string.p2p_from_here; diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java index cc98ca6c4..5664f5933 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java @@ -136,6 +136,7 @@ public final class PlacePageButtons extends Fragment implements Observer mPlacePageDistanceToTopObserver = new Observer<>() { private float mPlacePageCornerRadius; @@ -414,15 +418,16 @@ public class PlacePageController { switch (item) { - case BOOKMARK_SAVE, BOOKMARK_DELETE -> onBookmarkBtnClicked(); - case BACK -> onBackBtnClicked(); - case ROUTE_FROM -> onRouteFromBtnClicked(); - case ROUTE_TO -> onRouteToBtnClicked(); - case ROUTE_ADD -> onRouteAddBtnClicked(); - case ROUTE_REMOVE -> onRouteRemoveBtnClicked(); - case ROUTE_AVOID_TOLL -> onAvoidTollBtnClicked(); - case ROUTE_AVOID_UNPAVED -> onAvoidUnpavedBtnClicked(); - case ROUTE_AVOID_FERRY -> onAvoidFerryBtnClicked(); + case BOOKMARK_SAVE, BOOKMARK_DELETE -> onBookmarkBtnClicked(); + case TRACK_DELETE -> onTrackRemoveClicked(); + case BACK -> onBackBtnClicked(); + case ROUTE_FROM -> onRouteFromBtnClicked(); + case ROUTE_TO -> onRouteToBtnClicked(); + case ROUTE_ADD -> onRouteAddBtnClicked(); + case ROUTE_REMOVE -> onRouteRemoveBtnClicked(); + case ROUTE_AVOID_TOLL -> onAvoidTollBtnClicked(); + case ROUTE_AVOID_UNPAVED -> onAvoidUnpavedBtnClicked(); + case ROUTE_AVOID_FERRY -> onAvoidFerryBtnClicked(); } } @@ -439,6 +444,47 @@ public class PlacePageController BookmarkManager.INSTANCE.addNewBookmark(mMapObject.getLat(), mMapObject.getLon()); } + private void onTrackRemoveClicked() + { + // mMapObject is set to null when the place page closes + // We don't want users to interact with the buttons when the PP is closing + if (mMapObject == null) + return; + showTrackDeleteAlertDialog(); + } + + void showTrackDeleteAlertDialog() + { + if (mMapObject == null) + return; + dismissAlertDialog(); + mViewModel.isAlertDialogShowing = true; + if (mAlertDialog != null) + { + mAlertDialog.show(); + return; + } + mAlertDialog = new MaterialAlertDialogBuilder(requireContext(), R.style.MwmTheme_AlertDialog) + .setTitle(requireContext().getString(R.string.delete_track_dialog_title, mMapObject.getTitle())) + .setCancelable(true) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.delete, + (dialog, which) -> { + BookmarkManager.INSTANCE.deleteTrack(((Track) mMapObject).getTrackId()); + close(); + }) + .setOnDismissListener(dialog -> dismissAlertDialog()) + .show(); + } + + void dismissAlertDialog() + { + if (mAlertDialog == null) + return; + mAlertDialog.dismiss(); + mViewModel.isAlertDialogShowing = false; + } + private void onBackBtnClicked() { if (mMapObject == null) @@ -577,8 +623,12 @@ public class PlacePageController if (needToShowRoutingButtons && RoutingController.get().isStopPointAllowed()) buttons.add(PlacePageButtons.ButtonType.ROUTE_ADD); else + { buttons.add(mapObject.isBookmark() ? PlacePageButtons.ButtonType.BOOKMARK_DELETE : PlacePageButtons.ButtonType.BOOKMARK_SAVE); + if (mapObject.isTrack()) + buttons.add(PlacePageButtons.ButtonType.TRACK_DELETE); + } if (needToShowRoutingButtons) { @@ -610,6 +660,9 @@ public class PlacePageController // Place page will automatically open when the bottom sheet content is loaded so we can compute the peek height createPlacePageFragments(); updateButtons(mapObject, showBackButton, !mMapObject.isMyPosition()); + mAlertDialog = null; + if (mViewModel.isAlertDialogShowing) + showTrackDeleteAlertDialog(); } else close(); 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 5f9ebf711..bd29be8e0 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 @@ -6,6 +6,7 @@ import static app.organicmaps.sdk.util.Utils.getLocalizedFeatureType; import static app.organicmaps.sdk.util.Utils.getTagValueLocalized; import android.content.Context; +import android.graphics.drawable.Drawable; import android.location.Location; import android.net.Uri; import android.os.Bundle; @@ -16,45 +17,65 @@ import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import app.organicmaps.MwmActivity; import app.organicmaps.MwmApplication; import app.organicmaps.R; +import app.organicmaps.bookmarks.BookmarksSharingHelper; +import app.organicmaps.bookmarks.ChooseBookmarkCategoryFragment; import app.organicmaps.downloader.DownloaderStatusIcon; -import app.organicmaps.editor.OhState; //pastk: move? -import app.organicmaps.routing.RoutingController; +import app.organicmaps.downloader.MapManagerHelper; import app.organicmaps.sdk.Framework; +import app.organicmaps.sdk.bookmarks.data.Bookmark; +import app.organicmaps.sdk.bookmarks.data.BookmarkCategory; +import app.organicmaps.sdk.bookmarks.data.BookmarkManager; +import app.organicmaps.sdk.bookmarks.data.BookmarkSharingResult; import app.organicmaps.sdk.bookmarks.data.DistanceAndAzimut; +import app.organicmaps.sdk.bookmarks.data.Icon; +import app.organicmaps.sdk.bookmarks.data.KmlFileType; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.bookmarks.data.Metadata; +import app.organicmaps.sdk.bookmarks.data.PredefinedColors; +import app.organicmaps.sdk.bookmarks.data.Track; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.editor.Editor; -import app.organicmaps.sdk.editor.OpeningHours; //pastk: a part of Editor? +import app.organicmaps.sdk.editor.OhState; +import app.organicmaps.sdk.editor.OpeningHours; import app.organicmaps.sdk.editor.data.HoursMinutes; import app.organicmaps.sdk.editor.data.Timetable; import app.organicmaps.sdk.location.LocationListener; import app.organicmaps.sdk.location.SensorListener; +import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.util.DateUtils; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.sdk.util.concurrency.UiThread; +import app.organicmaps.sdk.widget.placepage.CoordinatesFormat; +import app.organicmaps.util.Graphics; import app.organicmaps.util.SharingUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; +import app.organicmaps.util.bottomsheet.MenuBottomSheetItem; import app.organicmaps.widget.ArrowView; 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.PlacePageTrackFragment; import app.organicmaps.widget.placepage.sections.PlacePageWikipediaFragment; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.button.MaterialButton; @@ -67,21 +88,24 @@ import java.util.Arrays; import java.util.List; public class PlacePageView extends Fragment - implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer + implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer, + ChooseBookmarkCategoryFragment.Listener, EditBookmarkFragment.EditBookmarkListener, + MenuBottomSheetFragment.MenuBottomSheetInterface, BookmarkManager.BookmarksSharingListener { private static final String PREF_COORDINATES_FORMAT = "coordinates_format"; private static final String BOOKMARK_FRAGMENT_TAG = "BOOKMARK_FRAGMENT_TAG"; + private static final String TRACK_FRAGMENT_TAG = "TRACK_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"; private static final String LINKS_FRAGMENT_TAG = "LINKS_FRAGMENT_TAG"; + private static final String TRACK_SHARE_MENU_ID = "TRACK_SHARE_MENU_ID"; private static final List visibleCoordsFormat = Arrays.asList(CoordinatesFormat.LatLonDMS, CoordinatesFormat.LatLonDecimal, CoordinatesFormat.OLCFull, CoordinatesFormat.UTM, CoordinatesFormat.MGRS, CoordinatesFormat.OSMLink); private View mFrame; - private Context mContext; // Preview. private ViewGroup mPreview; @@ -119,16 +143,21 @@ public class PlacePageView extends Fragment private MaterialTextView mTvOutdoorSeating; private View mEntrance; private MaterialTextView mTvEntrance; + private MaterialTextView mTvLastChecked; private View mEditPlace; private View mAddOrganisation; private View mAddPlace; private View mEditTopSpace; + private ImageView mColorIcon; + private MaterialTextView mTvCategory; + private ImageView mEditBookmark; // Data private CoordinatesFormat mCoordsFormat = CoordinatesFormat.LatLonDecimal; // Downloader`s stuff private DownloaderStatusIcon mDownloaderIcon; private MaterialTextView mDownloaderInfo; + private ActivityResultLauncher shareLauncher; private int mStorageCallbackSlot; @Nullable private CountryItem mCurrentCountry; @@ -184,6 +213,7 @@ public class PlacePageView extends Fragment @Nullable Bundle savedInstanceState) { mViewModel = new ViewModelProvider(requireActivity()).get(PlacePageViewModel.class); + shareLauncher = SharingUtils.RegisterLauncher(this); return inflater.inflate(R.layout.place_page, container, false); } @@ -231,8 +261,15 @@ public class PlacePageView extends Fragment mTvAddress.setOnLongClickListener(this); mTvAddress.setOnClickListener(this); + mColorIcon = mFrame.findViewById(R.id.item_icon); + mTvCategory = mFrame.findViewById(R.id.tv__category); + mEditBookmark = mFrame.findViewById(R.id.edit_Bookmark); + mColorIcon.setOnClickListener(this); + mTvCategory.setOnClickListener(this); + mEditBookmark.setOnClickListener(this); + MaterialButton shareButton = mPreview.findViewById(R.id.share_button); - shareButton.setOnClickListener((v) -> SharingUtils.shareMapObject(requireContext(), mMapObject)); + shareButton.setOnClickListener(this::shareClickListener); final MaterialButton closeButton = mPreview.findViewById(R.id.close_button); closeButton.setOnClickListener((v) -> mPlacePageViewListener.onPlacePageRequestClose()); @@ -271,6 +308,7 @@ public class PlacePageView extends Fragment mTvCuisine = mFrame.findViewById(R.id.tv__place_cuisine); mEntrance = mFrame.findViewById(R.id.ll__place_entrance); mTvEntrance = mEntrance.findViewById(R.id.tv__place_entrance); + mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked); mEditPlace = mFrame.findViewById(R.id.ll__place_editor); mEditPlace.setOnClickListener(this); mAddOrganisation = mFrame.findViewById(R.id.ll__add_organisation); @@ -300,6 +338,7 @@ public class PlacePageView extends Fragment { super.onStart(); mViewModel.getMapObject().observe(requireActivity(), this); + BookmarkManager.INSTANCE.addSharingListener(this); MwmApplication.from(requireContext()).getLocationHelper().addListener(this); MwmApplication.from(requireContext()).getSensorHelper().addListener(this); } @@ -309,6 +348,7 @@ public class PlacePageView extends Fragment { super.onStop(); mViewModel.getMapObject().removeObserver(this); + BookmarkManager.INSTANCE.removeSharingListener(this); MwmApplication.from(requireContext()).getLocationHelper().removeListener(this); MwmApplication.from(requireContext()).getSensorHelper().removeListener(this); UiThread.cancelDelayedTasks(updateOpenState); @@ -333,6 +373,13 @@ public class PlacePageView extends Fragment refreshMyPosition(loc); else refreshDistanceToObject(loc); + UiUtils.hideIf(mMapObject.isTrack(), mFrame.findViewById(R.id.ll__place_latlon), + mFrame.findViewById(R.id.ll__place_open_in)); + if (mMapObject.isTrack()) + { + UiUtils.hide(mTvSubtitle); + UiUtils.hide(mAvDirection, mTvDistance); + } } private void updateViewFragment(Class controllerClass, String fragmentTag, @@ -374,6 +421,12 @@ public class PlacePageView extends Fragment mMapObject.isBookmark()); } + private void updateTrackView() + { + updateViewFragment(PlacePageTrackFragment.class, TRACK_FRAGMENT_TAG, R.id.place_page_track_fragment, + mMapObject.isTrack()); + } + private boolean hasWikipediaEntry() { final String wikipediaLink = mMapObject.getMetadata(Metadata.MetadataType.FMD_WIKIPEDIA); @@ -415,6 +468,157 @@ public class PlacePageView extends Fragment mToolbar.setTitle(mMapObject.getTitle()); setTextAndColorizeSubtitle(); UiUtils.setTextAndHideIfEmpty(mTvAddress, mMapObject.getAddress()); + refreshCategoryPreview(); + } + + void refreshCategoryPreview() + { + View categoryContainer = mFrame.findViewById(R.id.category_container); + if (mMapObject.isTrack()) + { + Track track = (Track) mMapObject; + Drawable circle = + Graphics.drawCircle(track.getColor(), R.dimen.place_page_icon_size, requireContext().getResources()); + mColorIcon.setImageDrawable(circle); + mTvCategory.setText(BookmarkManager.INSTANCE.getCategoryById(track.getCategoryId()).getName()); + } + else if (mMapObject.isBookmark()) + { + Bookmark bookmark = (Bookmark) mMapObject; + Icon icon = bookmark.getIcon(); + if (icon != null) + { + Drawable circle = Graphics.drawCircleAndImage(icon.argb(), R.dimen.place_page_icon_size, icon.getResId(), + R.dimen.place_page_icon_mark_size, requireContext()); + mColorIcon.setImageDrawable(circle); + mTvCategory.setText(BookmarkManager.INSTANCE.getCategoryById(bookmark.getCategoryId()).getName()); + } + } + UiUtils.showIf(mMapObject.isTrack() || mMapObject.isBookmark(), categoryContainer); + } + + void showColorDialog() + { + final Bundle args = new Bundle(); + final FragmentManager manager = getChildFragmentManager(); + String className = BookmarkColorDialogFragment.class.getName(); + final FragmentFactory factory = manager.getFragmentFactory(); + final BookmarkColorDialogFragment dialogFragment = + (BookmarkColorDialogFragment) factory.instantiate(getContext().getClassLoader(), className); + dialogFragment.setArguments(args); + + if (mMapObject.isTrack()) + { + final Track track = (Track) mMapObject; + args.putInt(BookmarkColorDialogFragment.ICON_COLOR, PredefinedColors.getPredefinedColorIndex(track.getColor())); + dialogFragment.setOnColorSetListener((colorPos) -> { + int from = track.getColor(); + int to = PredefinedColors.getColor(colorPos); + if (from == to) + return; + track.setColor(to); + Drawable circle = Graphics.drawCircle(to, R.dimen.place_page_icon_size, requireContext().getResources()); + mColorIcon.setImageDrawable(circle); + }); + dialogFragment.show(requireActivity().getSupportFragmentManager(), null); + } + else if (mMapObject.isBookmark()) + { + final Bookmark bookmark = (Bookmark) mMapObject; + args.putInt(BookmarkColorDialogFragment.ICON_COLOR, bookmark.getIcon().getColor()); + args.putInt(BookmarkColorDialogFragment.ICON_RES, bookmark.getIcon().getResId()); + dialogFragment.setOnColorSetListener((colorPos) -> { + int from = bookmark.getIcon().argb(); + int to = PredefinedColors.getColor(colorPos); + if (from == to) + return; + bookmark.setIconColor(to); + Drawable circle = Graphics.drawCircleAndImage(to, R.dimen.place_page_icon_size, bookmark.getIcon().getResId(), + R.dimen.place_page_icon_mark_size, requireContext()); + mColorIcon.setImageDrawable(circle); + }); + dialogFragment.show(requireActivity().getSupportFragmentManager(), null); + } + } + + private void showCategoryList() + { + final Bundle args = new Bundle(); + final List categories = BookmarkManager.INSTANCE.getCategories(); + final FragmentManager manager = getChildFragmentManager(); + String className = ChooseBookmarkCategoryFragment.class.getName(); + final FragmentFactory factory = manager.getFragmentFactory(); + final ChooseBookmarkCategoryFragment frag = + (ChooseBookmarkCategoryFragment) factory.instantiate(getContext().getClassLoader(), className); + if (mMapObject.isTrack()) + { + Track track = (Track) mMapObject; + BookmarkCategory currentCategory = BookmarkManager.INSTANCE.getCategoryById(track.getCategoryId()); + final int index = categories.indexOf(currentCategory); + args.putInt(ChooseBookmarkCategoryFragment.CATEGORY_POSITION, index); + frag.setArguments(args); + frag.show(manager, null); + } + else if (mMapObject.isBookmark()) + { + Bookmark bookmark = (Bookmark) mMapObject; + BookmarkCategory currentCategory = BookmarkManager.INSTANCE.getCategoryById(bookmark.getCategoryId()); + final int index = categories.indexOf(currentCategory); + args.putInt(ChooseBookmarkCategoryFragment.CATEGORY_POSITION, index); + frag.setArguments(args); + frag.show(manager, null); + } + } + + @Override + public void onCategoryChanged(@NonNull BookmarkCategory newCategory) + { + if (mMapObject.isTrack()) + { + Track track = (Track) mMapObject; + BookmarkCategory previousCategory = BookmarkManager.INSTANCE.getCategoryById(track.getCategoryId()); + if (previousCategory == newCategory) + return; + BookmarkManager.INSTANCE.notifyCategoryChanging(track, newCategory.getId()); + mTvCategory.setText(newCategory.getName()); + track.setCategoryId(newCategory.getId()); + } + else if (mMapObject.isBookmark()) + { + Bookmark bookmark = (Bookmark) mMapObject; + BookmarkCategory previousCategory = BookmarkManager.INSTANCE.getCategoryById(bookmark.getCategoryId()); + if (previousCategory == newCategory) + return; + mTvCategory.setText(newCategory.getName()); + bookmark.setCategoryId(newCategory.getId()); + } + } + + void showBookmarkEditFragment() + { + if (mMapObject.isTrack()) + { + Track track = (Track) mMapObject; + final FragmentActivity activity = requireActivity(); + EditBookmarkFragment.editTrack(track.getCategoryId(), track.getTrackId(), activity, getChildFragmentManager(), + PlacePageView.this); + } + else if (mMapObject.isBookmark()) + { + Bookmark bookmark = (Bookmark) mMapObject; + final FragmentActivity activity = requireActivity(); + EditBookmarkFragment.editBookmark(bookmark.getCategoryId(), bookmark.getBookmarkId(), activity, + getChildFragmentManager(), PlacePageView.this); + } + } + + @Override + public void onBookmarkSaved(long bookmarkId, boolean movedFromCategory) + { + if (mMapObject.isTrack()) + BookmarkManager.INSTANCE.updateTrackPlacePage(); + else if (mMapObject.isBookmark()) + BookmarkManager.INSTANCE.updateBookmarkPlacePage(bookmarkId); } private void refreshDetails() @@ -440,7 +644,8 @@ public class PlacePageView extends Fragment final String cap = mMapObject.getMetadata(Metadata.MetadataType.FMD_CAPACITY); refreshMetadataOrHide(!TextUtils.isEmpty(cap) ? getString(R.string.capacity, cap) : "", mCapacity, mTvCapacity); - refreshMetadataOrHide(mMapObject.hasAtm() ? getString(R.string.type_amenity_atm) : "", mAtm, mTvAtm); + refreshMetadataOrHide(mMapObject.hasAtm() ? getString(app.organicmaps.sdk.R.string.type_amenity_atm) : "", mAtm, + mTvAtm); final String wheelchair = getLocalizedFeatureType(getContext(), mMapObject.getMetadata(Metadata.MetadataType.FMD_WHEELCHAIR)); @@ -458,7 +663,14 @@ public class PlacePageView extends Fragment refreshMetadataOrHide(outdoorSeating.equals("yes") ? getString(R.string.outdoor_seating) : "", mOutdoorSeating, mTvOutdoorSeating); - // showTaxiOffer(mapObject); + final String lastChecked = mMapObject.getMetadata(Metadata.MetadataType.FMD_CHECK_DATE); + if (!lastChecked.isEmpty()) + { + String periodSinceCheck = DateUtils.getRelativePeriodString(getResources(), lastChecked); + UiUtils.setTextAndShow(mTvLastChecked, requireContext().getString(R.string.existence_confirmed_time_ago, periodSinceCheck)); + } + else + UiUtils.hide(mTvLastChecked); if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning()) { @@ -477,9 +689,10 @@ public class PlacePageView extends Fragment MaterialTextView mTvAddPlace = mAddPlace.findViewById(R.id.tv__editor); final int editPlaceButtonColor = Editor.nativeShouldEnableEditPlace() - ? ContextCompat.getColor(getContext(), - UiUtils.getStyledResourceId(getContext(), androidx.appcompat.R.attr.colorAccent)) - : getResources().getColor(R.color.button_accent_text_disabled); + ? ContextCompat.getColor( + getContext(), + UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)) + : ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled); mTvEditPlace.setTextColor(editPlaceButtonColor); mTvAddBusiness.setTextColor(editPlaceButtonColor); mTvAddPlace.setTextColor(editPlaceButtonColor); @@ -492,6 +705,7 @@ public class PlacePageView extends Fragment updateWikipediaView(); updateBookmarkView(); updatePhoneView(); + updateTrackView(); } private void refreshWiFi() @@ -530,6 +744,8 @@ public class PlacePageView extends Fragment private void refreshDistanceToObject(Location l) { + if (mMapObject.isTrack()) + return; UiUtils.showIf(l != null, mTvDistance); if (l == null) return; @@ -658,6 +874,12 @@ public class PlacePageView extends Fragment } else if (id == R.id.direction_frame) showBigDirection(); + else if (id == R.id.item_icon) + showColorDialog(); + else if (id == R.id.edit_Bookmark) + showBookmarkEditFragment(); + else if (id == R.id.tv__category) + showCategoryList(); } private void showBigDirection() @@ -762,7 +984,7 @@ public class PlacePageView extends Fragment mStorageCallbackSlot = MapManager.nativeSubscribe(mStorageCallback); mDownloaderIcon - .setOnIconClickListener((v) -> MapManager.warn3gAndDownload(requireActivity(), mCurrentCountry.id, null)) + .setOnIconClickListener((v) -> MapManagerHelper.warn3gAndDownload(requireActivity(), mCurrentCountry.id, null)) .setOnCancelClickListener((v) -> MapManager.nativeCancel(mCurrentCountry.id)); mDownloaderIcon.show(true); UiUtils.show(mDownloaderInfo); @@ -816,7 +1038,7 @@ public class PlacePageView extends Fragment @Override public void onCompassUpdated(double north) { - if (mMapObject == null || mMapObject.isMyPosition()) + if (mMapObject == null || mMapObject.isMyPosition() || mMapObject.isTrack()) return; final Location location = MwmApplication.from(requireContext()).getLocationHelper().getSavedLocation(); @@ -838,6 +1060,50 @@ public class PlacePageView extends Fragment } } + void shareClickListener(View v) + { + if (mMapObject.isTrack()) + { + MenuBottomSheetFragment.newInstance(TRACK_SHARE_MENU_ID, getString(R.string.share_track)) + .show(getChildFragmentManager(), TRACK_SHARE_MENU_ID); + } + else + SharingUtils.shareMapObject(requireContext(), mMapObject); + } + + private void onShareTrackSelected(long trackId, KmlFileType kmlFileType) + { + BookmarksSharingHelper.INSTANCE.prepareTrackForSharing(requireActivity(), trackId, kmlFileType); + } + + @Nullable + @Override + public ArrayList getMenuBottomSheetItems(String id) + { + return switch (id) + { + case TRACK_SHARE_MENU_ID -> getTrackShareMenuItems(); + default -> null; + }; + } + + public ArrayList getTrackShareMenuItems() + { + Track track = (Track) mMapObject; + ArrayList items = new ArrayList<>(); + 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))); + return items; + } + + @Override + public void onPreparedFileForSharing(@NonNull BookmarkSharingResult result) + { + BookmarksSharingHelper.INSTANCE.onPreparedFileForSharing(requireActivity(), shareLauncher, result); + } + public interface PlacePageViewListener { // Called when the content has actually changed and we are ready to compute the peek height diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java index 57ca04eb3..a5e7cded0 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java @@ -3,7 +3,10 @@ package app.organicmaps.widget.placepage; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import app.organicmaps.sdk.bookmarks.data.Bookmark; +import app.organicmaps.sdk.bookmarks.data.ElevationInfo; import app.organicmaps.sdk.bookmarks.data.MapObject; +import app.organicmaps.sdk.bookmarks.data.Track; import java.util.List; public class PlacePageViewModel extends ViewModel @@ -12,6 +15,7 @@ public class PlacePageViewModel extends ViewModel private final MutableLiveData mMapObject = new MutableLiveData<>(); private final MutableLiveData mPlacePageWidth = new MutableLiveData<>(); private final MutableLiveData mPlacePageDistanceToTop = new MutableLiveData<>(); + public boolean isAlertDialogShowing = false; public LiveData> getCurrentButtons() { diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlaceOpeningHoursAdapter.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlaceOpeningHoursAdapter.java index b1b5297f9..80550753c 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlaceOpeningHoursAdapter.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlaceOpeningHoursAdapter.java @@ -11,7 +11,7 @@ import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; import app.organicmaps.sdk.editor.data.Timespan; import app.organicmaps.sdk.editor.data.Timetable; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import com.google.android.material.textview.MaterialTextView; import java.util.ArrayList; import java.util.Arrays; diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java index f200c0621..157bcebc0 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java @@ -24,15 +24,14 @@ import app.organicmaps.sdk.bookmarks.data.Bookmark; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.util.StringUtils; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.widget.placepage.EditBookmarkFragment; import app.organicmaps.widget.placepage.PlacePageViewModel; import com.google.android.material.textview.MaterialTextView; public class PlacePageBookmarkFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener, - Observer, - EditBookmarkFragment.EditBookmarkListener + Observer, EditBookmarkFragment.EditBookmarkListener { private View mFrame; private MaterialTextView mTvBookmarkNote; diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageOpeningHoursFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageOpeningHoursFragment.java index 060cdea8a..7165a1e10 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageOpeningHoursFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageOpeningHoursFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,14 +15,15 @@ import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.R; +import app.organicmaps.editor.data.TimeFormatUtils; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.bookmarks.data.Metadata; import app.organicmaps.sdk.editor.OpeningHours; -import app.organicmaps.editor.data.TimeFormatUtils; import app.organicmaps.sdk.editor.data.Timespan; import app.organicmaps.sdk.editor.data.Timetable; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.sdk.util.DateUtils; import app.organicmaps.util.ThemeUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.widget.placepage.PlacePageUtils; import app.organicmaps.widget.placepage.PlacePageViewModel; @@ -36,6 +38,7 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer< private MaterialTextView mTodayOpenTime; private MaterialTextView mTodayNonBusinessTime; private RecyclerView mFullWeekOpeningHours; + private MaterialTextView mLastCheckedDate; private PlaceOpeningHoursAdapter mOpeningHoursAdapter; private PlacePageViewModel mViewModel; @@ -58,10 +61,23 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer< mTodayOpenTime = view.findViewById(R.id.oh_today_open_time); mTodayNonBusinessTime = view.findViewById(R.id.oh_nonbusiness_time); mFullWeekOpeningHours = view.findViewById(R.id.rw__full_opening_hours); + mLastCheckedDate = view.findViewById(R.id.oh_check_date); mOpeningHoursAdapter = new PlaceOpeningHoursAdapter(); mFullWeekOpeningHours.setAdapter(mOpeningHoursAdapter); } + private static void setOrHideLastCheckedDate(MapObject mapObject, Resources resources, TextView checkDateView) + { + final String checkDate = mapObject.getMetadata(Metadata.MetadataType.FMD_CHECK_DATE_OPEN_HOURS); + if (!checkDate.isEmpty()) + { + String periodSinceCheck = DateUtils.getRelativePeriodString(resources, checkDate); + UiUtils.setTextAndShow(checkDateView, resources.getString(R.string.hours_confirmed_time_ago, periodSinceCheck)); + } + else + UiUtils.hide(checkDateView); + } + private void refreshTodayNonBusinessTime(Timespan[] closedTimespans) { final String hoursClosedLabel = getResources().getString(R.string.editor_hours_closed); @@ -102,6 +118,9 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer< final boolean isEmptyTT = (timetables == null || timetables.length == 0); final int color = ThemeUtils.getColor(requireContext(), android.R.attr.textColorPrimary); + final Resources resources = getResources(); + + setOrHideLastCheckedDate(mapObject, resources, mLastCheckedDate); if (isEmptyTT) { @@ -119,7 +138,6 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer< else { UiUtils.show(mFrame); - final Resources resources = getResources(); if (timetables[0].isFullWeek()) { final Timetable tt = timetables[0]; @@ -161,7 +179,7 @@ public class PlacePageOpeningHoursFragment extends Fragment implements Observer< else openTime = tt.workingTimespan.toWideString(); - refreshTodayOpeningHours(resources.getString(R.string.today), openTime, color); + refreshTodayOpeningHours(resources.getString(app.organicmaps.sdk.R.string.today), openTime, color); refreshTodayNonBusinessTime(tt.closedTimespans); break; diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageTrackFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageTrackFragment.java new file mode 100644 index 000000000..c74c4b650 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageTrackFragment.java @@ -0,0 +1,116 @@ +package app.organicmaps.widget.placepage.sections; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import app.organicmaps.R; +import app.organicmaps.sdk.bookmarks.data.BookmarkManager; +import app.organicmaps.sdk.bookmarks.data.ElevationInfo; +import app.organicmaps.sdk.bookmarks.data.MapObject; +import app.organicmaps.sdk.bookmarks.data.Track; +import app.organicmaps.util.UiUtils; +import app.organicmaps.widget.placepage.ElevationProfileViewRenderer; +import app.organicmaps.widget.placepage.PlacePageStateListener; +import app.organicmaps.widget.placepage.PlacePageViewModel; + +public class PlacePageTrackFragment extends Fragment + implements PlacePageStateListener, Observer, BookmarkManager.OnElevationActivePointChangedListener, + BookmarkManager.OnElevationCurrentPositionChangedListener +{ + private PlacePageViewModel mViewModel; + @Nullable + private Track mTrack; + private ElevationProfileViewRenderer mElevationProfileViewRenderer; + private View mFrame; + private View mElevationProfileView; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + mViewModel = new ViewModelProvider(requireActivity()).get(PlacePageViewModel.class); + return inflater.inflate(R.layout.placepage_track_fragment, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + mElevationProfileViewRenderer = new ElevationProfileViewRenderer(); + + mFrame = view; + mElevationProfileView = mFrame.findViewById(R.id.elevation_profile); + mElevationProfileViewRenderer.initialize(mElevationProfileView); + } + + @Override + public void onStart() + { + super.onStart(); + BookmarkManager.INSTANCE.setElevationActivePointChangedListener(this); + BookmarkManager.INSTANCE.setElevationCurrentPositionChangedListener(this); + mViewModel.getMapObject().observe(requireActivity(), this); + } + + @Override + public void onStop() + { + super.onStop(); + BookmarkManager.INSTANCE.setElevationActivePointChangedListener(null); + BookmarkManager.INSTANCE.setElevationCurrentPositionChangedListener(null); + mViewModel.getMapObject().removeObserver(this); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + } + + @Override + public void onChanged(@Nullable MapObject mapObject) + { + // MapObject could be something else than a Track if the user already has the place page + // opened and clicks on a non-Track POI. + // This callback would be called before the fragment had time to be destroyed + if (mapObject == null || !mapObject.isTrack()) + return; + + Track track = (Track) mapObject; + if (track.getElevationInfo() != null) + { + if (mTrack == null || mTrack.getTrackId() != track.getTrackId()) + { + mElevationProfileViewRenderer.render(track); + UiUtils.show(mElevationProfileView); + } + } + else + UiUtils.hide(mElevationProfileView); + mTrack = track; + } + + @Override + public void onElevationActivePointChanged() + { + if (mTrack == null) + return; + mElevationProfileViewRenderer.onChartElevationActivePointChanged(); + ElevationInfo.Point point = BookmarkManager.INSTANCE.getElevationActivePointCoordinates(mTrack.getTrackId()); + mTrack.setLat(point.getLatitude()); + mTrack.setLon(point.getLongitude()); + } + + @Override + public void onCurrentPositionChanged() + { + mElevationProfileViewRenderer.onChartCurrentPositionChanged(); + } +} diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageWikipediaFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageWikipediaFragment.java index b44fe919c..ef9fc45cc 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageWikipediaFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageWikipediaFragment.java @@ -15,7 +15,7 @@ import androidx.lifecycle.ViewModelProvider; import app.organicmaps.R; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.bookmarks.data.Metadata; -import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.widget.placepage.PlaceDescriptionActivity; import app.organicmaps.widget.placepage.PlacePageUtils; diff --git a/android/app/src/main/res/drawable/button_red.xml b/android/app/src/main/res/drawable/button_red.xml deleted file mode 100644 index f8132991f..000000000 --- a/android/app/src/main/res/drawable/button_red.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/button_red_disabled.xml b/android/app/src/main/res/drawable/button_red_disabled.xml deleted file mode 100644 index 62a50d357..000000000 --- a/android/app/src/main/res/drawable/button_red_disabled.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/button_red_normal.xml b/android/app/src/main/res/drawable/button_red_normal.xml deleted file mode 100644 index 9657730b0..000000000 --- a/android/app/src/main/res/drawable/button_red_normal.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/button_red_pressed.xml b/android/app/src/main/res/drawable/button_red_pressed.xml deleted file mode 100644 index 2015701bf..000000000 --- a/android/app/src/main/res/drawable/button_red_pressed.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_distance_travelled.xml b/android/app/src/main/res/drawable/ic_distance_travelled.xml new file mode 100644 index 000000000..ecc42eba4 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_distance_travelled.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_volunteer.xml b/android/app/src/main/res/drawable/ic_volunteer.xml new file mode 100644 index 000000000..f81cf8acc --- /dev/null +++ b/android/app/src/main/res/drawable/ic_volunteer.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/layout-h400dp/map_buttons_search_frame.xml b/android/app/src/main/res/layout-h400dp/map_buttons_search_frame.xml index f730693c3..c81214477 100644 --- a/android/app/src/main/res/layout-h400dp/map_buttons_search_frame.xml +++ b/android/app/src/main/res/layout-h400dp/map_buttons_search_frame.xml @@ -20,7 +20,7 @@ tools:visibility="visible"> - - - - - - - - - -