Compare commits
1 Commits
release/20
...
test/2025.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b3958d96c |
@@ -74,7 +74,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild build \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Release \
|
||||
-destination 'generic/platform=iOS' \
|
||||
|
||||
4
.github/workflows/ios-check.yaml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
xcrun simctl boot "${{ env.SIMULATOR_DEVICE }}" || true
|
||||
xcrun simctl bootstatus "${{ env.SIMULATOR_DEVICE }}" -b
|
||||
xcodebuild test \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild build \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Release \
|
||||
-destination 'generic/platform=iOS' \
|
||||
|
||||
2
.gitignore
vendored
@@ -62,7 +62,7 @@ iphone/*/build/*
|
||||
tools/emacsmode/build
|
||||
**/DerivedData/*
|
||||
**/xcshareddata/*
|
||||
!iphone/Maps/Maps.xcodeproj/xcshareddata/xcschemes/CoMaps.xcscheme
|
||||
!iphone/Maps/Maps.xcodeproj/xcshareddata/xcschemes/OMaps.xcscheme
|
||||
**/xcuserdata
|
||||
**/xcschemes
|
||||
iphone/**/*.moved-aside
|
||||
|
||||
@@ -7,10 +7,6 @@ CoMaps contributors:
|
||||
(in alphabetic order)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
clover sage
|
||||
Harry Bond <me@hbond.xyz>
|
||||
vikiawv
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Organic Maps (formerly OMaps) contributors:
|
||||
(in alphabetic order)
|
||||
|
||||
32
README.md
@@ -13,16 +13,16 @@
|
||||
# [CoMaps](https://comaps.app) – Hike, Bike, Drive Offline – Easy Map Navigation with Privacy
|
||||
|
||||
[
|
||||

|
||||

|
||||
](https://github.com/comaps/comaps/actions/workflows/android-check.yaml)
|
||||
[
|
||||

|
||||

|
||||
](https://github.com/comaps/comaps/actions/workflows/ios-check.yaml)
|
||||
[
|
||||

|
||||

|
||||
](https://opencollective.com/comaps)
|
||||
[
|
||||

|
||||

|
||||
](https://liberapay.com/CoMaps)
|
||||
|
||||
|
||||
@@ -32,16 +32,20 @@ A community-led free & open source maps app based on [OpenStreetMap](https://www
|
||||
There are apps for Android and iOS (and ARM MacOS).
|
||||
An alpha Linux / MacOS Qt desktop version, which is also suitable for Linux phones.
|
||||
|
||||
[<img src="docs/badges/apple-appstore.png" alt="App Store" width="160">](https://apps.apple.com/app/comaps/id6747180809)
|
||||
The June app release is available on Google Play, F-Droid and as an APK to download now! We are working on publishing in the iOS App Store as well, please stay tuned!
|
||||
|
||||
<!--
|
||||
[<img src="docs/badges/apple-appstore.png" alt="App Store" width="160">](https://apps.apple.com/app/comaps/id1567437057)
|
||||
-->
|
||||
[<img src="docs/badges/google-play.png" alt="Google Play" width="160">](https://play.google.com/store/apps/details?id=app.comaps.google)
|
||||
[<img src="docs/badges/fdroid.png" alt="F-Droid" width="160">](https://f-droid.org/en/packages/app.comaps.fdroid/)
|
||||
[<img src="docs/badges/codeberg.png" alt="Codeberg" width="160">](https://codeberg.org/comaps/comaps/releases)
|
||||
|
||||
<p float="left">
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/1.png" width="180" />
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/2.png" width="180" />
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/3.png" width="180" />
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/4.png" width="180" />
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/1.jpg" width="180" />
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/2.jpg" width="180" />
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/3.jpg" width="180" />
|
||||
<img src="android/app/src/fdroid/play/listings/en-US/graphics/phone-screenshots/4.jpg" width="180" />
|
||||
</p>
|
||||
|
||||
**Offline-focused**: Plan and navigate your trip abroad without the need for cellular service, search waypoints while on a distant hike, etc. All app functions are designed to work offline.
|
||||
@@ -101,9 +105,9 @@ There is a dedicated Zulip chat for active contributors: [comaps.zulipchat.com](
|
||||
|
||||
### Feedback
|
||||
|
||||
|
||||
- **Rate us on the [App Store](https://apps.apple.com/app/comaps/id6747180809)
|
||||
and [Google Play](https://play.google.com/store/apps/details?id=app.comaps.google)**.
|
||||
<!-- uncomment when linked resources are ready
|
||||
- **Rate us on the [App Store](https://apps.apple.com/app/comaps/id1567437057)
|
||||
and [Google Play](https://play.google.com/store/apps/details?id=app.comaps)**. -->
|
||||
- Star our repos on Codeberg
|
||||
- Report bugs and discuss features at [the issue tracker](https://codeberg.org/comaps/comaps/issues)
|
||||
|
||||
@@ -113,10 +117,6 @@ The app is free for everyone, so we rely on donations. Please [donate](https://o
|
||||
|
||||
The project's financial information is completely open and transparent at [our Open Collective](https://opencollective.com/comaps).
|
||||
|
||||
## Privacy
|
||||
|
||||
The Android application was analysed by the [Exodus platform](https://reports.exodus-privacy.eu.org/fr/reports/app.comaps.google/latest/)
|
||||
|
||||
## License and Copyright
|
||||
|
||||
Licensed under the Apache License, Version 2.0. See
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
To build, install and run e.g. a Web Debug version on your device/emulator: './gradlew runWebDebug'
|
||||
|
||||
Or to compile a redistributable Fdroid Test apk for testing: './gradlew assembleFdroidBeta'
|
||||
Or to compile a redistributable Fdroid Beta apk for testing: './gradlew assembleFdroidBeta'
|
||||
|
||||
Or to build test apks for all flavors: './gradlew assembleBeta'
|
||||
Or to build beta apks for all flavors: './gradlew assembleBeta'
|
||||
|
||||
To see all available build targets './gradlew tasks'
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ buildscript {
|
||||
// Detect flavors from the task name.
|
||||
def taskName = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
|
||||
def isFdroid = taskName.contains('fdroid')
|
||||
def isBeta = taskName.contains('beta')
|
||||
|
||||
dependencies {
|
||||
classpath libs.android.tools
|
||||
@@ -296,6 +297,7 @@ android {
|
||||
ndk.debugSymbolLevel = 'symbol_table'
|
||||
}
|
||||
|
||||
// TODO(@pastk): rename to "test" everywhere in code
|
||||
beta {
|
||||
applicationIdSuffix '.test'
|
||||
versionNameSuffix '-test'
|
||||
|
||||
@@ -1 +1 @@
|
||||
Jednoduchá navigace v mapě - Objevte více na své cestě ‐ Vyvíjeno komunitou
|
||||
Jednoduchá navigace v mapě – Objevte více na své cestě – Vyvíjeno komunitou
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
• OpenStreetMap-Daten vom 22. Juni
|
||||
• Optionale automatische Backups von Lesezeichen & Tracks
|
||||
• Neue 100m-Höhenlinien für Regionen die vorher gröbere/keine Isolinien hatten
|
||||
• Vegetation & Spielplätze werden früher angezeigt, neue Farben für Campingplätze & andere Einrichtungen
|
||||
• Pfade & Tracks werden standardmäßig bei höherem Zoom angezeigt, Outdoor-Stil für Detailübersicht
|
||||
• Aktion des linken Button nichtm mehr im Hamburger-Menü, stattdessen werden "Über & Hilfe" dort angezeigt
|
||||
• OpenStreetMap Daten vom 2. Juni
|
||||
• Neue Einstellungsoption zum Ändern oder Ausblenden der "Über CoMaps" Schaltfläche ganz links
|
||||
• Routen als GPS Track speichern
|
||||
• Qingdao Metro, Gärtnereien, Leitplanken, Leitern, Studios, Tanzsäle, Feuerstellen und Stundenhotels hinzugefügt
|
||||
• transparente Navigationsleiste im Light Mode
|
||||
• Mastodon und Bluesky Kontaktoptionen für POIs und im OSM-Editor hinzugefügt
|
||||
• Anzeige der Kompassgradzahl in der Richtungspfeilansicht
|
||||
• Übersetzungen aktualisiert
|
||||
|
||||
|
After Width: | Height: | Size: 747 KiB |
|
Before Width: | Height: | Size: 628 KiB |
|
After Width: | Height: | Size: 749 KiB |
|
Before Width: | Height: | Size: 532 KiB |
|
After Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 590 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 263 KiB |
@@ -1,7 +1,8 @@
|
||||
• OpenStreetMap data as of June 22
|
||||
• a setting for automatic bookmarks and tracks backup
|
||||
• added 100m-step altitude isolines to all regions that had worse or no isolines
|
||||
• display vegetation and playground color fills earlier, add fills to camp sites and some amenities
|
||||
• paths & tracks appear on the map later by default - still appear earlier in the outdoor style
|
||||
• hide active custom button action from the hamburger menu, move there About & Help from the settings
|
||||
• update map transport icons
|
||||
• OpenStreetMap data as of June 2
|
||||
• add a setting to change the leftmost button or hide it
|
||||
• save built routes as tracks
|
||||
• add Qingdao metro, plant nurseries, highway guard rails, ladders, studios, dance venues, firepits, love hotels
|
||||
• transparent system navigation bar in the light mode
|
||||
• add Mastodon and Bluesky contact options to POIs and OSM editor
|
||||
• display Azimuth angle in direction arrow view
|
||||
• update translations
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
• datos de OpenStreetMap a 22 de junio
|
||||
• añadido ajuste para la copia de seguridad automática de marcadores y trazas
|
||||
• añadidas isolíneas de 100 m a las regiones que tenían isolíneas peores o no tenían isolíneas
|
||||
• se muestra antes la vegetación y zonas de juego, añadidas áreas a campings y otros servicios
|
||||
• los senderos y pistas aparecen más tarde - siguen igual en estilo de outdoors
|
||||
• se oculta botón personalizado del menú y se mueve allí Acerca de y Ayuda desde los ajustes
|
||||
@@ -1,32 +0,0 @@
|
||||
Komunitateko doako eta iturburu irekiko aplikazioa OpenStreetMap datuetan oinarrituta eta gardentasuna, pribatutasuna eta irabazi asmorik gabeko konpromisoarekin indartua. Comaps Organic Maps-en fork edo aldaera bat da, eta hori, aldi berean, maps.me-ren forka da.
|
||||
|
||||
Irakurri proiektuaren zergatia eta haren norabidea <b> <i> codeberg.org/comaps </ i> </ b>.
|
||||
Sartu komunitatean eta lagundu maparik onena aplikatzen
|
||||
• Erabili aplikazioa eta horren berri eman
|
||||
• Eman feedbacka eta txostenetako gaiak
|
||||
• Eguneratu maparen datuak aplikazioan edo OpenStreetMap webgunean
|
||||
|
||||
‣ <b> Konexiorik gabe fokatuta </ b> Planifikatu eta nabigatu atzerrira bidaiatzea, telefono zerbitzu beharrik gabe, bilaketa-biderapenak urruneko ibilaldian eta abar. Aplikazio funtzio guztiak lineaz kanpo lan egiteko diseinatuta daude.
|
||||
‣ <b> Pribatutasuna errespetatzea </ b> errespetatzea: aplikazioa pribatutasunarekin diseinatuta dago, ez du pertsonak identifikatzen, ez du jarraipena egiten, eta ez du informazio pertsonala biltzen. Iragarkirik ez.
|
||||
‣ <b> Sinplea eta leundua </ b>: Ezinbestekoa da funtzionatzen duten ezaugarriak erabiltzeko.
|
||||
‣ <b> Zure bateria eta espazioa gordetzen ditu </ b>: ez du bateria xahutzen beste nabigazio aplikazioak bezala. Mapa trinkoak. Gorde espazio preziatua zure telefonoan.
|
||||
‣ <b> Librea eta komunitateak eraikitakoa: Jendeak aplikazioa eraikitzen lagundu zuen aplikazioa eraikitzen lagunduz OpenStreetMap, probatu eta funtzioei buruzko iritzia emanez eta garapen trebetasunak eta dirua lagunduz.
|
||||
‣ <b> Erabakiak eta finantza irekiak eta gardena, irabazi asmorik gabeko eta guztiz irekitako iturria. </ B>
|
||||
|
||||
<b> Ezaugarri nagusiak </ b>:
|
||||
• Deskargatu mapa zehatzak Google Maps-ekin eskuragarri ez dauden lekuekin
|
||||
• Mendiko modua nabarmendutako mendi ibilbideak, kanpinak, ur iturriak, gailurrak, sestra-lerroak, etab
|
||||
• Bideak eta bidegorriak
|
||||
• Jatetxe, gas geltokiak, hotelak, dendak, bisitak eta bestelako interesguneak
|
||||
• Bilatu izenaren edo helbide baten arabera edo interes-kategoriaren arabera
|
||||
• Oinez, txirrinduaz edo gidatzeko ahots-oharrekin nabigazioa
|
||||
• Markatu zure gogoko lekuak sakatze bakarrarekin
|
||||
• Lineaz kanpoko Wikipedia artikuluak
|
||||
• Metroaren garraio geruza eta jarraibideak
|
||||
• Arrastoen grabazioa
|
||||
• Laster-markak eta ibilbideak esportatu eta inportatu KML, KMZ, GPX formatuetan
|
||||
• Gauean erabiltzeko modu iluna
|
||||
• Hobetu mapako datuak guztiontzat oinarrizko editore integratua erabiliz
|
||||
|
||||
<b> Askatasuna hemen </ b> da
|
||||
Ezagutu zure bidaia, nabigatu munduan pribatutasunarekin eta komunitatez abangoardian!
|
||||
@@ -1 +1 @@
|
||||
CoMaps- Mendia, bizikleta, autoa, dena offline
|
||||
CoMaps- Mendia, bizikleta, autoa. Dena offline eta pribatutasunearekin
|
||||
|
||||
|
After Width: | Height: | Size: 737 KiB |
|
Before Width: | Height: | Size: 655 KiB |
|
After Width: | Height: | Size: 765 KiB |
|
Before Width: | Height: | Size: 532 KiB |
|
After Width: | Height: | Size: 735 KiB |
|
Before Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 594 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 263 KiB |
@@ -1,6 +1,8 @@
|
||||
• Données OpenStreetMap du 22 juin
|
||||
• Sauvegarde automatique des signets et traces GPS en local
|
||||
• Ajout des courbes d'altitude avec un précision de 100 mètres dans toutes les régions qui avaient peu de courbes ou aucune
|
||||
• Ajustements des styles notamment sur la végétation, les aires de jeu et les chemins
|
||||
• Masque l’action active du bouton personnalisé dans le menu hamburger
|
||||
• Correction de certains plantages et bugs
|
||||
• Données OpenStreetMap du 02 juin
|
||||
• Ajout d'une option pour personnaliser le bouton tout à gauche sur l'écran principal
|
||||
• Ajout de la possibilité d'enregistrer un itinéraire en tant que traces GPS
|
||||
• Ajout du métro de Qingdao, et divers objets sur la carte
|
||||
• Support de la barre de navigation transparent en mode clair
|
||||
• Ajout des tags Mastodon et Bluesky sur les lieux et dans l'éditeur
|
||||
• Affichage de l'azimut
|
||||
• Mise à jour des traductions
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Navegación doada - Descubre máis sobre o teu camiño - Creada pola comunidade
|
||||
@@ -1 +0,0 @@
|
||||
CoMaps - Aplicación de mapas privada, sen conexión
|
||||
@@ -0,0 +1,8 @@
|
||||
• Data di OpenStreetmap fino a giugno 2°
|
||||
• Nuova impostazione per cambiare o modificare la positione del tasto sinistra
|
||||
• Salvare i percorsi costruiti come tracce
|
||||
• È stato aggiunto: metropolitana di Qingdao, giardinaggi, guardrail, scale, studio,
|
||||
sala da ballo, focolari, love hotel
|
||||
• Barra di navigatione transparente di sisteme in moda luce
|
||||
• Aggiungi le opzioni di contatto Mastodon e Bluesky ai POI e all'editor di OSM
|
||||
• Visualizza l'angolo di azimut nella freccia di direzione
|
||||
@@ -1 +0,0 @@
|
||||
CoMaps - Wandel, fiets, rijdt offline met privacy
|
||||
@@ -0,0 +1,8 @@
|
||||
• Dados do OSM de 2/06
|
||||
• Adicionada uma configuração para alterar ou ocultar o botão mais à esquerda
|
||||
• Salve rotas construídas como trilhas
|
||||
• Adicionado metrô de Qingdao, viveiros de plantas, guarda-corpos de rodovias, escadas, estúdios, casas de dança, fogueiras e motéis
|
||||
• Barra de navegação do sistema transparente no modo claro
|
||||
• Adicionadas opções de contato Mastodon e Bluesky aos POIs e ao editor OSM
|
||||
• Exibição de ângulo de azimute na visualização de seta de direção
|
||||
• Novas traduções
|
||||
@@ -1,55 +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 <b><i>codeberg.org/comaps</i></b>.
|
||||
|
||||
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.
|
||||
|
||||
‣ <b>Foco offline</b>: 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.
|
||||
|
||||
‣ <b>Respeitando a privacidade</b>: 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.
|
||||
|
||||
‣ <b>Simples e sofisticado</b>: recursos essenciais e fáceis de usar que simplesmente funcionam.
|
||||
|
||||
‣ <b>Economiza bateria e espaço</b>: Não consome muita bateria como outros aplicativos de navegação. Mapas compactos economizam espaço precioso no seu celular.
|
||||
|
||||
‣ <b>Gratuito e desenvolvido pela comunidade</b>: 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.
|
||||
|
||||
‣ <b>Tomada de decisões e finanças abertas e transparentes, sem fins lucrativos e totalmente de código aberto.</b>
|
||||
|
||||
<b>Principais recursos</b>:
|
||||
|
||||
• 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
|
||||
|
||||
<b>A Liberdade Chegou</b>
|
||||
|
||||
Descubra sua jornada, navegue pelo mundo com privacidade e comunidade em primeiro lugar!
|
||||
@@ -1 +0,0 @@
|
||||
Navegação fácil nos mapas - Descubra mais sobre o seu percurso - Feito por todos
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Mapas e Navegação - Offline e Privada
|
||||
CoMaps - Andar, Pedalar, Dirigir Offline com Privacidade
|
||||
|
||||
@@ -1,33 +1,26 @@
|
||||
Бесплатное и свободное картографическое приложение, основанное на данных OpenStreetMap и подкреплённое обязательствами по прозрачности, конфиденциальности и некоммерческой направленности. CoMaps — это ответвление от Organic Maps, которое, в свою очередь, является ответвлением от Maps.ME.
|
||||
Бесплатное картографическое приложение с открытым исходным кодом, основанное на данных OpenStreetMap и подкрепленное обязательствами по прозрачности, конфиденциальности и некоммерческому характеру. CoMaps - это форк/ответвление Organic Maps, который, в свою очередь, является форком Maps.ME.
|
||||
|
||||
Подробнее о причинах проекта и его направлении читайте на <a href="https://codeberg.org/comaps">сайте</a>.
|
||||
|
||||
Присоединяйтесь к сообществу и помогите создать лучшее приложение с картами
|
||||
• Используйте приложение и распространяйте информацию о нём
|
||||
• Оставляйте отзывы и сообщайте о проблемах
|
||||
• Обновляйте данные карт в приложении или на веб-сайте OpenStreetMap
|
||||
|
||||
‣ <b>Приоритет на работу без интернета</b>: Планируйте и ориентируйтесь в путешествии за границей, не нуждаясь в сотовой связи и т.д. Всё в приложении рассчитано на работу в автономном режиме.
|
||||
‣ <b>Соблюдение конфиденциальности</b>: Приложение разработано с учётом требований конфиденциальности — оно не идентифицирует людей, не отслеживает и не собирает личную информацию. Без рекламы.
|
||||
‣ <b>Простота и отточенность</b>: Основные и простые в использовании функции, которые просто работают.
|
||||
‣ <b>Приоритет на работу без интернета</b>: Планируйте и ориентируйтесь в путешествии за границей, не нуждаясь в сотовой связи и т.д. Все функции приложения рассчитаны на работу в автономном режиме.
|
||||
‣ <b>Соблюдение конфиденциальности</b>: Приложение разработано с учетом требований конфиденциальности - оно не идентифицирует людей, не отслеживает и не собирает личную информацию. Без рекламы.
|
||||
‣ <b>Простота и Элегантность</b>: Необходимые и легкие в использовании функции, которые просто работают.
|
||||
‣ <b>Экономия заряда батареи и места на устройстве</b>: Не разряжает аккумулятор, как другие приложения для навигации. Компактные карты экономят драгоценное место на вашем телефоне.
|
||||
‣ <b>Бесплатное и созданное сообществом</b>: Такие люди, как и вы, помогали создавать приложение, добавляя места в OpenStreetMap, протестировав и оставляя отзывы о функциях, а также вложив свои навыки и деньги в разработку.
|
||||
‣ <b>Открытое и прозрачное принятие решений, финансовая отчётность, некоммерческая организация и полностью открытый исходный код.</b>
|
||||
‣ <b>Бесплатное и созданное сообществом</b>: Люди, подобные вам, помогали создавать приложение, добавляя места в OpenStreetMap, тестируя и оставляя отзывы о функциях, а также вкладывая свои навыки и деньги в разработку..
|
||||
‣ <b>Открытое и прозрачное принятие решений, финансовая отчетность, некоммерческая организация и полностью открытый исходный код.</b>
|
||||
|
||||
<b>Главные возможности</b>:
|
||||
• Скачиваемые и подробные карты с местами, которые недоступны в Google Maps
|
||||
• Уличный режим с отмеченными туристическими тропами, кемпингами, источниками воды, вершинами, контурными линиями и т.д.
|
||||
<b>Главные особенности</b>:
|
||||
• Загружаемые и подробные карты с местами, которые недоступны в Google Maps
|
||||
• Режим Outdoor с отмеченными туристическими тропами, кемпингами, источниками воды, вершинами, контурными линиями и т.д.
|
||||
• Пешеходные переходы и велодорожки
|
||||
• Интересные места, такие как: рестораны, заправочные станции, гостиницы, магазины, достопримечательности и многое другое
|
||||
• Поиск по названию или адресу или по категории достопримечательностей
|
||||
• Точки интереса, такие как: рестораны, заправочные станции, отели, магазины, достопримечательности и многое другое
|
||||
• Поиск по имени, адресу или категории достопримечательностей
|
||||
• Навигация с голосовыми уведомлениями для пешеходов, велосипедистов или водителей
|
||||
• Возможность добавлять любимые места в закладки одним нажатием
|
||||
• Скачиваемые страницы Википедии
|
||||
• Возможность добавлять любимые места в закладки одним касанием
|
||||
• Загружаемые страницы Википедии
|
||||
• Слой общественного транспорта (метро)
|
||||
• Запись маршрута
|
||||
• Экспорт и импорт закладок и маршрутов в форматах KML, KMZ, GPX
|
||||
• Тёмный режим для использования в ночное время
|
||||
• Улучшение данных карты для всех с помощью базового встроенного редактора
|
||||
• Темный режим для использования в ночное время
|
||||
• Улучшение картографических данных для всех с помощью базового встроенного редактора
|
||||
• Поддержка Android Auto и CarPlay
|
||||
|
||||
<b>Свобода здесь</b>
|
||||
Откройте для себя путешествия, навигацию по миру, ставя во главе приватность и сообщество!
|
||||
<i>Свобода здесь - Открой для себя поездки, навигацию по миру, ставя приватность и сообщество во главе</i>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
• карты OpenStreetMap от 22 июня
|
||||
• автоматическое резервное копирование меток и треков
|
||||
• линии высот с шагом 100м для всех регионов, где линии с этим шагом отсутствовали
|
||||
• цветная заливка растительности и игровых площадок отображается раньше, добавлена заливка для кемпингов и других объектов
|
||||
• тропы и грунтовки отображаются позже в стиле по умолчанию - используйте стиль «Активный отдых» для обзора троп
|
||||
• выбранная функция настраиваемой кнопки больше не дублируется в пунктах меню
|
||||
• карты OpenStreetMap от 2 июня
|
||||
• настройка для изменения функции левой кнопки или её скрытия
|
||||
• сохранение построенных маршрутов в виде треков
|
||||
• добавлены: метро в Qingdao, питомники растений, отбойники на шоссе, постоянные лестницы-стремянки, студии, места для танцев, кострища, отели любви
|
||||
• прозрачная полоска с системными кнопками (в светлом режиме)
|
||||
• в объекты на карте (а также в их редактор) добавлены Mastodon и Bluesky контакты
|
||||
• к стрелке направления на выбранный объект добавлен азимут
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Простая навигация по карте — Откройте больше за ваше путешествие. От сообщества
|
||||
@@ -1 +0,0 @@
|
||||
CoMaps - Карты и путешествия с приватностью
|
||||
@@ -1 +1 @@
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Покреће је заједница
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Захваљујући заједници
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: 2025.06.30-22-FDroid+25063022
|
||||
version: 2025.03.02-7-FDroid+25030207
|
||||
|
||||
@@ -1 +1 @@
|
||||
Jednoduchá navigace v mapě - Objevte více na své cestě ‐ Vyvíjeno komunitou
|
||||
Jednoduchá navigace v mapě – Objevte více na své cestě – Vyvíjeno komunitou
|
||||
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Navigace se soukromím
|
||||
CoMaps – Navigace se soukromím
|
||||
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Navi mit Datenschutz
|
||||
CoMaps
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Navegación doada - Descubre máis sobre o teu camiño - Creada pola comunidade
|
||||
@@ -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.
|
||||
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.
|
||||
|
||||
Unisciti alla nostra comunità e aiutaci a creare la migliore app di mappe.
|
||||
• usa l'app e consigliala
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Eenvoudige kaartnavigatie - Ontdek meer van je reis - Gemaakt door de community
|
||||
@@ -1 +0,0 @@
|
||||
CoMaps - Navigeer met privacy
|
||||
@@ -1 +0,0 @@
|
||||
Navegação fácil nos mapas - Descubra mais sobre o seu percurso - Feito por todos
|
||||
@@ -1,38 +0,0 @@
|
||||
Бесплатное и открытое приложение с картами, созданное сообществом на основе картографических данных OpenStreetMap и подкрепленное стремлением к прозрачности, уважению конфиденциальности и некоммерческой направленностью.
|
||||
|
||||
Подробнее о причинах проекта и его направлении читайте на <a href="https://codeberg.org/comaps">сайте</a>.
|
||||
|
||||
Присоединяйтесь к сообществу и помогите создать лучшее приложение с картами
|
||||
• Используйте приложение и распространяйте информацию о нём
|
||||
• Оставляйте отзывы и сообщайте о проблемах
|
||||
• Обновляйте данные карт в приложении или на веб-сайте OpenStreetMap
|
||||
|
||||
<i>Ваши отзыв и звёздочки будут для нас лучшей поддержкой!</i>
|
||||
|
||||
‣ <b>Приоритет на работу без интернета</b>: Планируйте и ориентируйтесь в путешествии за границей, не нуждаясь в сотовой связи и т.д. Всё в приложении рассчитано на работу в автономном режиме.
|
||||
‣ <b>Соблюдение конфиденциальности</b>: Приложение разработано с учётом требований конфиденциальности — оно не идентифицирует людей, не отслеживает и не собирает личную информацию. Без рекламы.
|
||||
‣ <b>Простота и отточенность</b>: Основные и простые в использовании функции, которые просто работают.
|
||||
‣ <b>Экономия заряда батареи и места на устройстве</b>: Не разряжает аккумулятор, как другие приложения для навигации. Компактные карты экономят драгоценное место на вашем телефоне.
|
||||
‣ <b>Бесплатное и созданное сообществом</b>: Такие люди, как и вы, помогали создавать приложение, добавляя места в OpenStreetMap, протестировав и оставляя отзывы о функциях, а также вложив свои навыки и деньги в разработку.
|
||||
‣ <b>Открытое и прозрачное принятие решений, финансовая отчётность, некоммерческая организация и полностью открытый исходный код.</b>
|
||||
|
||||
<b>Главные возможности</b>:
|
||||
• Скачиваемые и подробные карты с местами, которые недоступны в Google Maps
|
||||
• Уличный режим с отмеченными туристическими тропами, кемпингами, источниками воды, вершинами, контурными линиями и т.д.
|
||||
• Пешеходные переходы и велодорожки
|
||||
• Интересные места, такие как: рестораны, заправочные станции, гостиницы, магазины, достопримечательности и многое другое
|
||||
• Поиск по названию или адресу или по категории достопримечательностей
|
||||
• Навигация с голосовыми уведомлениями для пешеходов, велосипедистов или водителей
|
||||
• Возможность добавлять любимые места в закладки одним нажатием
|
||||
• Скачиваемые страницы Википедии
|
||||
• Слой общественного транспорта (метро)
|
||||
• Запись маршрута
|
||||
• Экспорт и импорт закладок и маршрутов в форматах KML, KMZ, GPX
|
||||
• Тёмный режим для использования в ночное время
|
||||
• Улучшение данных карты для всех с помощью базового встроенного редактора
|
||||
• Поддержка Андроид Авто
|
||||
|
||||
Пожалуйста сообщайте о ошибках, предлагайте идеи и присоединяйтесь к сообществу на сайте <a href="https://comaps.app">comaps.app</a>.
|
||||
|
||||
<b>Свобода!</b>
|
||||
Откройте для себя путешествия, навигацию по миру, ставя во главе приватность и сообщество!
|
||||
@@ -1 +0,0 @@
|
||||
Простая навигация по карте — Откройте больше за ваше путешествие. От сообщества
|
||||
@@ -1 +0,0 @@
|
||||
CoMaps - Оффлайн навигация
|
||||
@@ -32,5 +32,5 @@
|
||||
|
||||
Молимо Вас да пријавите проблеме са апликацијом, предложите идеје и придружите се нашој заједници на <b><i>comaps.app</i></b> страни.
|
||||
|
||||
<b>Сад је слободна</b>
|
||||
<b>Сад је слободно</b>
|
||||
Откријте своје путовање, путујте светом с приватношћу и заједницом на челу!
|
||||
|
||||
@@ -1 +1 @@
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Покреће је заједница
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Захваљујући заједници
|
||||
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Navigacija
|
||||
CoMaps - навигација
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
OpenStreetMap verilerine dayanan ve şeffaflık, gizlilik ve kar amacı gütmeyen olma taahhüdüyle güçlendirilen, topluluk tarafından yönetilen ücretsiz ve açık kaynaklı bir harita uygulaması.
|
||||
|
||||
Topluluğa katılın ve en iyi harita uygulamasını oluşturmaya yardımcı olun
|
||||
• Uygulamayı kullanın ve bunu herkese duyurun
|
||||
• Geri bildirimde bulunun ve sorunları bildirin
|
||||
• Harita verilerini uygulamada veya OpenStreetMap web sitesinde güncelleyin
|
||||
|
||||
<i>Geri bildirimleriniz ve 5 yıldızlı yorumlarınız bizim için en iyi destektir!</i>
|
||||
|
||||
‣ <b>Basit ve Cilalı</b>: sadece işe yarayan, kullanımı kolay temel özellikler.
|
||||
‣ <b>Çevrim dışı odaklı</b>: Cep telefonu hizmetine ihtiyaç duymadan yurtdışı seyahatinizi planlayın ve gezinin, uzun bir yürüyüş sırasında rota noktalarını arayın, vb. Tüm uygulama işlevleri çevrimdışı çalışmak üzere tasarlanmıştır.
|
||||
‣ <b>Gizliliğe Saygı</b>: Uygulama gizlilik düşünülerek tasarlanmıştır; kişileri tanımlamaz, takip etmez ve kişisel bilgi toplamaz. Reklamsız.
|
||||
‣ <b>Pilinizden ve Alanınızdan Tasarruf Edin</b>: Diğer navigasyon uygulamaları gibi pilinizi tüketmez. Kompakt haritalar telefonunuzda değerli alan tasarrufu sağlar.
|
||||
‣ <b>Ücretsiz ve Topluluk Tarafından Oluşturuldu</b>: Sizin gibi insanlar, OpenStreetMap'e yerler ekleyerek, özellikleri test ederek ve geri bildirimde bulunarak ve geliştirme becerilerinizi ve paranızı katkıda bulunarak uygulamanın oluşturulmasına yardımcı oldunuz..
|
||||
‣ <b>Açık ve Şeffaf Karar Alma ve Finansman, Kar Amacı Gütmeyen ve Tamamen Açık Kaynak.</b>
|
||||
|
||||
<b>Ana Özellikleri</b>:
|
||||
• Google Haritalar'da bulunmayan yerleri içeren indirilebilir detaylı haritalar
|
||||
• Vurgulanan yürüyüş parkurları, kamp alanları, su kaynakları, zirveler, kontur çizgileriyle açık hava modu gibi
|
||||
• Yürüyüş yolları ve bisiklet yolları
|
||||
• Restoranlar, benzin istasyonları, oteller, mağazalar, turistik yerler gibi ilgi çekici noktalar ve daha fazlası
|
||||
• İsme veya adrese göre veya ilgi noktası kategorisine göre arama yapın
|
||||
• Yürüyerek, bisikletle veya araçla seyahat edenler için sesli duyurularla navigasyon
|
||||
• Favori yerlerinizi tek bir dokunuşla yer imlerine ekleyin
|
||||
• Çevrim dışı Wikipedia makaleleri
|
||||
• Metro geçiş katmanı ve yönleri
|
||||
• Rota kaydı
|
||||
• Yer imlerini ve parkurları KML, KMZ, GPX formatlarında dışa ve içe aktarın
|
||||
• Geceleri kullanmak için karanlık mod
|
||||
• Temel bir yerleşik düzenleyici kullanarak herkes için harita verilerini iyileştirin
|
||||
• Android Auto desteği
|
||||
|
||||
Lütfen uygulama sorunlarını bildirin, fikir önerin ve <b><i>comaps.app</i></b> web sitesinde topluluğumuza katılın.
|
||||
|
||||
<b>Özgürlük Burada</b>
|
||||
Yolculuğunuzu keşfedin, gizlilik ve topluluk ön planda tutularak dünyayı keşfedin!
|
||||
@@ -44,7 +44,6 @@ import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import app.organicmaps.api.Const;
|
||||
import app.organicmaps.backup.PeriodicBackupRunner;
|
||||
import app.organicmaps.base.BaseMwmFragmentActivity;
|
||||
import app.organicmaps.base.OnBackPressListener;
|
||||
import app.organicmaps.bookmarks.BookmarkCategoriesActivity;
|
||||
@@ -140,7 +139,6 @@ import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_HELP_CODE;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_RECORD_TRACK_CODE;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_SETTINGS_CODE;
|
||||
import static app.organicmaps.util.PowerManagment.POWER_MANAGEMENT_TAG;
|
||||
import static app.organicmaps.util.concurrency.UiThread.runLater;
|
||||
|
||||
public class MwmActivity extends BaseMwmFragmentActivity
|
||||
implements PlacePageActivationListener,
|
||||
@@ -255,8 +253,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
@NonNull
|
||||
private DisplayManager mDisplayManager;
|
||||
|
||||
private PeriodicBackupRunner backupRunner;
|
||||
|
||||
ManageRouteBottomSheet mManageRouteBottomSheet;
|
||||
|
||||
private boolean mRemoveDisplayListener = true;
|
||||
@@ -611,8 +607,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
*/
|
||||
if (Map.isEngineCreated())
|
||||
onRenderingInitializationFinished();
|
||||
|
||||
backupRunner = new PeriodicBackupRunner(this);
|
||||
}
|
||||
|
||||
private void onSettingsResult(ActivityResult activityResult)
|
||||
@@ -844,7 +838,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
@Override
|
||||
public String getPrefsName()
|
||||
{
|
||||
return getString(R.string.about_help);
|
||||
return getString(R.string.help);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1358,11 +1352,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
final String backUrl = Framework.nativeGetParsedBackUrl();
|
||||
if (!TextUtils.isEmpty(backUrl))
|
||||
Utils.openUri(this, Uri.parse(backUrl), null);
|
||||
|
||||
if (backupRunner != null && !backupRunner.isAlreadyChecked() && backupRunner.isTimeToBackup())
|
||||
{
|
||||
backupRunner.doBackup();
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -2595,28 +2584,20 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
if (id.equals(MAIN_MENU_ID))
|
||||
{
|
||||
final String activeLeftButton = buttonsHolder.getActiveButtonCode();
|
||||
ArrayList<MenuBottomSheetItem> items = new ArrayList<>();
|
||||
|
||||
if (!BUTTON_ADD_PLACE_CODE.equals(activeLeftButton))
|
||||
items.add(new MenuBottomSheetItem(R.string.placepage_add_place_button, R.drawable.ic_plus, this::onAddPlaceOptionSelected));
|
||||
|
||||
items.add(new MenuBottomSheetItem(R.string.download_maps, R.drawable.ic_download, getDownloadMapsCounter(), this::onDownloadMapsOptionSelected));
|
||||
|
||||
if (!Config.getDonateUrl(getApplicationContext()).isEmpty())
|
||||
items.add(new MenuBottomSheetItem(R.string.placepage_add_place_button, R.drawable.ic_plus, this::onAddPlaceOptionSelected));
|
||||
items.add(new MenuBottomSheetItem(
|
||||
R.string.download_maps,
|
||||
R.drawable.ic_download,
|
||||
getDownloadMapsCounter(),
|
||||
this::onDownloadMapsOptionSelected
|
||||
));
|
||||
mDonatesUrl = Config.getDonateUrl(getApplicationContext());
|
||||
if (!TextUtils.isEmpty(mDonatesUrl))
|
||||
items.add(new MenuBottomSheetItem(R.string.donate, R.drawable.ic_donate, this::onDonateOptionSelected));
|
||||
|
||||
if (!BUTTON_SETTINGS_CODE.equals(activeLeftButton))
|
||||
items.add(new MenuBottomSheetItem(R.string.settings, R.drawable.ic_settings, this::onSettingsOptionSelected));
|
||||
|
||||
if (!BUTTON_RECORD_TRACK_CODE.equals(activeLeftButton))
|
||||
items.add(new MenuBottomSheetItem(R.string.start_track_recording, R.drawable.ic_track_recording_off, -1, this::onTrackRecordingOptionSelected));
|
||||
|
||||
items.add(new MenuBottomSheetItem(R.string.settings, R.drawable.ic_settings, this::onSettingsOptionSelected));
|
||||
items.add(new MenuBottomSheetItem(R.string.start_track_recording, R.drawable.ic_track_recording_off, -1, this::onTrackRecordingOptionSelected));
|
||||
items.add(new MenuBottomSheetItem(R.string.share_my_location, R.drawable.ic_share, this::onShareLocationOptionSelected));
|
||||
|
||||
if (!BUTTON_HELP_CODE.equals(activeLeftButton))
|
||||
items.add(new MenuBottomSheetItem(R.string.about_help, R.drawable.ic_question_mark, this::showHelp));
|
||||
|
||||
return items;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
package app.organicmaps.backup;
|
||||
|
||||
import static app.organicmaps.settings.BackupSettingsFragment.MAX_BACKUPS_DEFAULT_COUNT;
|
||||
import static app.organicmaps.settings.BackupSettingsFragment.MAX_BACKUPS_KEY;
|
||||
import static app.organicmaps.util.StorageUtils.isFolderWritable;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.util.UiUtils;
|
||||
import app.organicmaps.util.log.Logger;
|
||||
|
||||
public class BackupUtils
|
||||
{
|
||||
private static final String BACKUP_PREFIX = "backup_";
|
||||
private static final String BACKUP_EXTENSION = ".kmz";
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss").withLocale(Locale.US);
|
||||
private static final String TAG = BackupUtils.class.getSimpleName();
|
||||
|
||||
public static CharSequence formatReadableFolderPath(Context context, @NonNull Uri uri)
|
||||
{
|
||||
String docId = DocumentsContract.getTreeDocumentId(uri);
|
||||
String volumeId;
|
||||
String subPath = "";
|
||||
|
||||
int colonIndex = docId.indexOf(':');
|
||||
if (colonIndex >= 0)
|
||||
{
|
||||
volumeId = docId.substring(0, colonIndex);
|
||||
subPath = docId.substring(colonIndex + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
volumeId = docId;
|
||||
}
|
||||
|
||||
String volumeName;
|
||||
if ("primary".equalsIgnoreCase(volumeId))
|
||||
volumeName = context.getString(R.string.maps_storage_shared);
|
||||
else
|
||||
volumeName = context.getString(R.string.maps_storage_removable);
|
||||
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
sb.append(volumeName + ": \n", new AbsoluteSizeSpan(UiUtils.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)), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return sb;
|
||||
}
|
||||
|
||||
public static int getMaxBackups(SharedPreferences prefs)
|
||||
{
|
||||
String rawValue = prefs.getString(MAX_BACKUPS_KEY, String.valueOf(MAX_BACKUPS_DEFAULT_COUNT));
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(rawValue);
|
||||
} catch (NumberFormatException e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to parse max backups count, raw value: " + rawValue + " set to default: " + MAX_BACKUPS_DEFAULT_COUNT, e);
|
||||
prefs.edit()
|
||||
.putString(MAX_BACKUPS_KEY, String.valueOf(MAX_BACKUPS_DEFAULT_COUNT))
|
||||
.apply();
|
||||
return MAX_BACKUPS_DEFAULT_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
public static DocumentFile createUniqueBackupFolder(@NonNull DocumentFile parentDir, LocalDateTime backupTime)
|
||||
{
|
||||
String folderName = BACKUP_PREFIX + backupTime.format(DATE_FORMATTER);
|
||||
return parentDir.createDirectory(folderName);
|
||||
}
|
||||
|
||||
public static String getBackupName(LocalDateTime backupTime)
|
||||
{
|
||||
String formattedBackupTime = backupTime.format(DATE_FORMATTER);
|
||||
return BACKUP_PREFIX + formattedBackupTime + BACKUP_EXTENSION;
|
||||
}
|
||||
|
||||
public static DocumentFile[] getBackupFolders(DocumentFile parentDir)
|
||||
{
|
||||
List<DocumentFile> backupFolders = new ArrayList<>();
|
||||
for (DocumentFile file : parentDir.listFiles())
|
||||
{
|
||||
if (file.isDirectory() && file.getName() != null && file.getName().startsWith(BACKUP_PREFIX))
|
||||
backupFolders.add(file);
|
||||
}
|
||||
return backupFolders.toArray(new DocumentFile[0]);
|
||||
}
|
||||
|
||||
public static boolean isBackupFolderAvailable(Context context, String storedFolderPath)
|
||||
{
|
||||
return !TextUtils.isEmpty(storedFolderPath) && isFolderWritable(context, storedFolderPath);
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package app.organicmaps.backup;
|
||||
|
||||
import static app.organicmaps.backup.BackupUtils.getBackupName;
|
||||
import static app.organicmaps.backup.BackupUtils.getBackupFolders;
|
||||
import static app.organicmaps.util.StorageUtils.copyFileToDocumentFile;
|
||||
import static app.organicmaps.util.StorageUtils.deleteDirectoryRecursive;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import app.organicmaps.bookmarks.data.BookmarkCategory;
|
||||
import app.organicmaps.bookmarks.data.BookmarkManager;
|
||||
import app.organicmaps.bookmarks.data.BookmarkSharingResult;
|
||||
import app.organicmaps.bookmarks.data.KmlFileType;
|
||||
import app.organicmaps.util.concurrency.ThreadPool;
|
||||
import app.organicmaps.util.concurrency.UiThread;
|
||||
import app.organicmaps.util.log.Logger;
|
||||
|
||||
public class LocalBackupManager implements BookmarkManager.BookmarksSharingListener
|
||||
{
|
||||
public static final String TAG = LocalBackupManager.class.getSimpleName();
|
||||
|
||||
private final Activity activity;
|
||||
private final String backupFolderPath;
|
||||
private final int maxBackups;
|
||||
private Listener listener;
|
||||
|
||||
public LocalBackupManager(@NonNull Activity activity, @NonNull String backupFolderPath, int maxBackups)
|
||||
{
|
||||
this.activity = activity;
|
||||
this.backupFolderPath = backupFolderPath;
|
||||
this.maxBackups = maxBackups;
|
||||
}
|
||||
|
||||
public void doBackup()
|
||||
{
|
||||
BookmarkManager.INSTANCE.addSharingListener(this);
|
||||
|
||||
prepareBookmarkCategoriesForSharing();
|
||||
|
||||
if (listener != null)
|
||||
listener.onBackupStarted();
|
||||
}
|
||||
|
||||
public void setListener(@NonNull Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreparedFileForSharing(@NonNull BookmarkSharingResult result)
|
||||
{
|
||||
BookmarkManager.INSTANCE.removeSharingListener(this);
|
||||
|
||||
ThreadPool.getWorker().execute(() -> {
|
||||
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 ->
|
||||
{
|
||||
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(() -> {
|
||||
if (listener != null)
|
||||
{
|
||||
if (finalErrorCode == null)
|
||||
listener.onBackupFinished();
|
||||
else
|
||||
listener.onBackupFailed(finalErrorCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private boolean saveBackup(@NonNull BookmarkSharingResult result)
|
||||
{
|
||||
boolean isSuccess = false;
|
||||
Uri folderUri = Uri.parse(backupFolderPath);
|
||||
try
|
||||
{
|
||||
DocumentFile parentFolder = DocumentFile.fromTreeUri(activity, folderUri);
|
||||
if (parentFolder != null && parentFolder.canWrite())
|
||||
{
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
DocumentFile backupFolder = BackupUtils.createUniqueBackupFolder(parentFolder, now);
|
||||
if (backupFolder != null)
|
||||
{
|
||||
String backupName = getBackupName(now);
|
||||
DocumentFile backupFile = backupFolder.createFile(result.getMimeType(), backupName);
|
||||
if (backupFile != null && copyFileToDocumentFile(activity, new File(result.getSharingPath()), backupFile))
|
||||
{
|
||||
Logger.i(TAG, "Backup saved to " + backupFile.getUri());
|
||||
isSuccess = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.e(TAG, "Failed to create backup folder");
|
||||
}
|
||||
}
|
||||
cleanOldBackups(parentFolder);
|
||||
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to save backup", e);
|
||||
}
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
public void cleanOldBackups(DocumentFile parentDir)
|
||||
{
|
||||
DocumentFile[] backupFolders = getBackupFolders(parentDir);
|
||||
if (backupFolders.length > maxBackups)
|
||||
{
|
||||
Arrays.sort(backupFolders, Comparator.comparing(DocumentFile::getName));
|
||||
for (int i = 0; i < backupFolders.length - maxBackups; i++)
|
||||
{
|
||||
Logger.i(TAG, "Delete old backup " + backupFolders[i].getUri());
|
||||
deleteDirectoryRecursive(backupFolders[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareBookmarkCategoriesForSharing()
|
||||
{
|
||||
List<BookmarkCategory> categories = BookmarkManager.INSTANCE.getCategories();
|
||||
long[] categoryIds = new long[categories.size()];
|
||||
for (int i = 0; i < categories.size(); i++)
|
||||
categoryIds[i] = categories.get(i).getId();
|
||||
BookmarkManager.INSTANCE.prepareCategoriesForSharing(categoryIds, KmlFileType.Text);
|
||||
}
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
void onBackupStarted();
|
||||
|
||||
void onBackupFinished();
|
||||
|
||||
void onBackupFailed(ErrorCode errorCode);
|
||||
}
|
||||
|
||||
public enum ErrorCode
|
||||
{
|
||||
EMPTY_CATEGORY,
|
||||
ARCHIVE_ERROR,
|
||||
FILE_ERROR,
|
||||
UNSUPPORTED,
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package app.organicmaps.backup;
|
||||
|
||||
import static app.organicmaps.backup.BackupUtils.getMaxBackups;
|
||||
import static app.organicmaps.backup.BackupUtils.isBackupFolderAvailable;
|
||||
import static app.organicmaps.settings.BackupSettingsFragment.BACKUP_FOLDER_PATH_KEY;
|
||||
import static app.organicmaps.settings.BackupSettingsFragment.BACKUP_INTERVAL_KEY;
|
||||
import static app.organicmaps.settings.BackupSettingsFragment.LAST_BACKUP_TIME_KEY;
|
||||
import static app.organicmaps.util.StorageUtils.isFolderWritable;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import app.organicmaps.util.log.Logger;
|
||||
|
||||
public class PeriodicBackupRunner
|
||||
{
|
||||
private final Activity activity;
|
||||
private static final String TAG = PeriodicBackupRunner.class.getSimpleName();
|
||||
private final SharedPreferences prefs;
|
||||
private boolean alreadyChecked = false;
|
||||
|
||||
public PeriodicBackupRunner(Activity activity)
|
||||
{
|
||||
this.activity = activity;
|
||||
this.prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
}
|
||||
|
||||
public boolean isAlreadyChecked()
|
||||
{
|
||||
return alreadyChecked;
|
||||
}
|
||||
|
||||
public boolean isTimeToBackup()
|
||||
{
|
||||
long intervalMs = getBackupIntervalMs();
|
||||
|
||||
if (intervalMs <= 0)
|
||||
return false;
|
||||
|
||||
long lastBackupTime = prefs.getLong(LAST_BACKUP_TIME_KEY, 0);
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
alreadyChecked = true;
|
||||
|
||||
return (now - lastBackupTime) >= intervalMs;
|
||||
}
|
||||
|
||||
public void doBackup()
|
||||
{
|
||||
String storedFolderPath = prefs.getString(BACKUP_FOLDER_PATH_KEY, null);
|
||||
|
||||
if (isBackupFolderAvailable(activity, storedFolderPath))
|
||||
{
|
||||
Logger.i(TAG, "Performing periodic backup");
|
||||
performBackup(storedFolderPath, getMaxBackups(prefs));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.w(TAG, "Backup folder is not writable, passed path: " + storedFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
private long getBackupIntervalMs()
|
||||
{
|
||||
String defaultValue = "0";
|
||||
try
|
||||
{
|
||||
return Long.parseLong(prefs.getString(BACKUP_INTERVAL_KEY, defaultValue));
|
||||
} catch (NumberFormatException e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void performBackup(String backupFolderPath, int maxBackups)
|
||||
{
|
||||
LocalBackupManager backupManager = new LocalBackupManager(activity, backupFolderPath, maxBackups);
|
||||
backupManager.setListener(new LocalBackupManager.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onBackupStarted()
|
||||
{
|
||||
Logger.i(TAG, "Periodic backup started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackupFinished()
|
||||
{
|
||||
prefs.edit().putLong(LAST_BACKUP_TIME_KEY, System.currentTimeMillis()).apply();
|
||||
Logger.i(TAG, "Periodic backup finished");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackupFailed(LocalBackupManager.ErrorCode errorCode)
|
||||
{
|
||||
Logger.e(TAG, "Periodic backup was failed with code: " + errorCode);
|
||||
}
|
||||
});
|
||||
|
||||
backupManager.doBackup();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.organicmaps.bookmarks;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -20,9 +21,6 @@ import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import app.organicmaps.MwmApplication;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.adapter.OnItemClickListener;
|
||||
@@ -281,7 +279,7 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment<Bookmark
|
||||
}
|
||||
|
||||
private void showNoFileManagerError() {
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setMessage(R.string.error_no_file_manager_app)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
|
||||
@@ -69,8 +69,7 @@ public class Metadata implements Parcelable
|
||||
FMD_OUTDOOR_SEATING(48),
|
||||
FMD_NETWORK(49),
|
||||
FMD_CONTACT_FEDIVERSE(50),
|
||||
FMD_CONTACT_BLUESKY(51),
|
||||
FMD_PANORAMAX(52);
|
||||
FMD_CONTACT_BLUESKY(51);
|
||||
private final int mMetaType;
|
||||
|
||||
MetadataType(int metadataType)
|
||||
|
||||
@@ -41,7 +41,7 @@ public class HelpScreen extends BaseMapScreen
|
||||
{
|
||||
final Header.Builder builder = new Header.Builder();
|
||||
builder.setStartHeaderAction(Action.BACK);
|
||||
builder.setTitle(getCarContext().getString(R.string.about_help));
|
||||
builder.setTitle(getCarContext().getString(R.string.help));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ public class SettingsScreen extends BaseMapScreen
|
||||
private Item createHelpItem()
|
||||
{
|
||||
final Row.Builder builder = new Row.Builder();
|
||||
builder.setTitle(getCarContext().getString(R.string.about_help));
|
||||
builder.setTitle(getCarContext().getString(R.string.help));
|
||||
builder.setOnClickListener(() -> getScreenManager().push(new HelpScreen(getCarContext(), getSurfaceRenderer())));
|
||||
builder.setBrowsable(true);
|
||||
return builder.build();
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.text.style.StyleSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -27,7 +28,6 @@ import app.organicmaps.util.UiUtils;
|
||||
import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment;
|
||||
import app.organicmaps.util.bottomsheet.MenuBottomSheetItem;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -362,10 +362,10 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
private class ItemViewHolder extends BaseInnerViewHolder<CountryItem>
|
||||
{
|
||||
private final DownloaderStatusIcon mStatusIcon;
|
||||
private final MaterialTextView mName;
|
||||
private final MaterialTextView mSubtitle;
|
||||
private final MaterialTextView mFoundName;
|
||||
private final MaterialTextView mSize;
|
||||
private final TextView mName;
|
||||
private final TextView mSubtitle;
|
||||
private final TextView mFoundName;
|
||||
private final TextView mSize;
|
||||
|
||||
private void processClick(boolean clickOnStatus)
|
||||
{
|
||||
@@ -510,7 +510,7 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
static class HeaderViewHolder extends BaseInnerViewHolder<String>
|
||||
{
|
||||
@NonNull
|
||||
private final MaterialTextView mTitle;
|
||||
private final TextView mTitle;
|
||||
|
||||
HeaderViewHolder(@NonNull View frame)
|
||||
{
|
||||
|
||||
@@ -3,14 +3,13 @@ package app.organicmaps.downloader;
|
||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import android.app.ForegroundServiceStartNotAllowedException;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.List;
|
||||
@@ -41,11 +40,19 @@ public class DownloaderService extends Service implements MapManager.StorageCall
|
||||
Logger.i(TAG, "Downloading: " + MapManager.nativeIsDownloading());
|
||||
|
||||
var notification = mNotifier.buildProgressNotification();
|
||||
Logger.i(TAG, "Starting Downloader Foreground Service");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
ServiceCompat.startForeground(this, DownloaderNotifier.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
|
||||
else
|
||||
ServiceCompat.startForeground(this, DownloaderNotifier.NOTIFICATION_ID, notification, 0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
{
|
||||
try
|
||||
{
|
||||
startForeground(DownloaderNotifier.NOTIFICATION_ID, notification);
|
||||
} catch (ForegroundServiceStartNotAllowedException e)
|
||||
{
|
||||
Logger.e(TAG, "Oops! ForegroundService is not allowed", e);
|
||||
}
|
||||
} else
|
||||
{
|
||||
startForeground(DownloaderNotifier.NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ package app.organicmaps.downloader;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.widget.WheelProgressView;
|
||||
import app.organicmaps.util.ThemeUtils;
|
||||
@@ -16,7 +15,7 @@ import app.organicmaps.util.UiUtils;
|
||||
public class DownloaderStatusIcon
|
||||
{
|
||||
private final View mFrame;
|
||||
protected final ShapeableImageView mIcon;
|
||||
protected final ImageView mIcon;
|
||||
private final WheelProgressView mProgress;
|
||||
|
||||
private static final SparseIntArray sIconsCache = new SparseIntArray();
|
||||
|
||||
@@ -43,7 +43,7 @@ public class CopyrightFragment extends BaseMwmFragment
|
||||
{
|
||||
if (!mDelegate.onBackPressed())
|
||||
{
|
||||
((HelpActivity) requireActivity()).stackFragment(HelpFragment.class, getString(R.string.about_help), null);
|
||||
((HelpActivity) requireActivity()).stackFragment(HelpFragment.class, getString(R.string.help), null);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -43,21 +43,15 @@ public class LeftButtonsHolder
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getActiveButtonCode()
|
||||
public LeftButton getActiveButton()
|
||||
{
|
||||
String activeButtonCode = prefs.getString(leftButtonPreferenceKey, DEFAULT_BUTTON_CODE);
|
||||
if (!TextUtils.isEmpty(activeButtonCode))
|
||||
return activeButtonCode;
|
||||
return availableButtons.get(activeButtonCode);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LeftButton getActiveButton()
|
||||
{
|
||||
return availableButtons.get(getActiveButtonCode());
|
||||
}
|
||||
|
||||
public Collection<LeftButton> getAllButtons()
|
||||
{
|
||||
return availableButtons.values();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package app.organicmaps.location;
|
||||
|
||||
import android.app.ForegroundServiceStartNotAllowedException;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
@@ -17,7 +17,6 @@ import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationChannelCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import app.organicmaps.MwmActivity;
|
||||
import app.organicmaps.MwmApplication;
|
||||
@@ -159,11 +158,21 @@ public class TrackRecordingService extends Service implements LocationListener
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Starting Track Recording Foreground service");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
ServiceCompat.startForeground(this, TrackRecordingService.TRACK_REC_NOTIFICATION_ID, getNotificationBuilder(this).build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
|
||||
Logger.i(TAG, "Starting foreground service");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
{
|
||||
try
|
||||
{
|
||||
startForeground(TrackRecordingService.TRACK_REC_NOTIFICATION_ID, getNotificationBuilder(this).build());
|
||||
} catch (ForegroundServiceStartNotAllowedException e)
|
||||
{
|
||||
Logger.e(TAG, "Oops! ForegroundService is not allowed", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
ServiceCompat.startForeground(this, TrackRecordingService.TRACK_REC_NOTIFICATION_ID, getNotificationBuilder(this).build(), 0);
|
||||
{
|
||||
startForeground(TrackRecordingService.TRACK_REC_NOTIFICATION_ID, getNotificationBuilder(this).build());
|
||||
}
|
||||
|
||||
final LocationHelper locationHelper = LocationHelper.from(this);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public class LayerBottomSheetItem
|
||||
case SUBWAY:
|
||||
disabledResource = R.attr.subwayMenuDisabled;
|
||||
enabledResource = R.attr.subwayMenuEnabled;
|
||||
buttonTextResource = R.string.subway;
|
||||
buttonTextResource = R.string.button_layer_subway;
|
||||
break;
|
||||
case ISOLINES:
|
||||
disabledResource = R.attr.isoLinesMenuDisabled;
|
||||
|
||||
@@ -43,7 +43,6 @@ public class LayersAdapter extends RecyclerView.Adapter<LayerHolder>
|
||||
boolean isEnabled = item.getMode().isEnabled(context);
|
||||
|
||||
holder.mButton.setSelected(isEnabled);
|
||||
holder.mButton.setContentDescription(context.getString(item.getTitle()));
|
||||
holder.mTitle.setSelected(isEnabled);
|
||||
holder.mTitle.setText(item.getTitle());
|
||||
boolean isNewLayer = SharedPropertiesUtils.shouldShowNewMarkerForLayerMode(context,
|
||||
|
||||
@@ -213,13 +213,11 @@ public class MapButtonsController extends Fragment
|
||||
)
|
||||
{
|
||||
leftButtonView.setImageResource(R.drawable.ic_christmas_tree);
|
||||
leftButtonView.setContentDescription(getString(R.string.about_help));
|
||||
leftButtonView.setOnClickListener((v) -> mMapButtonClickListener.onMapButtonClick(MapButtons.help));
|
||||
}
|
||||
else
|
||||
{
|
||||
mLeftButton.drawIcon(leftButtonView);
|
||||
leftButtonView.setContentDescription(mLeftButton.getPrefsName());
|
||||
leftButtonView.setOnClickListener((v) -> mLeftButton.onClick(leftButtonView));
|
||||
}
|
||||
// else
|
||||
|
||||
@@ -266,8 +266,7 @@ public class NavigationController implements TrafficManager.TrafficCallback,
|
||||
mSpeedLimit.setSpeedLimit(0, false);
|
||||
return;
|
||||
}
|
||||
final int fSpeedLimit = StringUtils.nativeFormatSpeed(info.speedLimitMps);
|
||||
final boolean speedLimitExceeded = fSpeedLimit < StringUtils.nativeFormatSpeed(location.getSpeed());
|
||||
mSpeedLimit.setSpeedLimit(fSpeedLimit, speedLimitExceeded);
|
||||
final boolean speedLimitExceeded = info.speedLimitMps < location.getSpeed();
|
||||
mSpeedLimit.setSpeedLimit(StringUtils.nativeFormatSpeed(info.speedLimitMps), speedLimitExceeded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static app.organicmaps.util.Constants.Vendor.XIAOMI;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ForegroundServiceStartNotAllowedException;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.location.Location;
|
||||
@@ -27,7 +27,6 @@ import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationChannelCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import app.organicmaps.Framework;
|
||||
@@ -225,11 +224,21 @@ public class NavigationService extends Service implements LocationListener
|
||||
return START_NOT_STICKY; // The service will be stopped by stopSelf().
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Starting Navigation Foreground service");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
ServiceCompat.startForeground(this, NavigationService.NOTIFICATION_ID, getNotificationBuilder(this).build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
|
||||
Logger.i(TAG, "Starting foreground");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
{
|
||||
try
|
||||
{
|
||||
startForeground(NavigationService.NOTIFICATION_ID, getNotificationBuilder(this).build());
|
||||
} catch (ForegroundServiceStartNotAllowedException e)
|
||||
{
|
||||
Logger.e(TAG, "Oops! ForegroundService is not allowed", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
ServiceCompat.startForeground(this, NavigationService.NOTIFICATION_ID, getNotificationBuilder(this).build(), 0);
|
||||
{
|
||||
startForeground(NavigationService.NOTIFICATION_ID, getNotificationBuilder(this).build());
|
||||
}
|
||||
|
||||
final LocationHelper locationHelper = LocationHelper.from(this);
|
||||
|
||||
|
||||
@@ -28,9 +28,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import app.organicmaps.Framework;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.bookmarks.data.DistanceAndAzimut;
|
||||
@@ -70,21 +67,21 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
@NonNull
|
||||
private final ImageView mAltitudeChart;
|
||||
@NonNull
|
||||
private final MaterialTextView mTime;
|
||||
private final TextView mTime;
|
||||
@NonNull
|
||||
private final MaterialTextView mAltitudeDifference;
|
||||
private final TextView mAltitudeDifference;
|
||||
@NonNull
|
||||
private final TextView mTimeVehicle;
|
||||
@Nullable
|
||||
private final MaterialTextView mArrival;
|
||||
private final TextView mArrival;
|
||||
@NonNull
|
||||
private final View mActionFrame;
|
||||
@NonNull
|
||||
private final MaterialTextView mActionMessage;
|
||||
private final TextView mActionMessage;
|
||||
@NonNull
|
||||
private final View mActionButton;
|
||||
@NonNull
|
||||
private final ShapeableImageView mActionIcon;
|
||||
private final ImageView mActionIcon;
|
||||
@NonNull
|
||||
private final DotDividerItemDecoration mTransitViewDecorator;
|
||||
|
||||
@@ -101,10 +98,10 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
TextView error = (TextView) getViewById(activity, frame, R.id.error);
|
||||
Button start = (Button) getViewById(activity, frame, R.id.start);
|
||||
ImageView altitudeChart = (ImageView) getViewById(activity, frame, R.id.altitude_chart);
|
||||
MaterialTextView time = (MaterialTextView) getViewById(activity, frame, R.id.time);
|
||||
TextView time = (TextView) getViewById(activity, frame, R.id.time);
|
||||
TextView timeVehicle = (TextView) getViewById(activity, frame, R.id.time_vehicle);
|
||||
MaterialTextView altitudeDifference = (MaterialTextView) getViewById(activity, frame, R.id.altitude_difference);
|
||||
MaterialTextView arrival = (MaterialTextView) getViewById(activity, frame, R.id.arrival);
|
||||
TextView altitudeDifference = (TextView) getViewById(activity, frame, R.id.altitude_difference);
|
||||
TextView arrival = (TextView) getViewById(activity, frame, R.id.arrival);
|
||||
View actionFrame = getViewById(activity, frame, R.id.routing_action_frame);
|
||||
|
||||
return new RoutingBottomMenuController(activity, altitudeChartFrame, timeElevationLine, transitFrame,
|
||||
@@ -127,10 +124,10 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
@NonNull TextView error,
|
||||
@NonNull Button start,
|
||||
@NonNull ImageView altitudeChart,
|
||||
@NonNull MaterialTextView time,
|
||||
@NonNull MaterialTextView altitudeDifference,
|
||||
@NonNull TextView time,
|
||||
@NonNull TextView altitudeDifference,
|
||||
@NonNull TextView timeVehicle,
|
||||
@Nullable MaterialTextView arrival,
|
||||
@Nullable TextView arrival,
|
||||
@NonNull View actionFrame,
|
||||
@Nullable RoutingBottomMenuListener listener)
|
||||
{
|
||||
@@ -201,12 +198,12 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
|
||||
scrollToBottom(rv);
|
||||
|
||||
MaterialTextView totalTimeView = mTransitFrame.findViewById(R.id.total_time);
|
||||
TextView totalTimeView = mTransitFrame.findViewById(R.id.total_time);
|
||||
totalTimeView.setText(RoutingController.formatRoutingTime(mContext, info.getTotalTime(),
|
||||
R.dimen.text_size_routing_number));
|
||||
View dotView = mTransitFrame.findViewById(R.id.dot);
|
||||
View pedestrianIcon = mTransitFrame.findViewById(R.id.pedestrian_icon);
|
||||
MaterialTextView distanceView = mTransitFrame.findViewById(R.id.total_distance);
|
||||
TextView distanceView = mTransitFrame.findViewById(R.id.total_distance);
|
||||
UiUtils.showIf(info.getTotalPedestrianTimeInSec() > 0, dotView, pedestrianIcon, distanceView);
|
||||
distanceView.setText(info.getTotalPedestrianDistance() + " " + info.getTotalPedestrianDistanceUnits());
|
||||
}
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
package app.organicmaps.settings;
|
||||
|
||||
import static app.organicmaps.backup.BackupUtils.formatReadableFolderPath;
|
||||
import static app.organicmaps.backup.BackupUtils.getMaxBackups;
|
||||
import static app.organicmaps.backup.BackupUtils.isBackupFolderAvailable;
|
||||
import static app.organicmaps.util.StorageUtils.isFolderWritable;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.backup.LocalBackupManager;
|
||||
import app.organicmaps.util.log.Logger;
|
||||
|
||||
|
||||
public class BackupSettingsFragment
|
||||
extends BaseXmlSettingsFragment
|
||||
{
|
||||
private ActivityResultLauncher<Intent> folderPickerLauncher;
|
||||
|
||||
private static final String TAG = LocalBackupManager.class.getSimpleName();
|
||||
public static final String BACKUP_FOLDER_PATH_KEY = "backup_location";
|
||||
public static final String LAST_BACKUP_TIME_KEY = "last_backup_time";
|
||||
private static final String BACKUP_NOW_KEY = "backup_now";
|
||||
public static final String BACKUP_INTERVAL_KEY = "backup_history_interval";
|
||||
public static final String MAX_BACKUPS_KEY = "backup_history_count";
|
||||
public static final int MAX_BACKUPS_DEFAULT_COUNT = 10;
|
||||
public static final String DEFAULT_BACKUP_INTERVAL = "86400000"; // 24 hours in ms
|
||||
|
||||
private LocalBackupManager mBackupManager;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
@Override
|
||||
protected int getXmlResources()
|
||||
{
|
||||
return R.xml.prefs_backup;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
Preference backupLocationOption;
|
||||
@NonNull
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
ListPreference backupIntervalOption;
|
||||
@NonNull
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
Preference maxBackupsOption;
|
||||
@NonNull
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
Preference backupNowOption;
|
||||
@NonNull
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
Preference advancedCategory;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
folderPickerLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
boolean isSuccess = false;
|
||||
|
||||
String lastFolderPath = prefs.getString(BACKUP_FOLDER_PATH_KEY, null);
|
||||
|
||||
if (result.getResultCode() == Activity.RESULT_OK)
|
||||
{
|
||||
Intent data = result.getData();
|
||||
Logger.i(TAG, "Folder selection result: " + data);
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
Uri uri = data.getData();
|
||||
if (uri != null)
|
||||
{
|
||||
takePersistableUriPermission(uri);
|
||||
Logger.i(TAG, "Backup location changed to " + uri);
|
||||
prefs.edit().putString(BACKUP_FOLDER_PATH_KEY, uri.toString()).apply();
|
||||
setFormattedBackupPath(uri);
|
||||
|
||||
runBackup();
|
||||
|
||||
isSuccess = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.w(TAG, "Folder selection result is null");
|
||||
}
|
||||
}
|
||||
else if (result.getResultCode() == Activity.RESULT_CANCELED)
|
||||
{
|
||||
Logger.w(TAG, "User canceled folder selection");
|
||||
if (TextUtils.isEmpty(lastFolderPath))
|
||||
{
|
||||
prefs.edit().putString(BACKUP_FOLDER_PATH_KEY, null).apply();
|
||||
Logger.i(TAG, "Backup settings reset");
|
||||
initBackupLocationOption();
|
||||
}
|
||||
else if (isFolderWritable(requireActivity(), lastFolderPath))
|
||||
{
|
||||
Logger.i(TAG, "Backup location not changed, using previous value " + lastFolderPath);
|
||||
isSuccess = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.e(TAG, "Backup location not changed, but last folder is not writable: " + lastFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
resetLastBackupTime();
|
||||
updateStatusSummaryOption();
|
||||
|
||||
Logger.i(TAG, "Folder selection result: " + isSuccess);
|
||||
applyAdvancedSettings(isSuccess);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
|
||||
{
|
||||
super.onCreatePreferences(savedInstanceState, rootKey);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||
backupLocationOption = findPreference(BACKUP_FOLDER_PATH_KEY);
|
||||
backupIntervalOption = findPreference(BACKUP_INTERVAL_KEY);
|
||||
maxBackupsOption = findPreference(MAX_BACKUPS_KEY);
|
||||
backupNowOption = findPreference(BACKUP_NOW_KEY);
|
||||
|
||||
initBackupLocationOption();
|
||||
initBackupIntervalOption();
|
||||
initMaxBackupsOption();
|
||||
initBackupNowOption();
|
||||
}
|
||||
|
||||
|
||||
private void initBackupLocationOption()
|
||||
{
|
||||
String storedFolderPath = prefs.getString(BACKUP_FOLDER_PATH_KEY, null);
|
||||
boolean isEnabled = false;
|
||||
if (!TextUtils.isEmpty(storedFolderPath))
|
||||
{
|
||||
if (isFolderWritable(requireContext(), storedFolderPath))
|
||||
{
|
||||
setFormattedBackupPath(Uri.parse(storedFolderPath));
|
||||
isEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.e(TAG, "Backup location is not available, path: " + storedFolderPath);
|
||||
showBackupErrorAlertDialog(requireContext().getString(R.string.dialog_report_error_missing_folder));
|
||||
backupLocationOption.setSummary(requireContext().getString(R.string.pref_backup_now_summary_folder_unavailable));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
backupLocationOption.setSummary(requireContext().getString(R.string.pref_backup_location_summary_initial));
|
||||
}
|
||||
|
||||
applyAdvancedSettings(isEnabled);
|
||||
|
||||
backupLocationOption.setOnPreferenceClickListener(preference -> {
|
||||
launchFolderPicker();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setFormattedBackupPath(@NonNull Uri uri)
|
||||
{
|
||||
backupLocationOption.setSummary(formatReadableFolderPath(requireContext(), uri));
|
||||
}
|
||||
|
||||
private void initBackupIntervalOption()
|
||||
{
|
||||
String backupInterval = prefs.getString(BACKUP_INTERVAL_KEY, DEFAULT_BACKUP_INTERVAL);
|
||||
|
||||
CharSequence entry = getEntryForValue(backupIntervalOption, backupInterval);
|
||||
if (entry != null)
|
||||
backupIntervalOption.setSummary(entry);
|
||||
|
||||
backupIntervalOption.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
CharSequence newEntry = getEntryForValue(backupIntervalOption, newValue.toString());
|
||||
Logger.i(TAG, "auto backup interval changed to " + newEntry);
|
||||
if (newEntry != null)
|
||||
backupIntervalOption.setSummary(newEntry);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initMaxBackupsOption()
|
||||
{
|
||||
maxBackupsOption.setSummary(String.valueOf(getMaxBackups(prefs)));
|
||||
|
||||
maxBackupsOption.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
maxBackupsOption.setSummary(newValue.toString());
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initBackupNowOption()
|
||||
{
|
||||
updateStatusSummaryOption();
|
||||
backupNowOption.setOnPreferenceClickListener(preference -> {
|
||||
runBackup();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateStatusSummaryOption()
|
||||
{
|
||||
long lastBackupTime = prefs.getLong(LAST_BACKUP_TIME_KEY, 0L);
|
||||
|
||||
String summary;
|
||||
if (lastBackupTime > 0)
|
||||
{
|
||||
String time = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(lastBackupTime);
|
||||
summary = requireContext().getString(R.string.pref_backup_status_summary_success) + ": " + time;
|
||||
}
|
||||
else
|
||||
{
|
||||
summary = requireContext().getString(R.string.pref_backup_now_summary);
|
||||
}
|
||||
|
||||
backupNowOption.setSummary(summary);
|
||||
}
|
||||
|
||||
private void resetLastBackupTime()
|
||||
{
|
||||
prefs.edit().remove(LAST_BACKUP_TIME_KEY).apply();
|
||||
}
|
||||
|
||||
private void applyAdvancedSettings(boolean isBackupEnabled)
|
||||
{
|
||||
backupIntervalOption.setVisible(isBackupEnabled);
|
||||
maxBackupsOption.setVisible(isBackupEnabled);
|
||||
backupNowOption.setVisible(isBackupEnabled);
|
||||
}
|
||||
|
||||
|
||||
private void runBackup()
|
||||
{
|
||||
String currentFolderPath = prefs.getString(BACKUP_FOLDER_PATH_KEY, null);
|
||||
if (!TextUtils.isEmpty(currentFolderPath))
|
||||
{
|
||||
if (isFolderWritable(requireContext(), currentFolderPath))
|
||||
{
|
||||
mBackupManager = new LocalBackupManager(requireActivity(), currentFolderPath, getMaxBackups(prefs));
|
||||
mBackupManager.setListener(new LocalBackupManager.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onBackupStarted()
|
||||
{
|
||||
Logger.i(TAG, "Manual backup started");
|
||||
|
||||
backupNowOption.setEnabled(false);
|
||||
backupNowOption.setSummary(R.string.pref_backup_now_summary_progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackupFinished()
|
||||
{
|
||||
Logger.i(TAG, "Manual backup successful");
|
||||
|
||||
backupNowOption.setEnabled(true);
|
||||
backupNowOption.setSummary(R.string.pref_backup_now_summary_ok);
|
||||
|
||||
prefs.edit().putLong(LAST_BACKUP_TIME_KEY, System.currentTimeMillis()).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackupFailed(LocalBackupManager.ErrorCode errorCode)
|
||||
{
|
||||
String errorMessage;
|
||||
if (errorCode == LocalBackupManager.ErrorCode.EMPTY_CATEGORY)
|
||||
{
|
||||
errorMessage = requireContext().getString(R.string.pref_backup_now_summary_empty_lists);
|
||||
Logger.i(TAG, "Nothing to backup");
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = requireContext().getString(R.string.pref_backup_now_summary_failed);
|
||||
Logger.e(TAG, "Manual backup has failed: " + errorCode);
|
||||
}
|
||||
|
||||
backupNowOption.setEnabled(true);
|
||||
backupNowOption.setSummary(errorMessage);
|
||||
|
||||
if (errorCode != LocalBackupManager.ErrorCode.EMPTY_CATEGORY)
|
||||
{
|
||||
showBackupErrorAlertDialog(requireContext().getString(R.string.dialog_report_error_with_logs));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mBackupManager.doBackup();
|
||||
}
|
||||
else
|
||||
{
|
||||
backupNowOption.setSummary(R.string.pref_backup_now_summary_folder_unavailable);
|
||||
showBackupErrorAlertDialog(requireContext().getString(R.string.dialog_report_error_missing_folder));
|
||||
Logger.e(TAG, "Manual backup error: folder " + currentFolderPath + " unavailable");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
backupNowOption.setSummary(R.string.pref_backup_now_summary_folder_unavailable);
|
||||
Logger.e(TAG, "Manual backup error: no folder selected");
|
||||
}
|
||||
}
|
||||
|
||||
private void launchFolderPicker()
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
|
||||
|
||||
PackageManager packageManager = requireActivity().getPackageManager();
|
||||
if (intent.resolveActivity(packageManager) != null)
|
||||
folderPickerLauncher.launch(intent);
|
||||
else
|
||||
showNoFileManagerError();
|
||||
}
|
||||
|
||||
private void showNoFileManagerError()
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setMessage(R.string.error_no_file_manager_app)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showBackupErrorAlertDialog(String message)
|
||||
{
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.pref_backup_now_summary_failed)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void takePersistableUriPermission(Uri uri)
|
||||
{
|
||||
requireContext().getContentResolver().takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static CharSequence getEntryForValue(@NonNull ListPreference listPref, @NonNull CharSequence value)
|
||||
{
|
||||
CharSequence[] entryValues = listPref.getEntryValues();
|
||||
CharSequence[] entries = listPref.getEntries();
|
||||
|
||||
if (entryValues == null || entries == null)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < entryValues.length; i++)
|
||||
{
|
||||
if (entryValues[i].equals(value))
|
||||
return entries[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -184,15 +184,15 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
{
|
||||
getSettingsActivity().stackFragment(VoiceInstructionsSettingsFragment.class, getString(R.string.pref_tts_enable_title), null);
|
||||
}
|
||||
else if (key.equals(getString(R.string.pref_help)))
|
||||
{
|
||||
startActivity(new Intent(requireActivity(), HelpActivity.class));
|
||||
}
|
||||
else if (key.equals(getString(R.string.pref_map_locale)))
|
||||
{
|
||||
LanguagesFragment langFragment = (LanguagesFragment)getSettingsActivity().stackFragment(LanguagesFragment.class, getString(R.string.change_map_locale), null);
|
||||
langFragment.setListener(this);
|
||||
}
|
||||
else if (key.equals(getString(R.string.pref_backup)))
|
||||
{
|
||||
getSettingsActivity().stackFragment(BackupSettingsFragment.class, getString(R.string.pref_backup_title), null);
|
||||
}
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package app.organicmaps.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -11,13 +10,10 @@ import android.provider.DocumentsContract;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import app.organicmaps.BuildConfig;
|
||||
import app.organicmaps.util.log.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
@@ -327,76 +323,4 @@ public class StorageUtils
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean copyFileToDocumentFile(
|
||||
@NonNull Activity activity,
|
||||
@NonNull File sourceFile,
|
||||
@NonNull DocumentFile targetFile
|
||||
)
|
||||
{
|
||||
try (
|
||||
InputStream in = new FileInputStream(sourceFile);
|
||||
OutputStream out = activity.getContentResolver().openOutputStream(targetFile.getUri())
|
||||
)
|
||||
{
|
||||
if (out == null)
|
||||
{
|
||||
Logger.e(TAG, "Failed to open output stream for " + targetFile.getUri());
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
|
||||
while ((length = in.read(buffer)) > 0)
|
||||
out.write(buffer, 0, length);
|
||||
|
||||
out.flush();
|
||||
return true;
|
||||
} catch (IOException e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to copy file from " + sourceFile.getAbsolutePath() + " to " + targetFile.getUri(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteDirectoryRecursive(@NonNull DocumentFile dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (DocumentFile file : dir.listFiles())
|
||||
{
|
||||
if (file.isDirectory())
|
||||
deleteDirectoryRecursive(file);
|
||||
else
|
||||
file.delete();
|
||||
}
|
||||
dir.delete();
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to delete directory: " + dir.getUri(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFolderWritable(Context context, String folderPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri folderUri = Uri.parse(folderPath);
|
||||
DocumentFile folder = DocumentFile.fromTreeUri(context, folderUri);
|
||||
if (folder != null && folder.canWrite())
|
||||
{
|
||||
DocumentFile tempFile = folder.createFile("application/octet-stream", "temp_file");
|
||||
if (tempFile != null)
|
||||
{
|
||||
tempFile.delete();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to check if folder is writable: " + folderPath, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@ public final class PlacePageButtons extends Fragment implements Observer<List<Pl
|
||||
TextView title = parent.findViewById(R.id.title);
|
||||
|
||||
title.setText(current.getTitle());
|
||||
parent.setContentDescription(getString(current.getTitle()));
|
||||
@AttrRes final int tint = current.getType() == ButtonType.BOOKMARK_DELETE
|
||||
? R.attr.iconTintActive
|
||||
: R.attr.iconTint;
|
||||
|
||||
@@ -56,9 +56,6 @@ public class PlacePageLinksFragment extends Fragment implements Observer<MapObje
|
||||
private View mWikimedia;
|
||||
private TextView mTvWikimedia;
|
||||
|
||||
private View mPanoramax;
|
||||
private TextView mTvPanoramax;
|
||||
|
||||
private PlacePageViewModel mViewModel;
|
||||
private MapObject mMapObject;
|
||||
|
||||
@@ -166,11 +163,6 @@ public class PlacePageLinksFragment extends Fragment implements Observer<MapObje
|
||||
mTvLinePage = mFrame.findViewById(R.id.tv__place_line_page);
|
||||
mLinePage.setOnClickListener((v) -> openUrl(Metadata.MetadataType.FMD_CONTACT_LINE));
|
||||
mLinePage.setOnLongClickListener((v) -> copyUrl(mLinePage, Metadata.MetadataType.FMD_CONTACT_LINE));
|
||||
|
||||
mPanoramax = mFrame.findViewById(R.id.ll__place_panoramax);
|
||||
mTvPanoramax = mFrame.findViewById(R.id.tv__place_panoramax);
|
||||
mPanoramax.setOnClickListener((v) -> openUrl(Metadata.MetadataType.FMD_PANORAMAX));
|
||||
mTvPanoramax.setOnLongClickListener((v) -> copyUrl(mPanoramax, Metadata.MetadataType.FMD_PANORAMAX));
|
||||
}
|
||||
|
||||
private void openUrl(Metadata.MetadataType type)
|
||||
@@ -234,9 +226,6 @@ public class PlacePageLinksFragment extends Fragment implements Observer<MapObje
|
||||
|
||||
final String line = mMapObject.getMetadata(Metadata.MetadataType.FMD_CONTACT_LINE);
|
||||
refreshMetadataOrHide(line, mLinePage, mTvLinePage);
|
||||
|
||||
final String panoramax = mMapObject.getMetadata(Metadata.MetadataType.FMD_PANORAMAX);
|
||||
refreshMetadataOrHide(panoramax, mPanoramax, mTvPanoramax);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="144dp"
|
||||
android:height="144dp"
|
||||
android:viewportWidth="144"
|
||||
android:viewportHeight="144">
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="M40.34 29.42a41.6 41.6 0 0 0-19.66 5.03q-8.77 4.85-13.8 14.42-5.05 9.57-5.05 23.18 0 13.4 5.04 22.88a36 36 0 0 0 13.71 14.51q8.67 5.04 19.76 5.04 7.95 0 14.2-2.42A33.6 33.6 0 0 0 72.8 96.24a33 33 0 0 0 3.43-11.09l-17.64-0.1q-0.6 3.33-2.22 5.95a12 12 0 0 1-3.83 4.33 19 19 0 0 1-5.44 2.73q-2.93 0.9-6.45 0.9-6.15 0-10.99-3.02-4.82-3.03-7.46-9.07-2.61-6.05-2.62-14.82 0-8.56 2.62-14.61 2.63-6.06 7.46-9.28 4.64-3.22 11.09-3.22 3.53 0 6.55 1 3.13 1.01 5.44 3.03a14.5 14.5 0 0 1 3.83 4.63q1.51 2.63 2.02 6.05h17.64a37 37 0 0 0-3.83-12.8 30 30 0 0 0-7.76-9.47 34 34 0 0 0-10.99-5.95 43 43 0 0 0-13.3-2.01m71.29 21.16q-9.37 0-16.23 4.03a26.6 26.6 0 0 0-10.59 11.2q-3.62 7.14-3.62 16.82 0 9.48 3.62 16.73a28 28 0 0 0 10.69 11.2q6.84 4.01 16.28 4.02 9.24 0 16.07-4.03a27.4 27.4 0 0 0 10.59-11.19q3.72-7.25 3.73-16.73 0-9.87-3.73-16.93a27.6 27.6 0 0 0-10.59-11.09q-6.86-4.03-16.22-4.03m0.2 13.1a9.8 9.8 0 0 1 7.05 2.42 16 16 0 0 1 4.34 6.76q1.5 4.23 1.5 9.57a33 33 0 0 1-1.5 9.68 14 14 0 0 1-4.44 6.65q-2.72 2.52-7.05 2.52-4.24 0-7.26-2.52a15 15 0 0 1-4.44-6.65 31 31 0 0 1-1.4-9.68q0-5.34 1.4-9.57 1.52-4.24 4.44-6.76 2.92-2.41 7.36-2.42"
|
||||
android:pathData="M28.2,58.5q-1.6,0 -2.87,-0.74 -1.26,-0.73 -1.98,-2.09t-0.72,-3.21q0,-1.87 0.72,-3.23t1.98,-2.1q1.27,-0.72 2.87,-0.72 1.03,0 1.91,0.3 0.88,0.31 1.56,0.88 0.68,0.56 1.11,1.36 0.44,0.79 0.58,1.77L30.9,50.72q-0.1,-0.52 -0.34,-0.92 -0.23,-0.4 -0.58,-0.68t-0.8,-0.43q-0.43,-0.15 -0.96,-0.15 -1,0 -1.7,0.48t-1.08,1.36 -0.38,2.08 0.37,2.07 1.09,1.35 1.7,0.48q0.53,0 0.97,-0.14t0.78,-0.43 0.59,-0.68 0.35,-0.91h2.44q-0.14,0.97 -0.58,1.76 -0.43,0.8 -1.11,1.36t-1.57,0.88q-0.87,0.3 -1.9,0.3m10.56,0q-1.25,0 -2.2,-0.55 -0.95,-0.54 -1.48,-1.51 -0.53,-0.98 -0.53,-2.28t0.53,-2.27 1.48,-1.52 2.2,-0.55q1.27,0 2.21,0.55 0.95,0.54 1.48,1.52 0.54,0.97 0.54,2.27t-0.54,2.28 -1.48,1.51 -2.2,0.55m0,-1.88q0.57,0 1,-0.3 0.42,-0.3 0.65,-0.84 0.24,-0.55 0.24,-1.32t-0.24,-1.32q-0.23,-0.55 -0.66,-0.84 -0.42,-0.3 -0.99,-0.3 -0.55,0 -0.98,0.3 -0.42,0.29 -0.66,0.84 -0.23,0.55 -0.23,1.32 0,0.78 0.23,1.33 0.24,0.54 0.66,0.84t0.98,0.29m5.82,1.69L44.58,46.6h3.53l1.93,5.45 0.29,0.91 0.33,1.13q0.18,0.58 0.32,1.11l0.24,0.9h-0.6l0.23,-0.9 0.32,-1.11 0.34,-1.13 0.27,-0.9 1.91,-5.46h3.53v11.7h-2.37v-5.56l0.01,-0.93 0.03,-1.16 0.03,-1.25 0.03,-1.2h0.29l-0.38,1.27 -0.37,1.27 -0.34,1.13 -0.28,0.87 -1.98,5.57h-1.96l-2,-5.57 -0.3,-0.87 -0.35,-1.13 -0.38,-1.27 -0.39,-1.27h0.35l0.02,1.2 0.03,1.25 0.03,1.16 0.01,0.93v5.57zM61.72,58.45q-0.82,0 -1.47,-0.28 -0.64,-0.27 -1.01,-0.82 -0.37,-0.56 -0.37,-1.39 0,-0.7 0.26,-1.16 0.26,-0.47 0.7,-0.75 0.46,-0.3 1.04,-0.44 0.6,-0.15 1.24,-0.2 0.76,-0.09 1.2,-0.16 0.46,-0.07 0.65,-0.2 0.2,-0.14 0.2,-0.4v-0.06q0,-0.3 -0.17,-0.53 -0.16,-0.24 -0.47,-0.37 -0.3,-0.14 -0.72,-0.14t-0.75,0.14 -0.52,0.38q-0.18,0.24 -0.22,0.56h-2.17q0.05,-0.85 0.5,-1.48 0.46,-0.63 1.27,-0.97 0.82,-0.35 1.96,-0.35 0.85,0 1.52,0.2 0.67,0.19 1.12,0.56 0.47,0.37 0.7,0.9 0.24,0.52 0.24,1.19v5.63h-2.27v-1.17h-0.03q-0.22,0.4 -0.54,0.7t-0.79,0.45 -1.1,0.16m0.65,-1.61q0.56,0 0.96,-0.2t0.63,-0.57 0.22,-0.8v-0.83l-0.27,0.11 -0.39,0.1 -0.48,0.08 -0.53,0.09q-0.4,0.06 -0.72,0.2 -0.33,0.12 -0.52,0.34 -0.18,0.21 -0.18,0.54 0,0.3 0.15,0.5 0.16,0.22 0.45,0.33t0.68,0.1m5.98,4.75L68.35,50.01h2.29v1.12h0.02q0.26,-0.43 0.62,-0.72 0.37,-0.29 0.83,-0.43 0.48,-0.15 1.01,-0.15 1.06,0 1.86,0.55 0.81,0.55 1.26,1.52 0.46,0.97 0.46,2.25 0,1.3 -0.45,2.27 -0.45,0.98 -1.25,1.52t-1.9,0.54q-0.54,0 -1,-0.16 -0.46,-0.15 -0.82,-0.47t-0.59,-0.79h-0.02v4.53zM72.51,56.58q0.56,0 0.97,-0.28 0.42,-0.29 0.65,-0.83 0.23,-0.55 0.23,-1.33t-0.23,-1.32 -0.65,-0.83q-0.4,-0.3 -0.97,-0.3 -0.59,0 -1.03,0.31 -0.44,0.3 -0.69,0.85 -0.23,0.54 -0.23,1.29 0,0.74 0.23,1.29 0.25,0.55 0.69,0.85 0.45,0.3 1.03,0.3m9.18,1.92q-1.08,0 -1.92,-0.33 -0.84,-0.34 -1.35,-0.97 -0.5,-0.62 -0.58,-1.51h2.28q0.07,0.54 0.46,0.85 0.4,0.3 1.07,0.3 0.64,0 1,-0.24 0.37,-0.24 0.37,-0.63 0,-0.33 -0.28,-0.53 -0.27,-0.2 -0.79,-0.3l-1.45,-0.29q-1.21,-0.23 -1.84,-0.84 -0.63,-0.62 -0.63,-1.58 0,-0.78 0.43,-1.37t1.21,-0.91q0.8,-0.33 1.88,-0.33t1.88,0.35 1.25,0.97q0.46,0.63 0.48,1.49h-2.14q-0.01,-0.5 -0.4,-0.83 -0.38,-0.33 -1,-0.33 -0.6,0 -0.94,0.26 -0.34,0.25 -0.34,0.62 0,0.32 0.27,0.52t0.74,0.31l1.57,0.3q1.26,0.25 1.85,0.8 0.6,0.56 0.6,1.48 0,0.82 -0.48,1.44 -0.46,0.62 -1.3,0.96 -0.82,0.34 -1.9,0.34"
|
||||
android:fillColor="#fff"/>
|
||||
</vector>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_gravity="center_vertical" >
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -33,7 +33,7 @@
|
||||
tools:text="5 h 55 min • 1555km"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/altitude_difference"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="5 h 55 min • 1555km"/>
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/altitude_difference"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/altitude_chart_time_distance_height"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_gravity="center_vertical" >
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -33,7 +33,7 @@
|
||||
tools:text="5 h 55 min • 1555km"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/altitude_difference"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -64,7 +64,7 @@
|
||||
tools:text="5 h 55 min • 1555km"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/arrival"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
android:id="@+id/numbers"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -13,7 +13,7 @@
|
||||
android:ellipsize="end"
|
||||
android:layout_marginBottom="4dp"
|
||||
tools:text="5 h 55 min • 1555km"/>
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/arrival"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -23,7 +23,7 @@
|
||||
tools:text="Arrival 13:03"
|
||||
style="@style/MwmWidget.TextView.PlanDetail.Number.Secondary"
|
||||
android:textSize="@dimen/text_size_routing_plan_detail_arrival"/>
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/altitude_difference"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<include
|
||||
layout="@layout/toolbar_extended"/>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="@dimen/settings_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="?actionBarSize"
|
||||
@@ -18,6 +18,6 @@
|
||||
style="@style/MwmWidget.FrameLayout.Elevation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="@dimen/margin_half"
|
||||
android:layout_marginBottom="@dimen/margin_half">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -31,7 +31,7 @@
|
||||
tools:text="5 h 55 min • 1555km"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/altitude_difference"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/altitude_chart_time_distance_height"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_centerVertical="true"/>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -37,7 +37,7 @@
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@id/downloader_status_frame"
|
||||
android:layout_toStartOf="@id/size">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/found_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -46,7 +46,7 @@
|
||||
tools:text="Крымск"
|
||||
tools:background="#60FF00FF"/>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -54,7 +54,7 @@
|
||||
tools:text="Донецкая область"
|
||||
tools:background="#40FF0000"/>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:background="#400000FF">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
wheel:wheelSecondaryColor="?dividerHorizontal"
|
||||
wheel:wheelThickness="@dimen/margin_eighth"/>
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<ImageView
|
||||
android:id="@+id/downloader_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textColorHint="?android:textColorSecondary"
|
||||
app:endIconMode="custom"
|
||||
app:endIconCheckable="false"
|
||||
app:endIconContentDescription="@string/clear"
|
||||
app:endIconDrawable="@drawable/ic_clear_rounded"
|
||||
app:endIconTint="?android:textColorSecondary">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
@@ -81,7 +79,6 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignEnd="@id/divideerrr"
|
||||
android:background="?clickableBackground"
|
||||
android:contentDescription="@string/bookmark_color"
|
||||
android:padding="@dimen/margin_half"
|
||||
tools:src="@drawable/ic_bookmark_none" />
|
||||
</RelativeLayout>
|
||||
|
||||