mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-11 14:54:17 +00:00
Compare commits
211 Commits
generate-n
...
zy-live-lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c30f881798 | ||
|
|
93d841609f | ||
|
|
b8ff910c6f | ||
|
|
365c8fad6e | ||
|
|
c34d60a9d9 | ||
|
|
6af3e80f24 | ||
|
|
002477c0bb | ||
|
|
1347324c71 | ||
|
|
6749856dd2 | ||
|
|
9e21fdd5f2 | ||
|
|
32c27fb4c9 | ||
|
|
cb4472c325 | ||
|
|
d432ead844 | ||
|
|
c1137bd38a | ||
|
|
53e1361276 | ||
|
|
f2a0b4470f | ||
|
|
111fd10af9 | ||
|
|
52edb5da6d | ||
|
|
70215404c3 | ||
|
|
49b0ec164d | ||
|
|
018259bb0f | ||
|
|
b7733786df | ||
|
|
245646c45d | ||
|
|
6799f17c1b | ||
|
|
e3abbc712b | ||
|
|
0da7869c5b | ||
|
|
2e3a76fc94 | ||
|
|
85d4226eda | ||
|
|
3f9dfd6605 | ||
|
|
fdf698281a | ||
|
|
fd57e71ac4 | ||
|
|
d86049f6d8 | ||
|
|
32bba5bc5e | ||
|
|
3068a468a3 | ||
|
|
267378aa23 | ||
|
|
0a525b7483 | ||
|
|
ea4b39ea47 | ||
|
|
67f27482b8 | ||
|
|
b231f2de6e | ||
|
|
2609dd5588 | ||
|
|
08a87c1962 | ||
|
|
09b07c2631 | ||
|
|
42f5590210 | ||
|
|
f7d2e43f09 | ||
|
|
7617f85442 | ||
|
|
ffcb694961 | ||
|
|
3352fc13c1 | ||
|
|
268d8936ca | ||
|
|
a679198c09 | ||
|
|
6f3ac44e9a | ||
|
|
907c5cf39e | ||
|
|
b8e5a70cf7 | ||
|
|
464033a491 | ||
|
|
832f9bdd11 | ||
|
|
7d56fc6ba6 | ||
|
|
153e75175d | ||
|
|
4899a12d33 | ||
|
|
4a64bf05be | ||
|
|
381c1e3979 | ||
|
|
aa9ee3cbbf | ||
|
|
20d9185c79 | ||
|
|
90c18f4983 | ||
|
|
fe5d4f5286 | ||
|
|
13d7def519 | ||
|
|
93b35454eb | ||
|
|
de1c0a061d | ||
|
|
18ce55afa3 | ||
|
|
43ffd199a4 | ||
|
|
3e75e5e802 | ||
|
|
fc96d17ed7 | ||
|
|
e869fe1da1 | ||
|
|
f38953458d | ||
|
|
5e8d2e1a59 | ||
|
|
07e42c0626 | ||
|
|
14e45aa6db | ||
|
|
8bd7f9d59a | ||
|
|
ead092af79 | ||
|
|
c3f5986f12 | ||
|
|
143e0562e6 | ||
|
|
89cfc6f8e6 | ||
|
|
4788956720 | ||
|
|
f6ff08619e | ||
|
|
a4df3eaad5 | ||
|
|
55f55bbde1 | ||
|
|
c6abf26628 | ||
|
|
5b9b9929b8 | ||
|
|
d1aa8f5905 | ||
|
|
50130f9880 | ||
|
|
9923a28951 | ||
|
|
ff51988ddf | ||
|
|
7f693b9dfd | ||
|
|
42f0855ec8 | ||
|
|
008a785564 | ||
|
|
a62f9470ec | ||
|
|
bfe65e0de0 | ||
|
|
eee0401a66 | ||
|
|
4226e2f999 | ||
|
|
94bcd5f366 | ||
|
|
1b64151aee | ||
|
|
899df496ba | ||
|
|
47f959ec07 | ||
|
|
6e8a0f22e6 | ||
|
|
2a2007a473 | ||
|
|
db91e3ea92 | ||
|
|
3705abbbd6 | ||
|
|
f33bfffbe6 | ||
|
|
72eeceb021 | ||
|
|
372a7f47d3 | ||
|
|
49f4971015 | ||
|
|
45224d1bd0 | ||
|
|
26ec203e41 | ||
|
|
ee1081bc7d | ||
|
|
5050dea70f | ||
|
|
9bb29578d0 | ||
|
|
33e4894a56 | ||
|
|
aa34159ce8 | ||
|
|
1ccea5928f | ||
|
|
ec59698f4a | ||
|
|
0c2763f3ce | ||
|
|
c9a4483f50 | ||
|
|
0eafe3482e | ||
|
|
e4275cdd3c | ||
|
|
4ef9395442 | ||
|
|
30cf2b5770 | ||
|
|
6aed26c48e | ||
|
|
dad91b82a6 | ||
|
|
7024aace6f | ||
|
|
6291133a69 | ||
|
|
1c5121d447 | ||
|
|
4398e492b8 | ||
|
|
1907e039e3 | ||
|
|
26bad5dffb | ||
|
|
4f2b479b2c | ||
|
|
a972552155 | ||
|
|
dc54c45482 | ||
|
|
1975b6a0f0 | ||
|
|
8683853f46 | ||
|
|
d7e34c2685 | ||
|
|
0028306b26 | ||
|
|
b5354fd1e3 | ||
|
|
964f82510a | ||
|
|
faf49fc574 | ||
|
|
60b1ad232a | ||
|
|
1bac0fd4fc | ||
|
|
ed6dcc5cb2 | ||
|
|
33b440dd71 | ||
|
|
6a20269819 | ||
|
|
8cd1b41cd2 | ||
|
|
2b630964d0 | ||
|
|
76ecd8209e | ||
|
|
82d2932ba0 | ||
|
|
85540133b3 | ||
|
|
6ded75de9c | ||
|
|
08e8ebd434 | ||
|
|
6c92264fb0 | ||
|
|
4ba0fc51a5 | ||
|
|
29c363a581 | ||
|
|
4973f4fed2 | ||
|
|
8cffa508f3 | ||
|
|
3511dbb692 | ||
|
|
e238a317f1 | ||
|
|
2e85899fcb | ||
|
|
ce978d8ede | ||
|
|
d56f9700f5 | ||
|
|
cc042c3d1c | ||
|
|
4f739d98b0 | ||
|
|
4691de7a66 | ||
|
|
3c082b0629 | ||
|
|
a468bd3fab | ||
|
|
fc87316184 | ||
|
|
298518ae72 | ||
|
|
3bad6d25f0 | ||
|
|
d36361d669 | ||
|
|
688e20b1a6 | ||
|
|
85462161b2 | ||
|
|
b929823f6b | ||
|
|
22dd799585 | ||
|
|
6864d101e2 | ||
|
|
4bfb62b373 | ||
|
|
f1cf844986 | ||
|
|
f20c3bf50c | ||
|
|
e7cc602904 | ||
|
|
d473361e54 | ||
|
|
76d58e4a05 | ||
|
|
7b5878b010 | ||
|
|
3fabbae3f7 | ||
|
|
0add23fcf2 | ||
|
|
08bcb574fa | ||
|
|
db888f33c5 | ||
|
|
4a96d219f0 | ||
|
|
c2bc6c27aa | ||
|
|
1095e5dbc3 | ||
|
|
a1cbcc5885 | ||
|
|
641f2308c6 | ||
|
|
f858ebcce0 | ||
|
|
eb376f5afc | ||
|
|
71b47719af | ||
|
|
4f7230fcbe | ||
|
|
2dafdd4338 | ||
|
|
0237751afe | ||
|
|
e7fb3a2f2c | ||
|
|
e08d60bb40 | ||
|
|
de4252f86c | ||
|
|
9d87d77055 | ||
|
|
c88f59eb75 | ||
|
|
9b5c700ad8 | ||
|
|
7d5e6fabcd | ||
|
|
ebe0364030 | ||
|
|
43e7e1eb2e | ||
|
|
ce9af79a68 | ||
|
|
b54b77bce6 |
@@ -1,69 +1,55 @@
|
||||
# All non-assigned.
|
||||
* @organicmaps/mergers
|
||||
# Visual design.
|
||||
/android/app/src/main/res/drawable*/ @organicmaps/design
|
||||
/android/app/src/main/res/font/ @organicmaps/design
|
||||
/android/app/src/main/res/mipmap*/ @organicmaps/design
|
||||
/data/*.ttf @organicmaps/design
|
||||
/data/resources-svg/ @organicmaps/design
|
||||
/data/search-icons/ @organicmaps/design
|
||||
/iphone/Maps/Images.xcassets/ @organicmaps/design
|
||||
/android/app/src/main/res/drawable*/ @comaps/design
|
||||
/android/app/src/main/res/font/ @comaps/design
|
||||
/android/app/src/main/res/mipmap*/ @comaps/design
|
||||
/data/*.ttf @comaps/design
|
||||
/data/resources-svg/ @comaps/design
|
||||
/data/search-icons/ @comaps/design
|
||||
/iphone/Maps/Images.xcassets/ @comaps/design
|
||||
# Android.
|
||||
/android/ @organicmaps/android
|
||||
/android/app/src/main/java/app/organicmaps/car/ @organicmaps/android-auto
|
||||
/docs/ANDROID_LOCATION_TEST.md @organicmaps/android
|
||||
/docs/JAVA_STYLE.md @organicmaps/android
|
||||
/android/ @comaps/android
|
||||
/android/app/src/main/java/app/comaps/car/ @comaps/android-auto
|
||||
/docs/ANDROID_LOCATION_TEST.md @comaps/android
|
||||
/docs/JAVA_STYLE.md @comaps/android
|
||||
# no owner for translation changes
|
||||
/android/app/src/main/res/values*/strings.xml
|
||||
# iOS.
|
||||
/iphone/ @organicmaps/ios
|
||||
/xcode/ @organicmaps/ios
|
||||
/docs/OBJC_STYLE.md @organicmaps/ios
|
||||
/iphone/ @comaps/ios
|
||||
/xcode/ @comaps/ios
|
||||
/docs/OBJC_STYLE.md @comaps/ios
|
||||
# no owner for translation changes
|
||||
/iphone/plist.txt
|
||||
/iphone/Maps/LocalizedStrings/
|
||||
# Qt
|
||||
/qt/ @organicmaps/qt
|
||||
# Rendering
|
||||
/drape/ @organicmaps/rendering
|
||||
/drape_frontend/ @organicmaps/rendering
|
||||
# Map Data.
|
||||
/tools/python/maps_generator/ @organicmaps/data
|
||||
/generator/ @organicmaps/data
|
||||
/topography_generator/ @organicmaps/data
|
||||
/data/borders/ @organicmaps/data
|
||||
/data/conf/isolines/ @organicmaps/data
|
||||
/docs/SUBWAY_GENERATION.md @organicmaps/data
|
||||
/docs/MAPS.md @organicmaps/data
|
||||
/docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @organicmaps/data
|
||||
# no owner (changed often to add a new POI)
|
||||
/generator/generator_tests/osm_type_test.cpp
|
||||
# Map Styles.
|
||||
/data/styles/ @organicmaps/styles
|
||||
/data/types.txt @organicmaps/styles
|
||||
/data/visibility.txt @organicmaps/styles
|
||||
/data/mapcss-mapping.csv @organicmaps/styles
|
||||
/data/replaced_tags.txt @organicmaps/styles
|
||||
/data/classificator.txt @organicmaps/styles
|
||||
/data/drules_* @organicmaps/styles
|
||||
/data/styles/ @comaps/styles
|
||||
/data/types.txt @comaps/styles
|
||||
/data/visibility.txt @comaps/styles
|
||||
/data/mapcss-mapping.csv @comaps/styles
|
||||
/data/replaced_tags.txt @comaps/styles
|
||||
/data/classificator.txt @comaps/styles
|
||||
/data/drules_* @comaps/styles
|
||||
/docs/STYLES.md
|
||||
/tools/kothic/ @organicmaps/styles
|
||||
/tools/kothic/ @comaps/styles
|
||||
# DevOps.
|
||||
/.forgejo/workflows @organicmaps/devops
|
||||
/android/*gradle* @organicmaps/devops
|
||||
/docs/RELEASE_MANAGEMENT.md @organicmaps/devops
|
||||
/xcode/fastlane/ @organicmaps/devops
|
||||
# Growth.
|
||||
README.md @organicmaps/growth
|
||||
/.forgejo/FUNDING.yml @organicmaps/growth
|
||||
/android/app/src/fdroid/play/ @organicmaps/growth
|
||||
/android/app/src/google/play/ @organicmaps/growth
|
||||
/iphone/metadata/ @organicmaps/growth
|
||||
/.forgejo/workflows @comaps/devops
|
||||
/android/*gradle* @comaps/devops
|
||||
/docs/RELEASE_MANAGEMENT.md @comaps/devops
|
||||
/xcode/fastlane/ @comaps/devops
|
||||
/tools/python/maps_generator/ @comaps/devops
|
||||
/generator/ @comaps/devops
|
||||
/topography_generator/ @comaps/devops
|
||||
/data/borders/ @comaps/devops
|
||||
/data/conf/isolines/ @comaps/devops
|
||||
/docs/SUBWAY_GENERATION.md @comaps/devops
|
||||
/docs/MAPS.md @comaps/devops
|
||||
/docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @comaps/devops
|
||||
# Legal.
|
||||
LEGAL @organicmaps/legal
|
||||
LICENSE @organicmaps/legal
|
||||
NOTICE @organicmaps/legal
|
||||
CONTRIBUTORS @organicmaps/legal
|
||||
/docs/CODE_OF_CONDUCT.md @organicmaps/legal
|
||||
/docs/DCO.md @organicmaps/legal
|
||||
/docs/GOVERNANCE.md @organicmaps/legal
|
||||
LEGAL @comaps/admins
|
||||
LICENSE @comaps/admins
|
||||
NOTICE @comaps/admins
|
||||
CONTRIBUTORS @comaps/admins
|
||||
/docs/CODE_OF_CONDUCT.md @comaps/admins
|
||||
/docs/DCO.md @comaps/admins
|
||||
/docs/GOVERNANCE.md @comaps/admins
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
open_collective: comaps
|
||||
liberapay: comaps
|
||||
custom: ["https://comaps.app/donate/"]
|
||||
|
||||
@@ -105,17 +105,17 @@ jobs:
|
||||
run: |
|
||||
echo "Cloning $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY branch $FORGEJO_REF_NAME"
|
||||
cd ~
|
||||
git clone --recurse-submodules --shallow-submodules -b $FORGEJO_REF_NAME --single-branch $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY.git comaps
|
||||
git clone --depth 1 --recurse-submodules --shallow-submodules -b $FORGEJO_REF_NAME --single-branch $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY.git comaps
|
||||
- name: Checkout wikiparser repo
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~
|
||||
git clone https://codeberg.org/comaps/wikiparser.git
|
||||
git clone --depth 1 --single-branch https://codeberg.org/comaps/wikiparser.git
|
||||
- name: Checkout subways repo
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~
|
||||
git clone https://codeberg.org/comaps/subways.git
|
||||
git clone --depth 1 --single-branch https://codeberg.org/comaps/subways.git
|
||||
|
||||
copy-coasts:
|
||||
# if: inputs.run-copy-coasts
|
||||
|
||||
129
.forgejo/workflows/process_subways.yml
Normal file
129
.forgejo/workflows/process_subways.yml
Normal file
@@ -0,0 +1,129 @@
|
||||
name: process_subways
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
env:
|
||||
PLANET: /home/planet/planet/planet.o5m
|
||||
TMPDIR: /tmp
|
||||
HTML_DIR: "/mnt/4tbexternal/osm-planet/subway/validator"
|
||||
DUMP: "$HTML_DIR"
|
||||
SKIP_PLANET_UPDATE: "1"
|
||||
DEBIAN_FRONTEND: nonnteractive
|
||||
TZ: Etc/UTC
|
||||
|
||||
# /var/www/html/subways is mapped as a volume on cdn-fi-1
|
||||
# as is /tmp/planet
|
||||
jobs:
|
||||
clone-repos:
|
||||
name: Clone Git Repos
|
||||
runs-on: mapfilemaker
|
||||
container:
|
||||
image: codeberg.org/comaps/maps_generator:f6d53d54f794
|
||||
volumes:
|
||||
- /mnt/4tbexternal:/mnt/4tbexternal
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-process-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: "~"
|
||||
key: cache-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- name: Checkout subways repo
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~
|
||||
git clone --depth 1 --single-branch https://codeberg.org/comaps/subways.git
|
||||
|
||||
update-planet-o5m:
|
||||
name: Update O5M Planet
|
||||
runs-on: mapfilemaker
|
||||
container:
|
||||
image: codeberg.org/comaps/maps_generator:f6d53d54f794
|
||||
volumes:
|
||||
- /mnt/4tbexternal/:/mnt/4tbexternal/
|
||||
- /mnt/4tbexternal/osm-planet:/home/planet
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-process-subways-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Check for O5M Planet File
|
||||
shell: bash
|
||||
run: |
|
||||
if [ ! -f /home/planet/planet/planet.o5m ]; then
|
||||
echo "WARN: No file at /home/planet/planet/planet.o5m"
|
||||
|
||||
if [ ! -f /home/planet/planet/planet-latest.osm.pbf ]; then
|
||||
echo "ERROR: No file at /home/planet/planet/planet-latest.osm.pbf"
|
||||
ls -al /home/planet/
|
||||
ls -al /home/planet/planet/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Converting planet-latest.osm.pbf to planet.o5m"
|
||||
cd /home/planet/planet/
|
||||
osmconvert -v --drop-author --drop-version --hash-memory=4000 planet-latest.osm.pbf -o=planet.o5m
|
||||
echo "Conversion is done."
|
||||
fi
|
||||
- name: Update O5M planet
|
||||
run: |
|
||||
echo "Starting..."
|
||||
cd /home/planet/planet/
|
||||
rm -f planet-new.o5m
|
||||
osmupdate -v --drop-author --drop-version --hash-memory=4000 --max-merge=32 --out-o5m planet.o5m planet-new.o5m
|
||||
mv planet-new.o5m planet.o5m
|
||||
echo "Done."
|
||||
- name: Notify Zulip
|
||||
run: |
|
||||
curl -X POST https://comaps.zulipchat.com/api/v1/messages \
|
||||
-u $ZULIP_BOT_EMAIL:$ZULIP_API_KEY \
|
||||
--data-urlencode type=stream \
|
||||
--data-urlencode 'to="DevOps"' \
|
||||
--data-urlencode topic=codeberg-bot \
|
||||
--data-urlencode 'content=O5M planet update is done!'
|
||||
|
||||
update-subways:
|
||||
if: inputs.run-subways
|
||||
name: Update Subways
|
||||
runs-on: mapfilemaker
|
||||
needs:
|
||||
- clone-repos
|
||||
container:
|
||||
image: codeberg.org/comaps/maps_generator:f6d53d54f794
|
||||
volumes:
|
||||
- /mnt/4tbexternal/:/mnt/4tbexternal/
|
||||
- /mnt/4tbexternal/osm-planet:/home/planet
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: "~"
|
||||
key: cache-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- name: Update Subways
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~/comaps/
|
||||
cp tools/unix/maps/settings.sh.prod tools/unix/maps/settings.sh
|
||||
./tools/unix/maps/generate_subways.sh
|
||||
- name: Compare with VK validation
|
||||
shell: bash
|
||||
run: |
|
||||
cd ~/subways
|
||||
if [ -f "$HTML_DIR/index.html" ]; then
|
||||
echo "Comparing local validation with VK's validation..."
|
||||
python3 ./scripts/compare_html_validation.py "$HTML_DIR/index.html" \
|
||||
--vk-url "https://maps.vk.com/osm/tools/subways/latest/index.html"
|
||||
else
|
||||
echo "Local index.html not found at $HTML_DIR/index.html"
|
||||
exit 1
|
||||
fi
|
||||
- name: Notify Zulip
|
||||
run: |
|
||||
curl -X POST https://comaps.zulipchat.com/api/v1/messages \
|
||||
-u $ZULIP_BOT_EMAIL:$ZULIP_API_KEY \
|
||||
--data-urlencode type=stream \
|
||||
--data-urlencode 'to="DevOps"' \
|
||||
--data-urlencode topic=codeberg-bot \
|
||||
--data-urlencode 'content=Subways are done!'
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
open_collective: comaps
|
||||
liberapay: comaps
|
||||
custom: ["https://comaps.app/donate/"]
|
||||
6
.github/workflows/android-check.yaml
vendored
6
.github/workflows/android-check.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
pip install "protobuf<3.21" --break-system-packages
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 200 # enough to get all commits for the current day
|
||||
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
./gradlew -P${{ matrix.arch }} assemble${{ matrix.flavor }}
|
||||
|
||||
- name: Upload ${{ matrix.flavor }} apk
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: android-${{ matrix.flavor }}
|
||||
path: android/app/build/outputs/apk/**/CoMaps-*.apk
|
||||
|
||||
2
.github/workflows/ios-check.yaml
vendored
2
.github/workflows/ios-check.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
xcodebuild -downloadPlatform iOS
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Parallel submodules checkout
|
||||
shell: bash
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -155,6 +155,8 @@ android/huawei-appgallery.json
|
||||
android/res/xml/network_security_config.xml
|
||||
./server/
|
||||
iphone/Maps/app.omaps/
|
||||
# Generated file
|
||||
libs/indexer/localized_types_map.cpp
|
||||
|
||||
*.li
|
||||
|
||||
|
||||
@@ -116,20 +116,6 @@ endif()
|
||||
|
||||
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
|
||||
|
||||
if (PLATFORM_LINUX OR PLATFORM_ANDROID)
|
||||
find_program(LLD_FOUND ld.lld)
|
||||
if (LLD_FOUND)
|
||||
message(STATUS "Using ld.lld linker")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld")
|
||||
else()
|
||||
find_program(GOLD_FOUND ld.gold)
|
||||
if (GOLD_FOUND)
|
||||
message(STATUS "Using ld.gold")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=gold")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT SKIP_TESTS)
|
||||
enable_testing()
|
||||
# Enables ctest -T memcheck with valgrind
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
This file contains a list of people who have contributed to this project.
|
||||
Its not neccesarily comprehensive.
|
||||
It is not necessarily comprehensive as contributors must manually add themselves.
|
||||
Feel free to add yourself here along with your first contribution!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
CoMaps contributors:
|
||||
(in alphabetic order)
|
||||
(in alphabetical order)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Bastian Greshake Tzovaras
|
||||
clover sage
|
||||
Harry Bond <me@hbond.xyz>
|
||||
NoelClick
|
||||
thesupertechie
|
||||
vikiawv
|
||||
Yannik Bloscheck
|
||||
|
||||
|
||||
2
NOTICE
2
NOTICE
@@ -1,6 +1,6 @@
|
||||
Copyright 2020 My.com B.V. (Mail.Ru Group)
|
||||
Copyright 2025 Organic Maps Contributors
|
||||
Copyright 2025 CoMaps Contributors
|
||||
Copyright 2026 CoMaps Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
17
SECURITY.md
Normal file
17
SECURITY.md
Normal file
@@ -0,0 +1,17 @@
|
||||
## Reporting Vulnerabilities
|
||||
You can report a security vulnerability by creating an issue or send mail to security@comaps.app
|
||||
|
||||
## Verifying Fingerprints
|
||||
|
||||
To [verify](https://developer.android.com/studio/command-line/apksigner#usage-verify) the APK, use the following signing certificate fingerprints:
|
||||
```
|
||||
SHA-256: 4894e8e6963627ef660031d8593fe77297f835acb4e23810003e926135023b4c
|
||||
SHA-1: 8b7b5739f917e9f7c681671ced0c9c8562123ade
|
||||
MD5: 9cce0ffea281dc2f0e0a154d6d2e281e
|
||||
```
|
||||
|
||||
To verify CoMaps via [AppVerifier](https://github.com/soupslurpr/AppVerifier), use the following signing certificate fingerprint:
|
||||
```
|
||||
app.comaps
|
||||
48:94:E8:E6:96:36:27:EF:66:00:31:D8:59:3F:E7:72:97:F8:35:AC:B4:E2:38:10:00:3E:92:61:35:02:3B:4C
|
||||
```
|
||||
@@ -254,18 +254,20 @@ android {
|
||||
androidResources {
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.DS_Store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
noCompress = ['txt', 'bin', 'html', 'png', 'json', 'mwm', 'ttf', 'sdf', 'ui', 'config', 'csv', 'spv', 'obj']
|
||||
// Some languages not supported by Android require to be specified here to be applied
|
||||
localeFilters += [
|
||||
"en",
|
||||
"af",
|
||||
"ar",
|
||||
"az",
|
||||
"be",
|
||||
"bg",
|
||||
"bn",
|
||||
"ca",
|
||||
"cs",
|
||||
"da",
|
||||
"de",
|
||||
"el",
|
||||
"en",
|
||||
"en-rGB",
|
||||
"es",
|
||||
"es-rMX",
|
||||
@@ -275,28 +277,38 @@ android {
|
||||
"fi",
|
||||
"fr",
|
||||
"fr-rCA",
|
||||
"iw",
|
||||
"gl",
|
||||
"gsw",
|
||||
"he",
|
||||
"hi",
|
||||
"hu",
|
||||
"id",
|
||||
"in",
|
||||
"is",
|
||||
"it",
|
||||
"iw",
|
||||
"ja",
|
||||
"kw",
|
||||
"ko",
|
||||
"lt",
|
||||
"lv",
|
||||
"mr",
|
||||
"mt",
|
||||
"nb",
|
||||
"nb-rNO",
|
||||
"nl",
|
||||
"pl",
|
||||
"pt",
|
||||
"pt-rBR",
|
||||
"ro",
|
||||
"ru",
|
||||
"sl",
|
||||
"sk",
|
||||
"sr",
|
||||
"b+sr+Latn",
|
||||
"sv",
|
||||
"sw",
|
||||
"ta",
|
||||
"th",
|
||||
"tr",
|
||||
"uk",
|
||||
@@ -351,6 +363,8 @@ dependencies {
|
||||
implementation libs.androidx.recyclerview
|
||||
implementation libs.androidx.work.runtime
|
||||
implementation libs.androidx.lifecycle.process
|
||||
implementation libs.androidx.documentfile
|
||||
// 1.13 Material library version doesn't render properly alpha properties on map buttons
|
||||
implementation libs.android.material
|
||||
// Fix for app/organicmaps/util/FileUploadWorker.java:14: error: cannot access ListenableFuture
|
||||
// https://github.com/organicmaps/organicmaps/issues/6106
|
||||
|
||||
32
android/app/src/fdroid/play/listings/ca/full-description.txt
Normal file
32
android/app/src/fdroid/play/listings/ca/full-description.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
Una aplicació de mapes gratuïta i de codi obert dirigida per la comunitat basada en dades d'OpenStreetMap i reforçada amb el compromís amb la transparència, la privadesa i la no ànim de lucre. CoMaps és una derivació/fork d'Organic Maps, que al seu torn és una derivació de Maps.ME.
|
||||
|
||||
Llegiu sobre els motius del projecte i la seva direcció a <b><i>codeberg.org/comaps</i></b>.
|
||||
Uneix-te a la comunitat i ajuda a crear la millor aplicació de mapes
|
||||
• Utilitza l'aplicació i difon-la
|
||||
• Dona comentaris i informa de problemes
|
||||
• Actualitza les dades del mapa a l'aplicació o al lloc web d'OpenStreetMap
|
||||
|
||||
‣ <b>Enfocat fora de línia</b>: Planifica i navega pel teu viatge a l'estranger sense necessitat de servei mòbil, cerca punts de referència mentre fas una excursió llunyana, etc. Totes les funcions de l'aplicació estan dissenyades per funcionar fora de línia.
|
||||
‣ <b>Respecte a la privadesa</b>: L'aplicació està dissenyada tenint en compte la privadesa: no identifica persones, no fa seguiment i no recopila informació personal. Sense anuncis.
|
||||
‣ <b>Senzill i polit</b>: funcions essencials fàcils d'utilitzar que simplement funcionen.
|
||||
‣ <b>Estalvia bateria i espai</b>: No consumeix bateria com altres aplicacions de navegació. Els mapes compactes estalvien espai preciós al telèfon.
|
||||
‣ <b>Gratuït i creat per la comunitat</b>: Gent com tu ha ajudat a crear l'aplicació afegint llocs a OpenStreetMap, provant i donant comentaris sobre les funcions i aportant les seves habilitats de desenvolupament i diners.
|
||||
‣ <b>Presa de decisions i finances obertes i transparents, sense ànim de lucre i de codi obert.</b>
|
||||
|
||||
<b>Característiques principals</b>:
|
||||
• Mapes detallats descarregables amb llocs que no estan disponibles amb Google Maps
|
||||
• Mode exterior amb rutes de senderisme destacades, càmpings, fonts d'aigua, pics, corbes de nivell, etc.
|
||||
• Senders per caminar i carrils bici
|
||||
• Punts d'interès com restaurants, gasolineres, hotels, botigues, llocs d'interès i molts més
|
||||
• Cerca per nom, adreça o categoria de punt d'interès
|
||||
• Navegació amb anuncis de veu per caminar, anar amb bicicleta o conduir
|
||||
• Marca els teus llocs preferits amb un sol toc
|
||||
• Articles de la Viquipèdia fora de línia
|
||||
• Capa i indicacions de trànsit de metro
|
||||
• Enregistrament de rutes
|
||||
• Exporta i importa marcadors i rutes en formats KML, KMZ i GPX
|
||||
• Un mode fosc per utilitzar durant la nit
|
||||
• Millora les dades del mapa per a tothom mitjançant un editor bàsic integrat
|
||||
|
||||
<b>La llibertat és aquí</b>
|
||||
Descobreix el teu viatge, navega pel món amb la privadesa i la comunitat al capdavant!
|
||||
@@ -1,6 +1,6 @@
|
||||
Eine von der Community betriebene, kostenlose Open-Source Karten-App, die auf OpenStreetMap Daten basiert. Transparent und nicht gewinnorientiert. CoMaps ist ein Fork/Abspaltung von Organic Maps, die wiederum ein Fork/Abspaltung von Maps.Me ist.
|
||||
|
||||
Lese mehr über die Gründe und Ziele des Projektes unter <b><i>codeberg.org/comaps</i></b>.
|
||||
Lese mehr über die Gründe und Ziele des Projektes unter <b><i>codeberg.org/comaps</i></b> (auf Englisch).
|
||||
Werde Teil der Community und hilf mit, die beste Karten-App zu entwickeln
|
||||
• Nutze die App und erzähle anderen davon
|
||||
• Gib Feedback und melde Probleme
|
||||
@@ -9,9 +9,9 @@ Werde Teil der Community und hilf mit, die beste Karten-App zu entwickeln
|
||||
‣ <b>Einfach und ausgereift</b>: Essenzielle, leicht zu bedienende Funktionen, die einfach funktionieren.
|
||||
‣ <b>Offline-orientiert</b>: Plane und navigiere im Ausland ohne Mobilfunkverbindung, finde Wegpunkte auf abgelegenen Wanderungen usw. Alle Funktionen sind für den Offline-Einsatz konzipiert.
|
||||
‣ <b>Datenschutzfreundlich</b>: Die App wurde mit Fokus auf Privatsphäre entwickelt – keine Personenidentifikation, kein Tracking, keine Erfassung persönlicher Daten, keine Werbung.
|
||||
‣ <b>Spart Akku und Speicherplatz</b>: Verbraucht nicht unnötig Akku wie andere Navi-Apps. Kompakte Karten sparen Speicherplatz auf deinem Gerät.
|
||||
‣ <b>Spart Akku und Speicherplatz</b>: Verbraucht nicht unnötig viel Akku wie andere Navi-Apps. Kompakte Karten sparen Speicherplatz auf deinem Gerät.
|
||||
‣ <b>Kostenlos und von der Community entwickelt</b>: Menschen wie du haben geholfen, diese App zu entwickeln – durch das Hinzufügen von Orten zu OpenStreetMap, Testen von neuen Funktionen, Softwareentwicklung oder Spenden.
|
||||
‣ <b>Offen und transparent bei Entscheidungen und Finanzen, gemeinnützig und vollständig Open-Source</b>
|
||||
‣ <b>Offen und transparent bei Entscheidungen und Finanzen, gemeinnützig und vollständig Open-Source.</b>
|
||||
|
||||
<b>Hauptfunktionen</b>:
|
||||
• Detaillierte, herunterladbare Karten mit Orten, die bei Google Maps oft fehlen
|
||||
@@ -28,5 +28,5 @@ Werde Teil der Community und hilf mit, die beste Karten-App zu entwickeln
|
||||
• Dunkler Modus für die Nutzung bei Nacht
|
||||
• Kartenbearbeitung direkt in der App mit einem einfachen Editor
|
||||
|
||||
<b>Entdecke die Unabhängigkeit</b>
|
||||
Entdecke deine Reise – navigiere in der Welt mit Datenschutz!
|
||||
<b>Freiheit beginnt hier</b>
|
||||
Entdecke deine Reise, wobei Datenschutz und Gemeinschaft im Vordergrund stehen!
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
• OSM-Daten vom 6. Januar
|
||||
• Editor: POI mit mehr als einem Tag, (z.B. Arten von Kunstwerken wie Skulpturen...), mehr POI können als leer/stillgelegt werden
|
||||
• Miniatureisenbahnen & Kläranlagen hinzugefügt
|
||||
• Material 3-Dialoge & dunklerer Hintergrund im Darkmode
|
||||
• Fiktive Geschwindigkeitsbegrenzungen für Verbindungsstraßen entfernt
|
||||
• Versatz der Kameraausschnitt in der Navigation behoben
|
||||
• Weniger empfindliches langes Antippen
|
||||
|
||||
Weitere Details unter codeberg.org/comaps/comaps/releases
|
||||
@@ -1 +1 @@
|
||||
Einfache Navigation - Entdecken Sie mehr von Ihrer Reise - Community-Entwickelt
|
||||
Leichte Navigation – Erlebe mehr von deiner Reise – Community-unterstützt
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
A community led free & open source maps app based on OpenStreetMap data and reinforced with commitment to transparency, privacy and being not-for-profit. CoMaps is a fork/spin-off of Organic Maps, which in turn is a fork of Maps.ME.
|
||||
|
||||
Read on about reasons for the project and its direction at <b><i>codeberg.org/comaps</i></b>.
|
||||
Join the community there and help make the best maps app
|
||||
• Use the app and spread the word about it
|
||||
• Give feedback and report issues
|
||||
• Update map data in the app or on the OpenStreetMap website
|
||||
|
||||
‣ <b>Offline focused</b>: 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.
|
||||
‣ <b>Respecting Privacy</b>: The app is designed with privacy in mind - it does not identify people, does not track, and does not collect personal information. Ad free.
|
||||
‣ <b>Simple and Polished</b>: essential easy to use features that just work.
|
||||
‣ <b>Saves Your Battery and Space</b>: Doesn’t drain your battery like other navigation apps. Compact maps save precious space on your phone.
|
||||
‣ <b>Free and Built by the Community</b>: People like you helped build the app by adding places to OpenStreetMap, testing and giving feedback on features and contributing their development skills and money.
|
||||
‣ <b>Open and Transparent Decision-making and Financials, Not-for-profit and Fully Open Source.</b>
|
||||
|
||||
<b>Main Features</b>:
|
||||
• Downloadable detailed maps with places which are not available with Google Maps
|
||||
• Outdoor mode with highlighted hiking trails, campsites, water sources, peaks, contour lines, etc
|
||||
• Walking paths and cycleways
|
||||
• Points of interest like restaurants, gas stations, hotels, shops, sightseeing and many more
|
||||
• Search by name or an address or by point of interest category
|
||||
• Navigation with voice announcements for walking, cycling, or driving
|
||||
• Bookmark your favorite places with a single tap
|
||||
• Offline Wikipedia articles
|
||||
• Subway transit layer and directions
|
||||
• Track recording
|
||||
• Export and import bookmarks and tracks in KML, KMZ, GPX formats
|
||||
• A dark mode to use during the night
|
||||
• Improve map data for everyone using a basic built-in editor
|
||||
|
||||
<b>Freedom Is Here</b>
|
||||
Discover your journey, navigate the world with privacy and community at the forefront!
|
||||
@@ -0,0 +1,32 @@
|
||||
A community-led free & open source maps app based on OpenStreetMap data and reinforced with commitment to transparency, privacy and being not-for-profit. CoMaps is a fork/spin-off of Organic Maps, which in turn is a fork of Maps.ME.
|
||||
|
||||
Read on about reasons for the project and its direction at <b><i>codeberg.org/comaps</i></b>.
|
||||
Join the community there and help make the best maps app
|
||||
• Use the app and spread the word about it
|
||||
• Give feedback and report issues
|
||||
• Update map data in the app or on the OpenStreetMap website
|
||||
|
||||
‣ <b>Offline-focused</b>: 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.
|
||||
‣ <b>Respecting Privacy</b>: The app is designed with privacy in mind - does not identify people, does not track, and does not collect personal information. Ads-free.
|
||||
‣ <b>Simple and Polished</b>: essential easy to use features that just work.
|
||||
‣ <b>Saves Your Battery and Space</b>: Doesn’t drain your battery like other navigation apps. Compact maps save precious space on your phone.
|
||||
‣ <b>Free and Built by the Community</b>: People like you helped build the app by adding places to OpenStreetMap, testing and giving feedback on features and contributing their development skills and money.
|
||||
‣ <b>Open and Transparent Decision-making and Financials, Not-for-profit and Fully Open Source.</b>
|
||||
|
||||
<b>Main Features</b>:
|
||||
• Downloadable detailed maps with places which are not available with Google Maps
|
||||
• Outdoor mode with highlighted hiking trails, campsites, water sources, peaks, contour lines, etc
|
||||
• Walking paths and cycleways
|
||||
• Points of interest like restaurants, gas stations, hotels, shops, sightseeing and many more
|
||||
• Search by name or an address or by point of interest category
|
||||
• Navigation with voice announcements for walking, cycling, or driving
|
||||
• Bookmark your favorite places with a single tap
|
||||
• Offline Wikipedia articles
|
||||
• Subway transit layer and directions
|
||||
• Track recording
|
||||
• Export and import bookmarks and tracks in KML, KMZ, GPX formats
|
||||
• A dark mode to use during the night
|
||||
• Improve map data for everyone using a basic built-in editor
|
||||
|
||||
<b>Freedom Is Here</b>
|
||||
Discover your journey, navigate the world with privacy and community at the forefront!
|
||||
@@ -0,0 +1 @@
|
||||
Easy map navigation - Discover more of your journey - Powered by the community
|
||||
1
android/app/src/fdroid/play/listings/en-GB/title.txt
Normal file
1
android/app/src/fdroid/play/listings/en-GB/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Hike, Bike, Drive Offline with Privacy
|
||||
@@ -1,13 +1,9 @@
|
||||
• Fixed voice directions pronouncing weird symbols in the beginning
|
||||
• OpenStreetMap data as of November 23
|
||||
• OpenStreetMap data as of January 6
|
||||
• Editor: add POI types with more than one OSM tag, e.g. artwork subtypes sculptures, paintings..; more POI types could be marked as vacant/disused
|
||||
• Added miniature railways and wastewater treatment plants
|
||||
• Use Material 3 dialogs and darker background in dark mode
|
||||
• Removed fictional speed limits for link roads
|
||||
• Fixed camera cutout offset in navigation
|
||||
• Less sensitive long tap (full-screen mode)
|
||||
|
||||
Changes in the previous release:
|
||||
• Added trees
|
||||
• Made bus stop icons smaller and show up earlier
|
||||
• Reduce visibility of entrances
|
||||
• Added several other POI types
|
||||
• Show sand areas on the map
|
||||
• Add business is vacant option to the OSM editor
|
||||
• Improved road shields in Europe
|
||||
• Avoid paved roads routing option
|
||||
• Added icons to the settings page
|
||||
More details on codeberg.org/comaps/comaps/releases
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
• Datos OSM a 06/01
|
||||
• Editor: se añaden tipos de POI con más de una etiqueta, ej. esculturas, pinturas...; se podrán marcar más tipos como en desuso
|
||||
• Se añaden ferrocarriles mini y plantas de tratamiento de aguas
|
||||
• Se utilizan diálogos Material 3 y fondo más oscuro en el modo oscuro
|
||||
• Se eliminan límites de velocidad ficticios para enlaces
|
||||
• Se corrige desplazamiento en la navegación
|
||||
• Toque largo menos sensible (en pantalla completa)
|
||||
|
||||
Más detalles en codeberg.org/comaps/comaps/releases
|
||||
@@ -0,0 +1,9 @@
|
||||
• Données OpenStreetMap du 6 janvier
|
||||
• Éditeur : support des types de POI avec plus d’un tag OSM, par exemple types d’œuvres d’art sculptures, peintures..
|
||||
• Rendu des rails pour trains miniatures et des stations d'épuration
|
||||
• Utilisation des pop-ups Material 3 et d'un fond plus sombre en thème sombre
|
||||
• Suppression des limites de vitesse fictives sur les bretelles de routes
|
||||
• Correction des instructions de navigation sur les appareils avec des encoches
|
||||
• Appui long moins sensible (en mode plein écran)
|
||||
|
||||
Plus de détails sur codeberg.org/comaps/comaps/releases
|
||||
@@ -0,0 +1,7 @@
|
||||
• OpenStreetMap podaci od 6. siječnja
|
||||
• Uređivač: dodane točake interesa s više od jedne OSM oznake, npr. podtipovi umjetnina kao skulpture, slike...; više točaka interesa se sada može označiti kao upražnjene/napuštene
|
||||
• Dodane minijaturne željeznice i postrojenja za pročišćavanje otpadnih voda
|
||||
• Korištenje Material 3 dijaloga i tamnije pozadine u tamnom načinu rada
|
||||
• Uklonjena su izmišljena ograničenja brzine za spojne ceste
|
||||
|
||||
Više detalja na codeberg.org/comaps/comaps/releases
|
||||
32
android/app/src/fdroid/play/listings/kw/full-description.txt
Normal file
32
android/app/src/fdroid/play/listings/kw/full-description.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
Unn app mappys rydh hag ygor selys war an kemeneth, selys war data OpenStreetMap ha krefhys gans omrians dhe klerder, privetter, ha heb budh. CoMaps yw unn forgh a Organic Maps, unn forgh a Maps.ME.
|
||||
|
||||
Redya ma a-dro resons rag an ragdres hag y tu yn <b><i>codeberg.org/comaps</i></b>.
|
||||
Omjunya an kemeneth ena ha gweres gul an app mappys gwella
|
||||
• Devnydhya an app ha kevrenna y'n
|
||||
• Dasliva ha derivas kudynnow
|
||||
• Nowedhi data mappa yn an app po war an gwasiva OpenStreetMap
|
||||
|
||||
‣ <b>Fogellys war dhywarlinen</b>: Devisya ha viajya dha viaj tramor neb res rag gonis kellgowser, hwilas leow ha war unn gwandrans pell, h.e. Oll nasyow app yw desinys dhe oberi dhywarlinen.
|
||||
‣ <b>Ow Gul Revrons dhe Privetter</b>: An app yw desinys gans privetter yn brys - na aswon tus, na helerghi, ha na kuntel kudhlow personel. Heb argemynnow.
|
||||
‣ <b>Sempel ha Polsys</b>: nasyow es may oberi poran.
|
||||
‣ <b>Sawya dha Batri hag Efander</b>: Na gwakhe dha batri haval dhe appys navigacyon aral. Mappys kesstrothys sawya efander precyous war dha kellgowser.
|
||||
‣ <b>Rydh ha Byldys gans an Kemeneth</b>: Tus haval dhe ty gwerys byldya an app gans owth addya leos dhe OpenStreetMap, ow previ ha dasliva a-dro nasyow hag ow kevri aga sleynethow hag arghans i.
|
||||
‣ <b>Erviransow hag Arghansereth Ygor ha Treusweladow, Heb Budh, ha Kod Ygor yn Leun</b>
|
||||
|
||||
<b>Nasyow Chif</b>
|
||||
• Mappys manylys iskargadow gans leos ankavadow war Google Maps
|
||||
• Fordh Yn-Mes gans olow gwandrans, kampvaow, pennfentynnyow dowr, menydhyow, linennow kettres, h.e.
|
||||
• Hensyow kerdhyans ha diwrosyans
|
||||
• Leow kepar ha bostiow, petrolvaow, ostelyow, gwethjiow, troyll gwelyow, ha moy
|
||||
• Hwilas gans hanow po unn trigva po gans klass leow
|
||||
• Navigacyon gans gwarnyansow kewsys rag ow walkya, ow diwrosa, po ow lewa
|
||||
• Lyververkya dha leow drudh gans unn tava unnik
|
||||
• Artiklow dhywarlinen Wikipedya
|
||||
• Gwiskas metro ha tuyow
|
||||
• Helerghyans lerghow
|
||||
• Esperthi hag ynperthi lyververkys ha lerghow yn furvasow KML, KMZ, GPX
|
||||
• Unn fordh du rag an nos
|
||||
• Gwellhe data mappa rag peub ow devnydhya unn chanjyell selyek
|
||||
|
||||
<b>Rydhses Yw Omma</b>
|
||||
Trovya dha viaj, viajya an nor gans privetter ha kemeneth a-talenebek!
|
||||
@@ -0,0 +1 @@
|
||||
Navigacyon mappa es - Trovya moy a dha kerdh - Selys war an kemeneth
|
||||
1
android/app/src/fdroid/play/listings/kw/title.txt
Normal file
1
android/app/src/fdroid/play/listings/kw/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - Viajya gans Privetter
|
||||
32
android/app/src/fdroid/play/listings/lt/full-description.txt
Normal file
32
android/app/src/fdroid/play/listings/lt/full-description.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
Bendruomenės vystoma nemokama atvirojo kodo žemėlapių programa, pagrįsta „OpenStreetMap“ duomenimis ir sustiprinta įsipareigojimu užtikrinti skaidrumą, privatumą ir pelno nesiekimą. „CoMaps“ programa yra kilusi iš „Organic Maps“, o pastaroji – iš programos „Maps.ME“.
|
||||
|
||||
Apie šio projekto kilmę, jos priežastis ir palaikomą kryptį galite paskaityti adresu <b><i>codeberg.org/comaps</i></b>.
|
||||
Prisijunkite prie bendruomenės ir padėkite sukurti geriausią žemėlapių programą
|
||||
• Naudokitės programa ir paskleiskite žinią apie ją
|
||||
• Teikite atsiliepimus ir praneškite apie problemas
|
||||
• Atnaujinkite žemėlapių duomenis programoje arba „OpenStreetMap“ svetainėje
|
||||
|
||||
‣ <b>Skirta naudoti neprisijungus prie interneto</b>: planuokite maršrutus ir keliaukite užsienyje be mobiliojo ryšio, ieškokite kelionės taškų tolimuose žygiuose ir pan. Visos programos funkcijos yra pritaikytos veikti be interneto ryšio.
|
||||
‣ <b>Gerbia privatumą</b>: programėlė sukurta, teikiant prioritetą privatumui: neidentifikuoja žmonių, neseka ir nerenka asmeninės informacijos. Be reklamų.
|
||||
‣ <b>Paprasta ir išbaigta</b>: esminės, lengvai naudojamos funkcijos, kurios tiesiog veikia.
|
||||
‣ <b>Taupo bateriją ir vietą</b>: neišsekina baterijos kaip kitos navigacijos programos. Kompaktiški žemėlapiai tausoja vietą jūsų telefone.
|
||||
‣ <b>Nemokama ir kuriama bendruomenės</b>: paprasti žmonės – kaip jūs – padėjo sukurti šią programą, pildydami „OpenStreetMap“ žemėlapį, išbandydami ir pateikdami atsiliepimus apie funkcijas bei prisidėdami savo programavimo įgūdžiais ir pinigais.
|
||||
‣ <b>Atviras ir skaidrus sprendimų priėmimas ir finansai, ne pelno siekianti ir visiškai atviro kodo programa.</b>
|
||||
|
||||
<b>Pagrindinės funkcijos</b>:
|
||||
• Atsisiunčiami išsamūs žemėlapiai su vietomis, kurių nėra „Google Maps“;
|
||||
• Žygių pėsčiomis veiksena su pažymėtais pėsčiųjų takais, stovyklavietėmis, vandens šaltiniais, kalnų viršūnėmis, reljefo linijomis ir kt.;
|
||||
• Pėsčiųjų ir dviračių takai;
|
||||
• Potencialūs kelionės taškai, kaip antai restoranai, degalinės, viešbučiai, parduotuvės, lankytinos vietos ir daugelis kitų;
|
||||
• Paieška pagal pavadinimą, adresą arba lankytinos vietos kategoriją;
|
||||
• Navigacija su balso instrukcijomis pėstiesiems, dviratininkams ir motorinio transporto vairuotojams;
|
||||
• Mėgstamų vietų įsiminimas vienu bakstelėjimu;
|
||||
• „Vikipedijos“ straipsniai, prieinami neprisijungus prie interneto;
|
||||
• Kelionių metro sluoksnis ir maršrutai;
|
||||
• Trasų įrašymas;
|
||||
• Žymių ir trasų eksportavimas ir importavimas KML, KMZ, GPX formatais;
|
||||
• Tamsaus žemėlapio veiksena, skirta naudoti naktį;
|
||||
• Galimybė papildyti žemėlapio duomenis visiems, naudojantis įtaisytuoju baziniu redaktoriumi.
|
||||
|
||||
<b>Laisvė yra čia</b>
|
||||
Atraskite savo kelionę ir keliaukite po pasaulį, kuriame privatumas ir bendruomenė yra svarbiausia!
|
||||
@@ -0,0 +1 @@
|
||||
Paprasta ir patogi navigacija – Turiningesnės kelionės – Vystoma bendruomenės
|
||||
1
android/app/src/fdroid/play/listings/lt/title.txt
Normal file
1
android/app/src/fdroid/play/listings/lt/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps – keliaukite atsijungę ir privačiai
|
||||
@@ -0,0 +1,9 @@
|
||||
• Dados OSM de 6/1
|
||||
• Editor: adição de POIs com mais de uma etiqueta OSM, por exemplo esculturas e pinturas; mais tipos de POI podem ser marcados como vagos/desativados
|
||||
• Adição de ferrovias em miniatura e ETEs
|
||||
• Uso de diálogos Material 3 e fundo mais escuro no modo escuro
|
||||
• Remoção de limites de velocidade fictícios para vias de ligação
|
||||
• Correção de recorte da câmera na navegação
|
||||
• Toque longo menos sensível (modo tela cheia)
|
||||
|
||||
Mais detalhes em codeberg.org/comaps/comaps/releases
|
||||
@@ -0,0 +1,8 @@
|
||||
• Карты OpenStreetMap от 6 января
|
||||
• Редактор: возможность добавления POI, определяемых несколькими тегами, например церквей, мечетей, буддистских храмов..
|
||||
• Добавлены миниатюрные железные дороги и очистные сооружения
|
||||
• Использование диалогов Material 3 и более тёмного фона в тёмном режиме
|
||||
• Удалены фиктивные ограничения скорости для заездов / съездов с шоссе
|
||||
• Менее чувствительное длительное нажатие на экран (переход в полноэкранный режим)
|
||||
|
||||
Подробнее на codeberg.org/comaps/comaps/releases
|
||||
@@ -1,31 +1,31 @@
|
||||
Brezplačno in odprtokodno zemljevidno orodje, ki ga vodi skupnost, temelji na podatkih OpenStreetMap in je okrepljena s predanostjo transparentnosti, zasebnosti in nedobičkonosnosti. CoMaps je izpeljanka OrganicMaps, ta pa je izpeljanka Maps.ME.
|
||||
Brezplačno in odprtokodno zemljevidno orodje, ki ga vodi skupnost, temelji na podatkih OpenStreetMap in je okrepljena s predanostjo transparentnosti, zasebnosti in nepridobitnosti. CoMaps je izpeljanka OrganicMaps, ta pa je izpeljanka Maps.ME.
|
||||
|
||||
Preverite si o razlogih za ta projekt in njegovi usmerjenosti na <b><i>codeberg.org/comaps</i></b>.
|
||||
Preberite si o razlogih za ta projekt in njegovi usmerjenosti na <b><i>codeberg.org/comaps</i></b>.
|
||||
Pridružite se skupnosti in pomagajte narediti najboljše zemljevidno orodje
|
||||
• Uporabljajte orodje in širite glas o njem
|
||||
• Dajajte povratne informacije in poročajte o napakah
|
||||
• Posodabljajte podatke zemljevida v tem orodju ali na spletni strani OpenStreetMap
|
||||
|
||||
‣ <b>Osredotočeno na uporabo brez povezave</b>: Načrtujte in se usmerjajte na vašem potovanju v tujini vrez potrebe po mobilnih podatkih, iščite vmesne točke potocanja ko ste na daljšem pohodu ipd. Vse zmogljivosti orodja so zasnovane za delo brez povezave.
|
||||
‣ <b>Osredotočeno na uporabo brez povezave</b>: Načrtujte in se usmerjajte na vašem potovanju v tujini vrez potrebe po mobilnih podatkih, iščite vmesne točke potovanja ko ste na daljšem pohodu ipd. Vse zmogljivosti orodja so zasnovane za delo brez povezave.
|
||||
‣ <b>Spoštovanje zasebnosti</b>: orodje je zasnovano z mislijo na zasebnost – ne prepoznava oseb, ne sledi in ne zbira osebnih podatkov. Brez oglasov.
|
||||
‣ <b>Preprosto in dodelano</b>: nujne zmogljivosti, enostavne za uporabo, ki preprosto delujejo.
|
||||
‣ <b>Prihrani vašo baterijo in prostor.</b>: ne izčrpava vaše baterije kakor druga usmerjevalna orodja. Strnjeni zemljevidi prihranijo dragocen prostor na vašem telefonu.
|
||||
‣ <b>Brezplačno in ustvarjeno v skupnosti</b>: ljudje kot ste vi pomagajo ustvarjati to orodje, tako da dodajajo kraje na OpenStreetMap, preizkušajo in dajejo povratne informacije o zmogljivostih in prispevajo svoje razvijalske sposobnosti in sredstva.
|
||||
‣ <b>Odprto in transparentno odločanje in finance, nedobičkonosno in popolnoma odprtokodno.</b>
|
||||
‣ <b>Odprto in transparentno odločanje in finance, nepridobitno in popolnoma odprtokodno.</b>
|
||||
|
||||
<b>Glavne zmogljivosti</b>:
|
||||
• Prenosljivi podrobni zemljevidi s kraji, ki na Googlovoh zemljevidih niso na voljo.
|
||||
• Prenosljivi podrobni zemljevidi s kraji, ki na Googlovih zemljevidih niso na voljo.
|
||||
• Prikaz za dejavnosti na prostem s poudarjenimi pohodniškimi potmi, tabornimi prostori, vodnimi viri, vrhovi, plastnicami itd.
|
||||
• Pešpoti in kolesarke poti
|
||||
• Kraji zanimanja, npr. restavracije, bencinske črpalke, hoteli, trgovine, znamenitosti in mnogo več
|
||||
• Iščite po imenu, hišnemu naslovu ali po vrsti
|
||||
• Usmerjanje z glasovnimi obvestili za hojo, kolesarjenje ali vožnjo avtomobila.
|
||||
• Usmerjanje z glasovnimi navodili za hojo, kolesarjenje ali vožnjo avtomobila.
|
||||
• Zaznamujte svoje najljubše kraje s preprostim dotikom
|
||||
• Wikipedijini članki brez povezave
|
||||
• Prometna plast podzemne železnice z usmerjanjem
|
||||
• Izvozite ali uvozite zaznamke in sledi v oblikah KML, KMZ, GPX
|
||||
• Temni prikaz za uporabo ponoči
|
||||
• Izboljšajtw podatke zemljevida za vse z uporabo vgrajenega urejevalnika
|
||||
• Izboljšajte podatke zemljevida za vse z uporabo vgrajenega urejevalnika
|
||||
|
||||
<b>Svoboda je tu</b>
|
||||
Odkijte več o vašem potovanju, usmerjajte se po svetu s poudarkom na zasebnosti in skupnostnem delovanju!
|
||||
|
||||
@@ -1 +1 @@
|
||||
Enostavno usmerjanje – Odkrij več o svojem potovanju – Podprto v skupnosti
|
||||
Enostavno usmerjanje – Odkrijte več o svojem potovanju – Podprto v skupnosti
|
||||
|
||||
@@ -1 +1 @@
|
||||
CoMaps–Hodi, kolesari, vozi brez povezave, zasebno
|
||||
CoMaps – hodi in vozi brez povezave, zasebno
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
Besplatna aplikacija otvorenog koda koju održava zajednica i koja se zasniva na OpenStreetMap podacima. Neprofitna, transparentna i ceni vašu privatnost. CoMaps je nastala od aplikacije Organic Maps, koja je nastala od aplikacije Maps.ME.
|
||||
|
||||
Pročitajte o razlozima projekta i njegovog pravca na <b><i>codeberg.org/comaps</i></b>
|
||||
Pridružite se otvorenoj zajednici i pomozite da napravimo najbolju aplikaciju za mape
|
||||
• Koristite aplikaciju i proširite glas o tome
|
||||
• Dajte povratne informacije i prijavite probleme
|
||||
• Ažurirajte podatke na mapi u aplikaciji ili na sajtu OpenStreetMap
|
||||
|
||||
‣ <b>Fokusirano na oflajn rad</b>: Planirajte i upravljajte putovanjem u inostranstvu bez potrebe za mobilnim internetom, tražite usputne tačke dok ste na zabačenom planinskom putu, itd. Sve funkcionalnosti su dizajnirane za rad bez interneta.
|
||||
‣ <b>Poštovanje privatnosti</b>: Aplikacija je osmišljena sa privatnošću korisnika na umu - ne identifikuje ljude, ne prati ili prikuplja lične podatke. Nema reklama.
|
||||
‣ <b>Jednostavno i elegantno</b>: Aplikacija je laka za upotrebu i jednostavno funkcioniše.
|
||||
‣ <b>Čuva vašu bateriju i prostor</b>: Ne troši bateriju kao ostale aplikacije za navigaciju. Kompaktne mape čuvaju dragoceni prostor na vašem telefonu.
|
||||
‣ <b>Otvoreno i napravljeno od strane zajednice</b>: Ljudi poput vas su doprineli razvoju aplikacije dodavanjem lokacija na OpenStreetMap, testiranjem, davanjem povratnih informacija o aplikaciji i pomogli su svojim programerskim veštinama i donacijama.
|
||||
‣ <b>Otvoreno i transparentno donošenje odluka i upotreba donacija, neprofitna i potpuno otvorenog koda.</b>
|
||||
|
||||
<b> Glavne karakteristike </b>:
|
||||
• Preuzmimanje detaljne mape sa lokacijama koje nisu dostupne sa Google mapama
|
||||
• Režim za izlete sa istaknutim planinarskim stazama, kampovima, izvorima vode, planinskim vrhovima, izohipsama itd.
|
||||
• Staze za planinarenje i bicikle
|
||||
• Tačke interesa kao što su restorani, benzinske pumpe, hoteli, prodavnice, znamenitosti i još mnogo toga
|
||||
• Pretraga po nazivu, adresi ili kategorijama
|
||||
• Navigacija sa glasovnim instrukcijama za hodanje, vožnju bicikla ili automobila
|
||||
• Označivanje omiljenih mesta jednim dodirom
|
||||
• Oflajn članci Vikipedije
|
||||
• Tranzitni sloj podzemne železnice sa navigacijom
|
||||
• Snimanje GPS putanja
|
||||
• Uvoz i uvoz markera i putanja u KML, KMZ, GPX formate
|
||||
• Tamni režim za upotrebu tokom noći
|
||||
• Poboljšajte kartu za sve korisnike, koristeći osnovni editor za mape
|
||||
|
||||
<b> Sloboda je ovde </b>
|
||||
Otkrijte svoje putovanje, krećete se sa svetom sa privatnošću i zajednicom na prvom mestu!
|
||||
@@ -0,0 +1 @@
|
||||
Jednostavna navigacija - Saznajte više o svom putovanju - Održava zajednica
|
||||
1
android/app/src/fdroid/play/listings/sr-Latn/title.txt
Normal file
1
android/app/src/fdroid/play/listings/sr-Latn/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps - pešačenje, bicikl, vožnja sa privatnošću
|
||||
@@ -3,7 +3,7 @@ Eine von der Community betriebene, kostenlose Open-Source Karten App, die auf Op
|
||||
Werde Teil der Community und hilf mit, die beste Karten-App zu entwickeln
|
||||
• Nutze die App und erzähle anderen davon
|
||||
• Gib Feedback und melde Probleme
|
||||
• Aktualisiere Kartendaten in der App oder auf der OpenStreetMap Website
|
||||
• Aktualisiere Kartendaten in der App oder auf der OpenStreetMap-Webseite
|
||||
|
||||
<i>Dein Feedback und deine 5-Sterne-Bewertung sind die beste Unterstützung für uns!</i>
|
||||
|
||||
@@ -12,7 +12,7 @@ Werde Teil der Community und hilf mit, die beste Karten-App zu entwickeln
|
||||
‣ <b>Datenschutz ist uns wichtig!</b>: Die App wurde mit Fokus auf Privatsphäre entwickelt – kein Tracking, keine Erfassung persönlicher Daten, keine Werbung.
|
||||
‣ <b>Spart Akku und Speicherplatz</b>: Verbraucht nicht unnötig Akku wie andere Navi-Apps. Kompakte Karten sparen Speicherplatz auf Deinem Gerät.
|
||||
‣ <b>Kostenlos und von der Community entwickelt</b>: Menschen wie du haben geholfen, die App zu entwickeln – durch das Hinzufügen von Orten zu OpenStreetMap, Testen von neuen Funktionen, Softwareentwicklung oder Spenden.
|
||||
‣ <b>Offen und transparent bei Entscheidungen und Finanzen, gemeinnützig und vollständig Open Source</b>
|
||||
‣ <b>Offen und transparent bei Entscheidungen und Finanzen, gemeinnützig und vollständig Open Source.</b>
|
||||
|
||||
<b>Hauptfunktionen</b>:
|
||||
• Detaillierte, herunterladbare Karten mit Orten, die bei Google Maps oft fehlen
|
||||
@@ -24,13 +24,13 @@ Werde Teil der Community und hilf mit, die beste Karten-App zu entwickeln
|
||||
• Lesezeichen mit einem einzigen Tippen speichern
|
||||
• Offline verfügbare Wikipedia-Artikel
|
||||
• U- und S-Bahn-Netze
|
||||
• Aufzeichnen von GPS Tracks
|
||||
• Aufzeichnen von GPS-Tracks
|
||||
• Import und Export von Favoriten und Routen im KML-, KMZ- oder GPX-Format
|
||||
• Dunkler Modus für die Nutzung bei Nacht
|
||||
• Kartenbearbeitung direkt in der App mit einem einfachen Editor
|
||||
• Unterstützung für Android Auto
|
||||
|
||||
Bitte melde Probleme, schlage neue Funktionen vor und werde Teil der Community auf unserer Website: <b><i>comaps.app</i></b>
|
||||
Bitte melde Probleme, schlage neue Funktionen vor und werde Teil der Community auf unserer Website: <b><i>comaps.app/de/</i></b>
|
||||
|
||||
<b>Entdecke die Unabhängigkeit</b>
|
||||
Entdecke deine Reise – navigiere in der Welt mit Datenschutz!
|
||||
<b>Freiheit beginnt hier</b>
|
||||
Entdecke deine Reise, wobei Datenschutz und Gemeinschaft im Vordergrund stehen!
|
||||
|
||||
@@ -1 +1 @@
|
||||
Einfache Navigation - Entdecken Sie mehr von Ihrer Reise - Community-Entwickelt
|
||||
Leichte Navigation – Erlebe mehr von deiner Reise – Community-unterstützt
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
A community led free & open source maps app based on OpenStreetMap data and reinforced with commitment to transparency, privacy and being not-for-profit.
|
||||
|
||||
Join the community and help make the best maps app
|
||||
• Use the app and spread the word about it
|
||||
• Give feedback and report issues
|
||||
• Update map data in the app or on the OpenStreetMap website
|
||||
|
||||
<i>Your feedback and 5-star reviews are the best support for us!</i>
|
||||
|
||||
‣ <b>Simple and Polished</b>: essential easy to use features that just work.
|
||||
‣ <b>Offline focused</b>: 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.
|
||||
‣ <b>Respecting Privacy</b>: The app is designed with privacy in mind - it does not identify people, does not track, and does not collect personal information. Ad free.
|
||||
‣ <b>Saves Your Battery and Space</b>: Doesn’t drain your battery like other navigation apps. Compact maps save precious space on your phone.
|
||||
‣ <b>Free and Built by the Community</b>: People like you helped build the app by adding places to OpenStreetMap, testing and giving feedback on features and contributing their development skills and money.
|
||||
‣ <b>Open and Transparent Decision-making and Financials, Not-for-profit and Fully Open Source.</b>
|
||||
|
||||
<b>Main Features</b>:
|
||||
• Downloadable detailed maps with places which are not available with Google Maps
|
||||
• Outdoor mode with highlighted hiking trails, campsites, water sources, peaks, contour lines, etc
|
||||
• Walking paths and cycleways
|
||||
• Points of interest like restaurants, gas stations, hotels, shops, sightseeings and many more
|
||||
• Search by name or an address or by point of interest category
|
||||
• Navigation with voice announcements for walking, biking, or driving
|
||||
• Bookmark your favorite places with a single tap
|
||||
• Offline Wikipedia articles
|
||||
• Subway transit layer and directions
|
||||
• Track recording
|
||||
• Export and import bookmarks and tracks in KML, KMZ, GPX formats
|
||||
• A dark mode to use during the night
|
||||
• Improve map data for everyone using a basic built-in editor
|
||||
• Android Auto support
|
||||
|
||||
Please report app issues, suggest ideas and join our community at <b><i>comaps.app</i></b> website.
|
||||
|
||||
<b>Freedom Is Here</b>
|
||||
Discover your journey, navigate the world with privacy and community at the forefront!
|
||||
@@ -68,4 +68,4 @@ Por favor, informa de errores, sugiere ideas y únete a nuestra comunidad en el
|
||||
|
||||
<b>La Libertad Está Aquí</b>
|
||||
|
||||
Descubre tu camino, navega el mundo con privacidad y con la comunidad como prioridad.
|
||||
¡Descubre tu camino, navega el mundo con privacidad y con la comunidad como prioridad!
|
||||
|
||||
36
android/app/src/google/play/listings/lt/full-description.txt
Normal file
36
android/app/src/google/play/listings/lt/full-description.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
Bendruomenės vystoma nemokama atvirojo kodo žemėlapių programa, pagrįsta „OpenStreetMap“ duomenimis ir sustiprinta įsipareigojimu užtikrinti skaidrumą, privatumą ir pelno nesiekimą.
|
||||
|
||||
Prisijunkite prie bendruomenės ir padėkite sukurti geriausią žemėlapių programą
|
||||
• Naudokitės programa ir paskleiskite žinią apie ją
|
||||
• Teikite atsiliepimus ir praneškite apie problemas
|
||||
• Atnaujinkite žemėlapių duomenis programoje arba „OpenStreetMap“ svetainėje
|
||||
|
||||
<i>Jūsų atsiliepimai ir 5 žvaigždučių vertinimai yra geriausias palaikymas mums!</i>
|
||||
|
||||
‣ <b>Paprasta ir išbaigta</b>: esminės, lengvai naudojamos funkcijos, kurios tiesiog veikia.
|
||||
‣ <b>Skirta naudoti neprisijungus prie interneto</b>: planuokite maršrutus ir keliaukite užsienyje be mobiliojo ryšio, ieškokite kelionės taškų tolimuose žygiuose ir pan. Visos programos funkcijos yra pritaikytos veikti be interneto ryšio.
|
||||
‣ <b>Gerbia privatumą</b>: programėlė sukurta, teikiant prioritetą privatumui: neidentifikuoja žmonių, neseka ir nerenka asmeninės informacijos. Be reklamų.
|
||||
‣ <b>Taupo bateriją ir vietą</b>: neišsekina baterijos kaip kitos navigacijos programos. Kompaktiški žemėlapiai tausoja vietą jūsų telefone.
|
||||
‣ <b>Nemokama ir kuriama bendruomenės</b>: paprasti žmonės – kaip jūs – padėjo sukurti šią programą, pildydami „OpenStreetMap“ žemėlapį, išbandydami ir pateikdami atsiliepimus apie funkcijas bei prisidėdami savo programavimo įgūdžiais ir pinigais.
|
||||
‣ <b>Atviras ir skaidrus sprendimų priėmimas ir finansai, ne pelno siekianti ir visiškai atviro kodo programa.</b>
|
||||
|
||||
<b>Pagrindinės funkcijos</b>:
|
||||
• Atsisiunčiami išsamūs žemėlapiai su vietomis, kurių nėra „Google Maps“;
|
||||
• Žygių pėsčiomis veiksena su pažymėtais pėsčiųjų takais, stovyklavietėmis, vandens šaltiniais, kalnų viršūnėmis, reljefo linijomis ir kt.;
|
||||
• Pėsčiųjų ir dviračių takai;
|
||||
• Potencialūs kelionės taškai, kaip antai restoranai, degalinės, viešbučiai, parduotuvės, lankytinos vietos ir daugelis kitų;
|
||||
• Paieška pagal pavadinimą, adresą arba lankytinos vietos kategoriją;
|
||||
• Navigacija su balso instrukcijomis pėstiesiems, dviratininkams ir motorinio transporto vairuotojams;
|
||||
• Mėgstamų vietų įsiminimas vienu bakstelėjimu;
|
||||
• „Vikipedijos“ straipsniai, prieinami neprisijungus prie interneto;
|
||||
• Kelionių metro sluoksnis ir maršrutai;
|
||||
• Trasų įrašymas;
|
||||
• Žymių ir trasų eksportavimas ir importavimas KML, KMZ, GPX formatais;
|
||||
• Tamsaus žemėlapio veiksena, skirta naudoti naktį;
|
||||
• Galimybė papildyti žemėlapio duomenis visiems, naudojantis įtaisytuoju baziniu redaktoriumi;
|
||||
• „Android Auto“ palaikymas.
|
||||
|
||||
Praneškite apie programos problemas, siūlykite idėjas ir prisijunkite prie mūsų bendruomenės svetainėje <b><i>comaps.app</i></b>.
|
||||
|
||||
<b>Laisvė yra čia</b>
|
||||
Atraskite savo kelionę ir keliaukite po pasaulį, kuriame privatumas ir bendruomenė yra svarbiausia!
|
||||
@@ -0,0 +1 @@
|
||||
Paprasta ir patogi navigacija – Turiningesnės kelionės – Vystoma bendruomenės
|
||||
1
android/app/src/google/play/listings/lt/title.txt
Normal file
1
android/app/src/google/play/listings/lt/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
CoMaps – naviguokite privačiai
|
||||
@@ -1 +1 @@
|
||||
Enostavno usmerjanje – Odkrij več o svojem potovanju – Podprto v skupnosti
|
||||
Enostavno usmerjanje – Odkrijte več o svojem potovanju – Podprto v skupnosti
|
||||
|
||||
@@ -1 +1 @@
|
||||
CoMaps - Usmerjajte zasebno
|
||||
CoMaps - Usmerjajte se zasebno
|
||||
|
||||
@@ -500,6 +500,13 @@
|
||||
android:stopWithTask="false"
|
||||
/>
|
||||
|
||||
<service android:name=".location.LocationSharingService"
|
||||
android:foregroundServiceType="location"
|
||||
android:exported="false"
|
||||
android:enabled="true"
|
||||
android:stopWithTask="false"
|
||||
/>
|
||||
|
||||
<service
|
||||
android:name=".downloader.DownloaderService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.CallSuper;
|
||||
@@ -25,7 +26,15 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.checkbox.MaterialCheckBox;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import app.organicmaps.base.BaseMwmFragmentActivity;
|
||||
import app.organicmaps.dialog.CustomMapServerDialog;
|
||||
import app.organicmaps.downloader.MapManagerHelper;
|
||||
import app.organicmaps.intent.Factory;
|
||||
import app.organicmaps.sdk.Framework;
|
||||
@@ -38,11 +47,7 @@ import app.organicmaps.sdk.util.StringUtils;
|
||||
import app.organicmaps.util.UiUtils;
|
||||
import app.organicmaps.util.Utils;
|
||||
import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.checkbox.MaterialCheckBox;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -54,6 +59,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
|
||||
private MaterialTextView mTvMessage;
|
||||
private LinearProgressIndicator mProgress;
|
||||
private MaterialButton mBtnDownload;
|
||||
private MaterialButton mBtnAdvanced;
|
||||
private MaterialCheckBox mChbDownloadCountry;
|
||||
|
||||
private String mCurrentCountry;
|
||||
@@ -267,6 +273,14 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
|
||||
mProgress = findViewById(R.id.progressbar);
|
||||
mBtnDownload = findViewById(R.id.btn_download_resources);
|
||||
mChbDownloadCountry = findViewById(R.id.chb_download_country);
|
||||
mBtnAdvanced = findViewById(R.id.btn_advanced);
|
||||
|
||||
mBtnAdvanced.setOnClickListener(v -> {
|
||||
CustomMapServerDialog.show(this, url -> {
|
||||
prepareFilesDownload(false);
|
||||
});
|
||||
});
|
||||
mBtnAdvanced.setEnabled(true);
|
||||
|
||||
mBtnListeners = new View.OnClickListener[BTN_COUNT];
|
||||
mBtnNames = new String[BTN_COUNT];
|
||||
@@ -291,6 +305,11 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
mBtnDownload.setOnClickListener(mBtnListeners[action]);
|
||||
mBtnDownload.setText(mBtnNames[action]);
|
||||
|
||||
// Allow changing server only when idle or after an error.
|
||||
boolean advancedEnabled = (action == DOWNLOAD || action == TRY_AGAIN || action == RESUME);
|
||||
mBtnAdvanced.setEnabled(advancedEnabled);
|
||||
mBtnAdvanced.setAlpha(advancedEnabled ? 1f : 0.5f);
|
||||
}
|
||||
|
||||
private void doDownload()
|
||||
@@ -359,6 +378,9 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
|
||||
|
||||
private void finishFilesDownload(int result)
|
||||
{
|
||||
mBtnAdvanced.setEnabled(true);
|
||||
mBtnAdvanced.setAlpha(1f);
|
||||
|
||||
if (result == ERR_NO_MORE_FILES)
|
||||
{
|
||||
// World and WorldCoasts has been downloaded, we should register maps again to correctly add them to the model.
|
||||
@@ -424,16 +446,20 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
|
||||
default -> throw new AssertionError("Unexpected result code = " + result);
|
||||
};
|
||||
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(titleId)
|
||||
.setMessage(messageId)
|
||||
.setCancelable(true)
|
||||
.setOnCancelListener((dialog) -> setAction(PAUSE))
|
||||
.setOnCancelListener((dialog) -> setAction(RESUME))
|
||||
.setPositiveButton(R.string.try_again,
|
||||
(dialog, which) -> {
|
||||
setAction(TRY_AGAIN);
|
||||
onTryAgainClicked();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> {
|
||||
setAction(RESUME);
|
||||
})
|
||||
.setOnDismissListener(dialog -> mAlertDialog = null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ import app.organicmaps.sdk.routing.RoutingOptions;
|
||||
import app.organicmaps.sdk.search.SearchEngine;
|
||||
import app.organicmaps.sdk.settings.RoadType;
|
||||
import app.organicmaps.sdk.settings.UnitLocale;
|
||||
import app.organicmaps.sdk.sound.TtsPlayer;
|
||||
import app.organicmaps.sdk.util.Config;
|
||||
import app.organicmaps.sdk.util.LocationUtils;
|
||||
import app.organicmaps.sdk.util.PowerManagment;
|
||||
@@ -131,8 +132,6 @@ import app.organicmaps.widget.placepage.PlacePageViewModel;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -425,19 +424,32 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
private void shareMyLocation()
|
||||
{
|
||||
final Location loc = MwmApplication.from(this).getLocationHelper().getSavedLocation();
|
||||
if (loc != null)
|
||||
if (loc == null)
|
||||
{
|
||||
SharingUtils.shareLocation(this, loc);
|
||||
dismissLocationErrorDialog();
|
||||
mLocationErrorDialog = new MaterialAlertDialogBuilder(MwmActivity.this)
|
||||
.setMessage(R.string.unknown_current_position)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setOnDismissListener(dialog -> mLocationErrorDialog = null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
dismissLocationErrorDialog();
|
||||
mLocationErrorDialog = new MaterialAlertDialogBuilder(MwmActivity.this, R.style.MwmTheme_AlertDialog)
|
||||
.setMessage(R.string.unknown_current_position)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setOnDismissListener(dialog -> mLocationErrorDialog = null)
|
||||
.show();
|
||||
SharingUtils.shareLocation(this, loc);
|
||||
}
|
||||
|
||||
public void onLocationSharingStateChanged(boolean isSharing)
|
||||
{
|
||||
mMapButtonsViewModel.setLocationSharingState(isSharing);
|
||||
MapButtonsController mapButtonsController =
|
||||
(MapButtonsController) getSupportFragmentManager().findFragmentById(R.id.map_buttons);
|
||||
if (mapButtonsController != null)
|
||||
mapButtonsController.updateMenuBadge();
|
||||
|
||||
// Update share location button color in navigation menu
|
||||
if (mNavigationController != null)
|
||||
mNavigationController.refreshShareLocationColor();
|
||||
}
|
||||
|
||||
private void showDownloader(boolean openDownloaded)
|
||||
@@ -655,7 +667,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
else
|
||||
{
|
||||
dismissAlertDialog();
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.message_invalid_feature_position)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setOnDismissListener(dialog -> mAlertDialog = null)
|
||||
@@ -712,7 +724,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
if (!TextUtils.isEmpty(appName))
|
||||
{
|
||||
setTitle(appName);
|
||||
((MaterialTextView) mPointChooser.findViewById(R.id.title)).setText(appName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,7 +1166,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
|
||||
if (type == IsolinesState.EXPIREDDATA)
|
||||
{
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.downloader_update_maps)
|
||||
.setMessage(R.string.isolines_activation_error_dialog)
|
||||
.setPositiveButton(
|
||||
@@ -1682,6 +1693,13 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
mMapButtonsViewModel.setLayoutMode(MapButtonsController.LayoutMode.regular);
|
||||
refreshLightStatusBar();
|
||||
Utils.keepScreenOn(Config.isKeepScreenOnEnabled(), getWindow());
|
||||
|
||||
// Stop location sharing when navigation ends
|
||||
if (app.organicmaps.location.LocationSharingManager.getInstance().isSharing())
|
||||
{
|
||||
app.organicmaps.location.LocationSharingManager.getInstance().stopSharing();
|
||||
onLocationSharingStateChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1774,7 +1792,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
dismissAlertDialog();
|
||||
mAlertDialog =
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.unable_to_calc_alert_title)
|
||||
.setMessage(R.string.unable_to_calc_alert_subtitle)
|
||||
.setPositiveButton(R.string.settings,
|
||||
@@ -1797,7 +1815,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
builder.append(getString(resId)).append("\n\n");
|
||||
|
||||
dismissAlertDialog();
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.dialog_routing_disclaimer_title)
|
||||
.setMessage(builder.toString())
|
||||
.setCancelable(false)
|
||||
@@ -1813,6 +1831,26 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
return false;
|
||||
}
|
||||
|
||||
private void deliverTtsMessage()
|
||||
{
|
||||
if (Config.isTtsMessageDelivered())
|
||||
return;
|
||||
|
||||
String languageDisplayName = TtsPlayer.INSTANCE.getLanguageDisplayName();
|
||||
|
||||
if (languageDisplayName != null)
|
||||
{
|
||||
String navigationStartMessage = getResources().getString(R.string.navigation_start_tts_message);
|
||||
navigationStartMessage += languageDisplayName;
|
||||
Toast.makeText(this, navigationStartMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
else
|
||||
Toast.makeText(this, getResources().getString(R.string.navigation_start_tts_disabled_message), Toast.LENGTH_LONG)
|
||||
.show();
|
||||
|
||||
Config.setTtsMessageDelivered();
|
||||
}
|
||||
|
||||
private boolean showStartPointNotice()
|
||||
{
|
||||
final RoutingController controller = RoutingController.get();
|
||||
@@ -1826,7 +1864,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
return true;
|
||||
|
||||
final MapObject endPoint = Objects.requireNonNull(controller.getEndPoint());
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.p2p_only_from_current)
|
||||
.setMessage(R.string.p2p_reroute_from_current)
|
||||
.setCancelable(false)
|
||||
@@ -2020,7 +2058,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
mPreciseLocationDialogShown = true;
|
||||
final MaterialAlertDialogBuilder builder =
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle("⚠ " + getString(R.string.limited_accuracy))
|
||||
.setMessage(R.string.precise_location_is_disabled_long_text)
|
||||
.setNegativeButton(R.string.close, (dialog, which) -> dialog.dismiss())
|
||||
@@ -2054,7 +2092,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
return;
|
||||
}
|
||||
|
||||
mLocationErrorDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
mLocationErrorDialog = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.enable_location_services)
|
||||
.setMessage(R.string.location_is_disabled_long_text)
|
||||
.setOnDismissListener(dialog -> mLocationErrorDialog = null)
|
||||
@@ -2147,7 +2185,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
return;
|
||||
}
|
||||
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.enable_location_services)
|
||||
.setMessage(R.string.location_is_disabled_long_text)
|
||||
.setOnDismissListener(dialog -> mLocationErrorDialog = null)
|
||||
@@ -2189,6 +2227,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
if (!showRoutingDisclaimer())
|
||||
return;
|
||||
|
||||
deliverTtsMessage();
|
||||
|
||||
closeFloatingPanels();
|
||||
setFullscreen(false);
|
||||
RoutingController.get().start();
|
||||
@@ -2231,7 +2271,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
|
||||
dismissAlertDialog();
|
||||
final MaterialAlertDialogBuilder builder =
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.current_location_unknown_error_title)
|
||||
.setCancelable(true)
|
||||
.setMessage(R.string.power_save_dialog_summary)
|
||||
@@ -2256,7 +2296,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
dismissAlertDialog();
|
||||
mAlertDialog =
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.load_kmz_title)
|
||||
.setMessage(getString(R.string.unknown_file_type, uri))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@@ -2273,7 +2313,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
{
|
||||
dismissAlertDialog();
|
||||
mAlertDialog =
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.load_kmz_title)
|
||||
.setMessage(getString(R.string.failed_to_open_file, uri, error))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@@ -2295,7 +2335,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
public void onBookmarksFileImportFailed()
|
||||
{
|
||||
dismissAlertDialog();
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.load_kmz_title)
|
||||
.setMessage(R.string.load_kmz_failed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@@ -2556,7 +2596,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
|
||||
private void reportUnsupported()
|
||||
{
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.unsupported_phone)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.close, (dlg, which) -> this.moveTaskToBack(true))
|
||||
|
||||
@@ -120,7 +120,7 @@ public class SplashActivity extends AppCompatActivity
|
||||
private void showFatalErrorDialog(@StringRes int titleId, @StringRes int messageId, Exception error)
|
||||
{
|
||||
mCanceled = true;
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_M3_AlertDialog)
|
||||
.setTitle(titleId)
|
||||
.setMessage(messageId)
|
||||
.setPositiveButton(
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
package app.organicmaps.api;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.organicmaps.sdk.util.log.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* HTTP API client for location sharing server.
|
||||
* Sends encrypted location updates to the server.
|
||||
*/
|
||||
public class LocationSharingApiClient
|
||||
{
|
||||
private static final String TAG = LocationSharingApiClient.class.getSimpleName();
|
||||
|
||||
private static final int CONNECT_TIMEOUT_MS = 10000;
|
||||
private static final int READ_TIMEOUT_MS = 10000;
|
||||
|
||||
private final String mServerBaseUrl;
|
||||
private final String mSessionId;
|
||||
private final Executor mExecutor;
|
||||
|
||||
public interface Callback
|
||||
{
|
||||
void onSuccess();
|
||||
void onFailure(@NonNull String error);
|
||||
}
|
||||
|
||||
public LocationSharingApiClient(@NonNull String serverBaseUrl, @NonNull String sessionId)
|
||||
{
|
||||
mServerBaseUrl = serverBaseUrl.endsWith("/") ? serverBaseUrl : serverBaseUrl + "/";
|
||||
mSessionId = sessionId;
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session on the server.
|
||||
* @param callback Result callback
|
||||
*/
|
||||
public void createSession(@Nullable Callback callback)
|
||||
{
|
||||
mExecutor.execute(() -> {
|
||||
try
|
||||
{
|
||||
String url = mServerBaseUrl + "api/v1/session";
|
||||
String requestBody = "{\"sessionId\":\"" + mSessionId + "\"}";
|
||||
|
||||
int responseCode = postJson(url, requestBody);
|
||||
|
||||
if (responseCode >= 200 && responseCode < 300)
|
||||
{
|
||||
Logger.d(TAG, "Session created successfully: " + mSessionId);
|
||||
if (callback != null)
|
||||
callback.onSuccess();
|
||||
}
|
||||
else
|
||||
{
|
||||
String error = "Server returned error: " + responseCode;
|
||||
Logger.w(TAG, error);
|
||||
if (callback != null)
|
||||
callback.onFailure(error);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to create session", e);
|
||||
if (callback != null)
|
||||
callback.onFailure(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update location on the server with encrypted payload.
|
||||
* @param encryptedPayloadJson Encrypted payload JSON (from native code)
|
||||
* @param callback Result callback
|
||||
*/
|
||||
public void updateLocation(@NonNull String encryptedPayloadJson, @Nullable Callback callback)
|
||||
{
|
||||
mExecutor.execute(() -> {
|
||||
try
|
||||
{
|
||||
String url = mServerBaseUrl + "api/v1/location/" + mSessionId;
|
||||
|
||||
int responseCode = postJson(url, encryptedPayloadJson);
|
||||
|
||||
if (responseCode >= 200 && responseCode < 300)
|
||||
{
|
||||
Logger.d(TAG, "Location updated successfully");
|
||||
if (callback != null)
|
||||
callback.onSuccess();
|
||||
}
|
||||
else
|
||||
{
|
||||
String error = "Server returned error: " + responseCode;
|
||||
Logger.w(TAG, error);
|
||||
if (callback != null)
|
||||
callback.onFailure(error);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to update location", e);
|
||||
if (callback != null)
|
||||
callback.onFailure(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* End the session on the server.
|
||||
*/
|
||||
public void endSession()
|
||||
{
|
||||
mExecutor.execute(() -> {
|
||||
try
|
||||
{
|
||||
String url = mServerBaseUrl + "api/v1/session/" + mSessionId;
|
||||
deleteRequest(url);
|
||||
Logger.d(TAG, "Session ended: " + mSessionId);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to end session", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a POST request with JSON body.
|
||||
* @param urlString URL to send request to
|
||||
* @param jsonBody JSON request body
|
||||
* @return HTTP response code
|
||||
* @throws IOException on network error
|
||||
*/
|
||||
private int postJson(@NonNull String urlString, @NonNull String jsonBody) throws IOException
|
||||
{
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
try
|
||||
{
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
|
||||
connection.setReadTimeout(READ_TIMEOUT_MS);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
// Write body
|
||||
byte[] bodyBytes = jsonBody.getBytes(StandardCharsets.UTF_8);
|
||||
connection.setFixedLengthStreamingMode(bodyBytes.length);
|
||||
|
||||
try (OutputStream os = connection.getOutputStream())
|
||||
{
|
||||
os.write(bodyBytes);
|
||||
os.flush();
|
||||
}
|
||||
|
||||
return connection.getResponseCode();
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a DELETE request.
|
||||
* @param urlString URL to send request to
|
||||
* @return HTTP response code
|
||||
* @throws IOException on network error
|
||||
*/
|
||||
private int deleteRequest(@NonNull String urlString) throws IOException
|
||||
{
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
try
|
||||
{
|
||||
connection.setRequestMethod("DELETE");
|
||||
connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
|
||||
connection.setReadTimeout(READ_TIMEOUT_MS);
|
||||
|
||||
return connection.getResponseCode();
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,14 +60,14 @@ public enum BookmarksSharingHelper
|
||||
case BookmarkSharingResult.SUCCESS ->
|
||||
SharingUtils.shareBookmarkFile(context, launcher, result.getSharingPath(), result.getMimeType());
|
||||
case BookmarkSharingResult.EMPTY_CATEGORY ->
|
||||
new MaterialAlertDialogBuilder(context, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.bookmarks_error_title_share_empty)
|
||||
.setMessage(R.string.bookmarks_error_message_share_empty)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
case BookmarkSharingResult.ARCHIVE_ERROR, BookmarkSharingResult.FILE_ERROR ->
|
||||
{
|
||||
new MaterialAlertDialogBuilder(context, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.dialog_routing_system_error)
|
||||
.setMessage(R.string.bookmarks_error_message_share_general)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
|
||||
@@ -42,6 +42,7 @@ public class ChooseBookmarkCategoryFragment
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||
{
|
||||
View root = inflater.inflate(R.layout.choose_bookmark_category_fragment, container, false);
|
||||
getDialog().getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||
mRecycler = root.findViewById(R.id.recycler);
|
||||
mRecycler.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
return root;
|
||||
|
||||
@@ -77,6 +77,7 @@ public class ChooseBookmarksSortingTypeFragment
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
|
||||
{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
getDialog().getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||
|
||||
final Bundle args = getArguments();
|
||||
if (args == null)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package app.organicmaps.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.sdk.Framework;
|
||||
|
||||
public final class CustomMapServerDialog
|
||||
{
|
||||
public interface OnUrlAppliedListener
|
||||
{
|
||||
void onUrlApplied(@NonNull String url);
|
||||
}
|
||||
|
||||
private CustomMapServerDialog() {}
|
||||
|
||||
public static void show(@NonNull Context context,
|
||||
@Nullable OnUrlAppliedListener listener)
|
||||
{
|
||||
View dialogView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.dialog_custom_map_server, null);
|
||||
TextInputLayout til = dialogView.findViewById(R.id.til_custom_map_server);
|
||||
TextInputEditText edit = dialogView.findViewById(R.id.edit_custom_map_server);
|
||||
|
||||
SharedPreferences prefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String current = prefs.getString(context.getString(R.string.pref_custom_map_download_url), "");
|
||||
edit.setText(current);
|
||||
|
||||
MaterialAlertDialogBuilder builder =
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_resources_custom_url_title)
|
||||
.setMessage(R.string.download_resources_custom_url_message)
|
||||
.setView(dialogView)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.save, null);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(dlg -> {
|
||||
Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
||||
ok.setOnClickListener(v -> {
|
||||
String url = edit.getText() != null ? edit.getText().toString().trim() : "";
|
||||
|
||||
if (!url.isEmpty()
|
||||
&& !url.startsWith("http://")
|
||||
&& !url.startsWith("https://"))
|
||||
{
|
||||
til.setError(context.getString(R.string.download_resources_custom_url_error_scheme));
|
||||
return;
|
||||
}
|
||||
|
||||
til.setError(null);
|
||||
|
||||
String normalizedUrl = Framework.normalizeServerUrl(url);
|
||||
|
||||
prefs.edit()
|
||||
.putString(context.getString(R.string.pref_custom_map_download_url), normalizedUrl)
|
||||
.apply();
|
||||
|
||||
// Apply to native
|
||||
Framework.applyCustomMapDownloadUrl(context, normalizedUrl);
|
||||
|
||||
if (listener != null)
|
||||
listener.onUrlApplied(normalizedUrl);
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ public class EditTextDialogFragment extends BaseMwmDialogFragment
|
||||
negativeButtonText = args.getString(ARG_NEGATIVE_BUTTON);
|
||||
}
|
||||
|
||||
AlertDialog editTextDialog = new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
AlertDialog editTextDialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setView(buildView())
|
||||
.setNegativeButton(negativeButtonText, null)
|
||||
.setPositiveButton(positiveButtonText, null)
|
||||
@@ -152,7 +152,7 @@ public class EditTextDialogFragment extends BaseMwmDialogFragment
|
||||
if (mInputValidator != null)
|
||||
{
|
||||
final String maybeError = mInputValidator.validate(activity, input);
|
||||
mEtInputLayout.getEditText().setError(maybeError);
|
||||
mEtInputLayout.setError(maybeError);
|
||||
mEtInputLayout.requestFocus();
|
||||
return maybeError == null;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
{
|
||||
if (RoutingController.get().isNavigating())
|
||||
{
|
||||
new MaterialAlertDialogBuilder(adapter.mActivity, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(adapter.mActivity)
|
||||
.setTitle(R.string.downloader_delete_map)
|
||||
.setMessage(R.string.downloader_delete_map_while_routing_dialog)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@@ -133,7 +133,7 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
return;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(adapter.mActivity, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(adapter.mActivity)
|
||||
.setTitle(R.string.downloader_delete_map)
|
||||
.setMessage(R.string.downloader_delete_map_dialog)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
@@ -49,7 +49,7 @@ public class MapManagerHelper
|
||||
return;
|
||||
}
|
||||
|
||||
final AlertDialog dlg = new MaterialAlertDialogBuilder(activity, R.style.MwmTheme_AlertDialog)
|
||||
final AlertDialog dlg = new MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.country_status_download_failed)
|
||||
.setMessage(getErrorCodeStrRes(errorData.errorCode))
|
||||
.setNegativeButton(R.string.cancel,
|
||||
@@ -72,7 +72,7 @@ public class MapManagerHelper
|
||||
|
||||
private static void notifyNoSpaceInternal(Activity activity)
|
||||
{
|
||||
new MaterialAlertDialogBuilder(activity, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.downloader_no_space_title)
|
||||
.setMessage(R.string.downloader_no_space_message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
@@ -123,7 +123,7 @@ public class MapManagerHelper
|
||||
return false;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(activity, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.download_over_mobile_header)
|
||||
.setMessage(R.string.download_over_mobile_message)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
@@ -53,7 +53,6 @@ public class AdvancedTimetableFragment extends BaseMwmFragment implements View.O
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
refreshTimetables();
|
||||
}
|
||||
|
||||
private void initViews(View view)
|
||||
|
||||
@@ -196,10 +196,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
{
|
||||
final Context context = mInputBuildingLevels.getContext();
|
||||
final boolean isValid = Editor.nativeIsLevelValid(s.toString());
|
||||
UiUtils.setInputError(mInputBuildingLevels,
|
||||
isValid ? null
|
||||
: context.getString(R.string.error_enter_correct_storey_number,
|
||||
Editor.nativeGetMaxEditableBuildingLevels()));
|
||||
mInputBuildingLevels.setError(isValid ? null : context.getString(R.string.error_enter_correct_storey_number,
|
||||
Editor.nativeGetMaxEditableBuildingLevels()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -495,7 +493,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
}
|
||||
});
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
return new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.editor_socket)
|
||||
.setView(dialogView)
|
||||
.setPositiveButton(R.string.save,
|
||||
@@ -948,7 +946,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
|
||||
mDisused.setVisibility(Editor.nativeCanMarkPlaceAsDisused() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (Editor.nativeIsMapObjectUploaded())
|
||||
if (Editor.nativeAreSomeFeatureChangesUploaded())
|
||||
{
|
||||
mReset.setText(R.string.editor_place_doesnt_exist);
|
||||
return;
|
||||
@@ -966,7 +964,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
|
||||
private void reset()
|
||||
{
|
||||
if (Editor.nativeIsMapObjectUploaded())
|
||||
if (Editor.nativeAreSomeFeatureChangesUploaded())
|
||||
{
|
||||
placeDoesntExist();
|
||||
return;
|
||||
@@ -999,7 +997,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
message = R.string.editor_reset_edits_message;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(message)
|
||||
.setPositiveButton(title,
|
||||
(dialog, which) -> {
|
||||
@@ -1022,7 +1020,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
|
||||
private void placeDisused()
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.editor_mark_business_vacant_title)
|
||||
.setMessage(R.string.editor_mark_business_vacant_description)
|
||||
.setPositiveButton(R.string.editor_submit, (dlg, which) -> {
|
||||
|
||||
@@ -237,6 +237,7 @@ public class EditorHostFragment
|
||||
for (LocalizedName name : sNames)
|
||||
languages.add(name.lang);
|
||||
args.putStringArrayList(LanguagesFragment.EXISTING_LOCALIZED_NAMES, languages);
|
||||
args.putBoolean(LanguagesFragment.INCLUDE_LOCAL_LANGUAGE, false);
|
||||
editWithFragment(Mode.LANGUAGE, R.string.choose_language, args, LanguagesFragment.class, false);
|
||||
}
|
||||
|
||||
@@ -352,7 +353,7 @@ public class EditorHostFragment
|
||||
|
||||
private void processNoFeatures()
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.downloader_no_space_title)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
@@ -386,7 +387,7 @@ public class EditorHostFragment
|
||||
|
||||
private void showNoobDialog()
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.editor_share_to_all_dialog_title)
|
||||
.setMessage(getString(R.string.editor_share_to_all_dialog_message_1) + " "
|
||||
+ getString(R.string.editor_share_to_all_dialog_message_2))
|
||||
|
||||
@@ -155,7 +155,7 @@ public class FeatureCategoryFragment
|
||||
// Duplicate of showNoobDialog()
|
||||
private void showNoteConfirmationDialog(double lat, double lon, String noteText)
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.editor_share_to_all_dialog_title)
|
||||
.setMessage(getString(R.string.editor_share_to_all_dialog_message_1) + " "
|
||||
+ getString(R.string.editor_share_to_all_dialog_message_2))
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package app.organicmaps.editor;
|
||||
|
||||
import static app.organicmaps.sdk.editor.data.Language.DEFAULT_LANG_CODE;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.os.ConfigurationCompat;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.base.BaseMwmRecyclerFragment;
|
||||
import app.organicmaps.sdk.editor.Editor;
|
||||
import app.organicmaps.sdk.editor.data.Language;
|
||||
@@ -21,6 +25,7 @@ import java.util.Set;
|
||||
public class LanguagesFragment extends BaseMwmRecyclerFragment<LanguagesAdapter>
|
||||
{
|
||||
final static String EXISTING_LOCALIZED_NAMES = "ExistingLocalizedNames";
|
||||
final static String INCLUDE_LOCAL_LANGUAGE = "IncludeLocalLanguage";
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
@@ -34,6 +39,8 @@ public class LanguagesFragment extends BaseMwmRecyclerFragment<LanguagesAdapter>
|
||||
protected LanguagesAdapter createAdapter()
|
||||
{
|
||||
Bundle args = getArguments();
|
||||
boolean includeLocalLanguage =
|
||||
args != null ? args.getBoolean(INCLUDE_LOCAL_LANGUAGE) : true;
|
||||
Set<String> existingLanguages =
|
||||
args != null ? new HashSet<>(args.getStringArrayList(EXISTING_LOCALIZED_NAMES)) : new HashSet<>();
|
||||
|
||||
@@ -68,6 +75,12 @@ public class LanguagesFragment extends BaseMwmRecyclerFragment<LanguagesAdapter>
|
||||
|
||||
languages.addAll(0, systemLanguages.stream().filter(Objects::nonNull).toList());
|
||||
|
||||
if (includeLocalLanguage) {
|
||||
String localLanguageLabel = getString(R.string.pref_maplanguage_local);
|
||||
Language localLanguage = new Language(DEFAULT_LANG_CODE, localLanguageLabel);
|
||||
languages.add(0, localLanguage);
|
||||
}
|
||||
|
||||
return new LanguagesAdapter(this, languages.toArray(new Language[languages.size()]));
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ public class OsmLoginFragment extends BaseMwmToolbarFragment
|
||||
|
||||
private void onAuthFail()
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.editor_login_error_dialog)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -23,6 +22,7 @@ import app.organicmaps.util.Utils;
|
||||
import app.organicmaps.util.WindowInsetUtils;
|
||||
import app.organicmaps.widget.StackedButtonDialogFragment;
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ProfileFragment extends BaseMwmToolbarFragment
|
||||
private MaterialTextView mEditsSent;
|
||||
private MaterialTextView mProfileName;
|
||||
private ShapeableImageView mProfileImage;
|
||||
private ProgressBar mProfileInfoLoading;
|
||||
private CircularProgressIndicator mProfileInfoLoading;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
|
||||
@@ -66,7 +66,7 @@ public class FaqFragment extends BaseMwmFragment
|
||||
FloatingActionButton feedbackFab = root.findViewById(R.id.feedback_fab);
|
||||
feedbackFab.setOnClickListener(
|
||||
v
|
||||
-> new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
-> new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.feedback)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setItems(new CharSequence[] {getString(R.string.feedback_general), getString(R.string.report_a_bug)},
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package app.organicmaps.location;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* AES-256-GCM encryption/decryption for location data.
|
||||
*/
|
||||
public class LocationCrypto
|
||||
{
|
||||
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||
private static final int GCM_IV_LENGTH = 12; // 96 bits
|
||||
private static final int GCM_TAG_LENGTH = 128; // 128 bits
|
||||
|
||||
/**
|
||||
* Encrypt plaintext JSON using AES-256-GCM.
|
||||
* @param base64Key Base64-encoded 256-bit key
|
||||
* @param plaintextJson JSON string to encrypt
|
||||
* @return JSON string with encrypted payload: {"iv":"...","ciphertext":"...","authTag":"..."}
|
||||
*/
|
||||
@Nullable
|
||||
public static String encrypt(@NonNull String base64Key, @NonNull String plaintextJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Decode the base64 key
|
||||
byte[] key = Base64.decode(base64Key, Base64.NO_WRAP);
|
||||
if (key.length != 32) // 256 bits
|
||||
{
|
||||
android.util.Log.e("LocationCrypto", "Invalid key size: " + key.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate random IV
|
||||
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
|
||||
// Create cipher
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
|
||||
|
||||
// Encrypt
|
||||
byte[] plaintext = plaintextJson.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] ciphertextWithTag = cipher.doFinal(plaintext);
|
||||
|
||||
// Split ciphertext and auth tag
|
||||
// In GCM mode, doFinal() returns ciphertext + tag
|
||||
int ciphertextLength = ciphertextWithTag.length - (GCM_TAG_LENGTH / 8);
|
||||
byte[] ciphertext = new byte[ciphertextLength];
|
||||
byte[] authTag = new byte[GCM_TAG_LENGTH / 8];
|
||||
|
||||
System.arraycopy(ciphertextWithTag, 0, ciphertext, 0, ciphertextLength);
|
||||
System.arraycopy(ciphertextWithTag, ciphertextLength, authTag, 0, authTag.length);
|
||||
|
||||
// Build JSON response
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("iv", Base64.encodeToString(iv, Base64.NO_WRAP));
|
||||
result.put("ciphertext", Base64.encodeToString(ciphertext, Base64.NO_WRAP));
|
||||
result.put("authTag", Base64.encodeToString(authTag, Base64.NO_WRAP));
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
android.util.Log.e("LocationCrypto", "Encryption failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt encrypted payload using AES-256-GCM.
|
||||
* @param base64Key Base64-encoded 256-bit key
|
||||
* @param encryptedPayloadJson JSON string with format: {"iv":"...","ciphertext":"...","authTag":"..."}
|
||||
* @return Decrypted plaintext JSON string
|
||||
*/
|
||||
@Nullable
|
||||
public static String decrypt(@NonNull String base64Key, @NonNull String encryptedPayloadJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse encrypted payload
|
||||
JSONObject payload = new JSONObject(encryptedPayloadJson);
|
||||
byte[] iv = Base64.decode(payload.getString("iv"), Base64.NO_WRAP);
|
||||
byte[] ciphertext = Base64.decode(payload.getString("ciphertext"), Base64.NO_WRAP);
|
||||
byte[] authTag = Base64.decode(payload.getString("authTag"), Base64.NO_WRAP);
|
||||
|
||||
// Decode the base64 key
|
||||
byte[] key = Base64.decode(base64Key, Base64.NO_WRAP);
|
||||
if (key.length != 32) // 256 bits
|
||||
{
|
||||
android.util.Log.e("LocationCrypto", "Invalid key size: " + key.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Combine ciphertext and auth tag for GCM decryption
|
||||
byte[] ciphertextWithTag = new byte[ciphertext.length + authTag.length];
|
||||
System.arraycopy(ciphertext, 0, ciphertextWithTag, 0, ciphertext.length);
|
||||
System.arraycopy(authTag, 0, ciphertextWithTag, ciphertext.length, authTag.length);
|
||||
|
||||
// Create cipher
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
|
||||
|
||||
// Decrypt
|
||||
byte[] plaintext = cipher.doFinal(ciphertextWithTag);
|
||||
|
||||
return new String(plaintext, StandardCharsets.UTF_8);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
android.util.Log.e("LocationCrypto", "Decryption failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package app.organicmaps.location;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.util.SharingUtils;
|
||||
|
||||
/**
|
||||
* Dialog for starting/stopping live location sharing and managing the share URL.
|
||||
*/
|
||||
public class LocationSharingDialog extends DialogFragment
|
||||
{
|
||||
private static final String TAG = LocationSharingDialog.class.getSimpleName();
|
||||
|
||||
@Nullable
|
||||
private TextView mStatusText;
|
||||
@Nullable
|
||||
private TextView mShareUrlText;
|
||||
@Nullable
|
||||
private Button mStartStopButton;
|
||||
@Nullable
|
||||
private Button mCopyButton;
|
||||
@Nullable
|
||||
private Button mShareButton;
|
||||
|
||||
private LocationSharingManager mManager;
|
||||
|
||||
public static void show(@NonNull FragmentManager fragmentManager)
|
||||
{
|
||||
LocationSharingDialog dialog = new LocationSharingDialog();
|
||||
dialog.show(fragmentManager, TAG);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState)
|
||||
{
|
||||
mManager = LocationSharingManager.getInstance();
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
|
||||
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_location_sharing, null);
|
||||
|
||||
initViews(view);
|
||||
updateUI();
|
||||
|
||||
builder.setView(view);
|
||||
builder.setTitle(R.string.location_sharing_title);
|
||||
builder.setNegativeButton(R.string.close, (dialog, which) -> dismiss());
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private void initViews(@NonNull View root)
|
||||
{
|
||||
mStatusText = root.findViewById(R.id.status_text);
|
||||
mShareUrlText = root.findViewById(R.id.share_url_text);
|
||||
mStartStopButton = root.findViewById(R.id.start_stop_button);
|
||||
mCopyButton = root.findViewById(R.id.copy_button);
|
||||
mShareButton = root.findViewById(R.id.share_button);
|
||||
|
||||
if (mStartStopButton != null)
|
||||
{
|
||||
mStartStopButton.setOnClickListener(v -> {
|
||||
if (mManager.isSharing())
|
||||
stopSharing();
|
||||
else
|
||||
startSharing();
|
||||
});
|
||||
}
|
||||
|
||||
if (mCopyButton != null)
|
||||
{
|
||||
mCopyButton.setOnClickListener(v -> copyUrl());
|
||||
}
|
||||
|
||||
if (mShareButton != null)
|
||||
{
|
||||
mShareButton.setOnClickListener(v -> shareUrl());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI()
|
||||
{
|
||||
boolean isSharing = mManager.isSharing();
|
||||
|
||||
if (mStatusText != null)
|
||||
{
|
||||
mStatusText.setText(isSharing
|
||||
? R.string.location_sharing_status_active
|
||||
: R.string.location_sharing_status_inactive);
|
||||
}
|
||||
|
||||
if (mShareUrlText != null)
|
||||
{
|
||||
String url = mManager.getShareUrl();
|
||||
if (url != null && isSharing)
|
||||
{
|
||||
mShareUrlText.setText(url);
|
||||
mShareUrlText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
mShareUrlText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (mStartStopButton != null)
|
||||
{
|
||||
mStartStopButton.setText(isSharing
|
||||
? R.string.location_sharing_stop
|
||||
: R.string.location_sharing_start);
|
||||
}
|
||||
|
||||
// Show/hide copy and share buttons
|
||||
int visibility = isSharing ? View.VISIBLE : View.GONE;
|
||||
if (mCopyButton != null)
|
||||
mCopyButton.setVisibility(visibility);
|
||||
if (mShareButton != null)
|
||||
mShareButton.setVisibility(visibility);
|
||||
}
|
||||
|
||||
private void startSharing()
|
||||
{
|
||||
String shareUrl = mManager.startSharing();
|
||||
|
||||
if (shareUrl != null)
|
||||
{
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.location_sharing_started,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
updateUI();
|
||||
|
||||
// Notify the activity
|
||||
if (getActivity() instanceof app.organicmaps.MwmActivity)
|
||||
{
|
||||
((app.organicmaps.MwmActivity) getActivity()).onLocationSharingStateChanged(true);
|
||||
}
|
||||
|
||||
// Auto-copy URL to clipboard
|
||||
copyUrlToClipboard(shareUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.location_sharing_failed_to_start,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopSharing()
|
||||
{
|
||||
mManager.stopSharing();
|
||||
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.location_sharing_stopped,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
updateUI();
|
||||
|
||||
// Notify the activity
|
||||
if (getActivity() instanceof app.organicmaps.MwmActivity)
|
||||
{
|
||||
((app.organicmaps.MwmActivity) getActivity()).onLocationSharingStateChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyUrl()
|
||||
{
|
||||
String url = mManager.getShareUrl();
|
||||
if (url != null)
|
||||
{
|
||||
copyUrlToClipboard(url);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyUrlToClipboard(@NonNull String url)
|
||||
{
|
||||
ClipboardManager clipboard = (ClipboardManager)
|
||||
requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
|
||||
if (clipboard != null)
|
||||
{
|
||||
ClipData clip = ClipData.newPlainText("Location Share URL", url);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.location_sharing_url_copied,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void shareUrl()
|
||||
{
|
||||
String url = mManager.getShareUrl();
|
||||
if (url == null)
|
||||
return;
|
||||
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.location_sharing_share_message, url));
|
||||
startActivity(Intent.createChooser(shareIntent, getString(R.string.location_sharing_share_url)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package app.organicmaps.location;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.BatteryManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.organicmaps.MwmApplication;
|
||||
import app.organicmaps.sdk.routing.RoutingController;
|
||||
import app.organicmaps.sdk.util.Config;
|
||||
import app.organicmaps.sdk.util.log.Logger;
|
||||
|
||||
/**
|
||||
* Singleton manager for live location sharing functionality.
|
||||
* Coordinates between LocationHelper, RoutingController, and LocationSharingService.
|
||||
*/
|
||||
public class LocationSharingManager
|
||||
{
|
||||
private static final String TAG = LocationSharingManager.class.getSimpleName();
|
||||
|
||||
private static LocationSharingManager sInstance;
|
||||
|
||||
@Nullable
|
||||
private String mSessionId;
|
||||
@Nullable
|
||||
private String mEncryptionKey;
|
||||
@Nullable
|
||||
private String mShareUrl;
|
||||
private boolean mIsSharing = false;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private LocationSharingManager()
|
||||
{
|
||||
mContext = MwmApplication.sInstance;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static synchronized LocationSharingManager getInstance()
|
||||
{
|
||||
if (sInstance == null)
|
||||
sInstance = new LocationSharingManager();
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start live location sharing.
|
||||
* @return Share URL that can be sent to others
|
||||
*/
|
||||
@Nullable
|
||||
public String startSharing()
|
||||
{
|
||||
if (mIsSharing)
|
||||
{
|
||||
Logger.w(TAG, "Location sharing already active");
|
||||
return mShareUrl;
|
||||
}
|
||||
|
||||
// Generate session credentials via native code
|
||||
String[] credentials = nativeGenerateSessionCredentials();
|
||||
if (credentials == null || credentials.length != 2)
|
||||
{
|
||||
Logger.e(TAG, "Failed to generate session credentials");
|
||||
return null;
|
||||
}
|
||||
|
||||
mSessionId = credentials[0];
|
||||
mEncryptionKey = credentials[1];
|
||||
|
||||
// Generate share URL using configured server
|
||||
String serverUrl = Config.LocationSharing.getServerUrl();
|
||||
mShareUrl = nativeGenerateShareUrl(mSessionId, mEncryptionKey, serverUrl);
|
||||
if (mShareUrl == null)
|
||||
{
|
||||
Logger.e(TAG, "Failed to generate share URL");
|
||||
return null;
|
||||
}
|
||||
|
||||
mIsSharing = true;
|
||||
|
||||
// Start foreground service
|
||||
Intent intent = new Intent(mContext, LocationSharingService.class);
|
||||
intent.putExtra(LocationSharingService.EXTRA_SESSION_ID, mSessionId);
|
||||
intent.putExtra(LocationSharingService.EXTRA_ENCRYPTION_KEY, mEncryptionKey);
|
||||
intent.putExtra(LocationSharingService.EXTRA_SERVER_URL, serverUrl);
|
||||
intent.putExtra(LocationSharingService.EXTRA_UPDATE_INTERVAL, Config.LocationSharing.getUpdateInterval());
|
||||
|
||||
mContext.startForegroundService(intent);
|
||||
|
||||
Logger.i(TAG, "Location sharing started, session ID: " + mSessionId);
|
||||
|
||||
return mShareUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop live location sharing.
|
||||
*/
|
||||
public void stopSharing()
|
||||
{
|
||||
if (!mIsSharing)
|
||||
{
|
||||
Logger.w(TAG, "Location sharing not active");
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop foreground service
|
||||
Intent intent = new Intent(mContext, LocationSharingService.class);
|
||||
mContext.stopService(intent);
|
||||
|
||||
mIsSharing = false;
|
||||
mSessionId = null;
|
||||
mEncryptionKey = null;
|
||||
mShareUrl = null;
|
||||
|
||||
Logger.i(TAG, "Location sharing stopped");
|
||||
}
|
||||
|
||||
public boolean isSharing()
|
||||
{
|
||||
return mIsSharing;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getShareUrl()
|
||||
{
|
||||
return mShareUrl;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSessionId()
|
||||
{
|
||||
return mSessionId;
|
||||
}
|
||||
|
||||
public void setUpdateIntervalSeconds(int seconds)
|
||||
{
|
||||
Config.LocationSharing.setUpdateInterval(seconds);
|
||||
}
|
||||
|
||||
public int getUpdateIntervalSeconds()
|
||||
{
|
||||
return Config.LocationSharing.getUpdateInterval();
|
||||
}
|
||||
|
||||
public void setServerBaseUrl(@NonNull String url)
|
||||
{
|
||||
Config.LocationSharing.setServerUrl(url);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getServerBaseUrl()
|
||||
{
|
||||
return Config.LocationSharing.getServerUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current battery level (0-100).
|
||||
*/
|
||||
public int getBatteryLevel()
|
||||
{
|
||||
BatteryManager bm = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
|
||||
if (bm == null)
|
||||
return 100;
|
||||
|
||||
return bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently navigating with an active route.
|
||||
*/
|
||||
public boolean isNavigating()
|
||||
{
|
||||
return RoutingController.get().isNavigating();
|
||||
}
|
||||
|
||||
// Native methods (implemented in JNI)
|
||||
|
||||
/**
|
||||
* Generate new session credentials (ID and encryption key).
|
||||
* @return Array of [sessionId, encryptionKey]
|
||||
*/
|
||||
@Nullable
|
||||
private static native String[] nativeGenerateSessionCredentials();
|
||||
|
||||
/**
|
||||
* Generate shareable URL from credentials.
|
||||
* @param sessionId Session ID (UUID)
|
||||
* @param encryptionKey Base64-encoded encryption key
|
||||
* @param serverBaseUrl Server base URL
|
||||
* @return Share URL
|
||||
*/
|
||||
@Nullable
|
||||
private static native String nativeGenerateShareUrl(String sessionId, String encryptionKey, String serverBaseUrl);
|
||||
|
||||
/**
|
||||
* Encrypt location payload.
|
||||
* @param encryptionKey Base64-encoded encryption key
|
||||
* @param payloadJson JSON payload to encrypt
|
||||
* @return Encrypted payload JSON (with iv, ciphertext, authTag) or null on failure
|
||||
*/
|
||||
@Nullable
|
||||
public static native String nativeEncryptPayload(String encryptionKey, String payloadJson);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package app.organicmaps.location;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import app.organicmaps.MwmActivity;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.sdk.routing.RoutingInfo;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Helper for creating and updating location sharing notifications.
|
||||
*/
|
||||
public class LocationSharingNotification
|
||||
{
|
||||
public static final String CHANNEL_ID = "LOCATION_SHARING";
|
||||
private static final String CHANNEL_NAME = "Live Location Sharing";
|
||||
|
||||
private final Context mContext;
|
||||
private final NotificationManagerCompat mNotificationManager;
|
||||
|
||||
public LocationSharingNotification(@NonNull Context context)
|
||||
{
|
||||
mContext = context;
|
||||
mNotificationManager = NotificationManagerCompat.from(context);
|
||||
createNotificationChannel();
|
||||
}
|
||||
|
||||
private void createNotificationChannel()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
return;
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_LOW); // Low importance = no sound/vibration
|
||||
|
||||
channel.setDescription("Notifications for active live location sharing");
|
||||
channel.setShowBadge(false);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
|
||||
NotificationManager nm = mContext.getSystemService(NotificationManager.class);
|
||||
if (nm != null)
|
||||
nm.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build notification for location sharing service.
|
||||
* @param stopIntent PendingIntent to stop sharing
|
||||
* @return Notification object
|
||||
*/
|
||||
@NonNull
|
||||
public Notification buildNotification(@NonNull PendingIntent stopIntent)
|
||||
{
|
||||
return buildNotification(stopIntent, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build notification with copy URL action.
|
||||
* @param stopIntent PendingIntent to stop sharing
|
||||
* @param copyUrlIntent PendingIntent to copy URL (optional)
|
||||
* @return Notification object
|
||||
*/
|
||||
@NonNull
|
||||
public Notification buildNotification(
|
||||
@NonNull PendingIntent stopIntent,
|
||||
@Nullable PendingIntent copyUrlIntent)
|
||||
{
|
||||
Intent notificationIntent = new Intent(mContext, MwmActivity.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||
mContext,
|
||||
0,
|
||||
notificationIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_share)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setShowWhen(false)
|
||||
.setAutoCancel(false);
|
||||
|
||||
// Title
|
||||
builder.setContentTitle(mContext.getString(R.string.location_sharing_active));
|
||||
|
||||
// No subtitle - keep it simple
|
||||
|
||||
// Copy URL action button (if provided)
|
||||
if (copyUrlIntent != null)
|
||||
{
|
||||
builder.addAction(
|
||||
R.drawable.ic_share,
|
||||
mContext.getString(R.string.location_sharing_copy_url),
|
||||
copyUrlIntent);
|
||||
}
|
||||
|
||||
// Stop action button
|
||||
builder.addAction(
|
||||
R.drawable.ic_close,
|
||||
mContext.getString(R.string.location_sharing_stop),
|
||||
stopIntent);
|
||||
|
||||
// Set foreground service type for Android 10+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
{
|
||||
builder.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing notification.
|
||||
* @param notificationId Notification ID
|
||||
* @param notification Updated notification
|
||||
*/
|
||||
public void updateNotification(int notificationId, @NonNull Notification notification)
|
||||
{
|
||||
mNotificationManager.notify(notificationId, notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel notification.
|
||||
* @param notificationId Notification ID
|
||||
*/
|
||||
public void cancelNotification(int notificationId)
|
||||
{
|
||||
mNotificationManager.cancel(notificationId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
package app.organicmaps.location;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import app.organicmaps.MwmActivity;
|
||||
import app.organicmaps.MwmApplication;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.api.LocationSharingApiClient;
|
||||
import app.organicmaps.sdk.location.LocationHelper;
|
||||
import app.organicmaps.sdk.location.LocationListener;
|
||||
import app.organicmaps.sdk.routing.RoutingController;
|
||||
import app.organicmaps.sdk.routing.RoutingInfo;
|
||||
import app.organicmaps.sdk.util.log.Logger;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Foreground service for live GPS location sharing.
|
||||
* Monitors location updates and posts encrypted data to server at regular intervals.
|
||||
*/
|
||||
public class LocationSharingService extends Service implements LocationListener
|
||||
{
|
||||
private static final String TAG = LocationSharingService.class.getSimpleName();
|
||||
private static final int NOTIFICATION_ID = 0x1002; // Unique ID for location sharing
|
||||
|
||||
// Intent extras
|
||||
public static final String EXTRA_SESSION_ID = "session_id";
|
||||
public static final String EXTRA_ENCRYPTION_KEY = "encryption_key";
|
||||
public static final String EXTRA_SERVER_URL = "server_url";
|
||||
public static final String EXTRA_UPDATE_INTERVAL = "update_interval";
|
||||
|
||||
// Actions for notification buttons
|
||||
private static final String ACTION_STOP = "app.organicmaps.ACTION_STOP_LOCATION_SHARING";
|
||||
private static final String ACTION_COPY_URL = "app.organicmaps.ACTION_COPY_LOCATION_URL";
|
||||
|
||||
@Nullable
|
||||
private String mSessionId;
|
||||
@Nullable
|
||||
private String mEncryptionKey;
|
||||
@Nullable
|
||||
private String mServerUrl;
|
||||
private int mUpdateIntervalSeconds = 20;
|
||||
|
||||
@Nullable
|
||||
private Location mLastLocation;
|
||||
private long mLastUpdateTimestamp = 0;
|
||||
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable mUpdateTask = this::processLocationUpdate;
|
||||
|
||||
@Nullable
|
||||
private LocationSharingApiClient mApiClient;
|
||||
@Nullable
|
||||
private LocationSharingNotification mNotificationHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
Logger.i(TAG, "Service created");
|
||||
|
||||
mNotificationHelper = new LocationSharingNotification(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(@Nullable Intent intent, int flags, int startId)
|
||||
{
|
||||
if (intent == null)
|
||||
{
|
||||
Logger.w(TAG, "Null intent, stopping service");
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
// Handle stop action from notification
|
||||
if (ACTION_STOP.equals(intent.getAction()))
|
||||
{
|
||||
Logger.i(TAG, "Stop action received from notification");
|
||||
LocationSharingManager.getInstance().stopSharing();
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
// Handle copy URL action from notification
|
||||
if (ACTION_COPY_URL.equals(intent.getAction()))
|
||||
{
|
||||
Logger.i(TAG, "Copy URL action received from notification");
|
||||
String shareUrl = LocationSharingManager.getInstance().getShareUrl();
|
||||
if (shareUrl != null)
|
||||
{
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText("Location Share URL", shareUrl);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
android.widget.Toast.makeText(this, R.string.location_sharing_url_copied, android.widget.Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
// Extract session info
|
||||
mSessionId = intent.getStringExtra(EXTRA_SESSION_ID);
|
||||
mEncryptionKey = intent.getStringExtra(EXTRA_ENCRYPTION_KEY);
|
||||
mServerUrl = intent.getStringExtra(EXTRA_SERVER_URL);
|
||||
mUpdateIntervalSeconds = intent.getIntExtra(EXTRA_UPDATE_INTERVAL, 20);
|
||||
|
||||
if (mSessionId == null || mEncryptionKey == null || mServerUrl == null)
|
||||
{
|
||||
Logger.e(TAG, "Missing session info, stopping service");
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
// Initialize API client
|
||||
mApiClient = new LocationSharingApiClient(mServerUrl, mSessionId);
|
||||
|
||||
// Create session on server
|
||||
mApiClient.createSession(new LocationSharingApiClient.Callback()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess()
|
||||
{
|
||||
Logger.i(TAG, "Session created on server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull String error)
|
||||
{
|
||||
Logger.w(TAG, "Failed to create session on server: " + error);
|
||||
}
|
||||
});
|
||||
|
||||
// Start foreground with notification
|
||||
Notification notification = mNotificationHelper != null
|
||||
? mNotificationHelper.buildNotification(getStopIntent(), getCopyUrlIntent())
|
||||
: buildFallbackNotification();
|
||||
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
|
||||
// Register for location updates
|
||||
LocationHelper locationHelper = MwmApplication.sInstance.getLocationHelper();
|
||||
locationHelper.addListener(this);
|
||||
|
||||
Logger.i(TAG, "Service started for session: " + mSessionId);
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
Logger.i(TAG, "Service destroyed");
|
||||
|
||||
// Unregister location listener
|
||||
LocationHelper locationHelper = MwmApplication.sInstance.getLocationHelper();
|
||||
locationHelper.removeListener(this);
|
||||
|
||||
// Cancel pending updates
|
||||
mHandler.removeCallbacks(mUpdateTask);
|
||||
|
||||
// Send session end to server (optional)
|
||||
if (mApiClient != null && mSessionId != null)
|
||||
mApiClient.endSession();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent)
|
||||
{
|
||||
return null; // Not a bound service
|
||||
}
|
||||
|
||||
// LocationHelper.LocationListener implementation
|
||||
|
||||
@Override
|
||||
public void onLocationUpdated(@NonNull Location location)
|
||||
{
|
||||
mLastLocation = location;
|
||||
|
||||
// No need to update notification - it's simple and static now
|
||||
|
||||
// Schedule update if needed
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
|
||||
// Private methods
|
||||
|
||||
private void scheduleUpdate()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
long timeSinceLastUpdate = (now - mLastUpdateTimestamp) / 1000; // Convert to seconds
|
||||
|
||||
if (timeSinceLastUpdate >= mUpdateIntervalSeconds)
|
||||
{
|
||||
// Remove any pending updates
|
||||
mHandler.removeCallbacks(mUpdateTask);
|
||||
// Execute immediately
|
||||
mHandler.post(mUpdateTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void processLocationUpdate()
|
||||
{
|
||||
if (mLastLocation == null || mEncryptionKey == null || mApiClient == null)
|
||||
return;
|
||||
|
||||
// Check battery level
|
||||
int batteryLevel = getBatteryLevel();
|
||||
if (batteryLevel < 10)
|
||||
{
|
||||
Logger.w(TAG, "Battery level too low (" + batteryLevel + "%), stopping sharing");
|
||||
LocationSharingManager.getInstance().stopSharing();
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build payload JSON
|
||||
JSONObject payload = buildPayloadJson(mLastLocation, batteryLevel);
|
||||
if (payload == null)
|
||||
return;
|
||||
|
||||
// Encrypt payload
|
||||
String encryptedJson = LocationCrypto.encrypt(mEncryptionKey, payload.toString());
|
||||
if (encryptedJson == null)
|
||||
{
|
||||
Logger.e(TAG, "Failed to encrypt payload");
|
||||
return;
|
||||
}
|
||||
|
||||
// Send to server
|
||||
mApiClient.updateLocation(encryptedJson, new LocationSharingApiClient.Callback()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess()
|
||||
{
|
||||
Logger.d(TAG, "Location update sent successfully");
|
||||
mLastUpdateTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull String error)
|
||||
{
|
||||
Logger.w(TAG, "Failed to send location update: " + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JSONObject buildPayloadJson(@NonNull Location location, int batteryLevel)
|
||||
{
|
||||
try
|
||||
{
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("timestamp", System.currentTimeMillis() / 1000); // Unix timestamp
|
||||
json.put("lat", location.getLatitude());
|
||||
json.put("lon", location.getLongitude());
|
||||
json.put("accuracy", location.getAccuracy());
|
||||
|
||||
if (location.hasSpeed())
|
||||
json.put("speed", location.getSpeed());
|
||||
|
||||
if (location.hasBearing())
|
||||
json.put("bearing", location.getBearing());
|
||||
|
||||
// Check if navigating
|
||||
RoutingInfo routingInfo = getNavigationInfo();
|
||||
if (routingInfo != null && routingInfo.distToTarget != null)
|
||||
{
|
||||
json.put("mode", "navigation");
|
||||
|
||||
// Calculate ETA (current time + time remaining)
|
||||
if (routingInfo.totalTimeInSeconds > 0)
|
||||
{
|
||||
long etaTimestamp = (System.currentTimeMillis() / 1000) + routingInfo.totalTimeInSeconds;
|
||||
json.put("eta", etaTimestamp);
|
||||
}
|
||||
|
||||
// Distance remaining in meters
|
||||
if (routingInfo.distToTarget != null)
|
||||
{
|
||||
json.put("distanceRemaining", routingInfo.distToTarget.mDistance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
json.put("mode", "standalone");
|
||||
}
|
||||
|
||||
json.put("batteryLevel", batteryLevel);
|
||||
|
||||
return json;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to build payload JSON", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private RoutingInfo getNavigationInfo()
|
||||
{
|
||||
if (!RoutingController.get().isNavigating())
|
||||
return null;
|
||||
|
||||
return RoutingController.get().getCachedRoutingInfo();
|
||||
}
|
||||
|
||||
private int getBatteryLevel()
|
||||
{
|
||||
BatteryManager bm = (BatteryManager) getSystemService(BATTERY_SERVICE);
|
||||
if (bm == null)
|
||||
return 100;
|
||||
|
||||
return bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PendingIntent getStopIntent()
|
||||
{
|
||||
Intent stopIntent = new Intent(this, LocationSharingService.class);
|
||||
stopIntent.setAction(ACTION_STOP);
|
||||
return PendingIntent.getService(this, 0, stopIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PendingIntent getCopyUrlIntent()
|
||||
{
|
||||
Intent copyIntent = new Intent(this, LocationSharingService.class);
|
||||
copyIntent.setAction(ACTION_COPY_URL);
|
||||
return PendingIntent.getService(this, 1, copyIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Notification buildFallbackNotification()
|
||||
{
|
||||
Intent notificationIntent = new Intent(this, MwmActivity.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
return new NotificationCompat.Builder(this, LocationSharingNotification.CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.location_sharing_active))
|
||||
.setSmallIcon(R.drawable.ic_share)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ public class MapButtonsController extends Fragment
|
||||
mBadgeDrawable.setMaxCharacterCount(0);
|
||||
mBadgeDrawable.setHorizontalOffset(verticalOffset);
|
||||
mBadgeDrawable.setVerticalOffset(dpToPx(9, context));
|
||||
mBadgeDrawable.setBackgroundColor(ContextCompat.getColor(context, R.color.base_accent));
|
||||
mBadgeDrawable.setBackgroundColor(ContextCompat.getColor(context, R.color.active_track_recording));
|
||||
mBadgeDrawable.setVisible(enable);
|
||||
BadgeUtils.attachBadgeDrawable(mBadgeDrawable, menuButton);
|
||||
}
|
||||
@@ -322,7 +322,8 @@ public class MapButtonsController extends Fragment
|
||||
mBadgeDrawable.setVisible(count > 0);
|
||||
BadgeUtils.attachBadgeDrawable(mBadgeDrawable, menuButton);
|
||||
|
||||
updateMenuBadge(TrackRecorder.nativeIsTrackRecordingEnabled());
|
||||
final boolean isTrackRecording = TrackRecorder.nativeIsTrackRecordingEnabled();
|
||||
updateMenuBadge(isTrackRecording);
|
||||
}
|
||||
|
||||
public void updateLayerButton()
|
||||
|
||||
@@ -16,6 +16,7 @@ public class MapButtonsViewModel extends ViewModel
|
||||
private final MutableLiveData<SearchWheel.SearchOption> mSearchOption = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mTrackRecorderState =
|
||||
new MutableLiveData<>(TrackRecorder.nativeIsTrackRecordingEnabled());
|
||||
private final MutableLiveData<Boolean> mLocationSharingState = new MutableLiveData<>(false);
|
||||
|
||||
public MutableLiveData<Boolean> getButtonsHidden()
|
||||
{
|
||||
@@ -86,4 +87,14 @@ public class MapButtonsViewModel extends ViewModel
|
||||
{
|
||||
return mTrackRecorderState;
|
||||
}
|
||||
|
||||
public void setLocationSharingState(boolean state)
|
||||
{
|
||||
mLocationSharingState.setValue(state);
|
||||
}
|
||||
|
||||
public MutableLiveData<Boolean> getLocationSharingState()
|
||||
{
|
||||
return mLocationSharingState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class TrafficButtonController implements TrafficManager.TrafficCallback
|
||||
if (mDialog != null && mDialog.isShowing())
|
||||
return;
|
||||
|
||||
mDialog = new MaterialAlertDialogBuilder(mActivity, R.style.MwmTheme_AlertDialog)
|
||||
mDialog = new MaterialAlertDialogBuilder(mActivity)
|
||||
.setMessage(R.string.common_check_internet_connection_dialog)
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> TrafficManager.INSTANCE.setEnabled(false))
|
||||
.setCancelable(true)
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class BaseRoutingErrorDialogFragment extends BaseMwmDialogFragment
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
parseArguments();
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
beforeDialogCreated(builder);
|
||||
|
||||
@@ -98,6 +98,11 @@ public class NavigationController implements TrafficManager.TrafficCallback, Nav
|
||||
mSpeedLimit = topFrame.findViewById(R.id.nav_speed_limit);
|
||||
mCurrentSpeed = topFrame.findViewById(R.id.nav_current_speed);
|
||||
|
||||
View mTopbar = topFrame.findViewById(R.id.statutbar);
|
||||
ViewCompat.setOnApplyWindowInsetsListener(mTopbar,(v, windowInsets) -> {
|
||||
UiUtils.setViewNavigationTopInsetsMargin(v, windowInsets);
|
||||
return windowInsets;
|
||||
});
|
||||
// Show a blank view below the navbar to hide the menu content
|
||||
final View navigationBarBackground = mFrame.findViewById(R.id.nav_bottom_sheet_nav_bar);
|
||||
final View nextTurnContainer = mFrame.findViewById(R.id.nav_next_turn_container);
|
||||
@@ -205,6 +210,11 @@ public class NavigationController implements TrafficManager.TrafficCallback, Nav
|
||||
mNavMenu.refreshTts();
|
||||
}
|
||||
|
||||
public void refreshShareLocationColor()
|
||||
{
|
||||
mNavMenu.updateShareLocationColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnabled()
|
||||
{
|
||||
|
||||
@@ -26,8 +26,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import app.organicmaps.MwmActivity;
|
||||
import app.organicmaps.MwmApplication;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.location.LocationSharingDialog;
|
||||
import app.organicmaps.sdk.Framework;
|
||||
import app.organicmaps.sdk.bookmarks.data.DistanceAndAzimut;
|
||||
import app.organicmaps.sdk.routing.RouteMarkData;
|
||||
@@ -144,6 +146,9 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
mActionButton.setOnClickListener(this);
|
||||
View actionSearchButton = actionFrame.findViewById(R.id.btn__search_point);
|
||||
actionSearchButton.setOnClickListener(this);
|
||||
View shareLocationButton = actionFrame.findViewById(R.id.btn__share_location);
|
||||
if (shareLocationButton != null)
|
||||
shareLocationButton.setOnClickListener(this);
|
||||
mActionIcon = mActionButton.findViewById(R.id.iv__icon);
|
||||
UiUtils.hide(mAltitudeChartFrame, mActionFrame);
|
||||
mListener = listener;
|
||||
@@ -472,6 +477,11 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
||||
final RouteMarkType pointType = (RouteMarkType) mActionMessage.getTag();
|
||||
mListener.onSearchRoutePoint(pointType);
|
||||
}
|
||||
else if (id == R.id.btn__share_location)
|
||||
{
|
||||
if (mContext instanceof MwmActivity)
|
||||
LocationSharingDialog.show(((MwmActivity) mContext).getSupportFragmentManager());
|
||||
}
|
||||
else if (id == R.id.btn__manage_route)
|
||||
mListener.onManageRouteOpen();
|
||||
else if (id == R.id.btn__save)
|
||||
|
||||
@@ -6,12 +6,10 @@ import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.XmlRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.util.Utils;
|
||||
import app.organicmaps.util.WindowInsetUtils.ScrollableContentInsetsListener;
|
||||
|
||||
@@ -44,7 +42,6 @@ abstract class BaseXmlSettingsFragment extends PreferenceFragmentCompat
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
|
||||
{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.bg_cards));
|
||||
RecyclerView recyclerView = getListView();
|
||||
ViewCompat.setOnApplyWindowInsetsListener(recyclerView, new ScrollableContentInsetsListener(recyclerView));
|
||||
}
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
package app.organicmaps.settings;
|
||||
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
|
||||
import static app.organicmaps.sdk.editor.data.Language.DEFAULT_LANG_CODE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import app.organicmaps.MwmApplication;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.dialog.CustomMapServerDialog;
|
||||
import app.organicmaps.downloader.OnmapDownloader;
|
||||
import app.organicmaps.editor.LanguagesFragment;
|
||||
import app.organicmaps.editor.ProfileActivity;
|
||||
@@ -35,7 +44,7 @@ import app.organicmaps.sdk.util.SharedPropertiesUtils;
|
||||
import app.organicmaps.sdk.util.log.LogsManager;
|
||||
import app.organicmaps.util.ThemeSwitcher;
|
||||
import app.organicmaps.util.Utils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -73,6 +82,8 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
initScreenSleepEnabledPrefsCallbacks();
|
||||
initShowOnLockScreenPrefsCallbacks();
|
||||
initLeftButtonPrefs();
|
||||
initCustomMapDownloadUrlPrefsCallbacks();
|
||||
initLocationSharingPrefsCallbacks();
|
||||
}
|
||||
|
||||
private void initLeftButtonPrefs()
|
||||
@@ -132,8 +143,13 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
private void updateMapLanguageCodeSummary()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_map_locale));
|
||||
Locale locale = new Locale(MapLanguageCode.getMapLanguageCode());
|
||||
pref.setSummary(locale.getDisplayLanguage());
|
||||
String mapLanguageCode = MapLanguageCode.getMapLanguageCode();
|
||||
if (mapLanguageCode.equals(DEFAULT_LANG_CODE)) {
|
||||
pref.setSummary(R.string.pref_maplanguage_local);
|
||||
} else {
|
||||
Locale locale = new Locale(mapLanguageCode);
|
||||
pref.setSummary(locale.getDisplayLanguage());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRoutingSettingsPrefsSummary()
|
||||
@@ -486,7 +502,7 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
if (MapManager.nativeIsDownloading())
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.downloading_is_active)
|
||||
.setMessage(R.string.cant_change_this_setting)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@@ -535,6 +551,34 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
});
|
||||
}
|
||||
|
||||
private void initCustomMapDownloadUrlPrefsCallbacks()
|
||||
{
|
||||
Preference customUrlPref = getPreference(getString(R.string.pref_custom_map_download_url));
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||
|
||||
String current = prefs.getString(getString(R.string.pref_custom_map_download_url), "");
|
||||
String normalizedUrl = Framework.normalizeServerUrl(current);
|
||||
|
||||
// Initial summary
|
||||
customUrlPref.setSummary(normalizedUrl.isEmpty()
|
||||
? getString(R.string.download_resources_custom_url_summary_none)
|
||||
: normalizedUrl);
|
||||
|
||||
// Sync native
|
||||
Framework.applyCustomMapDownloadUrl(requireContext(), normalizedUrl);
|
||||
|
||||
// Show dialog
|
||||
customUrlPref.setOnPreferenceClickListener(preference -> {
|
||||
CustomMapServerDialog.show(requireContext(), url -> {
|
||||
preference.setSummary(url.isEmpty()
|
||||
? getString(R.string.download_resources_custom_url_summary_none)
|
||||
: url);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void removePreference(@NonNull String categoryKey, @NonNull Preference preference)
|
||||
{
|
||||
final PreferenceCategory category = getPreference(categoryKey);
|
||||
@@ -542,6 +586,29 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
category.removePreference(preference);
|
||||
}
|
||||
|
||||
private void initLocationSharingPrefsCallbacks()
|
||||
{
|
||||
// Server URL preference
|
||||
final EditTextPreference serverUrlPref = getPreference(getString(R.string.pref_location_sharing_server_url));
|
||||
serverUrlPref.setText(Config.LocationSharing.getServerUrl());
|
||||
serverUrlPref.setSummary(Config.LocationSharing.getServerUrl());
|
||||
serverUrlPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String url = (String) newValue;
|
||||
Config.LocationSharing.setServerUrl(url);
|
||||
serverUrlPref.setSummary(url);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Update interval preference
|
||||
final ListPreference intervalPref = getPreference(getString(R.string.pref_location_sharing_update_interval));
|
||||
intervalPref.setValue(String.valueOf(Config.LocationSharing.getUpdateInterval()));
|
||||
intervalPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
int seconds = Integer.parseInt((String) newValue);
|
||||
Config.LocationSharing.setUpdateInterval(seconds);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLanguageSelected(Language language)
|
||||
{
|
||||
|
||||
@@ -97,7 +97,7 @@ public class StoragePathFragment extends BaseSettingsFragment
|
||||
final String oldPath = storages.get(currentIndex).mPath;
|
||||
final String newPath = storages.get(newIndex).mPath;
|
||||
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setCancelable(false)
|
||||
.setTitle(R.string.move_maps)
|
||||
.setPositiveButton(R.string.ok, (dlg, which) -> moveStorage(newPath, oldPath))
|
||||
@@ -133,7 +133,7 @@ public class StoragePathFragment extends BaseSettingsFragment
|
||||
|
||||
if (!result)
|
||||
{
|
||||
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.move_maps_error)
|
||||
.setPositiveButton(
|
||||
R.string.report_a_bug,
|
||||
|
||||
@@ -90,8 +90,7 @@ public enum ThemeSwitcher
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES);
|
||||
else
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
|
||||
if (RoutingController.get().isVehicleNavigation())
|
||||
style = MapStyle.VehicleDark;
|
||||
@@ -104,8 +103,7 @@ public enum ThemeSwitcher
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO);
|
||||
else
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
|
||||
if (RoutingController.get().isVehicleNavigation())
|
||||
style = MapStyle.VehicleClear;
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.WindowCompat;
|
||||
@@ -196,7 +195,7 @@ public final class UiUtils
|
||||
public static void showHomeUpButton(MaterialToolbar toolbar)
|
||||
{
|
||||
toolbar.setNavigationIcon(
|
||||
ThemeUtils.getResource(toolbar.getContext(), androidx.appcompat.R.attr.homeAsUpIndicator));
|
||||
UiUtils.getStyledResourceId(toolbar.getContext(), androidx.appcompat.R.attr.homeAsUpIndicator));
|
||||
}
|
||||
|
||||
// this method returns the total height of the display (in pixels) including notch and other touchable areas
|
||||
@@ -209,15 +208,7 @@ public final class UiUtils
|
||||
}
|
||||
public static void setInputError(@NonNull TextInputLayout layout, @StringRes int error)
|
||||
{
|
||||
setInputError(layout, error == 0 ? null : layout.getContext().getString(error));
|
||||
}
|
||||
|
||||
public static void setInputError(@NonNull TextInputLayout layout, String error)
|
||||
{
|
||||
layout.getEditText().setError(error);
|
||||
layout.getEditText().setTextColor(error == null
|
||||
? ThemeUtils.getColor(layout.getContext(), android.R.attr.textColorPrimary)
|
||||
: ContextCompat.getColor(layout.getContext(), R.color.base_red));
|
||||
layout.setError(error == 0 ? null : layout.getContext().getString(error));
|
||||
}
|
||||
|
||||
public static void setFullscreen(@NonNull Activity activity, boolean fullscreen)
|
||||
@@ -281,6 +272,14 @@ public final class UiUtils
|
||||
view.setPadding(systemInsets.left, systemInsets.top, systemInsets.right, view.getPaddingBottom());
|
||||
}
|
||||
|
||||
public static void setViewNavigationTopInsetsMargin(View view, WindowInsetsCompat windowInsets)
|
||||
{
|
||||
final Insets systemInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
lp.topMargin = systemInsets.top;
|
||||
view.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
public static void setupNavigationIcon(@NonNull MaterialToolbar toolbar, @NonNull View.OnClickListener listener)
|
||||
{
|
||||
View customNavigationButton = toolbar.findViewById(R.id.back);
|
||||
|
||||
@@ -184,20 +184,6 @@ public class Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static void showFacebookPage(Activity activity)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Exception is thrown if we don't have installed Facebook application.
|
||||
getPackageInfo(activity.getPackageManager(), Constants.Package.FB_PACKAGE, 0);
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.Url.FB_OM_COMMUNITY_NATIVE)));
|
||||
}
|
||||
catch (final Exception e)
|
||||
{
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.Url.FB_OM_COMMUNITY_HTTP)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void openUrl(@NonNull Context context, @Nullable String url)
|
||||
{
|
||||
if (TextUtils.isEmpty(url))
|
||||
|
||||
@@ -65,8 +65,6 @@ public class MenuAdapter extends RecyclerView.Adapter<MenuAdapter.ViewHolder>
|
||||
iv.setImageResource(R.drawable.ic_track_recording_on);
|
||||
iv.setImageTintMode(null);
|
||||
viewHolder.getTitleTextView().setText(R.string.stop_track_recording);
|
||||
badge.setBackgroundResource(R.drawable.track_recorder_badge);
|
||||
badge.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class MenuBottomSheetFragment extends BottomSheetDialogFragment
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState)
|
||||
{
|
||||
return new BottomSheetDialog(requireContext(), getTheme()) {
|
||||
return new BottomSheetDialog(requireContext(), R.style.MwmTheme_BottomSheetDialog) {
|
||||
@Override
|
||||
public void onAttachedToWindow()
|
||||
{
|
||||
|
||||
@@ -61,6 +61,7 @@ public class StackedButtonsDialog extends AppCompatDialog implements View.OnClic
|
||||
setCancelable(mCancelable);
|
||||
setOnCancelListener(mCancelListener);
|
||||
setContentView(R.layout.dialog_stacked_buttons);
|
||||
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||
|
||||
TextView title = findViewById(R.id.tv__title);
|
||||
UiUtils.setTextAndHideIfEmpty(title, mTitle);
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.location.LocationSharingDialog;
|
||||
import app.organicmaps.sdk.routing.RoutingInfo;
|
||||
import app.organicmaps.sdk.sound.TtsPlayer;
|
||||
import app.organicmaps.sdk.util.DateUtils;
|
||||
@@ -26,6 +27,7 @@ public class NavMenu
|
||||
private final View mHeaderFrame;
|
||||
|
||||
private final ShapeableImageView mTts;
|
||||
private final ShapeableImageView mShareLocation;
|
||||
private final MaterialTextView mEtaValue;
|
||||
private final MaterialTextView mEtaAmPm;
|
||||
private final MaterialTextView mTimeHourValue;
|
||||
@@ -97,12 +99,16 @@ public class NavMenu
|
||||
mRouteProgress = bottomFrame.findViewById(R.id.navigation_progress);
|
||||
|
||||
// Bottom frame buttons
|
||||
mShareLocation = bottomFrame.findViewById(R.id.share_location);
|
||||
mShareLocation.setOnClickListener(v -> onShareLocationClicked());
|
||||
ShapeableImageView mSettings = bottomFrame.findViewById(R.id.settings);
|
||||
mSettings.setOnClickListener(v -> onSettingsClicked());
|
||||
mTts = bottomFrame.findViewById(R.id.tts_volume);
|
||||
mTts.setOnClickListener(v -> onTtsClicked());
|
||||
MaterialButton stop = bottomFrame.findViewById(R.id.stop);
|
||||
stop.setOnClickListener(v -> onStopClicked());
|
||||
|
||||
updateShareLocationColor();
|
||||
}
|
||||
|
||||
private void onStopClicked()
|
||||
@@ -110,6 +116,22 @@ public class NavMenu
|
||||
mNavMenuListener.onStopClicked();
|
||||
}
|
||||
|
||||
private void onShareLocationClicked()
|
||||
{
|
||||
LocationSharingDialog.show(mActivity.getSupportFragmentManager());
|
||||
// Update color after dialog is shown (in case state changes)
|
||||
mShareLocation.postDelayed(this::updateShareLocationColor, 500);
|
||||
}
|
||||
|
||||
public void updateShareLocationColor()
|
||||
{
|
||||
final boolean isLocationSharing = app.organicmaps.location.LocationSharingManager.getInstance().isSharing();
|
||||
final int color = isLocationSharing
|
||||
? androidx.core.content.ContextCompat.getColor(mActivity, R.color.active_location_sharing)
|
||||
: app.organicmaps.util.ThemeUtils.getColor(mActivity, R.attr.iconTint);
|
||||
mShareLocation.setImageTintList(android.content.res.ColorStateList.valueOf(color));
|
||||
}
|
||||
|
||||
private void onSettingsClicked()
|
||||
{
|
||||
mNavMenuListener.onSettingsClicked();
|
||||
|
||||
@@ -47,7 +47,7 @@ public class BookmarkColorDialogFragment extends BaseMwmDialogFragment
|
||||
mIconResId = getArguments().getInt(ICON_RES);
|
||||
}
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
return new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setView(buildView())
|
||||
.setTitle(R.string.choose_color)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
@@ -464,7 +464,7 @@ public class PlacePageController
|
||||
mAlertDialog.show();
|
||||
return;
|
||||
}
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(requireContext(), R.style.MwmTheme_AlertDialog)
|
||||
mAlertDialog = new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(requireContext().getString(R.string.delete_track_dialog_title, mMapObject.getTitle()))
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
@@ -154,6 +154,7 @@ public class PlacePageView extends Fragment
|
||||
private MaterialTextView mTvLastChecked;
|
||||
private View mEditPlace;
|
||||
private View mAddPlace;
|
||||
private View mMapTooOld;
|
||||
private View mEditTopSpace;
|
||||
private ShapeableImageView mColorIcon;
|
||||
private MaterialTextView mTvCategory;
|
||||
@@ -318,6 +319,7 @@ public class PlacePageView extends Fragment
|
||||
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
|
||||
mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
|
||||
mAddPlace = mFrame.findViewById(R.id.ll__place_add);
|
||||
mMapTooOld = mFrame.findViewById(R.id.cv__map_too_old);
|
||||
mEditTopSpace = mFrame.findViewById(R.id.edit_top_space);
|
||||
latlon.setOnLongClickListener(this);
|
||||
address.setOnLongClickListener(this);
|
||||
@@ -426,8 +428,9 @@ public class PlacePageView extends Fragment
|
||||
|
||||
private void updateBookmarkView()
|
||||
{
|
||||
boolean enabled = mMapObject.isBookmark() || mMapObject.isTrack();
|
||||
updateViewFragment(PlacePageBookmarkFragment.class, BOOKMARK_FRAGMENT_TAG, R.id.place_page_bookmark_fragment,
|
||||
mMapObject.isBookmark());
|
||||
enabled);
|
||||
}
|
||||
|
||||
private void updateTrackView()
|
||||
@@ -684,39 +687,84 @@ public class PlacePageView extends Fragment
|
||||
|
||||
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
|
||||
{
|
||||
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace);
|
||||
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace, mMapTooOld);
|
||||
}
|
||||
else
|
||||
{
|
||||
UiUtils.showIf(Editor.nativeShouldShowEditPlace(), mEditPlace);
|
||||
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
|
||||
UiUtils.hide(mMapTooOld);
|
||||
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
|
||||
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
|
||||
mTvEditPlace.setOnClickListener(this);
|
||||
mTvAddPlace.setOnClickListener(this);
|
||||
mTvEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
|
||||
mTvAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
|
||||
final int editTextButtonColor =
|
||||
Editor.nativeShouldEnableEditPlace()
|
||||
|
||||
boolean shouldEnableEditPlace = Editor.nativeShouldEnableEditPlace();
|
||||
|
||||
if (shouldEnableEditPlace)
|
||||
{
|
||||
mTvEditPlace.setEnabled(true);
|
||||
mTvAddPlace.setEnabled(true);
|
||||
mTvEditPlace.setOnClickListener(this);
|
||||
mTvAddPlace.setOnClickListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
String countryId = MapManager.nativeGetSelectedCountry();
|
||||
|
||||
if (countryId != null && MapManager.nativeIsMapTooOldToEdit(countryId))
|
||||
{
|
||||
// map editing is disabled because the map is too old
|
||||
mTvEditPlace.setEnabled(true);
|
||||
mTvAddPlace.setEnabled(true);
|
||||
mTvEditPlace.setOnClickListener((v) -> {
|
||||
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
|
||||
});
|
||||
mTvAddPlace.setOnClickListener((v) -> {
|
||||
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
|
||||
});
|
||||
|
||||
CountryItem map = CountryItem.fill(countryId);
|
||||
|
||||
if (map.status == CountryItem.STATUS_UPDATABLE || map.status == CountryItem.STATUS_DONE
|
||||
|| map.status == CountryItem.STATUS_FAILED)
|
||||
{
|
||||
UiUtils.show(mMapTooOld);
|
||||
|
||||
boolean canUpdateMap = map.status != CountryItem.STATUS_DONE;
|
||||
MaterialButton mTvUpdateTooOldMap = mMapTooOld.findViewById(R.id.mb__update_too_old_map);
|
||||
UiUtils.showIf(canUpdateMap, mTvUpdateTooOldMap);
|
||||
|
||||
MaterialTextView mapTooOldDescription = mMapTooOld.findViewById(R.id.tv__map_too_old_description);
|
||||
if (canUpdateMap)
|
||||
{
|
||||
mapTooOldDescription.setText(R.string.place_page_map_too_old_description);
|
||||
mTvUpdateTooOldMap.setOnClickListener((v) -> {
|
||||
MapManagerHelper.warn3gAndDownload(requireActivity(), map.id, null);
|
||||
UiUtils.hide(mMapTooOld);
|
||||
});
|
||||
}
|
||||
else
|
||||
mapTooOldDescription.setText(R.string.place_page_app_too_old_description);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// map editing is disabled for other reasons
|
||||
mTvEditPlace.setEnabled(false);
|
||||
mTvAddPlace.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
final int editButtonColor =
|
||||
shouldEnableEditPlace
|
||||
? ContextCompat.getColor(
|
||||
getContext(),
|
||||
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
|
||||
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
|
||||
final ColorStateList editStrokeButtonColor = new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{android.R.attr.state_enabled}, // enabled
|
||||
new int[]{-android.R.attr.state_enabled} // disabled
|
||||
},
|
||||
new int[]{
|
||||
ContextCompat.getColor(
|
||||
getContext(),
|
||||
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)),
|
||||
ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled)
|
||||
});
|
||||
mTvEditPlace.setTextColor(editTextButtonColor);
|
||||
mTvAddPlace.setTextColor(editTextButtonColor);
|
||||
mTvEditPlace.setStrokeColor(editStrokeButtonColor);
|
||||
mTvAddPlace.setStrokeColor(editStrokeButtonColor);
|
||||
|
||||
mTvEditPlace.setTextColor(editButtonColor);
|
||||
mTvAddPlace.setTextColor(editButtonColor);
|
||||
mTvEditPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
|
||||
mTvAddPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
|
||||
UiUtils.showIf(
|
||||
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
|
||||
mEditTopSpace);
|
||||
|
||||
@@ -23,6 +23,7 @@ import app.organicmaps.R;
|
||||
import app.organicmaps.sdk.bookmarks.data.Bookmark;
|
||||
import app.organicmaps.sdk.bookmarks.data.BookmarkManager;
|
||||
import app.organicmaps.sdk.bookmarks.data.MapObject;
|
||||
import app.organicmaps.sdk.bookmarks.data.Track;
|
||||
import app.organicmaps.sdk.util.StringUtils;
|
||||
import app.organicmaps.util.UiUtils;
|
||||
import app.organicmaps.util.Utils;
|
||||
@@ -41,6 +42,7 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL
|
||||
private PlacePageViewModel mViewModel;
|
||||
|
||||
private Bookmark currentBookmark;
|
||||
private Track currentTrack;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -88,7 +90,15 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL
|
||||
|
||||
private void updateBookmarkDetails()
|
||||
{
|
||||
final String notes = currentBookmark.getBookmarkDescription();
|
||||
String notes = null;
|
||||
if (currentBookmark != null)
|
||||
{
|
||||
notes = currentBookmark.getBookmarkDescription();
|
||||
}
|
||||
if (currentTrack != null)
|
||||
{
|
||||
notes = currentTrack.getTrackDescription();
|
||||
}
|
||||
if (TextUtils.isEmpty(notes))
|
||||
{
|
||||
UiUtils.hide(mTvBookmarkNote);
|
||||
@@ -120,8 +130,16 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL
|
||||
public void onClick(View v)
|
||||
{
|
||||
final FragmentActivity activity = requireActivity();
|
||||
EditBookmarkFragment.editBookmark(currentBookmark.getCategoryId(), currentBookmark.getBookmarkId(), activity,
|
||||
getChildFragmentManager(), PlacePageBookmarkFragment.this);
|
||||
if (currentBookmark != null)
|
||||
{
|
||||
EditBookmarkFragment.editBookmark(currentBookmark.getCategoryId(), currentBookmark.getBookmarkId(), activity,
|
||||
getChildFragmentManager(), PlacePageBookmarkFragment.this);
|
||||
}
|
||||
else if (currentTrack != null)
|
||||
{
|
||||
EditBookmarkFragment.editBookmark(currentTrack.getCategoryId(), currentTrack.getTrackId(), activity,
|
||||
getChildFragmentManager(), PlacePageBookmarkFragment.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,6 +170,11 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL
|
||||
currentBookmark = (Bookmark) mapObject;
|
||||
updateBookmarkDetails();
|
||||
}
|
||||
if (mapObject != null && mapObject.isTrack())
|
||||
{
|
||||
currentTrack = (Track) mapObject;
|
||||
updateBookmarkDetails();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
<solid android:color="#51585E" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M20,8 C20.82725,8 21.5,8.67275 21.5,9.5 L21.5,18.5 C21.5,19.32725 20.82725,20 20,20 L8,20 C7.17275,20 6.5,19.32725 6.5,18.5 L6.5,9.5 C6.5,8.67275 7.17275,8 8,8 L20,8 Z M20.00075,14 L8,14 L8,18.5 L20.0015,18.5 L20.00075,14 Z M14,15.5 L14,17 L9.5,17 L9.5,15.5 L14,15.5 Z M20,9.5 L8,9.5 L8,11 L20,11 L20,9.5 Z" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,25 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
<solid android:color="#51585E" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M7,9.53073333 L14,6.3 L21,9.53073333 L21,10.60766 L19.9230733,10.60766 L19.9230733,18.68461 L21,18.68461 L21,20.2999767 L7,20.2999767 L7,18.68461 L8.07692667,18.68461 L8.07692667,10.60766 L7,10.60766 L7,9.53073333 Z M16.6923167,15.2756333 C16.6923167,14.0852833 15.4814333,13.1208 13.9976667,13.1208 C13.50468,13.1208 13.09784,12.7993017 13.09784,12.4012583 C13.09784,12.003215 13.4998967,11.6817167 13.9976667,11.6817167 C14.4954483,11.6817167 14.8974933,12.0070417 14.8974933,12.4012583 L16.69241,12.4012583 C16.69241,11.4673883 15.94096,10.6789083 14.8974933,10.3803583 L14.8974933,9.53068667 L13.1025767,9.53068667 L13.1025767,10.3803583 C12.0591567,10.6788967 11.30766,11.46733 11.30766,12.4012583 C11.30766,13.5916083 12.5185433,14.5560917 14.00231,14.5560917 C14.5000917,14.5560917 14.9021367,14.87759 14.9021367,15.2756333 C14.9021367,15.6736767 14.50008,15.995175 14.00231,15.995175 C13.5045283,15.995175 13.1024833,15.66985 13.1024833,15.2756333 L11.3075667,15.2756333 C11.3075667,16.2095033 12.0590167,16.9979833 13.1024833,17.2965333 L13.1024833,18.146205 L14.8974,18.146205 L14.8974,17.2965333 C15.94082,16.997995 16.6923167,16.2095617 16.6923167,15.2756333 Z" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,25 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
<solid android:color="#802D19" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:pathData="M10.7398398,14.5988166 C11.2766539,14.5988166 11.7741396,14.7668787 12.1823664,15.0532612 C11.7724801,15.4914625 11.5235582,16.0775925 11.5235582,16.7240712 L11.529303,16.8892426 L11.5491056,17.0705775 C11.6012609,17.429281 11.7280184,17.7632492 11.9167798,18.0556079 L11.9546499,18.1104694 L11.9142567,18.13405 C11.1417478,18.5947243 10.6376499,19.4380664 10.6376499,20.3887564 L10.637,21.073 L5.952,21.073 L5.95232411,17.1079171 C5.95232411,15.7221656 7.06098553,14.5988166 8.46443574,14.5988166 L10.7398398,14.5988166 Z M14.7363802,18.7647355 C15.6343829,18.7647355 16.3623501,19.4913492 16.3623501,20.3887564 L16.362,21.073 L11.637,21.073 L11.6376499,20.3887564 C11.6376499,19.4918257 12.3552335,18.7647355 13.2636198,18.7647355 L14.7363802,18.7647355 Z M19.4916069,12.4068343 C20.8792516,12.4068343 22.0041478,13.5296391 22.0041478,14.9163636 L22.004,21.073 L17.362,21.073 L17.3623501,20.3887564 L17.3567032,20.2194334 L17.3385166,20.043376 C17.2273964,19.2299207 16.7526866,18.5346787 16.0784713,18.1328785 L16.0416499,18.1124694 L16.0806774,18.0575117 C16.336328,17.665886 16.4764418,17.2070581 16.4764418,16.7240712 C16.4764418,15.6254025 15.757471,14.7010059 14.7609634,14.3838791 C15.0006762,13.2537372 15.9971232,12.4068343 17.215814,12.4068343 L19.4916069,12.4068343 Z M14,15.2664694 C14.8194057,15.2664694 15.4764418,15.9151249 15.4764418,16.7240712 C15.4764418,17.5330608 14.819449,18.1816731 14,18.1816731 C13.1805943,18.1816731 12.5235582,17.5330175 12.5235582,16.7240712 C12.5235582,15.9150816 13.1805943,15.2664694 14,15.2664694 Z M9.60213775,9.19402109 C10.8681135,9.19402109 11.8832295,10.1961892 11.8832295,11.4460053 C11.8832295,12.6958883 10.8681805,13.6979895 9.60213775,13.6979895 C8.33616197,13.6979895 7.32104605,12.6958214 7.32104605,11.4460053 C7.32104605,10.1961223 8.33616197,9.19402109 9.60213775,9.19402109 Z M18.3537105,7.00111521 C19.6199026,7.00111521 20.635192,8.00345456 20.635192,9.25348422 C20.635192,10.5035808 19.6199695,11.5058532 18.3537105,11.5058532 C17.0875184,11.5058532 16.072229,10.5035139 16.072229,9.25348422 C16.072229,8.00338763 17.0875184,7.00111521 18.3537105,7.00111521 Z"
|
||||
android:fillColor="#000" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,25 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
<solid android:color="#8C491C" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:pathData="M12.5416667,11.1416783 L12.5416667,7.70257833 C12.5416667,7.31455667 12.232395,7 11.8466717,7 L11.7783342,7 C11.3945008,7 11.0833392,7.32498667 11.0833392,7.70257833 L11.0833392,11.1416783 L9.62500583,11.1416783 L9.62500583,7.70257833 C9.62500583,7.31455667 9.31573417,7 8.93001083,7 L8.86167333,7 C8.47784,7 8.16667833,7.32498667 8.16667833,7.70257833 L8.16667833,11.1416783 C8.16667833,12.749345 9.62501167,14.0000117 11.0104283,14.0000117 L10.8211833,20.055945 C10.8048897,20.5773283 11.2048417,21 11.7169383,21 L11.9080967,21 C12.4190967,21 12.815985,20.58308 12.7940167,20.055945 L12.54169,14.0000117 C14.0000233,14.0000117 15.7500233,12.749345 15.4583567,11.1416783 L15.4583567,7.70257833 C15.4583567,7.31455667 15.149085,7 14.7633617,7 L14.6950242,7 C14.3111908,7 14.0000292,7.32498667 14.0000292,7.70257833 L14.0000292,11.1416783 L12.5416958,11.1416783 L12.5416667,11.1416783 Z M16.3333333,14.933345 L18.0833333,14.933345 L18.0833333,20.130845 C18.0833333,20.6108933 18.4717167,21.0000467 18.9583333,21.0000467 C19.4415783,21.0000467 19.8333333,20.6109983 19.8333333,20.1242883 L19.8333333,7.000455 C17.3903333,7.000455 16.275,8.69912167 16.3333333,10.0337883 L16.3333333,14.9337883 L16.3333333,14.933345 Z"
|
||||
android:fillColor="#000" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,25 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
<solid android:color="#802D19" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:pathData="M14.8166667,12.2166667 L14.8166667,17.525 C14.8166667,20.0076667 12.8076667,22.0166667 10.325,22.0166667 C7.84233333,22.0166667 5.83333333,20.0076667 5.83333333,17.525 L5.83333333,12.2166667 L14.8166667,12.2166667 Z M12.3666667,17.9333333 L8.28333333,17.9333333 C8.28333333,18.6111667 9.198,19.1583333 10.325,19.1583333 C11.452,19.1583333 12.3666667,18.6111667 12.3666667,17.9333333 Z M22.1666667,6.5 L22.1666667,11.8083333 C22.1666667,14.291 20.1576667,16.3 17.675,16.3 C16.94,16.3 16.2458333,16.1121667 15.6333333,15.8018333 L15.6333333,11.4 L13.1833333,11.4 L13.1833333,6.5 L22.1666667,6.5 Z M12.3666667,14.6666667 C11.9175,14.6666667 11.55,15.0341667 11.55,15.4833333 C11.55,15.9325 11.9175,16.3 12.3666667,16.3 C12.8158333,16.3 13.1833333,15.9325 13.1833333,15.4833333 C13.1833333,15.0341667 12.8158333,14.6666667 12.3666667,14.6666667 Z M8.28333333,14.6666667 C7.83416667,14.6666667 7.46666667,15.0341667 7.46666667,15.4833333 C7.46666667,15.9325 7.83416667,16.3 8.28333333,16.3 C8.7325,16.3 9.1,15.9325 9.1,15.4833333 C9.1,15.0341667 8.7325,14.6666667 8.28333333,14.6666667 Z M17.675,12.2166667 C16.548,12.2166667 15.6333333,12.7638333 15.6333333,13.4416667 L19.7166667,13.4416667 C19.7166667,12.7638333 18.802,12.2166667 17.675,12.2166667 Z M15.6333333,9.01533333 C15.1841667,9.01533333 14.8166667,9.38283333 14.8166667,9.832 C14.8166667,10.2811667 15.1841667,10.6486667 15.6333333,10.6486667 C16.0825,10.6486667 16.45,10.2893333 16.45,9.832 C16.45,9.38283333 16.0825,9.01533333 15.6333333,9.01533333 Z M19.7166667,9.01533333 C19.2675,9.01533333 18.9,9.38283333 18.9,9.832 C18.9,10.2811667 19.2675,10.6486667 19.7166667,10.6486667 C20.1658333,10.6486667 20.5333333,10.2893333 20.5333333,9.832 C20.5333333,9.38283333 20.1658333,9.01533333 19.7166667,9.01533333 Z"
|
||||
android:fillColor="#000" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,25 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
<solid android:color="#6B425C" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M11.3735667,18.307716 C10.6313217,18.307716 10.0307333,18.9134843 10.0307333,19.653816 C10.0307333,20.394206 10.631275,20.999916 11.3735667,20.999916 C12.1158117,20.999916 12.72305,20.3941477 12.72305,19.653816 C12.72305,18.913426 12.115765,18.307716 11.3735667,18.307716 Z M6.8,7.48804933 C6.8,7.813281 7.06993167,8.07692433 7.38562,8.07692433 L8.2,8.07692433 L10.3,14.5385077 C9.55654167,14.5385077 8.9539,15.3205827 8.9539,16.286291 L8.9539,16.0215277 C8.9539,16.9867927 9.739055,17.769311 10.7023833,17.769311 L18.8085,17.769311 C19.1352133,17.769311 19.4000583,17.5195393 19.4000583,17.180436 L19.4000583,17.2812663 C19.4000583,16.9560347 19.129765,16.6923913 18.8139717,16.6923913 L10.6169717,16.6923913 C10.29328,16.6923913 10.030885,16.4426197 10.030885,16.1035163 L10.030885,16.2043467 C10.030885,15.879115 10.2980983,15.6154717 10.6084667,15.6154717 L18.0620667,15.6154717 C19.2169033,15.6154717 19.113315,15.3197567 19.2870667,14.969325 L20.7161167,10.1360583 C20.7721167,10.0380583 20.8001167,9.91905833 20.8001167,9.80005833 C20.8001167,9.41505833 20.4851167,9.10005833 20.2616533,9.1539 L9.74707,9.1539 L8.90007,7 L7.37395333,7 C7.05702833,7 6.8,7.24977167 6.8,7.588875 L6.8,7.48804467 L6.8,7.48804933 Z M17.8350333,18.307716 C17.0927883,18.307716 16.4922,18.9134843 16.4922,19.653816 C16.4922,20.394206 17.0927417,20.999916 17.8350333,20.999916 C18.577325,20.999916 19.1845167,20.3941477 19.1845167,19.653816 C19.1845167,18.913426 18.5772317,18.307716 17.8350333,18.307716 Z" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,25 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
<solid android:color="#574469" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M17.846514,4.676 C17.5575662,4.7216586 17.3186367,4.92551065 17.2280519,5.20366514 C17.1374671,5.48181964 17.2105329,5.78727723 17.4171807,5.99433333 L19.428514,8.00566667 C19.799514,8.37666667 20.062014,8.83516667 20.1973474,9.33333333 L19.523014,9.33333333 C18.2431807,9.33333333 17.1896807,10.3868333 17.1896807,11.6666667 C17.1896807,12.9465 18.2431807,14 19.523014,14 L20.300007,14 L20.300007,19.4448333 C20.300007,19.8835 19.9616807,20.2218333 19.523014,20.2218333 C19.315878,20.2250062 19.1162779,20.1442015 18.9696836,19.997827 C18.8230893,19.8514525 18.7419852,19.6519738 18.7448474,19.4448333 L18.7448474,17.8885 C18.7448474,16.6098333 17.6913474,15.5551667 16.411514,15.5551667 L15.6333474,15.5551667 L15.6333474,7.77816667 C15.6345903,7.36522389 15.4711795,6.96881891 15.1792931,6.67671349 C14.8874066,6.38460807 14.4911243,6.22090001 14.0781807,6.22183333 L7.85634735,6.22183333 C7.44320185,6.22058891 7.04661893,6.38415963 6.75447962,6.67629894 C6.46234031,6.96843825 6.2987696,7.36502117 6.30000697,7.77816667 L6.30000697,20.2218333 C6.2987696,20.6349788 6.46234031,21.0315618 6.75447962,21.3237011 C7.04661893,21.6158404 7.44320185,21.7794111 7.85634735,21.7781737 L14.0781807,21.7781737 C14.4911243,21.7791 14.8874066,21.6153919 15.1792931,21.3232865 C15.4711795,21.0311811 15.6345903,20.6347761 15.6333474,20.2218333 L15.6333474,17.1115 L16.411514,17.1115 C16.8501807,17.1115 17.1896807,17.4498333 17.1896807,17.8885 L17.1896807,19.4448333 C17.1896807,20.7235 18.2431807,21.7781737 19.523014,21.7781737 C20.8016807,21.7781737 21.8563474,20.7235 21.8563474,19.4448333 L21.8563474,10.1115 C21.8563474,8.90940677 21.3788136,7.75655029 20.5286807,6.90666667 L18.5173474,4.89416667 C18.3411731,4.71808596 18.0913541,4.63698153 17.8453474,4.676 L17.846514,4.676 Z M8.63218069,7.77816667 L13.2988474,7.77816667 C13.730514,7.77816667 14.077014,8.12466667 14.077014,8.55516667 L14.077014,12.4448333 L7.85634735,12.4448333 L7.85634735,8.55516667 C7.85634735,8.12466667 8.20284735,7.77816667 8.63334735,7.77816667 L8.63218069,7.77816667 Z M19.523014,10.5 C20.1673462,10.5 20.6896807,11.0223345 20.6896807,11.6666667 C20.6896807,12.3109989 20.1673462,12.8333333 19.523014,12.8333333 C18.8786818,12.8333333 18.3563474,12.3109989 18.3563474,11.6666667 C18.3563474,11.0223345 18.8786818,10.5 19.523014,10.5 Z" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user