Compare commits
126 Commits
test/2025.
...
2025.06.30
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fe3526bf4 | ||
|
|
b31819fcfb | ||
|
|
fdc05a2a6c | ||
|
|
1584adc4cb | ||
|
|
4623291461 | ||
|
|
b409d805cc | ||
|
|
ac30139432 | ||
|
|
df3850b86c | ||
|
|
70c3f725f9 | ||
|
|
a830e4b444 | ||
|
|
142759c4d0 | ||
|
|
5ee1c2a2a0 | ||
|
|
a21f2125bc | ||
|
|
5346193f80 | ||
|
|
a94cb3531d | ||
|
|
ceb94232a6 | ||
|
|
5ea4bfef34 | ||
|
|
6014a899d1 | ||
|
|
c4d8ac0426 | ||
|
|
b6b02ba9d7 | ||
|
|
a274ed927e | ||
|
|
bbadd3f338 | ||
|
|
0e9bc6f960 | ||
|
|
a448422ad6 | ||
|
|
e3d8454b6c | ||
|
|
99be248ec8 | ||
|
|
cd0ffab996 | ||
|
|
2dca80cd0f | ||
|
|
d01de6ade7 | ||
|
|
f7f73f04b1 | ||
|
|
eee299f6cd | ||
|
|
5b43e4ec9b | ||
|
|
733c0ae8a8 | ||
|
|
30ee5c30e1 | ||
|
|
d562f23256 | ||
|
|
ecb44b5ae9 | ||
|
|
c49cf48575 | ||
|
|
4581907f00 | ||
|
|
423d45a1bb | ||
|
|
4d8e7c39fd | ||
|
|
273eeed9f3 | ||
|
|
754748123c | ||
|
|
b58bacddf1 | ||
|
|
a985bf8349 | ||
|
|
b4f115a8fa | ||
|
|
df89761eba | ||
|
|
25e49b0fe1 | ||
|
|
6a10cffe51 | ||
|
|
495167c11f | ||
|
|
d44d671550 | ||
|
|
d44d575368 | ||
|
|
fbddff009e | ||
|
|
58bab61890 | ||
|
|
464b3cf59a | ||
|
|
4a48d43240 | ||
|
|
d68544de13 | ||
|
|
ae1c975627 | ||
|
|
090b7c21fc | ||
|
|
126d4f6373 | ||
|
|
78b54acad4 | ||
|
|
35cda6d342 | ||
|
|
1ad5975790 | ||
|
|
275695b85e | ||
|
|
d8d93bc0cb | ||
|
|
2ecdf10fde | ||
|
|
40164a01d7 | ||
|
|
d6478a0b4d | ||
|
|
c04c3b7783 | ||
|
|
e960e8240c | ||
|
|
ef18d2dee8 | ||
|
|
0cb1372075 | ||
|
|
d74f7c1594 | ||
|
|
32eefec665 | ||
|
|
25ff7aaf97 | ||
|
|
abf19976f6 | ||
|
|
efab994b0f | ||
|
|
8b3d47481f | ||
|
|
32b64356b3 | ||
|
|
7b600efd5c | ||
|
|
bdcee82a13 | ||
|
|
48dba27018 | ||
|
|
d78a7d83e6 | ||
|
|
8e6840d85d | ||
|
|
3503d0993e | ||
|
|
1932bc0bfb | ||
|
|
793537457e | ||
|
|
20bae88649 | ||
|
|
cfd68f0830 | ||
|
|
82b1c8dc96 | ||
|
|
21c9de51c5 | ||
|
|
c9b02b438d | ||
|
|
14a1c1b665 | ||
|
|
e4e6f0b3c5 | ||
|
|
7b4b08c8c2 | ||
|
|
e500a9e5fa | ||
|
|
fe92bf2359 | ||
|
|
fa7cb55ada | ||
|
|
bb03e9334c | ||
|
|
2b2ee51923 | ||
|
|
9014fde479 | ||
|
|
4dd049dad7 | ||
|
|
957afb1709 | ||
|
|
92d7499012 | ||
|
|
f5db0e7cce | ||
|
|
ee49ed57b2 | ||
|
|
a7e06ce39d | ||
|
|
f746ab2c2d | ||
|
|
a88798fbf2 | ||
|
|
18be61e789 | ||
|
|
494b00b862 | ||
|
|
99a0f3973e | ||
|
|
ecc454a2bd | ||
|
|
0051c7120d | ||
|
|
b7e0c12409 | ||
|
|
e2efbbe68c | ||
|
|
d677112edd | ||
|
|
b79724f248 | ||
|
|
5d0b8f1c04 | ||
|
|
48353637ac | ||
|
|
f32462ce94 | ||
|
|
f3bc6d9d8b | ||
|
|
529272cd54 | ||
|
|
1362f8651d | ||
|
|
21b948c9e3 | ||
|
|
b5d6a2cd8a | ||
|
|
195f380024 |
8
.forgejo/workflows/dco.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
name: dco
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: codeberg-tiny
|
||||
steps:
|
||||
- uses: https://github.com/KineticCafe/actions-dco@v1
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
LANG: en_US.UTF-8 # Fastlane complains that the terminal is using ASCII.
|
||||
LANGUAGE: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
TEST_RESULTS_BUNDLE_NAME: OMaps-Test-Results
|
||||
TEST_RESULTS_BUNDLE_NAME: CoMaps-Test-Results
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -74,8 +74,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme OMaps \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 16 Pro Max,OS=latest' \
|
||||
@@ -97,8 +97,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild build \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme OMaps \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Release \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-quiet \
|
||||
|
||||
14
.github/workflows/ios-check.yaml
vendored
@@ -15,6 +15,7 @@ jobs:
|
||||
LANGUAGE: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
TEST_RESULTS_BUNDLE_NAME: CoMaps-Test-Results
|
||||
SIMULATOR_DEVICE: 'iPhone 16 Pro Max'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -50,12 +51,15 @@ jobs:
|
||||
if: matrix.buildType == 'Debug'
|
||||
shell: bash
|
||||
run: |
|
||||
# Start sim before the build to make sure it's booted when tests start.
|
||||
xcrun simctl boot "${{ env.SIMULATOR_DEVICE }}" || true
|
||||
xcrun simctl bootstatus "${{ env.SIMULATOR_DEVICE }}" -b
|
||||
xcodebuild test \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme OMaps \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 16 Pro Max,OS=latest' \
|
||||
-destination "platform=iOS Simulator,name=${{ env.SIMULATOR_DEVICE }},OS=latest" \
|
||||
-quiet \
|
||||
-resultBundlePath ${{ env.TEST_RESULTS_BUNDLE_NAME }}.xcresult \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
@@ -74,8 +78,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
xcodebuild build \
|
||||
-workspace xcode/omim.xcworkspace \
|
||||
-scheme OMaps \
|
||||
-workspace xcode/CoMaps.xcworkspace \
|
||||
-scheme CoMaps \
|
||||
-configuration Release \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-quiet \
|
||||
|
||||
2
.gitignore
vendored
@@ -62,7 +62,7 @@ iphone/*/build/*
|
||||
tools/emacsmode/build
|
||||
**/DerivedData/*
|
||||
**/xcshareddata/*
|
||||
!iphone/Maps/Maps.xcodeproj/xcshareddata/xcschemes/OMaps.xcscheme
|
||||
!iphone/Maps/Maps.xcodeproj/xcshareddata/xcschemes/CoMaps.xcscheme
|
||||
**/xcuserdata
|
||||
**/xcschemes
|
||||
iphone/**/*.moved-aside
|
||||
|
||||
@@ -7,6 +7,10 @@ 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,20 +32,16 @@ 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.
|
||||
|
||||
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/apple-appstore.png" alt="App Store" width="160">](https://apps.apple.com/app/comaps/id6747180809)
|
||||
[<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.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" />
|
||||
<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" />
|
||||
</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.
|
||||
@@ -105,9 +101,9 @@ There is a dedicated Zulip chat for active contributors: [comaps.zulipchat.com](
|
||||
|
||||
### Feedback
|
||||
|
||||
<!-- 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)**. -->
|
||||
|
||||
- **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)**.
|
||||
- Star our repos on Codeberg
|
||||
- Report bugs and discuss features at [the issue tracker](https://codeberg.org/comaps/comaps/issues)
|
||||
|
||||
@@ -117,6 +113,10 @@ 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 Beta apk for testing: './gradlew assembleFdroidBeta'
|
||||
Or to compile a redistributable Fdroid Test apk for testing: './gradlew assembleFdroidBeta'
|
||||
|
||||
Or to build beta apks for all flavors: './gradlew assembleBeta'
|
||||
Or to build test apks for all flavors: './gradlew assembleBeta'
|
||||
|
||||
To see all available build targets './gradlew tasks'
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ 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
|
||||
@@ -297,7 +296,6 @@ 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,8 +1,6 @@
|
||||
• 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
|
||||
• 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
|
||||
|
||||
|
Before Width: | Height: | Size: 747 KiB |
|
After Width: | Height: | Size: 628 KiB |
|
Before Width: | Height: | Size: 749 KiB |
|
After Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 730 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 590 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 263 KiB |
@@ -1,8 +1,7 @@
|
||||
• 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
|
||||
• 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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
• 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
|
||||
@@ -0,0 +1,32 @@
|
||||
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 eta pribatutasunearekin
|
||||
CoMaps- Mendia, bizikleta, autoa, dena offline
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
Yhteisövetoinen, ilmainen ja avoimeen lähdekoodiin perustuva karttasovellus, jonka pohjalla käytetään OpenStreetMapin avointa karttadataa. Sovelluksen kehityksessä on sitouduttu läpinäkyvyyteen, yksityisyyteen ja voittoa tavoittelemattomuuteen. CoMapsin projekti on haarautunut Organic Mapsista, joka taas on haarautunut aiemmin Maps.ME:stä
|
||||
|
||||
Lue lisää projektin tavotteista ja suunnasta osoitteesta <b><i>codeberg.org/comaps</i></b>.
|
||||
Liity yhteisöön ja auta kehittämään paras saatavilla oleva karttasovellus
|
||||
• Käytä sovellusta ja kerro siitä myös muille
|
||||
• Anna palautetta ja raportoi ongelmia
|
||||
• Päivitä karttoja, joko sovelluksessa tai OpenStreetMapin verkkosivuilla
|
||||
|
||||
‣ <b>Offline-painotteinen</b>: Suunnittele ja navigoi ulkomailla ilman mobiiliverkkoja. Kaikki sovelluksen toiminnot on suunniteltu käytettäväksi ilman verkkoyhteyttä.
|
||||
‣ <b>Kunnioittaa yksityisyyttä</b>: Sovellus on suunniteltu yksilön yksityisyys silmälläpitäen. Sovellus ei tunnista tai kerää tietoja sinusta. Mainosvapaa.
|
||||
‣ <b>Yksinkertainen ja viimeistelty</b>: Olennaiset ominaisuudet, joita on helppo käyttää.
|
||||
‣ <b>Säästä akkua ja tallennustilaa</b>: Ei kuluta akkua, kuten muut navigointisovellukset. Kompaktit kartat säästävät arvokasta tallennustilaa puhelimessasi.
|
||||
‣ <b>Ilmainen ja yhteisön luoma</b>: Vapaaehtoiset, kuten sinä olette auttaneet sovelluksen kehityksessä lisäämällä paikkoja OpenStreetMap:iin, testaamalla sovellusta ja antamalla palautetta. Voit myös auttaa kehittämällä ominaisuuksia ja lahjoittamalla sovelluskehitykseen
|
||||
‣ <b>Avoin ja läpinäkyvä päätöksenteko sekä rahoitus. Voittoa tavoittelematon ja täysin avoimeen lähdekoodiin perustuva.</b>
|
||||
|
||||
<b>Tärkeimmät ominaisuudet</b>:
|
||||
• Ladattavat yksityiskohtaiset kartat paikoista, joita ei löydy edes Google Maps:sta
|
||||
• Ulkoilutila, josta löytyy korostettuna reitit, leirintäpaikat, vesipisteet, huiput ja korkeuserot yms.
|
||||
• Kävely- ja pyörätiet
|
||||
• Kiinnostavat paikat, kuten ravintolat, huoltoasemat, hotellit, kaupat, nähtävyydet ja monta muuta
|
||||
• Etsi nimellä, osoitteella tai kiinnostavan paikan kategorialla
|
||||
• Navigointi ääni-ilmoituksilla kävellessä, pyöräillessä tai ajaessa
|
||||
• Tallenna suosikkipaikkasi yhdellä napautuksella
|
||||
• Offline Wikipedia-artikkelit
|
||||
• Maanalaisen liikenteen tasot ja ohjeet
|
||||
• Reittien tallennus
|
||||
• Tuo ja vie kirjanmerkkejä ja reittejä KML-, KMZ- ja GPX-formaateissa
|
||||
• Tumma tila iltaa ja yötä varten
|
||||
• Paranna karttadataa kaikille sisäänrakennetulla editorilla
|
||||
|
||||
<b>Vapaus on täällä</b>
|
||||
Löydä matkasi ja navigoi maailmalla yksityisyyden ja yhteisön tukemana!
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Vaella, pyöräile, autoile ilman verkkoyhteyttä, yksityisesti
|
||||
CoMaps - Navigoi ilman verkkoyhteyttä yksityisesti
|
||||
|
||||
|
Before Width: | Height: | Size: 737 KiB |
|
After Width: | Height: | Size: 655 KiB |
|
Before Width: | Height: | Size: 765 KiB |
|
After Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 735 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 594 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 263 KiB |
@@ -1,8 +1,6 @@
|
||||
• 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
|
||||
• 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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Navegación doada - Descubre máis sobre o teu camiño - Creada pola comunidade
|
||||
1
android/app/src/fdroid/play/listings/gl-ES/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Aplicación de mapas privada, sen conexión
|
||||
@@ -1,8 +0,0 @@
|
||||
• 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
android/app/src/fdroid/play/listings/nl-NL/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Wandel, fiets, rijdt offline met privacy
|
||||
@@ -1,8 +0,0 @@
|
||||
• 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
|
||||
55
android/app/src/fdroid/play/listings/pt/full-description.txt
Normal file
@@ -0,0 +1,55 @@
|
||||
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!
|
||||
@@ -0,0 +1 @@
|
||||
Navegação fácil nos mapas - Descubra mais sobre o seu percurso - Feito por todos
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Andar, Pedalar, Dirigir Offline com Privacidade
|
||||
CoMaps - Mapas e Navegação - Offline e Privada
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
Бесплатное картографическое приложение с открытым исходным кодом, основанное на данных OpenStreetMap и подкрепленное обязательствами по прозрачности, конфиденциальности и некоммерческому характеру. CoMaps - это форк/ответвление Organic Maps, который, в свою очередь, является форком Maps.ME.
|
||||
Бесплатное и свободное картографическое приложение, основанное на данных OpenStreetMap и подкреплённое обязательствами по прозрачности, конфиденциальности и некоммерческой направленности. CoMaps — это ответвление от Organic Maps, которое, в свою очередь, является ответвлением от Maps.ME.
|
||||
|
||||
‣ <b>Приоритет на работу без интернета</b>: Планируйте и ориентируйтесь в путешествии за границей, не нуждаясь в сотовой связи и т.д. Все функции приложения рассчитаны на работу в автономном режиме.
|
||||
‣ <b>Соблюдение конфиденциальности</b>: Приложение разработано с учетом требований конфиденциальности - оно не идентифицирует людей, не отслеживает и не собирает личную информацию. Без рекламы.
|
||||
‣ <b>Простота и Элегантность</b>: Необходимые и легкие в использовании функции, которые просто работают.
|
||||
Подробнее о причинах проекта и его направлении читайте на <a href="https://codeberg.org/comaps">сайте</a>.
|
||||
|
||||
Присоединяйтесь к сообществу и помогите создать лучшее приложение с картами
|
||||
• Используйте приложение и распространяйте информацию о нём
|
||||
• Оставляйте отзывы и сообщайте о проблемах
|
||||
• Обновляйте данные карт в приложении или на веб-сайте OpenStreetMap
|
||||
|
||||
‣ <b>Приоритет на работу без интернета</b>: Планируйте и ориентируйтесь в путешествии за границей, не нуждаясь в сотовой связи и т.д. Всё в приложении рассчитано на работу в автономном режиме.
|
||||
‣ <b>Соблюдение конфиденциальности</b>: Приложение разработано с учётом требований конфиденциальности — оно не идентифицирует людей, не отслеживает и не собирает личную информацию. Без рекламы.
|
||||
‣ <b>Простота и отточенность</b>: Основные и простые в использовании функции, которые просто работают.
|
||||
‣ <b>Экономия заряда батареи и места на устройстве</b>: Не разряжает аккумулятор, как другие приложения для навигации. Компактные карты экономят драгоценное место на вашем телефоне.
|
||||
‣ <b>Бесплатное и созданное сообществом</b>: Люди, подобные вам, помогали создавать приложение, добавляя места в OpenStreetMap, тестируя и оставляя отзывы о функциях, а также вкладывая свои навыки и деньги в разработку..
|
||||
‣ <b>Открытое и прозрачное принятие решений, финансовая отчетность, некоммерческая организация и полностью открытый исходный код.</b>
|
||||
‣ <b>Бесплатное и созданное сообществом</b>: Такие люди, как и вы, помогали создавать приложение, добавляя места в OpenStreetMap, протестировав и оставляя отзывы о функциях, а также вложив свои навыки и деньги в разработку.
|
||||
‣ <b>Открытое и прозрачное принятие решений, финансовая отчётность, некоммерческая организация и полностью открытый исходный код.</b>
|
||||
|
||||
<b>Главные особенности</b>:
|
||||
• Загружаемые и подробные карты с местами, которые недоступны в Google Maps
|
||||
• Режим Outdoor с отмеченными туристическими тропами, кемпингами, источниками воды, вершинами, контурными линиями и т.д.
|
||||
<b>Главные возможности</b>:
|
||||
• Скачиваемые и подробные карты с местами, которые недоступны в Google Maps
|
||||
• Уличный режим с отмеченными туристическими тропами, кемпингами, источниками воды, вершинами, контурными линиями и т.д.
|
||||
• Пешеходные переходы и велодорожки
|
||||
• Точки интереса, такие как: рестораны, заправочные станции, отели, магазины, достопримечательности и многое другое
|
||||
• Поиск по имени, адресу или категории достопримечательностей
|
||||
• Интересные места, такие как: рестораны, заправочные станции, гостиницы, магазины, достопримечательности и многое другое
|
||||
• Поиск по названию или адресу или по категории достопримечательностей
|
||||
• Навигация с голосовыми уведомлениями для пешеходов, велосипедистов или водителей
|
||||
• Возможность добавлять любимые места в закладки одним касанием
|
||||
• Загружаемые страницы Википедии
|
||||
• Возможность добавлять любимые места в закладки одним нажатием
|
||||
• Скачиваемые страницы Википедии
|
||||
• Слой общественного транспорта (метро)
|
||||
• Запись маршрута
|
||||
• Экспорт и импорт закладок и маршрутов в форматах KML, KMZ, GPX
|
||||
• Темный режим для использования в ночное время
|
||||
• Улучшение картографических данных для всех с помощью базового встроенного редактора
|
||||
• Поддержка Android Auto и CarPlay
|
||||
• Тёмный режим для использования в ночное время
|
||||
• Улучшение данных карты для всех с помощью базового встроенного редактора
|
||||
|
||||
<i>Свобода здесь - Открой для себя поездки, навигацию по миру, ставя приватность и сообщество во главе</i>
|
||||
<b>Свобода здесь</b>
|
||||
Откройте для себя путешествия, навигацию по миру, ставя во главе приватность и сообщество!
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
• карты OpenStreetMap от 2 июня
|
||||
• настройка для изменения функции левой кнопки или её скрытия
|
||||
• сохранение построенных маршрутов в виде треков
|
||||
• добавлены: метро в Qingdao, питомники растений, отбойники на шоссе, постоянные лестницы-стремянки, студии, места для танцев, кострища, отели любви
|
||||
• прозрачная полоска с системными кнопками (в светлом режиме)
|
||||
• в объекты на карте (а также в их редактор) добавлены Mastodon и Bluesky контакты
|
||||
• к стрелке направления на выбранный объект добавлен азимут
|
||||
• карты OpenStreetMap от 22 июня
|
||||
• автоматическое резервное копирование меток и треков
|
||||
• линии высот с шагом 100м для всех регионов, где линии с этим шагом отсутствовали
|
||||
• цветная заливка растительности и игровых площадок отображается раньше, добавлена заливка для кемпингов и других объектов
|
||||
• тропы и грунтовки отображаются позже в стиле по умолчанию - используйте стиль «Активный отдых» для обзора троп
|
||||
• выбранная функция настраиваемой кнопки больше не дублируется в пунктах меню
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Простая навигация по карте — Откройте больше за ваше путешествие. От сообщества
|
||||
1
android/app/src/fdroid/play/listings/ru-RU/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Карты и путешествия с приватностью
|
||||
@@ -1 +1 @@
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Захваљујући заједници
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Покреће је заједница
|
||||
|
||||
@@ -1,2 +1,32 @@
|
||||
Gönüllüler tarafından yürütülen, OpenStreetMap harita verisini kullanan, şeffaf, mahremiyete saygılı, kamu yararına olma kararlılığıyla güçlendirilmiş bir özgür yazılım. CoMaps OrganicMaps isimli, esasen Maps.ME'nin çatalı olan bir özgür yazılımın çatalıdır.
|
||||
Projenin ortaya çıkma sebebini ve gidişatını <b><i>codeberg.org/comaps</i></b>'den okuyabilirsiniz.
|
||||
OpenStreetMap'in verilerine ve kar amacı gütmeyen olma taahhüdündeki topluluk liderliğine dayanan şeffaflık, gizlilik, ücretsiz ve açık kaynaklı haritalar uygulaması. Comaps, Organic Maps çatalı/düzenlemesidir, bu da bir Maps.ME çatalıdır.
|
||||
|
||||
Projenin sebepleri ve rotası hakkında <b><i>codeberg.org/comaps</i></b> adresinden bilgi edinebilirsiniz.
|
||||
Oradaki topluluğa katılın ve en iyi harita uygulamasını yapmanıza yardımcı olun
|
||||
• Uygulamayı kullanın ve bu bilgiyi yayın
|
||||
• Geri bildirim verin ve sorunları bildirin
|
||||
• Uygulamada veya OpenStreetMap internet sitesinde harita verilerini güncelleyin
|
||||
|
||||
‣ <b>Çevrimdışı odaklı</b>: Hücresel veriye ihtiyaç duymadan yurt dışında seyahatinizi planlayın ve gezin, uzak bir yürüyüş sırasında durak noktaları arama, vb. Tüm uygulama işlevleri çevrimdışı çalışacak şekilde tasarlanmıştır.
|
||||
‣ <b>Gizliliğe saygı gösterir</b>: Uygulama gizlilik göz önünde bulundurularak tasarlanmıştır - insanları fişlemez, izlemez ve kişisel bilgileri toplamaz. Reklamsızdır.
|
||||
‣ <b>Basit ve parlak</b>: Sadece işe yarayan kullanımı kolay özellikler.
|
||||
‣ <b>Pilinizi ve hafızanızı kurtarın</b>: Pilinizi diğer gezinme uygulamaları gibi boşaltmaz. Sıkıştırılmış haritalar telefonunuzdaki değerli hafızadan tasarruf eder.
|
||||
‣ <b>Özgür ve topluluk tarafından inşa edilmiştir</b>: Sizin gibi insanlar, OpenStreetMap'e yerler ekleyerek, özellikleri test edip hakkında geri bildirim vererek, geliştirme becerileri ve paralarıyla katkıda bulunarak uygulamanın oluşturulmasına yardımcı oldu.
|
||||
‣ <b>Açık, şeffaf finans ve karar verme, kar amacı gütmeyen ve tamamen açık kaynaklı.</b>
|
||||
|
||||
<b>Ana Özellikler</b>:
|
||||
• Google Haritalarda mevcut olmayan yerlerle, indirilebilir ve ayrıntılı haritalar
|
||||
• Vurgulanmış yürüyüş parkurları, kamp alanları, su kaynakları, zirveler, yükseklik çizgileri, vb.
|
||||
• Yürüyüş ve bisiklet yolları
|
||||
• Lokantalar, benzin istasyonları, oteller, mağazalar, şahin tepeleri ve daha fazla ilgi çekici nokta
|
||||
• Ada, adrese veya ilgi alanına göre arayın
|
||||
• Yürüyüş, bisiklete binme veya sürüş için sesli bildirimlerle gezinme
|
||||
• En sevdiğiniz yerlere tek bir dokunuşla yer işareti koyun
|
||||
• Çevrimdışı Vikipedi makaleleri
|
||||
• Metro katmanı ve tarifler
|
||||
• Rota kaydı
|
||||
• KML, KMZ, GPX biçimlerindeki yer imlerini ve izlerini dışa veya içe aktarın
|
||||
• Gece boyunca kullanılacak karanlık kip
|
||||
• Temel yerleşik bir arayüz kullanan herkes için harita verilerini geliştirin
|
||||
|
||||
<b>Özgürlük Burada</b>
|
||||
Yolculuğunuzu keşfedin, dünyayı gizlilik ve topluluk desteğiyle gezin!
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: 2025.03.02-7-FDroid+25030207
|
||||
version: 2025.06.30-22-FDroid+25063022
|
||||
|
||||
@@ -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: Navigation Datenschutz
|
||||
CoMaps - Navi mit Datenschutz
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
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. CoMaps è uno spin-off di Organic Maps, che a sua volta deriva da Maps.ME.
|
||||
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.
|
||||
|
||||
Unisciti alla nostra comunità e aiutaci a creare la migliore app di mappe.
|
||||
• usa l'app e consigliala
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Eenvoudige kaartnavigatie - Ontdek meer van je reis - Gemaakt door de community
|
||||
1
android/app/src/google/play/listings/nl-NL/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Navigeer met privacy
|
||||
@@ -0,0 +1 @@
|
||||
Navegação fácil nos mapas - Descubra mais sobre o seu percurso - Feito por todos
|
||||
@@ -0,0 +1,38 @@
|
||||
Бесплатное и открытое приложение с картами, созданное сообществом на основе картографических данных 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>
|
||||
Откройте для себя путешествия, навигацию по миру, ставя во главе приватность и сообщество!
|
||||
@@ -0,0 +1 @@
|
||||
Простая навигация по карте — Откройте больше за ваше путешествие. От сообщества
|
||||
1
android/app/src/google/play/listings/ru-RU/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Оффлайн навигация
|
||||
@@ -1,6 +1,6 @@
|
||||
Бесплатна апликација за мапе отвореног кода коју води заједница заснована на OpenStreetMap подацима и ојачана посвећеношћу транспарентности, приватности и непрофитности.
|
||||
|
||||
Придружите се заједници и помозите да направите најбољу навигацију
|
||||
Придружите се заједници и помозите да направимо најбољу навигацију
|
||||
• Користите апликацију и ширите информације о њој
|
||||
• Оставите повратне информације и пријавите проблеме
|
||||
• Ажурирајте мапе из апликације или на сајту OpenStreetMap
|
||||
@@ -32,5 +32,5 @@
|
||||
|
||||
Молимо Вас да пријавите проблеме са апликацијом, предложите идеје и придружите се нашој заједници на <b><i>comaps.app</i></b> страни.
|
||||
|
||||
<b>Сад је слободно</b>
|
||||
<b>Сад је слободна</b>
|
||||
Откријте своје путовање, путујте светом с приватношћу и заједницом на челу!
|
||||
|
||||
@@ -1 +1 @@
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Захваљујући заједници
|
||||
Једноставна навигација - Сазнајте више о свом путовању - Покреће је заједница
|
||||
|
||||
@@ -1 +1 @@
|
||||
CoMaps - навигација
|
||||
CoMaps - Navigacija
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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!
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Mahremiyetli Seyahat
|
||||
CoMaps - Gizlilikle Gezin
|
||||
|
||||
@@ -44,6 +44,7 @@ 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;
|
||||
@@ -139,6 +140,7 @@ 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,
|
||||
@@ -253,6 +255,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
@NonNull
|
||||
private DisplayManager mDisplayManager;
|
||||
|
||||
private PeriodicBackupRunner backupRunner;
|
||||
|
||||
ManageRouteBottomSheet mManageRouteBottomSheet;
|
||||
|
||||
private boolean mRemoveDisplayListener = true;
|
||||
@@ -607,6 +611,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
*/
|
||||
if (Map.isEngineCreated())
|
||||
onRenderingInitializationFinished();
|
||||
|
||||
backupRunner = new PeriodicBackupRunner(this);
|
||||
}
|
||||
|
||||
private void onSettingsResult(ActivityResult activityResult)
|
||||
@@ -838,7 +844,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
@Override
|
||||
public String getPrefsName()
|
||||
{
|
||||
return getString(R.string.help);
|
||||
return getString(R.string.about_help);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1352,6 +1358,11 @@ 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
|
||||
@@ -2584,20 +2595,28 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
if (id.equals(MAIN_MENU_ID))
|
||||
{
|
||||
final String activeLeftButton = buttonsHolder.getActiveButtonCode();
|
||||
ArrayList<MenuBottomSheetItem> items = new ArrayList<>();
|
||||
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))
|
||||
|
||||
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.donate, R.drawable.ic_donate, this::onDonateOptionSelected));
|
||||
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));
|
||||
|
||||
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.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;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
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,7 +1,6 @@
|
||||
package app.organicmaps.bookmarks;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -21,6 +20,9 @@ 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;
|
||||
@@ -279,7 +281,7 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment<Bookmark
|
||||
}
|
||||
|
||||
private void showNoFileManagerError() {
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setMessage(R.string.error_no_file_manager_app)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
|
||||
@@ -69,7 +69,8 @@ public class Metadata implements Parcelable
|
||||
FMD_OUTDOOR_SEATING(48),
|
||||
FMD_NETWORK(49),
|
||||
FMD_CONTACT_FEDIVERSE(50),
|
||||
FMD_CONTACT_BLUESKY(51);
|
||||
FMD_CONTACT_BLUESKY(51),
|
||||
FMD_PANORAMAX(52);
|
||||
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.help));
|
||||
builder.setTitle(getCarContext().getString(R.string.about_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.help));
|
||||
builder.setTitle(getCarContext().getString(R.string.about_help));
|
||||
builder.setOnClickListener(() -> getScreenManager().push(new HelpScreen(getCarContext(), getSurfaceRenderer())));
|
||||
builder.setBrowsable(true);
|
||||
return builder.build();
|
||||
|
||||
@@ -11,7 +11,6 @@ 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;
|
||||
@@ -28,6 +27,7 @@ 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 TextView mName;
|
||||
private final TextView mSubtitle;
|
||||
private final TextView mFoundName;
|
||||
private final TextView mSize;
|
||||
private final MaterialTextView mName;
|
||||
private final MaterialTextView mSubtitle;
|
||||
private final MaterialTextView mFoundName;
|
||||
private final MaterialTextView 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 TextView mTitle;
|
||||
private final MaterialTextView mTitle;
|
||||
|
||||
HeaderViewHolder(@NonNull View frame)
|
||||
{
|
||||
|
||||
@@ -3,13 +3,14 @@ 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;
|
||||
@@ -40,19 +41,11 @@ public class DownloaderService extends Service implements MapManager.StorageCall
|
||||
Logger.i(TAG, "Downloading: " + MapManager.nativeIsDownloading());
|
||||
|
||||
var notification = mNotifier.buildProgressNotification();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
{
|
||||
try
|
||||
{
|
||||
startForeground(DownloaderNotifier.NOTIFICATION_ID, notification);
|
||||
} catch (ForegroundServiceStartNotAllowedException e)
|
||||
{
|
||||
Logger.e(TAG, "Oops! ForegroundService is not allowed", e);
|
||||
}
|
||||
} else
|
||||
{
|
||||
startForeground(DownloaderNotifier.NOTIFICATION_ID, notification);
|
||||
}
|
||||
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);
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ 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;
|
||||
@@ -15,7 +16,7 @@ import app.organicmaps.util.UiUtils;
|
||||
public class DownloaderStatusIcon
|
||||
{
|
||||
private final View mFrame;
|
||||
protected final ImageView mIcon;
|
||||
protected final ShapeableImageView 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.help), null);
|
||||
((HelpActivity) requireActivity()).stackFragment(HelpFragment.class, getString(R.string.about_help), null);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -43,15 +43,21 @@ public class LeftButtonsHolder
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LeftButton getActiveButton()
|
||||
public String getActiveButtonCode()
|
||||
{
|
||||
String activeButtonCode = prefs.getString(leftButtonPreferenceKey, DEFAULT_BUTTON_CODE);
|
||||
if (!TextUtils.isEmpty(activeButtonCode))
|
||||
return availableButtons.get(activeButtonCode);
|
||||
return 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,6 +17,7 @@ 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;
|
||||
@@ -158,21 +159,11 @@ public class TrackRecordingService extends Service implements LocationListener
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
else
|
||||
{
|
||||
startForeground(TrackRecordingService.TRACK_REC_NOTIFICATION_ID, getNotificationBuilder(this).build());
|
||||
}
|
||||
ServiceCompat.startForeground(this, TrackRecordingService.TRACK_REC_NOTIFICATION_ID, getNotificationBuilder(this).build(), 0);
|
||||
|
||||
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.button_layer_subway;
|
||||
buttonTextResource = R.string.subway;
|
||||
break;
|
||||
case ISOLINES:
|
||||
disabledResource = R.attr.isoLinesMenuDisabled;
|
||||
|
||||
@@ -43,6 +43,7 @@ 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,11 +213,13 @@ 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
|
||||
@@ -482,7 +484,10 @@ public class MapButtonsController extends Fragment
|
||||
.build();
|
||||
ViewCompat.setOnApplyWindowInsetsListener(mFrame, insetsListener);
|
||||
// Fixes insets on older Androids and with a search opened via API on all Androids.
|
||||
mFrame.post(() -> ViewCompat.requestApplyInsets(mFrame));
|
||||
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.Q)
|
||||
mFrame.postDelayed(() -> ViewCompat.requestApplyInsets(mFrame), 1250);
|
||||
else
|
||||
mFrame.post(() -> ViewCompat.requestApplyInsets(mFrame));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -266,7 +266,8 @@ public class NavigationController implements TrafficManager.TrafficCallback,
|
||||
mSpeedLimit.setSpeedLimit(0, false);
|
||||
return;
|
||||
}
|
||||
final boolean speedLimitExceeded = info.speedLimitMps < location.getSpeed();
|
||||
mSpeedLimit.setSpeedLimit(StringUtils.nativeFormatSpeed(info.speedLimitMps), speedLimitExceeded);
|
||||
final int fSpeedLimit = StringUtils.nativeFormatSpeed(info.speedLimitMps);
|
||||
final boolean speedLimitExceeded = fSpeedLimit < StringUtils.nativeFormatSpeed(location.getSpeed());
|
||||
mSpeedLimit.setSpeedLimit(fSpeedLimit, 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,6 +27,7 @@ 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;
|
||||
@@ -224,21 +225,11 @@ public class NavigationService extends Service implements LocationListener
|
||||
return START_NOT_STICKY; // The service will be stopped by stopSelf().
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
else
|
||||
{
|
||||
startForeground(NavigationService.NOTIFICATION_ID, getNotificationBuilder(this).build());
|
||||
}
|
||||
ServiceCompat.startForeground(this, NavigationService.NOTIFICATION_ID, getNotificationBuilder(this).build(), 0);
|
||||
|
||||
final LocationHelper locationHelper = LocationHelper.from(this);
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ 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;
|
||||
@@ -67,21 +70,21 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
@NonNull
|
||||
private final ImageView mAltitudeChart;
|
||||
@NonNull
|
||||
private final TextView mTime;
|
||||
private final MaterialTextView mTime;
|
||||
@NonNull
|
||||
private final TextView mAltitudeDifference;
|
||||
private final MaterialTextView mAltitudeDifference;
|
||||
@NonNull
|
||||
private final TextView mTimeVehicle;
|
||||
@Nullable
|
||||
private final TextView mArrival;
|
||||
private final MaterialTextView mArrival;
|
||||
@NonNull
|
||||
private final View mActionFrame;
|
||||
@NonNull
|
||||
private final TextView mActionMessage;
|
||||
private final MaterialTextView mActionMessage;
|
||||
@NonNull
|
||||
private final View mActionButton;
|
||||
@NonNull
|
||||
private final ImageView mActionIcon;
|
||||
private final ShapeableImageView mActionIcon;
|
||||
@NonNull
|
||||
private final DotDividerItemDecoration mTransitViewDecorator;
|
||||
|
||||
@@ -98,10 +101,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);
|
||||
TextView time = (TextView) getViewById(activity, frame, R.id.time);
|
||||
MaterialTextView time = (MaterialTextView) getViewById(activity, frame, R.id.time);
|
||||
TextView timeVehicle = (TextView) getViewById(activity, frame, R.id.time_vehicle);
|
||||
TextView altitudeDifference = (TextView) getViewById(activity, frame, R.id.altitude_difference);
|
||||
TextView arrival = (TextView) getViewById(activity, frame, R.id.arrival);
|
||||
MaterialTextView altitudeDifference = (MaterialTextView) getViewById(activity, frame, R.id.altitude_difference);
|
||||
MaterialTextView arrival = (MaterialTextView) getViewById(activity, frame, R.id.arrival);
|
||||
View actionFrame = getViewById(activity, frame, R.id.routing_action_frame);
|
||||
|
||||
return new RoutingBottomMenuController(activity, altitudeChartFrame, timeElevationLine, transitFrame,
|
||||
@@ -124,10 +127,10 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
@NonNull TextView error,
|
||||
@NonNull Button start,
|
||||
@NonNull ImageView altitudeChart,
|
||||
@NonNull TextView time,
|
||||
@NonNull TextView altitudeDifference,
|
||||
@NonNull MaterialTextView time,
|
||||
@NonNull MaterialTextView altitudeDifference,
|
||||
@NonNull TextView timeVehicle,
|
||||
@Nullable TextView arrival,
|
||||
@Nullable MaterialTextView arrival,
|
||||
@NonNull View actionFrame,
|
||||
@Nullable RoutingBottomMenuListener listener)
|
||||
{
|
||||
@@ -198,12 +201,12 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
|
||||
scrollToBottom(rv);
|
||||
|
||||
TextView totalTimeView = mTransitFrame.findViewById(R.id.total_time);
|
||||
MaterialTextView 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);
|
||||
TextView distanceView = mTransitFrame.findViewById(R.id.total_distance);
|
||||
MaterialTextView distanceView = mTransitFrame.findViewById(R.id.total_distance);
|
||||
UiUtils.showIf(info.getTotalPedestrianTimeInSec() > 0, dotView, pedestrianIcon, distanceView);
|
||||
distanceView.setText(info.getTotalPedestrianDistance() + " " + info.getTotalPedestrianDistanceUnits());
|
||||
}
|
||||
|
||||
@@ -38,15 +38,17 @@ public class RoutingErrorDialogFragment extends BaseRoutingErrorDialogFragment
|
||||
ResultCodesHelper.getDialogTitleSubtitle(requireContext(), mResultCode, mMissingMaps.size());
|
||||
Pair<String, String> titleMessage = resHolder.getTitleMessage();
|
||||
|
||||
TextView titleView = new TextView(requireContext());
|
||||
titleView.setText(titleMessage.first);
|
||||
titleView.setPadding(65, 32, 32, 16);
|
||||
titleView.setTextSize(18);
|
||||
titleView.setMaxLines(4);
|
||||
titleView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
titleView.setTypeface(null, Typeface.BOLD);
|
||||
builder.setCustomTitle(titleView);
|
||||
|
||||
if (!TextUtils.isEmpty(titleMessage.first))
|
||||
{
|
||||
TextView titleView = new TextView(requireContext());
|
||||
titleView.setText(titleMessage.first);
|
||||
titleView.setPadding(65, 32, 32, 16);
|
||||
titleView.setTextSize(18);
|
||||
titleView.setMaxLines(4);
|
||||
titleView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
titleView.setTypeface(null, Typeface.BOLD);
|
||||
builder.setCustomTitle(titleView);
|
||||
}
|
||||
mMessage = titleMessage.second;
|
||||
builder.setNegativeButton(resHolder.getCancelBtnResId(), null);
|
||||
if (ResultCodesHelper.isDownloadable(mResultCode, mMissingMaps.size()))
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
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,5 +1,6 @@
|
||||
package app.organicmaps.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -10,10 +11,13 @@ 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;
|
||||
@@ -323,4 +327,76 @@ 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,6 +113,7 @@ 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,6 +56,9 @@ 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;
|
||||
|
||||
@@ -163,6 +166,11 @@ 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)
|
||||
@@ -226,6 +234,9 @@ 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="108"
|
||||
android:viewportHeight="108">
|
||||
android:viewportWidth="144"
|
||||
android:viewportHeight="144">
|
||||
<path
|
||||
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: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:fillColor="#fff"/>
|
||||
</vector>
|
||||
|
||||
15
android/app/src/main/res/drawable/ic_panoramax.xml
Normal file
@@ -23,7 +23,7 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_gravity="center_vertical" >
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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" />
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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">
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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"/>
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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" >
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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" />
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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" />
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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">
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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"/>
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
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"/>
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/altitude_difference"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||