Compare commits

..

1 Commits

Author SHA1 Message Date
x7z4w
4c8ff9c22e [routing] Faster IndexGraph
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-11-08 13:21:11 +00:00
1105 changed files with 8456 additions and 29790 deletions

View File

@@ -2,130 +2,44 @@ name: map-generator
on: on:
workflow_dispatch: # Manual trigger workflow_dispatch: # Manual trigger
inputs: inputs:
map-generator-test: jobs:
description: 'Test (non-prod) generation?' description: 'Which job(s) to run right now?'
required: false required: true
default: false default: 'all'
type: boolean type: choice
# run-copy-coasts: options:
# description: 'Copy last used coastlines?' - all
# required: false - copy-coasts
# default: true - planet
# type: boolean - wiki
run-isolines: - isolines
description: 'Update altitude isolines?' - subways
required: false - tiger
default: false - maps
type: boolean
run-tiger:
description: 'Update TIGER address data?'
required: false
default: true
type: boolean
run-planet-pbf:
description: 'Update PBF planet (for Wiki & subways)?'
required: false
default: true
type: boolean
run-subways:
description: 'Update subways?'
required: false
default: true
type: boolean
run-wiki:
description: 'Update Wikipedia descriptions?'
required: false
default: true
type: boolean
run-planet-o5m:
description: 'Update O5M planet (for mapgen)?'
required: false
default: true
type: boolean
run-mapgen:
description: 'Run maps generation?'
required: false
default: true
type: boolean
map-generator-continue:
description: 'Continue previous map generation?'
required: false
default: false
type: boolean
# map-generator-countries:
# description: 'Generate specific MWMs? (i.e. "US_New York_*, foo")'
# required: false
# type: string
run-upload:
description: 'Upload latest maps to CDN?'
required: false
default: false
type: boolean
# reset:
# description: 'Reset part of the system?'
# required: false
# default: 'no'
# type: choice
# options:
# - 'no'
# - wiki-ratelimit
## RCLONE_CONF is multi-line text containing keys and credentials for us2,ru1,fi1,de1 servers
env: env:
RCLONE_CONF: ${{ secrets.RCLONE_CONF }}
WIKIMEDIA_USERNAME: ${{ secrets.WIKIMEDIA_USERNAME }} WIKIMEDIA_USERNAME: ${{ secrets.WIKIMEDIA_USERNAME }}
WIKIMEDIA_PASSWORD: ${{ secrets.WIKIMEDIA_PASSWORD }} WIKIMEDIA_PASSWORD: ${{ secrets.WIKIMEDIA_PASSWORD }}
ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }} S3_KEY_ID: ${{ secrets.S3_KEY_ID }}
ZULIP_API_KEY: ${{ secrets.ZULIP_API_KEY }} S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
MWMTEST: ${{ inputs.map-generator-test }} S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
MWMCONTINUE: ${{ inputs.map-generator-continue }} S3_BUCKET: ${{ secrets.S3_BUCKET }}
# MWMCOUNTRIES: ${{ inputs.map-generator-countries }} SFTP_USER: ${{ secrets.SFTP_USER }}
SFTP_PASSWORD: ${{ secrets.SFTP_PASSWORD }}
SFTP_HOST: ${{ secrets.SFTP_HOST }}
SFTP_PATH: ${{ secrets.SFTP_PATH }}
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
TZ: Etc/UTC TZ: Etc/UTC
jobs: 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 }}-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: Checkout main repo
shell: bash
run: |
echo "Cloning $FORGEJO_SERVER_URL/$FORGEJO_REPOSITORY branch $FORGEJO_REF_NAME"
cd ~
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 --depth 1 --single-branch https://codeberg.org/comaps/wikiparser.git
- name: Checkout subways repo
shell: bash
run: |
cd ~
git clone --depth 1 --single-branch https://codeberg.org/comaps/subways.git
copy-coasts: copy-coasts:
# if: inputs.run-copy-coasts if: inputs.jobs == 'copy-coasts' || inputs.jobs == 'all'
name: Copy Previously Generated Coasts name: Copy Previously Generated Coasts
runs-on: mapfilemaker runs-on: mapfilemaker
container: container:
image: codeberg.org/comaps/maps_generator:f6d53d54f794 image: ubuntu:latest
volumes: volumes:
- /mnt/4tbexternal/:/mnt/4tbexternal/ - /media/4tbexternal:/media/4tbexternal
- /mnt/4tbexternal/osm-planet:/home/planet
concurrency: concurrency:
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true cancel-in-progress: true
@@ -133,445 +47,336 @@ jobs:
- name: Copy Coasts - name: Copy Coasts
shell: bash shell: bash
run: | run: |
echo "WorldCoasts available:" if [ -f /media/4tbexternal/osm-maps/*/intermediate_data/WorldCoasts.geom ]; then
ls -al /mnt/4tbexternal/osm-maps/*/intermediate_data/WorldCoasts.* cp /media/4tbexternal/osm-maps/*/intermediate_data/WorldCoasts.geom /media/4tbexternal/osm-planet/latest_coasts.geom
cp /media/4tbexternal/osm-maps/*/intermediate_data/WorldCoasts.rawgeom /media/4tbexternal/osm-planet/latest_coasts.rawgeom
if [ -f /mnt/4tbexternal/osm-maps/*/intermediate_data/WorldCoasts.geom ]; then
echo "Before:"
ls -al /home/planet/latest_coasts*
# TODO: don't copy coasts from test generations
cp -p /mnt/4tbexternal/osm-maps/*/intermediate_data/WorldCoasts.geom /home/planet/latest_coasts.geom
cp -p /mnt/4tbexternal/osm-maps/*/intermediate_data/WorldCoasts.rawgeom /home/planet/latest_coasts.rawgeom
echo "After:"
ls -al /home/planet/latest_coasts*
else
echo "No WorldCoasts found."
fi fi
update-isolines: update-planet:
if: inputs.run-isolines if: inputs.jobs == 'planet' || inputs.jobs == 'all'
name: Update Isolines name: Update Planet
runs-on: mapfilemaker runs-on: mapfilemaker
needs:
- clone-repos
container: container:
image: codeberg.org/comaps/maps_generator:f6d53d54f794 image: ubuntu:latest
volumes: volumes:
- /mnt/4tbexternal/:/mnt/4tbexternal/ - /media/4tbexternal:/media/4tbexternal
- /mnt/4tbexternal/osm-planet:/home/planet
concurrency: concurrency:
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/cache@v4 - name: Install dependencies
with:
path: "~"
key: cache-${{ github.run_id }}-${{ github.run_attempt }}
# TODO: we only need to update these if our SRTM or countries change
# TODO: after update, verify that sizable files exist: /home/planet/isolines/*.isolines
- name: Update Isolines
shell: bash
# TODO: preserve previous isolines version?
# TODO: cleanup the tmp-tiles dir after completion
run: |
cd ~/comaps/
./tools/unix/build_omim.sh -p ~ -R topography_generator_tool
rm -rf /home/planet/isolines/
mkdir /home/planet/isolines/
~/omim-build-relwithdebinfo/topography_generator_tool \
--profiles_path=./data/conf/isolines/isolines-profiles.json \
--countries_to_generate_path=./data/conf/isolines/countries-to-generate.json \
--tiles_isolines_out_dir=/home/planet/isolines/tmp-tiles/ \
--countries_isolines_out_dir=/home/planet/isolines/ \
--data_dir=./data/ \
--srtm_path=/home/planet/SRTM-patched-europe/ \
--threads=96
- name: Check isolines
shell: bash shell: bash
run: | run: |
NUMISO=$(ls -al /home/planet/isolines/*.isolines | wc -l) apt-get update -y
echo "Found $NUMISO isolines" apt-get install -y pyosmium osmium-tool python3-venv python3-pip wget2
if [ $NUMISO -lt 10 ]; then rm -f /usr/lib/python*/EXTERNALLY-MANAGED
echo "ERROR: Did generation fail?" pip3 install "protobuf<4"
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=Isolines are done!'
update-tiger:
if: inputs.run-tiger
name: Update TIGER
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: Build address_parser
shell: bash
run: |
cd ~/comaps
#rm -rf ~/omim-build-relwithdebinfo/CMakeCache.txt
#rm -rf ~/omim-build-relwithdebinfo/CMakeFiles
./tools/unix/build_omim.sh -p ~ -R address_parser_tool
- name: Update TIGER from Nominatim
shell: bash
# TODO: use curl instead of wget2
run: |
# TODO: maybe remove old osm-planet/tiger first?
cd /home/planet/
mkdir -p tiger
wget2 https://nominatim.org/data/tiger-nominatim-preprocessed-latest.csv.tar.gz
cd ~/comaps
tar -xOzf /home/planet/tiger-nominatim-preprocessed-latest.csv.tar.gz | ~/omim-build-relwithdebinfo/address_parser_tool --output_path=/home/planet/tiger
update-planet-pbf:
if: inputs.run-planet-pbf
name: Update PBF 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 }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
steps:
- name: Download Planet File if Absent - name: Download Planet File if Absent
shell: bash shell: bash
# TODO: replace wget2 with curl -Z
run: | run: |
if [ ! -d /home/planet/planet/ ]; then if [ ! -d /media/4tbexternal/osm-planet/planet/ ]; then
mkdir -p /home/planet/planet/ mkdir -p /media/4tbexternal/osm-planet/planet/
fi fi
if [ ! -f /home/planet/planet/planet-latest.osm.pbf ]; then if [ ! -f /media/4tbexternal/osm-planet/planet/planet-latest.osm.pbf ]; then
cd /home/planet/planet/ cd /media/4tbexternal/osm-planet/planet/
wget2 --verbose --progress=bar --continue https://ftpmirror.your.org/pub/openstreetmap/pbf/planet-latest.osm.pbf wget2 --verbose --progress=bar --continue --debug https://ftpmirror.your.org/pub/openstreetmap/pbf/planet-latest.osm.pbf
else
echo "planet-latest.osm.pbf was found, raw download not required."
fi fi
- name: Update PBF Planet - name: Update Planet
shell: bash shell: bash
run: | run: |
cd /home/planet/planet/ cd /media/4tbexternal/osm-planet/planet/
rm -f planet-latest-new.osm.pbf pyosmium-up-to-date planet-latest.osm.pbf -o planet-latest-new.osm.pbf -vv --size 16384
pyosmium-up-to-date planet-latest.osm.pbf -o planet-latest-new.osm.pbf -v --size 16384
mv planet-latest-new.osm.pbf planet-latest.osm.pbf mv planet-latest-new.osm.pbf planet-latest.osm.pbf
- name: Notify Zulip - name: Converting planet-latest.osm.pbf to planet.o5m
run: | run: /root/OM/osmctools/osmconvert planet-latest.osm.pbf -o=planet.o5m
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=PBF 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: 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!'
wiki-update: wiki-update:
if: inputs.run-wiki if: inputs.jobs == 'wiki' || inputs.jobs == 'all'
name: Update Wikipedia name: Update Wikipedia
runs-on: mapfilemaker runs-on: mapfilemaker
needs:
- clone-repos
container: container:
image: codeberg.org/comaps/maps_generator:f6d53d54f794 image: ubuntu:latest
volumes: volumes:
- /mnt/4tbexternal/:/mnt/4tbexternal/ - /media/4tbexternal:/media/4tbexternal
- /mnt/4tbexternal/osm-planet:/home/planet
concurrency: concurrency:
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/cache@v4 - name: Install dependencies
with: shell: bash
path: "~" run: |
key: cache-${{ github.run_id }}-${{ github.run_attempt }} apt-get update -y
apt-get install -y jq curl wget2 rustc cargo git ca-certificates
- name: Clone wikiparser if necessary
shell: bash
run: |
if [ ! -d /media/4tbexternal/wikiparser ]; then
cd /media/4tbexternal
git clone https://codeberg.org/comaps/wikiparser.git
fi
- name: Check for planet file - name: Check for planet file
shell: bash shell: bash
# TODO: remove debug output
run: | run: |
if [ ! -f /home/planet/planet/planet-latest.osm.pbf ]; then if [ ! -f /media/4tbexternal/osm-planet/planet/planet-latest.osm.pbf ]; then
echo "ERROR: No file at /home/planet/planet/planet-latest.osm.pbf" echo "ERROR: No file at /media/4tbexternal/osm-planet/planet/planet-latest.osm.pbf"
ls -al /home/planet/ ls -al /media/4tbexternal/
ls -al /home/planet/planet/ ls -al /media/4tbexternal/osm-planet/
exit 1 ls -al /media/4tbexternal/osm-planet/planet/
fi
- name: Only get new dumps once per 30 days
shell: bash
run: |
if [[ '${{ inputs.reset }}' == 'wiki-ratelimit' ]]; then
echo "Bypassing wiki rate limit upon request."
exit 0
fi
datediff() {
d1=$(date -d "$1" +%s)
d2=$(date -d "$2" +%s)
echo $(( (d1 - d2) / 86400 ))
}
RECENTDUMPDATE=$(find /home/planet/wikipedia/dumps/ -mindepth 1 -maxdepth 1 -iname "2*" -type d | sort -n -r | head -1 | cut -d/ -f6)
TODAY=$(date +%Y%m%d)
DATEDIFF=$(datediff $TODAY $RECENTDUMPDATE)
if [ $DATEDIFF -lt 30 ]; then
echo "ERROR: The most recent wiki dump is from $RECENTDUMPDATE, $DATEDIFF days ago. Wikimedia limits users to 15 snapshot requests per month."
echo "Set the 'reset' option to 'wiki-ratelimit' to bypass this."
ls -al /home/planet/wikipedia/dumps/
exit 1 exit 1
fi fi
- name: Update Wikipedia from Enterprise API - name: Update Wikipedia from Enterprise API
shell: bash shell: bash
run: | run: |
#todo: curl in download.sh can fail when rate limited and even save error messages to the output. need to validate. mkdir -p /media/4tbexternal/osm-planet/wikipedia/dumps
#downloading all languages can also trigger rate limits or fail as well. needs work. mkdir -p /media/4tbexternal/osm-planet/wikipedia/build
#also: a failure to download means a failure to build, and could result in no wiki descriptions etc. cd /media/4tbexternal/wikiparser
#also-also: do we want to remove old wiki data in planet between builds? pastk: no need, its being updated / augmented
mkdir -p /home/planet/wikipedia/dumps
mkdir -p /home/planet/wikipedia/build
cd ~/wikiparser
ls -al ls -al
echo "Downloading ..." echo "Downloading ..."
./download.sh /home/planet/wikipedia/dumps ./download.sh /media/4tbexternal/osm-planet/wikipedia/dumps
ls -al /home/planet/wikipedia/dumps/*
echo "Running ..." echo "Running ..."
./run.sh /home/planet/wikipedia/build \ ./run.sh /media/4tbexternal/osm-planet/wikipedia/build \
/home/planet/planet/planet-latest.osm.pbf \ /media/4tbexternal/osm-planet/planet/planet-latest.osm.pbf \
/home/planet/wikipedia/dumps/latest/*.tar.gz /media/4tbexternal/osm-planet/wikipedia/dumps/latest/*.tar.gz
echo "DONE" echo "DONE"
- name: Check that the latest dumps are present, recent, and not super tiny
shell: bash
run: |
FAILCHECK=0
# Check all .tar.gz files in /home/planet/wikipedia/dumps/latest/ update-isolines:
for file in /home/planet/wikipedia/dumps/latest/*.tar.gz; do if: inputs.jobs == 'isolines' || inputs.jobs == 'all'
# Check if file exists (handles case where glob doesn't match) name: Update Isolines
[ -e "$file" ] || continue
# Get file size in MB and modification time in days
size_mb=$(stat -f%z "$file" 2>/dev/null | awk '{print int($1/1024/1024)}' || stat -c%s "$file" | awk
'{print int($1/1024/1024)}')
days_old=$(find "$file" -mtime -7 | wc -l)
# Verify conditions
if [ "$size_mb" -lt 100 ]; then
echo "FAIL: $file is only ${size_mb}MB (< 100MB)"
FAILCHECK=1
elif [ "$days_old" -eq 0 ]; then
echo "FAIL: $file is older than 7 days"
ls -al $file
FAILCHECK=1
else
echo "PASS: $file (${size_mb}MB, modified within 7 days)"
fi
done
exit $FAILCHECK
- 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=Wiki update is done!'
update-planet-o5m:
if: inputs.run-planet-o5m
name: Update O5M Planet
runs-on: mapfilemaker runs-on: mapfilemaker
container: container:
image: codeberg.org/comaps/maps_generator:f6d53d54f794 image: ubuntu:latest
volumes: volumes:
- /mnt/4tbexternal/:/mnt/4tbexternal/ - /media/4tbexternal:/media/4tbexternal
- /mnt/4tbexternal/osm-planet:/home/planet
concurrency: concurrency:
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- name: Check for O5M Planet File - name: Install dependencies
shell: bash shell: bash
run: | run: |
if [ ! -f /home/planet/planet/planet.o5m ]; then apt-get update -qq \
echo "WARN: No file at /home/planet/planet/planet.o5m" && apt-get install -y --no-install-recommends \
curl \
if [ ! -f /home/planet/planet/planet-latest.osm.pbf ]; then osmctools \
echo "ERROR: No file at /home/planet/planet/planet-latest.osm.pbf" rclone \
ls -al /home/planet/ git \
ls -al /home/planet/planet/ ca-certificates \
exit 1 openssh-client \
fi sshpass \
vim \
echo "Converting planet-latest.osm.pbf to planet.o5m" wget \
cd /home/planet/planet/ build-essential \
osmconvert -v --drop-author --drop-version --hash-memory=4000 planet-latest.osm.pbf -o=planet.o5m clang \
echo "Conversion is done." cmake \
fi python3 \
- name: Update O5M planet python3-pip \
python3.12-venv \
qt6-base-dev \
qt6-positioning-dev \
libc++-dev \
libfreetype-dev \
libglvnd-dev \
libgl1-mesa-dev \
libharfbuzz-dev \
libicu-dev \
libqt6svg6-dev \
libqt6positioning6-plugins \
libqt6positioning6 \
libsqlite3-dev \
libxrandr-dev \
libxinerama-dev \
libxcursor-dev \
libxi-dev \
zlib1g-dev
rm -f /usr/lib/python*/EXTERNALLY-MANAGED
pip3 install "protobuf<4"
- name: Clone main repo if necessary
shell: bash
run: | run: |
echo "Starting..." if [ ! -d /media/4tbexternal/comaps-init ]; then
cd /home/planet/planet/ apt-get update -qq && apt-get install -y --no-install-recommends git
rm -f planet-new.o5m cd /media/4tbexternal
osmupdate -v --drop-author --drop-version --hash-memory=4000 --max-merge=32 --out-o5m planet.o5m planet-new.o5m git clone --recurse-submodules --shallow-submodules -b rebase-generator-pastk-wb251014 --single-branch https://codeberg.org/comaps/comaps.git comaps-init
mv planet-new.o5m planet.o5m fi
echo "Done." - name: Update Isolines
- name: Notify Zulip shell: bash
run: | run: |
curl -X POST https://comaps.zulipchat.com/api/v1/messages \ cd /media/4tbexternal/comaps-init/
-u $ZULIP_BOT_EMAIL:$ZULIP_API_KEY \ ./tools/unix/build_omim.sh -R topography_generator_tool
--data-urlencode type=stream \ rm -rf ../osm-planet/isolines/
--data-urlencode 'to="DevOps"' \ mkdir ../osm-planet/isolines/
--data-urlencode topic=codeberg-bot \ ../omim-build-relwithdebinfo/topography_generator_tool \
--data-urlencode 'content=O5M planet update is done!' --profiles_path=./data/conf/isolines/isolines-profiles.json \
--countries_to_generate_path=./data/conf/isolines/countries-to-generate.json \
--tiles_isolines_out_dir=../osm-planet/isolines/tmp-tiles/ \
--countries_isolines_out_dir=../osm-planet/isolines/ \
--data_dir=./data/ \
--srtm_path=../osm-planet/SRTM-patched-europe/ \
--threads=22
update-subways:
if: inputs.jobs == 'subways' || inputs.jobs == 'all'
name: Update Subways
runs-on: mapfilemaker
container:
image: ubuntu:latest
volumes:
- /media/4tbexternal:/media/4tbexternal
concurrency:
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
steps:
- name: Install dependencies
shell: bash
run: |
apt-get update -qq && apt-get install -y --no-install-recommends curl osmctools osmium-tool python3-venv ca-certificates git python3-pip
rm -f /usr/lib/python*/EXTERNALLY-MANAGED
pip3 install "protobuf<4"
- name: Clone subways if necessary
shell: bash
run: |
if [ ! -d /media/4tbexternal/subways ]; then
cd /media/4tbexternal
git clone https://codeberg.org/comaps/subways.git
fi
- name: Clone main repo if necessary
shell: bash
run: |
if [ ! -d /media/4tbexternal/comaps-init ]; then
cd /media/4tbexternal
git clone --recurse-submodules --shallow-submodules -b rebase-generator-pastk-wb251014 --single-branch https://codeberg.org/comaps/comaps.git comaps-init
fi
- name: Update Subways
shell: bash
run: |
cd /media/4tbexternal/comaps-init/
cp tools/unix/maps/settings.sh.prod tools/unix/maps/settings.sh
./tools/unix/maps/generate_subways.sh
update-tiger:
if: inputs.jobs == 'tiger' || inputs.jobs == 'all'
name: Update TIGER
runs-on: mapfilemaker
container:
image: ubuntu:latest
volumes:
- /media/4tbexternal:/media/4tbexternal
concurrency:
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
steps:
- name: Install dependencies
shell: bash
run: |
apt-get update -qq && apt-get install -y --no-install-recommends \
build-essential \
clang \
cmake \
ninja-build \
ca-certificates \
git \
wget2
- name: Clone main repo if necessary
shell: bash
run: |
if [ ! -d /media/4tbexternal/comaps-init ]; then
cd /media/4tbexternal
git clone --recurse-submodules --shallow-submodules -b rebase-generator-pastk-wb251014 --single-branch https://codeberg.org/comaps/comaps.git comaps-init
fi
- name: Build address_parser
shell: bash
run: |
cd /media/4tbexternal/comaps-init
rm -rf ../omim-build-relwithdebinfo/CMakeCache.txt
rm -rf ../omim-build-relwithdebinfo/CMakeFiles
./tools/unix/build_omim.sh -R address_parser_tool
- name: Update TIGER from Nominatim
shell: bash
run: |
cd /media/4tbexternal/osm-planet/
wget2 https://nominatim.org/data/tiger-nominatim-preprocessed-latest.csv.tar.gz
tar -xOzf tiger-nominatim-preprocessed-latest.csv.tar.gz | /media/4tbexternal/omim-build-relwithdebinfo/address_parser_tool --output_path=./tiger
generate-maps: generate-maps:
if: inputs.run-mapgen if: inputs.jobs == 'maps' || inputs.jobs == 'all'
name: Generate Maps name: Generate Maps
runs-on: mapfilemaker runs-on: mapfilemaker
needs:
- clone-repos
timeout-minutes: 40320
container: container:
image: codeberg.org/comaps/maps_generator:f6d53d54f794 image: ubuntu:latest
volumes: volumes:
- /mnt/4tbexternal/:/mnt/4tbexternal/ - /media/4tbexternal:/media/4tbexternal
- /mnt/4tbexternal/osm-planet:/home/planet
options: --ulimit nofile=262144:262144 options: --ulimit nofile=262144:262144
concurrency: concurrency:
group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-map-generator-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/cache@v4 - name: Install dependencies
with: shell: bash
path: "~" run: |
key: cache-${{ github.run_id }}-${{ github.run_attempt }} apt-get update -qq \
&& apt-get install -y --no-install-recommends \
curl \
osmctools \
rclone \
git \
ca-certificates \
openssh-client \
sshpass \
vim \
wget \
build-essential \
clang \
cmake \
ninja-build \
python3 \
python3-pip \
python3.12-venv \
qt6-base-dev \
qt6-positioning-dev \
libc++-dev \
libfreetype-dev \
libglvnd-dev \
libgl1-mesa-dev \
libharfbuzz-dev \
libicu-dev \
libqt6svg6-dev \
libqt6positioning6-plugins \
libqt6positioning6 \
libsqlite3-dev \
libxrandr-dev \
libxinerama-dev \
libxcursor-dev \
libxi-dev \
zlib1g-dev
- name: Clone repo if necessary
shell: bash
run: |
if [ ! -d /media/4tbexternal/comaps-init ]; then
cd /media/4tbexternal
git clone --recurse-submodules --shallow-submodules -b rebase-generator-pastk-wb251014 --single-branch https://codeberg.org/comaps/comaps.git comaps-init
fi
- name: Make output folders if necessary - name: Make output folders if necessary
shell: bash shell: bash
run: | run: |
if [ ! -d /mnt/4tbexternal/osm-maps ]; then if [ ! -d /media/4tbexternal/osm-maps ]; then
mkdir -p /mnt/4tbexternal/osm-maps mkdir -p /media/4tbexternal/osm-maps
fi fi
- name: Get SRTM if necessary - name: Get SRTM if necessary
# TODO: it should be a separate step like Wiki or isolines
shell: bash shell: bash
run: | run: |
if [ ! -d /home/planet/SRTM-patched-europe/ ]; then if [ ! -d /media/4tbexternal/osm-planet/SRTM-patched-europe/ ]; then
echo "ERROR: NO SRTM" echo "ERROR: NO SRTM"
exit 1 exit 1
fi fi
- name: Symlink paths for repo scripts
shell: bash
run: |
mkdir -p /root/OM
ln -s /media/4tbexternal/comaps-init /root/OM/organicmaps
ln -s /media/4tbexternal/osm-planet /home/planet
ln -s /media/4tbexternal/osm-maps /root/OM/maps_build
- name: Run docker_maps_generator.sh - name: Run docker_maps_generator.sh
shell: bash shell: bash
run: | run: |
cd ~/comaps cd /root/OM/organicmaps
bash ./tools/unix/maps/docker_maps_generator.sh ./tools/unix/docker_maps_generator.sh
- 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=Generator is done!'
upload-maps:
if: inputs.run-upload
name: Upload Maps
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 }}-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: Write config file
run: |
mkdir -p ~/.config/rclone/
echo "${{ secrets.RCLONE_CONF }}" > ~/.config/rclone/rclone.conf
- name: Upload map files to CDNs
shell: bash
run: |
shopt -s nullglob
buildfolder=$(find /mnt/4tbexternal/osm-maps/ -mindepth 1 -maxdepth 1 -iname "2*" -type d | sort -n -r | head -1 | cut -d/ -f5)
builddate=$(find /mnt/4tbexternal/osm-maps/*/ -mindepth 1 -maxdepth 1 -iname "2*" -type d | sort -n -r | head -1 | cut -d/ -f6)
mwmfiles=( /mnt/4tbexternal/osm-maps/$buildfolder/$builddate/*.mwm )
if (( ${#mwmfiles[@]} )); then
echo "<$(date +%T)> Uploading maps from $buildfolder/$builddate..."
cd ~/comaps/tools/unix/maps
./upload_to_cdn.sh /mnt/4tbexternal/osm-maps/$buildfolder/$builddate
echo "<$(date +%T)> Finished uploading maps from $buildfolder/$builddate."
else
echo "<$(date +%T)> No MWM files in /mnt/4tbexternal/osm-maps/$buildfolder/$builddate/*.mwm, not uploading maps."
echo "<$(date +%T)> Found top level: $(ls -alt /mnt/4tbexternal/osm-maps/*)"
echo "<$(date +%T)> Found second level: $(ls -alt /mnt/4tbexternal/osm-maps/$buildfolder/*)"
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=Upload is done!'

View File

@@ -2,5 +2,3 @@
480fa6c2fcf53be296504ac6ba8e6b3d70f92b42 480fa6c2fcf53be296504ac6ba8e6b3d70f92b42
a6ede2b1466f0c9d8a443600ef337ba6b5832e58 a6ede2b1466f0c9d8a443600ef337ba6b5832e58
1377b81bf1cac72bb6da192da7fed6696d5d5281 1377b81bf1cac72bb6da192da7fed6696d5d5281
0288b97b1367bb971eded1018f560598ea274e6c
bf30165b5f5de0907c3c64524a3bf8121624b0b7

View File

@@ -8,14 +8,14 @@ on:
jobs: jobs:
ios-check: ios-check:
name: Build iOS name: Build iOS
runs-on: macos-26 runs-on: macos-15
env: env:
DEVELOPER_DIR: /Applications/Xcode_26.1.app/Contents/Developer DEVELOPER_DIR: /Applications/Xcode_26.app/Contents/Developer
LANG: en_US.UTF-8 # Fastlane complains that the terminal is using ASCII. LANG: en_US.UTF-8 # Fastlane complains that the terminal is using ASCII.
LANGUAGE: en_US.UTF-8 LANGUAGE: en_US.UTF-8
LC_ALL: en_US.UTF-8 LC_ALL: en_US.UTF-8
TEST_RESULTS_BUNDLE_NAME: CoMaps-Test-Results TEST_RESULTS_BUNDLE_NAME: CoMaps-Test-Results
SIMULATOR_DEVICE: 'iPhone 17 Pro Max' SIMULATOR_DEVICE: 'iPhone 16 Pro Max'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -31,9 +31,6 @@ jobs:
brew install qt \ brew install qt \
optipng optipng
pip3 install "protobuf<3.21" --break-system-packages pip3 install "protobuf<3.21" --break-system-packages
xcodebuild -downloadComponent metalToolchain
xcodebuild -downloadPlatform iOS
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -41,11 +38,11 @@ jobs:
shell: bash shell: bash
run: git submodule update --depth 1 --init --recursive --jobs=$(($(sysctl -n hw.logicalcpu) * 20)) run: git submodule update --depth 1 --init --recursive --jobs=$(($(sysctl -n hw.logicalcpu) * 20))
- name: Configure repository - name: Configure repository
shell: bash shell: bash
run: ./configure.sh run: ./configure.sh
- name: Configure Xcode cache - name: Configure XCode cache
uses: irgaly/xcode-cache@v1 uses: irgaly/xcode-cache@v1
with: with:
key: xcode-cache-deriveddata-${{ github.workflow }}-${{ matrix.buildType }}-${{ github.sha }} key: xcode-cache-deriveddata-${{ github.workflow }}-${{ matrix.buildType }}-${{ github.sha }}

5
.gitignore vendored
View File

@@ -9,7 +9,6 @@ Makefile.Release
object_script.*.Debug object_script.*.Debug
object_script.*.Release object_script.*.Release
compile_commands.json compile_commands.json
*.local.*
stxxl.errlog stxxl.errlog
stxxl.log stxxl.log
@@ -188,6 +187,10 @@ tools/python/maps_generator/var/etc/map_generator.ini
tools/python/routing/etc/*.ini tools/python/routing/etc/*.ini
tools/unix/maps/settings.sh tools/unix/maps/settings.sh
# Helpers
/node_modules/
/package-lock.json
# Visual Studio # Visual Studio
.vs .vs

View File

@@ -1,83 +0,0 @@
///////////////////////// ankerl::unordered_dense::{map, set} /////////////////////////
// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion.
// Version 4.8.1
// https://github.com/martinus/unordered_dense
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 Martin Leitner-Ankerl <martin.ankerl@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef ANKERL_STL_H
#define ANKERL_STL_H
#include <array> // for array
#include <cstdint> // for uint64_t, uint32_t, std::uint8_t, UINT64_C
#include <cstring> // for size_t, memcpy, memset
#include <functional> // for equal_to, hash
#include <initializer_list> // for initializer_list
#include <iterator> // for pair, distance
#include <limits> // for numeric_limits
#include <memory> // for allocator, allocator_traits, shared_ptr
#include <optional> // for optional
#include <stdexcept> // for out_of_range
#include <string> // for basic_string
#include <string_view> // for basic_string_view, hash
#include <tuple> // for forward_as_tuple
#include <type_traits> // for enable_if_t, declval, conditional_t, ena...
#include <utility> // for forward, exchange, pair, as_const, piece...
#include <vector> // for vector
// <memory_resource> includes <mutex>, which fails to compile if
// targeting GCC >= 13 with the (rewritten) win32 thread model, and
// targeting Windows earlier than Vista (0x600). GCC predefines
// _REENTRANT when using the 'posix' model, and doesn't when using the
// 'win32' model.
#if defined __MINGW64__ && defined __GNUC__ && __GNUC__ >= 13 && !defined _REENTRANT
// _WIN32_WINNT is guaranteed to be defined here because of the
// <cstdint> inclusion above.
# ifndef _WIN32_WINNT
# error "_WIN32_WINNT not defined"
# endif
# if _WIN32_WINNT < 0x600
# define ANKERL_MEMORY_RESOURCE_IS_BAD() 1 // NOLINT(cppcoreguidelines-macro-usage)
# endif
#endif
#ifndef ANKERL_MEMORY_RESOURCE_IS_BAD
# define ANKERL_MEMORY_RESOURCE_IS_BAD() 0 // NOLINT(cppcoreguidelines-macro-usage)
#endif
#if defined(__has_include) && !defined(ANKERL_UNORDERED_DENSE_DISABLE_PMR)
# if __has_include(<memory_resource>) && !ANKERL_MEMORY_RESOURCE_IS_BAD()
# define ANKERL_UNORDERED_DENSE_PMR std::pmr // NOLINT(cppcoreguidelines-macro-usage)
# include <memory_resource> // for polymorphic_allocator
# elif __has_include(<experimental/memory_resource>)
# define ANKERL_UNORDERED_DENSE_PMR std::experimental::pmr // NOLINT(cppcoreguidelines-macro-usage)
# include <experimental/memory_resource> // for polymorphic_allocator
# endif
#endif
#if defined(_MSC_VER) && defined(_M_X64)
# include <intrin.h>
# pragma intrinsic(_umul128)
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -114,6 +114,19 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo")
add_compile_options(-fno-omit-frame-pointer) add_compile_options(-fno-omit-frame-pointer)
endif() endif()
# Linux GCC LTO plugin fix.
if (PLATFORM_LINUX AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_BUILD_TYPE MATCHES "^Rel"))
# To force errors if LTO was not enabled.
add_compile_options(-fno-fat-lto-objects)
# To fix ar and ranlib "plugin needed to handle lto object".
string(REGEX MATCH "[0-9]+" GCC_MAJOR_VERSION ${CMAKE_CXX_COMPILER_VERSION})
file(GLOB_RECURSE plugin /usr/lib/gcc/*/${GCC_MAJOR_VERSION}*/liblto_plugin.so)
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> --plugin ${plugin} qcs <TARGET> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> --plugin ${plugin} <TARGET>")
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> --plugin ${plugin} qcs <TARGET> <OBJECTS>")
set(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> --plugin ${plugin} <TARGET>")
endif()
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
if (PLATFORM_LINUX OR PLATFORM_ANDROID) if (PLATFORM_LINUX OR PLATFORM_ANDROID)
@@ -162,10 +175,10 @@ if (NOT PLATFORM_IPHONE AND NOT PLATFORM_ANDROID)
find_package(Qt6 COMPONENTS REQUIRED ${qt_components} PATHS $ENV{QT_PATH} /opt/homebrew/opt/qt@6 /usr/local/opt/qt@6 /usr/lib/x86_64-linux-gnu/qt6) find_package(Qt6 COMPONENTS REQUIRED ${qt_components} PATHS $ENV{QT_PATH} /opt/homebrew/opt/qt@6 /usr/local/opt/qt@6 /usr/lib/x86_64-linux-gnu/qt6)
set(MINIMUM_REQUIRED_QT_VERSION 6.4.0) set(MINIMUM_REQUIRED_QT_VERSION 6.4.0)
if (Qt6_VERSION VERSION_LESS ${MINIMUM_REQUIRED_QT_VERSION}) if (Qt6Widgets_VERSION VERSION_LESS ${MINIMUM_REQUIRED_QT_VERSION})
message(FATAL_ERROR "Unsupported Qt version: ${Qt6_VERSION}, the minimum required is ${MINIMUM_REQUIRED_QT_VERSION}") message(FATAL_ERROR "Unsupported Qt version: ${Qt6Widgets_VERSION}, the minimum required is ${MINIMUM_REQUIRED_QT_VERSION}")
else() else()
message(STATUS "Found Qt version: ${Qt6_VERSION}") message(STATUS "Found Qt version: ${Qt6Widgets_VERSION}")
endif() endif()
endif() endif()
@@ -209,9 +222,6 @@ if (PLATFORM_DESKTOP AND NOT WITH_SYSTEM_PROVIDED_3PARTY)
include_directories("${PROJECT_BINARY_DIR}/3party/gflags/include") include_directories("${PROJECT_BINARY_DIR}/3party/gflags/include")
endif() endif()
# Fix for #include <boost/regex.hpp>
include_directories("${OMIM_ROOT}/3party/boost")
# Used in qt/ and shaders/ # Used in qt/ and shaders/
find_package(Python3 REQUIRED COMPONENTS Interpreter) find_package(Python3 REQUIRED COMPONENTS Interpreter)

View File

@@ -1,16 +1,15 @@
This file contains a list of people who have contributed to this project. This file contains a list of people who have contributed to this project.
It is not necessarily comprehensive as contributors must manually add themselves. Its not neccesarily comprehensive.
Feel free to add yourself here along with your first contribution! Feel free to add yourself here along with your first contribution!
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
CoMaps contributors: CoMaps contributors:
(in alphabetical order) (in alphabetic order)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
Bastian Greshake Tzovaras Bastian Greshake Tzovaras
clover sage clover sage
Harry Bond <me@hbond.xyz> Harry Bond <me@hbond.xyz>
thesupertechie
vikiawv vikiawv
Yannik Bloscheck Yannik Bloscheck

View File

@@ -377,7 +377,6 @@ play {
track.set('production') track.set('production')
defaultToAppBundles.set(true) defaultToAppBundles.set(true)
releaseStatus.set(ReleaseStatus.IN_PROGRESS) releaseStatus.set(ReleaseStatus.IN_PROGRESS)
userFraction.set(0.2d) // Rollout to 20% of users
serviceAccountCredentials.set(file('google-play.json')) serviceAccountCredentials.set(file('google-play.json'))
} }

View File

@@ -1,32 +0,0 @@
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!

View File

@@ -1,33 +0,0 @@
En fællesskabdrevet og åben source kortapp, baseret på kortdata fra OpenStreetMap og styrket i forpligtelsen til værdierne gennemsigtighed, privatlivets fred, og non-profit. CoMaps udspringer af Organic Maps, som selv udsprang af Maps.ME.
Læs mere om grundlaget for projektet og dets udviklingsretnign på <b><i>codeberg.org/comaps</i></b>.
Slut dig til fælleskabet og hjælp til med at bygge den bedste kortapp i verden.
• Brug appen og fortæl andre om den
• Giv feedback anmeld fejl
• Opdater kortdata i appen eller på OpenStreetMap-hjemmesiden.
‣ <b>Offlinefokuseret</b>: Planlæg din rute og find vej i udlandet uden brug af mobildata, søg og find afsidesliggende mål på en afsidesliggende vandretur, mm. Alle funktioner er designet til at fungere uden internetforbindelse.
‣ <b>Respekt for privatlivets fred</b>: Appen er designet med henblik på at respektere dit privatliv den identificerer dig ikke, indeholder ingen sporingsmekanismer, og insamler ingen personlig information. Appen er reklamefri.
‣ <b>Enkel og elegant</b>: de essentielle funktioner er nemme at bruge, og de virker bare.
‣ <b>Sparer på batteriet og på lagerpladsen</b>: Dræner ikke dit batteri hurtigt, som andre kortapps. De kompakte kortfiler minimerer varigt lagerpladsforbrug.
‣ <b>Gratis og bygget i fællesskab</b>: Folk som dig har hjulpet med denne app ved at tilføje steder til OpenStreetMap, ved at teste appens funktioner og give feedback på dem og ved at bidrage til udviklingen af appen med deres tid og penge.
‣ <b>Åben og gennemsigtig beslutningstagningsproces og finanser, non-profit, og fuldt ud åben source.</b>
<b>Hovedfunktioner</b>
• Hent detaljerede kort, der indeholder steder som ikke findes i mange kommericelle kort.
• En frilufts-tilstand med markede vandrestier, teltpladser, kilder, bjerg- og bakketoppe, højdekonturlinjer, mm.
• Gangstier og cykelstier
• Steder, der kan besøges, som f.eks. restauranter, tankstationer, hoteller, butikker, seværdigheder og mange andre.
• Søg efter stednavn, adresse, eller type af sted.
• Gem dine yndlingssteder som bogmærker med et enkelt tryk.
• iCloud synkronisering af bogmærker og optagede spor.
• Offline artikler fra Wikipedia.
• Metro-lag med navigation.
• Optagelse af spor.
• Eksport og import af bogmærker og spor i formaterne KML, KMZ og GPX.
• Mørk tilstand til brug om natten.
• Mulighed for at forbedre kortet vha. en indbygget editor.
• CarPlay understøttes.
<b>Friheden er ankommet</b>
Opdag din rejse, find vej i verden med privatliv og fællesskab i førersædet!

View File

@@ -1,10 +1,8 @@
• OpenStreetMap-Daten vom 9. Dezember • OpenStreetMap-Daten vom 4. November
Material 3 Design Aktualisierte Karten-Icons, inkl. Farben für Unterhaltungs-, Sport- & andere Unternehmen
• Im OSM-Editor können nun Ladestationen hinzugefügt werden • Informationen zu Steckdosen an EV-Ladestationen
• Schuko und Typ E Ladestationen hinzugefügt • Symbole für Sportzentren, Veranstaltungsorte, Massagesalons, Gästehäuser und einige stillgelegte Unternehmen
• Verbesserte Suchvorschläge • Verbesserungen bei der Suche
Litauische und lettische Sprachankündigungen Behebung eines Absturzes bei der Suche
Die Fahranweisungen wurden vergrößert Verbesserte Sprachführung während der Navigation
• Der Zoomlevel passt sich an die Distanz zur nächsten Abbiegung an Weitere Änderungen finden in unseren Codeberg-Versionshinweisen!
• Neue Anordnung der Einstellungen
Weitere Einzelheiten auf codeberg.org/comaps/comaps/releases

Binary file not shown.

Before

Width:  |  Height:  |  Size: 636 KiB

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 263 KiB

View File

@@ -1,10 +1,8 @@
• OpenStreetMap data as of December 9 • OpenStreetMap data as of November 4
Use Material 3 themes Recategorized map icons including some new colors for entertainment, sports and other businesses
Support charging sockets in OSM Editor Display info about available sockets on charging stations
• Added schuko/type-E charge sockets • Added bandstands, backless benches and loungers
Improved search results ranking New icons for different sport centres, event venues, massage salons, guest houses and some disused businesses
Enabled Lithuanian and Latvian in voice announcements Multiple search improvements and crash fix
• Improved size of driving indications • Improved voice guidance during navigation
• Base zoom level on distance to next turn Check our Codeberg release notes for more changes!
• Reordered settings
More details on codeberg.org/comaps/comaps/releases

View File

@@ -1,32 +0,0 @@
Komunum-gvidata senpaga kaj malfermkoda mapapliko bazita sur OpenStreetMap-datumoj kaj fortigita per komitado al transparencio, privateco kaj ne-lucro. CoMaps estas forko/spin-off de Organic Maps, kiu turne estas forko de Maps.ME.
Legu plu pri la kialoj por la projekto kaj ĝia direkto ĉe <b><i>codeberg.org/comaps</i></b>.
Aliĝu al la komunumo tie kaj helpu fari la plej bonan mapaplikon
• Uzu la aplikon kaj disvastigu la vorton pri ĝi
• Donu rimarkojn kaj raportu problemojn
• Ĝisdatigu mapajn datumojn en la apliko aŭ sur la OpenStreetMap-retejo
‣ <b>Senkonekta-fokusa</b>: Planu kaj navigu vian vojaĝon eksterlande sen bezono de ĉelulara servico, serĉu vojpunktojn dum malproksima promenado, ktp. Ĉiuj funkcioj de la apliko estas dezajnitaj por funkcii senkonekte.
‣ <b>Rispektante Privatecon</b>: La apliko estas dezajnita kun privateco en menso — ne identigas homojn, ne sekvas, kaj ne kolektas personajn informojn. Sen reklamoj.
‣ <b>Simpla kaj Perfekta</b>: esencaj, facile uzeblaj funkcioj kiuj simple funkcias.
‣ <b>Konservas Vian Baterion kaj Spacon</b>: Ne elĉerpas vian baterion kiel aliaj navigaj aplikaĵoj. Kompaktaj mapoj konservas precian spacon sur via telefono.
‣ <b>Sena kaj Konstruita de la Komunumo</b>: Homoj kiel vi helpis konstrui la aplikon per aldonado de lokoj al OpenStreetMap, testado kaj donado de rimarkoj pri funkcioj kaj kontribuado de siaj programadaj kapabloj kaj mono.
‣ <b>Malferma kaj Transparenta Decidado kaj Financoj, Ne-lucra kaj Tute Malfermkoda.</b>
<b>Ĉefaj Funkcioj</b>:
• Elŝuteblaj detalegaj mapoj kun lokoj kiuj ne estas disponeblaj per Google Maps
• Eksteraj modo kun elstarigitaj promenaj vojoj, kampoj, akvofontoj, pintoj, konturlinioj, ktp
• Promenaj vojoj kaj biciklaj vojoj
• Interesaj punktoj kiel restoracioj, benzinstacioj, hoteloj, vendejoj, vidindaĵoj kaj multaj aliaj
• Serĉo laŭ nomo aŭ adreso aŭ laŭ kategorio de interesaj punktoj
• Navigado kun voĉaj anoncoj por promenado, biciklado aŭ veturado
• Marku viajn favorajn lokojn per unu tuŝo
• Senkonektaj Vikipedio-artikoloj
• Metroa transporta tavolo kaj indikoj
• Enregistraĵo de vojoj
• Eksporto kaj importo de markiloj kaj vojoj en KML, KMZ, GPX formatoj
• Malhela modo por uzi nokte
• Plibonigu mapajn datumojn por ĉiuj uzante bazan enkonstruitan redaktilon
<b>Libereco Estas Ĉi Tie</b>
Malkovru vian vojaĝon, navigu la mondon kun privateco kaj komunumo en la antaŭa plano!

View File

@@ -1 +0,0 @@
Facila mapnaviĝado - Malkovru pli de via vojaĝo - Subtenata de la komunumo

View File

@@ -1 +0,0 @@
CoMaps - Migru, Biciklu, Veturigu Eksterrete

View File

@@ -1,11 +1,7 @@
• Datos de OpenStreetMap a fecha 9/12. • Datos OSM del 04/11
Uso de temas Material 3. Iconos del mapa recategorizados, incluyendo nuevos colores
Compatibilidad con enchufes de recarga en Editor. Visualización de información sobre enchufes disponibles en estaciones de recarga
Se añaden enchufes de recarga schuko/tipo E. Adición de iconos para diferentes centros deportivos, lugares de eventos, salones de masajes, posadas y algunos establecimientos comerciales desactivados
Se mejora la búsqueda. Varias mejoras y correcciones de errores en la búsqueda
Se habilitan el lituano y el letón en las indicaciones de voz. Mejora en la orientación por voz durante la navegación
• Se aumenta el tamaño de las indicaciones de conducción. Más detalles en Codeberg
• Nivel de zoom base según la distancia al siguiente giro.
• Se han reordenado los ajustes.
Más detalles en codeberg.org/comaps/comaps/releases

Binary file not shown.

Before

Width:  |  Height:  |  Size: 605 KiB

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

After

Width:  |  Height:  |  Size: 263 KiB

View File

@@ -1,10 +1,8 @@
• Données OpenStreetMap du 9 Décembre • Données OpenStreetMap au 4 novembre
Utilisation de Material 3 Recatégorisation des icônes sur la carte avec ajout de nouvelles couleurs pour certains types de lieux
Support de l'édition des bornes de recharge dans l'éditeur OSM Affichage des prises sur les bornes électriques
• Ajout du type de prise schuko/type-E • Ajout d'icônes pour les centres sportifs, salles d'événements, salon de massage et autres lieux
Amélioration de l'ordre des résultats de recherche Multiple améliorations dans la recherche
Ajout du lituanien et du letton dans le guidage vocal Correction d'un plantage dans la recherche
• Amélioration de la taille des instructions dans la navigation • Amélioration de la synthèse vocale durant la navigation
• Niveau de zoom basé sur la distance jusquau prochain virage Plus d'informations sur notre Codeberg
• Réorganisation des paramètres
Plus de détails sur codeberg.org/comaps/comaps/releases

View File

@@ -1 +1 @@
CoMaps - Randonnée, Vélo, Conduite hors ligne CoMaps - Rando, vélo, conduite hors ligne & privée

View File

@@ -1,10 +0,0 @@
• Podaci OpenStreetMap karte od 9. prosinca
• Korištenje Material 3 tema
• Podrška za utičnice za punjenje u OSM Editoru
• Dodane šuko/tip-E utičnice
• Poboljšano rangiranje rezultata pretraživanja
• Omogućeni litvanski i latvijski jezici u glasovnim najavama
• Povećana veličina indikatora vožnje
• Razina zumiranja se mijenja ovisno o udaljenosti do sljedećeg skretanja
• Promijenjen redoslijed postavki
Više detalja na codeberg.org/comaps/comaps/releases

View File

@@ -1 +0,0 @@
Paprasta ir patogi navigacija Turiningos kelionės Vystoma bendruomenės

View File

@@ -1 +0,0 @@
CoMaps keliaukite atsijungę ir privačiai

View File

@@ -1,10 +0,0 @@
• Dane OpenStreetMap z 9 grudnia
• Użycie motywów Material 3
• Obsługa gniazd ładowania w Edytorze OSM
• Dodane gniazda ładowania schuko/type-E
• Poprawiony ranking wyników wyszukiwania
• Dodane litewskie i łotewskie komunikaty głosowe
• Poprawiony rozmiar znaków drogowych
• Poziom powiększenia oparty na odległości do następnego manewru
• Zmieniona kolejność ustawień
Więcej szczegółów na codeberg.org/comaps/comaps/releases

View File

@@ -1,10 +1,7 @@
• Dados OpenStreetMap atualizados em 9 de dezembro • Dados OSM de 04/11
Uso do estilo Material 3 Ícones do mapa recategorizados, incluindo novas cores
Suporte para tomadas de carregamento no Editor OSM Exibição de informações sobre tomadas disponíveis em eletropostos
• Adição de tomadas de carregamento Schuko/Tipo E • Adição de ícones para diferentes centros esportivos, locais de eventos, salões de massagem, pousadas e alguns estabelecimentos comerciais desativados
Melhoria na classificação dos resultados de busca Diversas melhorias e correção de erro na busca
Adição dos idiomas letão e lituano nas orientações por voz Melhoria na orientação por voz durante a navegação
• Melhoria no tamanho das indicações de direção Confira nossas notas de lançamento no Codeberg para mais detalhes!
• Nível de zoom baseado em distância até a próxima curva
• Configurações reordenadas
Mais detalhes em codeberg.org/comaps/comaps/releases

View File

@@ -1,32 +0,0 @@
Uma aplicação pela comunidade, grátis e open-source, de mapas baseada em dados do OpenStreetMap e reforçada com compromisso para transparência, privacidade e sem fins lucrativos. CoMaps é um fork/spin-off de Organic Maps, que, por sua vez, é um fork de Maps.ME
Leia sobre as razões deste projeto e a sua direção em <b><i>codeberg.org/comaps</i></b>.
Junte-se à comunidade e ajude a fazer a melhor aplicação de mapas
• Use a aplicação e partilhe-a com outros
• Dê feedback e reporte problemas
• Atualize os dados de mapa na aplicação ou no site do OpenStreetMap
‣ <b>Simples e Polida</b>: funcionalidades essenciais fáceis que “somente funcionam”.
‣ <b>Foco Offline</b>: Planeie e navegue as suas viagens no estrangeiro sem dados móveis, procure locais numa caminhada distante, etc. Todas as funções da aplicação foram criadas com intenção de serem usadas sem internet.
‣ <b>Respeita a privacidade</b>: A aplicação foi criada com privacidade em mente — não identifica o utilizador, não rastreia, e não usa a sua informação pessoal. Sem anúncios.
‣ <b>Saves Your Battery and Space</b>: Não esgota a sua bateria ao contrário de outras aplicações. Mapas compactos salvam espaço no seu telemóvel.
‣ <b>Gratuita e Feita pela Comunidade</b>: Pessoas como si ajudam a criar a aplicação ao adicionar locais ao OpenStreetMap, testando e dando opiniões em funcionalidades e contribuindo com dotes de desenvolvimento e dinheiro.
‣ <b>Decisões e Finanças Abertas e Transparentes, Sem fins lucrativos e Open-Source.</b>
<b>Funcionalidades principais</b>:
• Mapas detalhados descarregáveis com locais que não estão disponíveis com o Google Maps
• Modo ao Ar Livre com trilhos de caminhada destacados, acampamentos, fontes de água, cumes, curvas de nível, etc
• Caminhos pedestres e ciclovias
• Pontos de interesse como restaurantes, estações de serviço, hotéis, lojas, atrações e muitos mais
• Pesquise por nome, endereço, ou por categoria de ponto de interesse
• Navegação com anúncios de voz ao caminhar, pedalar ou conduzir
• Marque os seus locais favoritos com um único clique
• Artigos da Wikipédia Offline
• Camada de metro e direções
• Gravação de Percursos
• Exportar e importar marcadores e percursos em formatos KML, KMZ, GPX
• Um modo escuro para usar durante a noite
• Melhore a informação do mapa para todos com um editor básico embebido
<b>A liberdade chegou</b>
Descubra a sua jornada, navegue o mundo com privacidade e a comunidade à frente!

View File

@@ -1 +0,0 @@
Navegação fácil nos mapas - Descubra mais sobre o seu percurso - Feito por todos

View File

@@ -1 +0,0 @@
CoMaps - Mapas e Navegação - Offline e Privada

View File

@@ -0,0 +1,8 @@
• Карты OpenStreetMap от 4 ноября
• Обновлены цвета иконок на карте, добавлены новые цвета для развлечений, спорта, некоторых бизнесов
На зарядных станциях показываются имеющиеся типы разъёмов
• Добавлены эстрады, скамейки без спинок и лежаки
• Новые иконки для разных спорт центров, массажных салонов, гостевых домов, некоторых закрытых бизнесов
• Несколько улучшений и исправлений в поиске
• Улучшены голосовые подсказки при навигации
Подробнее смотрите на codeberg.org/comaps/comaps/releases

View File

@@ -1,32 +0,0 @@
OpenStreetMap தரவை அடிப்படையாகக் கொண்ட சமூகம் தலைமையிலான இலவச மற்றும் திறந்த மூல வரைபட பயன்பாடு மற்றும் வெளிப்படைத்தன்மை, தனியுரிமை மற்றும் இலாப நோக்கற்றது ஆகியவற்றுக்கான அர்ப்பணிப்புடன் வலுவூட்டப்பட்டது. CoMaps என்பது ஆர்கானிக் மேப்சின் ஃபோர்க்/ச்பின்-ஆஃப் ஆகும், இது Maps.ME இன் ஃபோர்க் ஆகும்.
திட்டத்திற்கான காரணங்கள் மற்றும் அதன் திசையை <b><i>codeberg.org/comaps</i></b> இல் படிக்கவும்.
அங்குள்ள சமூகத்தில் சேர்ந்து சிறந்த வரைபட பயன்பாட்டை உருவாக்க உதவுங்கள்
• பயன்பாட்டைப் பயன்படுத்தி, அதைப் பற்றிய தகவலைப் பரப்புங்கள்
• கருத்துக்களை வழங்கவும் மற்றும் சிக்கல்களைப் புகாரளிக்கவும்
• பயன்பாட்டில் அல்லது OpenStreetMap இணையதளத்தில் வரைபடத் தரவைப் புதுப்பிக்கவும்
‣ <b>ஆஃப்லைனில் கவனம் செலுத்தப்பட்டது</b>: செல்லுலார் சேவையின் தேவையின்றி உங்களின் வெளிநாட்டுப் பயணத்தைத் திட்டமிட்டு வழிநடத்துங்கள், தொலைதூர பயணத்தில் இருக்கும் போது வழிப் புள்ளிகளைத் தேடுங்கள்.
‣ <b>தனியுரிமைக்கு மதிப்பளித்தல்</b>: பயன்பாடு தனியுரிமையை மனதில் கொண்டு வடிவமைக்கப்பட்டுள்ளது - நபர்களை அடையாளம் காணாது, கண்காணிக்காது மற்றும் தனிப்பட்ட தகவல்களைச் சேகரிக்காது. விளம்பரங்கள் இல்லாதது.
‣ <b>எளிமையான மற்றும் மெருகூட்டப்பட்டது</b>: செயல்படும் நற்பொருத்தங்கள் பயன்படுத்த எளிதானது.
‣ <b>உங்கள் பேட்டரி மற்றும் இடத்தைச் சேமிக்கிறது</b>: மற்ற வழிசெலுத்தல் பயன்பாடுகளைப் போல உங்கள் பேட்டரியை வெளியேற்றாது. சிறிய வரைபடங்கள் உங்கள் தொலைபேசியில் விலைமதிப்பற்ற இடத்தை சேமிக்கின்றன.
‣ <b>இலவசம் மற்றும் சமூகத்தால் உருவாக்கப்பட்டது</b>: OpenStreetMap இல் இடங்களைச் சேர்ப்பதன் மூலமும், சோதனை செய்து, அம்சங்களைப் பற்றிய கருத்துக்களை வழங்குவதன் மூலமும், அவர்களின் மேம்பாட்டுத் திறன்களையும் பணத்தையும் பங்களிப்பதன் மூலமும் உங்களைப் போன்றவர்கள் பயன்பாட்டை உருவாக்க உதவியுள்ளனர்.
‣ <b>திறந்த மற்றும் வெளிப்படையான முடிவெடுக்கும் மற்றும் நிதியியல், இலாப நோக்கற்ற மற்றும் முழு திறந்த மூல.</b>
<b>முக்கிய அம்சங்கள்</b>:
• கூகுள் மேப்சில் இல்லாத இடங்களுடன் தரவிறக்கம் செய்யக்கூடிய விரிவான வரைபடங்கள்
• ஐகிங் பாதைகள், முகாம்கள், நீர் ஆதாரங்கள், சிகரங்கள், விளிம்பு கோடுகள் போன்றவற்றைக் கொண்ட வெளிப்புறப் பயன்முறை
• நடைபாதைகள் மற்றும் சைக்கிள் பாதைகள்
• உணவகங்கள், எரிவாயு நிலையங்கள், ஓட்டல்கள், கடைகள், சுற்றிப்பார்க்கும் இடங்கள் மற்றும் பல போன்ற ஆர்வமுள்ள இடங்கள்
• பெயர் அல்லது முகவரி அல்லது ஆர்வமுள்ள வகை மூலம் தேடவும்
• நடைபயிற்சி, சைக்கிள் ஓட்டுதல் அல்லது வண்டி ஓட்டுவதற்கான குரல் அறிவிப்புகளுடன் வழிசெலுத்தல்
• ஒரே தட்டினால் உங்களுக்குப் பிடித்த இடங்களை புத்தகக்குறி செய்யவும்
• இணைப்பில்லாத விக்கிபீடியா கட்டுரைகள்
• சுரங்கப்பாதை போக்குவரத்து அடுக்கு மற்றும் திசைகள்
• ட்ராக் ரெக்கார்டிங்
• KML, KMZ, GPX வடிவங்களில் புக்மார்க்குகள் மற்றும் டிராக்குகளை ஏற்றுமதி மற்றும் இறக்குமதி செய்யுங்கள்
• இரவில் பயன்படுத்த ஒரு இருண்ட பயன்முறை
• அடிப்படை உள்ளமைக்கப்பட்ட எடிட்டரைப் பயன்படுத்தி அனைவருக்கும் வரைபடத் தரவை மேம்படுத்தவும்
<b>சுதந்திரம் இங்கே உள்ளது</b>
உங்கள் பயணத்தைக் கண்டறியவும், தனியுரிமை மற்றும் சமூகத்தை முன்னணியில் கொண்டு உலகிற்கு செல்லவும்!

View File

@@ -1 +0,0 @@
எளிய வழிகாட்டி - பயணத்தை மேலும் கண்டறி - சமூகத்தால் இயக்கப்படுகிறது

View File

@@ -1 +0,0 @@
இணைவரைபடங்கள் - மலையேறு, வண்டி, தனிமையில் இயக்கு

View File

@@ -68,4 +68,4 @@ Por favor, informa de errores, sugiere ideas y únete a nuestra comunidad en el
<b>La Libertad Está Aquí</b> <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.

View File

@@ -1 +0,0 @@
Paprasta ir patogi navigacija Turiningos kelionės Vystoma bendruomenės

View File

@@ -1 +0,0 @@
CoMaps naviguokite privačiai

View File

@@ -1,36 +0,0 @@
Aplikacja mapowa tworzona przez społeczność, darmowa i open source, oparta na danych OpenStreetMap, z pełnym naciskiem na transparentność, prywatność i działanie non-profit.
Dołącz do społeczności i pomóż tworzyć najlepszą aplikację mapową
• Korzystaj z aplikacji i polecaj ją innym
• Przekazuj opinie i zgłaszaj problemy
• Aktualizuj dane mapy w aplikacji lub na stronie OpenStreetMap
<i>Twoje opinie i oceny na 5 gwiazdek są dla nas najlepszym wsparciem!</i>
‣ <b>Prosta i dopracowana</b>: najważniejsze, łatwe w użyciu funkcje, które po prostu działają.
‣ <b>Skoncentrowana na trybie offline</b>: planuj i nawiguj za granicą bez sieci komórkowej, wyszukuj punkty na odległych szlakach wszystkie funkcje działają offline.
‣ <b>Z poszanowaniem prywatności</b>: aplikacja nie identyfikuje użytkowników, nie śledzi i nie zbiera danych osobowych. Bez reklam.
‣ <b>Oszczędza baterię i miejsce</b>: nie zużywa baterii jak inne aplikacje nawigacyjne, a kompaktowe mapy oszczędzają miejsce w telefonie.
‣ <b>Darmowa i tworzona przez społeczność</b>: ludzie tacy jak Ty dodają miejsca do OpenStreetMap, testują funkcje, zgłaszają opinie i wspierają projekt.
‣ <b>Otwarte i przejrzyste decyzje i finanse, działanie non-profit, pełne open source.</b>
<b>Główne funkcje</b>:
• Pobierane szczegółowe mapy z miejscami, których nie ma w Google Maps
• Tryb outdoor ze szlakami, biwakami, źródłami wody, szczytami, warstwicami itp.
• Ścieżki piesze i rowerowe
• Punkty zainteresowania: restauracje, stacje paliw, hotele, sklepy, atrakcje i wiele innych
• Wyszukiwanie po nazwie, adresie lub kategorii
• Nawigacja z komunikatami głosowymi dla pieszych, rowerzystów i kierowców
• Dodawanie ulubionych miejsc jednym tapnięciem
• Artykuły Wikipedii dostępne offline
• Warstwa metra i wskazówki dojazdu
• Nagrywanie śladu trasy
• Eksport i import ulubionych miejsc i tras w formatach KML, KMZ, GPX
• Tryb ciemny do używania nocą
• Poprawianie danych mapy za pomocą prostego wbudowanego edytora
• Obsługa Android Auto
Zgłaszaj problemy, proponuj pomysły i dołącz do społeczności na stronie <b><i>comaps.app</i></b>.
<b>Wolność jest tutaj</b>
Odkrywaj swoją drogę i nawiguj po świecie z prywatnością i społecznością na pierwszym miejscu!

View File

@@ -1,36 +0,0 @@
திறந்ததெருவரைபடம் தரவை அடிப்படையாகக் கொண்ட சமூகத்தால் வழிநடத்தப்படும் இலவச & திறந்த மூல வரைபடப் பயன்பாடு, வெளிப்படைத்தன்மை, தனியுரிமை மற்றும் இலாப நோக்கற்ற தன்மை ஆகியவற்றிற்கான அர்ப்பணிப்புடன் வலுப்படுத்தப்பட்டுள்ளது.
சமூகத்தில் சேர்ந்து சிறந்த வரைபடப் பயன்பாட்டை உருவாக்க உதவுங்கள்
• பயன்பாட்டைப் பயன்படுத்தி அதைப் பற்றிய செய்தியைப் பரப்புங்கள்
• கருத்துத் தெரிவிக்கவும் சிக்கல்களைப் புகாரளிக்கவும்
• பயன்பாட்டில் அல்லது OpenStreetMap வலைத்தளத்தில் வரைபடத் தரவைப் புதுப்பிக்கவும்
<i>உங்கள் கருத்து மற்றும் 5-நட்சத்திர மதிப்புரைகள் எங்களுக்குச் சிறந்த ஆதரவாகும்!</i>
‣ <b>எளிமையான மற்றும் மெருகூட்டப்பட்ட</b>: அத்தியாவசியமான பயன்படுத்த எளிதான அம்சங்கள் வேலை செய்கின்றன.
‣ <b>ஆஃப்லைனில் கவனம் செலுத்துகிறது</b>: செல்லுலார் சேவை தேவை இல்லாமல் உங்கள் வெளிநாட்டு பயணத்தைத் திட்டமிட்டு வழிநடத்துங்கள், தொலைதூர நடைபயணத்தின்போது வழிப்புள்ளிகளைத் தேடுங்கள் போன்றவை. அனைத்து பயன்பாட்டு செயல்பாடுகளும் ஆஃப்லைனில் வேலை செய்ய வடிவமைக்கப்பட்டுள்ளன.
‣ <b>தனியுரிமையை மதித்தல்</b>: பயன்பாடு தனியுரிமையை மனதில் கொண்டு வடிவமைக்கப்பட்டுள்ளது - மக்களை அடையாளம் காணாது, கண்காணிக்காது மற்றும் தனிப்பட்ட தகவல்களைச் சேகரிக்காது. விளம்பரங்கள் இல்லாதது.
‣ <b>உங்கள் பேட்டரி மற்றும் இடத்தைச் சேமிக்கிறது</b>: பிற வழிசெலுத்தல் பயன்பாடுகளைப் போல உங்கள் பேட்டரியை வெளியேற்றாது. சிறிய வரைபடங்கள் உங்கள் தொலைபேசியில் விலைமதிப்பற்ற இடத்தைச் சேமிக்கின்றன.
‣ <b>இலவசம் மற்றும் சமூகத்தால் உருவாக்கப்பட்டது</b>: உங்களைப் போன்றவர்கள் OpenStreetMap இல் இடங்களைச் சேர்ப்பதன் மூலமும், அம்சங்கள்குறித்து சோதித்துப் பார்த்துக் கருத்து தெரிவிப்பதன் மூலமும், அவர்களின் மேம்பாட்டுத் திறன்கள் மற்றும் பணத்தை பங்களிப்பதன் மூலமும் பயன்பாட்டை உருவாக்க உதவினார்கள்.
‣ <b>திறந்த மற்றும் வெளிப்படையான முடிவெடுக்கும் மற்றும் நிதி, இலாப நோக்கற்ற மற்றும் முழுமையாகத் திறந்த மூல.</b>
<b>முக்கிய பண்புகள்</b>:
• கூகிள் வரைபடத்தில் கிடைக்காத இடங்களுடன் பதிவிறக்கம் செய்யக்கூடிய விரிவான வரைபடங்கள்
• ஹைகிங் பாதைகள், முகாம் தளங்கள், நீர் ஆதாரங்கள், சிகரங்கள், விளிம்புக் கோடுகள் போன்றவற்றுடன் வெளிப்புற பயன்முறை
• நடைபாதைகள் மற்றும் சைக்கிள் பாதைகள்
• உணவகங்கள், எரிவாயு நிலையங்கள், ஹோட்டல்கள், கடைகள், பார்வையிடல்கள் மற்றும் பல போன்ற ஆர்வமுள்ள இடங்கள்
• பெயர் அல்லது முகவரி அல்லது ஆர்வமுள்ள இட வகைமூலம் தேடுங்கள்
• நடைபயிற்சி, சைக்கிள் ஓட்டுதல் அல்லது வாகனம் ஓட்டுவதற்கான குரல் அறிவிப்புகளுடன் வழிசெலுத்தல்
• உங்களுக்குப் பிடித்த இடங்களை ஒரே தட்டலில் புக்மார்க் செய்யவும்
• ஆஃப்லைன் விக்கிபீடியா கட்டுரைகள்
• சுரங்கப்பாதை போக்குவரத்து அடுக்கு மற்றும் திசைகள்
• பதிவுசெய்தலைக் கண்காணிக்கவும்
• KML, KMZ, GPX வடிவங்களில் புக்மார்க்குகள் மற்றும் தடங்களை ஏற்றுமதி செய்து இறக்குமதி செய்யவும்
• இரவில் பயன்படுத்த ஒரு இருண்ட பயன்முறை
• அடிப்படை உள்ளமைக்கப்பட்ட எடிட்டரைப் பயன்படுத்தி அனைவருக்கும் வரைபடத் தரவை மேம்படுத்தவும்
• ஆண்டாய்டு தானி ஆதரவு
பயன்பாட்டு சிக்கல்களைப் புகாரளிக்கவும், யோசனைகளைப் பரிந்துரைக்கவும் மற்றும் எங்கள் சமூகத்தில் சேரவும் <b><i>comaps.app</i></b> வலைத்தளம்.
<b>சுதந்திரம் இங்கே உள்ளது</b>
உங்கள் பயணத்தைக் கண்டறியவும், தனியுரிமை மற்றும் சமூகத்தை முன்னணியில் வைத்து உலகை வழிநடத்தவும்!

View File

@@ -1 +0,0 @@
எளிய வழிகாட்டி - பயணத்தை மேலும் கண்டறி - சமூகத்தால் இயக்கப்படுகிறது

View File

@@ -1 +0,0 @@
இணைவரைபடங்கள் - தனியுரிமை

View File

@@ -28,6 +28,7 @@ import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener; import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;

View File

@@ -253,7 +253,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
mProgress.setMax(bytes); mProgress.setMax(bytes);
// Start progress at 1% according to M3 guidelines // Start progress at 1% according to M3 guidelines
mProgress.setProgressCompat(bytes / 100, true); mProgress.setProgressCompat(bytes/100, true);
} }
else else
finishFilesDownload(bytes); finishFilesDownload(bytes);
@@ -372,7 +372,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
mTvMessage.setText(getString(R.string.downloading_country_can_proceed, item.name, fileSizeString)); mTvMessage.setText(getString(R.string.downloading_country_can_proceed, item.name, fileSizeString));
mProgress.setMax((int) item.totalSize); mProgress.setMax((int) item.totalSize);
// Start progress at 1% according to M3 guidelines // Start progress at 1% according to M3 guidelines
mProgress.setProgressCompat((int) (item.totalSize / 100), true); mProgress.setProgressCompat((int) (item.totalSize/100), true);
mCountryDownloadListenerSlot = MapManager.nativeSubscribe(mCountryDownloadListener); mCountryDownloadListenerSlot = MapManager.nativeSubscribe(mCountryDownloadListener);
MapManagerHelper.startDownload(mCurrentCountry); MapManagerHelper.startDownload(mCurrentCountry);
@@ -424,17 +424,17 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
default -> throw new AssertionError("Unexpected result code = " + result); default -> throw new AssertionError("Unexpected result code = " + result);
}; };
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
.setTitle(titleId) .setTitle(titleId)
.setMessage(messageId) .setMessage(messageId)
.setCancelable(true) .setCancelable(true)
.setOnCancelListener((dialog) -> setAction(PAUSE)) .setOnCancelListener((dialog) -> setAction(PAUSE))
.setPositiveButton(R.string.try_again, .setPositiveButton(R.string.try_again,
(dialog, which) -> { (dialog, which) -> {
setAction(TRY_AGAIN); setAction(TRY_AGAIN);
onTryAgainClicked(); onTryAgainClicked();
}) })
.setOnDismissListener(dialog -> mAlertDialog = null) .setOnDismissListener(dialog -> mAlertDialog = null)
.show(); .show();
} }
} }

View File

@@ -48,6 +48,7 @@ import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -107,7 +108,6 @@ import app.organicmaps.sdk.routing.RoutingOptions;
import app.organicmaps.sdk.search.SearchEngine; import app.organicmaps.sdk.search.SearchEngine;
import app.organicmaps.sdk.settings.RoadType; import app.organicmaps.sdk.settings.RoadType;
import app.organicmaps.sdk.settings.UnitLocale; import app.organicmaps.sdk.settings.UnitLocale;
import app.organicmaps.sdk.sound.TtsPlayer;
import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.Config;
import app.organicmaps.sdk.util.LocationUtils; import app.organicmaps.sdk.util.LocationUtils;
import app.organicmaps.sdk.util.PowerManagment; import app.organicmaps.sdk.util.PowerManagment;
@@ -133,6 +133,7 @@ import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
@@ -1813,26 +1814,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
return false; 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() private boolean showStartPointNotice()
{ {
final RoutingController controller = RoutingController.get(); final RoutingController controller = RoutingController.get();
@@ -2209,8 +2190,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
if (!showRoutingDisclaimer()) if (!showRoutingDisclaimer())
return; return;
deliverTtsMessage();
closeFloatingPanels(); closeFloatingPanels();
setFullscreen(false); setFullscreen(false);
RoutingController.get().start(); RoutingController.get().start();

View File

@@ -38,9 +38,8 @@ public class OsmUploadWork extends Worker
{ {
final Constraints c = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build(); final Constraints c = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(OsmUploadWork.class).setConstraints(c); OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(OsmUploadWork.class).setConstraints(c);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
{ builder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST);
builder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST);
} }
final OneTimeWorkRequest wr = builder.build(); final OneTimeWorkRequest wr = builder.build();
WorkManager.getInstance(context).beginUniqueWork("UploadOsmChanges", ExistingWorkPolicy.KEEP, wr).enqueue(); WorkManager.getInstance(context).beginUniqueWork("UploadOsmChanges", ExistingWorkPolicy.KEEP, wr).enqueue();

View File

@@ -17,6 +17,7 @@ import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.sdk.util.log.Logger;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -10,6 +10,7 @@ import androidx.fragment.app.DialogFragment;
public class BaseMwmDialogFragment extends DialogFragment public class BaseMwmDialogFragment extends DialogFragment
{ {
protected int getStyle() protected int getStyle()
{ {
return STYLE_NORMAL; return STYLE_NORMAL;

View File

@@ -18,7 +18,9 @@ import androidx.fragment.app.FragmentManager;
import app.organicmaps.MwmApplication; import app.organicmaps.MwmApplication;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.SplashActivity; import app.organicmaps.SplashActivity;
import app.organicmaps.sdk.util.Config;
import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.sdk.util.log.Logger;
import app.organicmaps.util.RtlUtils;
import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.appbar.MaterialToolbar;
import java.util.Objects; import java.util.Objects;
@@ -40,6 +42,7 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this, SystemBarStyle.dark(Color.TRANSPARENT)); EdgeToEdge.enable(this, SystemBarStyle.dark(Color.TRANSPARENT));
RtlUtils.manageRtl(this);
if (!MwmApplication.from(this).getOrganicMaps().arePlatformAndCoreInitialized()) if (!MwmApplication.from(this).getOrganicMaps().arePlatformAndCoreInitialized())
{ {
final Intent intent = Objects.requireNonNull(getIntent()); final Intent intent = Objects.requireNonNull(getIntent());

View File

@@ -282,13 +282,11 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
{ {
if (isEmptySearchResults()) if (isEmptySearchResults())
{ {
requirePlaceholder().setContent(R.string.search_not_found, R.string.search_not_found_query, requirePlaceholder().setContent(R.string.search_not_found, R.string.search_not_found_query, R.drawable.ic_search_fail);
R.drawable.ic_search_fail);
} }
else if (isEmpty()) else if (isEmpty())
{ {
requirePlaceholder().setContent(R.string.bookmarks_empty_list_title, R.string.bookmarks_empty_list_message, requirePlaceholder().setContent(R.string.bookmarks_empty_list_title, R.string.bookmarks_empty_list_message, R.drawable.ic_bookmarks);
R.drawable.ic_bookmarks);
} }
boolean isEmptyRecycler = isEmpty() || isEmptySearchResults(); boolean isEmptyRecycler = isEmpty() || isEmptySearchResults();

View File

@@ -23,6 +23,7 @@ import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils; import app.organicmaps.util.Utils;
import app.organicmaps.widget.recycler.RecyclerClickListener; import app.organicmaps.widget.recycler.RecyclerClickListener;
import app.organicmaps.widget.recycler.RecyclerLongClickListener; import app.organicmaps.widget.recycler.RecyclerLongClickListener;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.checkbox.MaterialCheckBox; import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.imageview.ShapeableImageView;
@@ -457,12 +458,10 @@ public class Holders
String formattedDesc = desc.replace("\n", "<br>"); String formattedDesc = desc.replace("\n", "<br>");
Spanned spannedDesc = Utils.fromHtml(formattedDesc); Spanned spannedDesc = Utils.fromHtml(formattedDesc);
if (!TextUtils.isEmpty(spannedDesc)) if (!TextUtils.isEmpty(spannedDesc)) {
{
mDescText.setText(spannedDesc); mDescText.setText(spannedDesc);
} }
else else {
{
mDescText.setText(R.string.list_description_empty); mDescText.setText(R.string.list_description_empty);
} }
} }

View File

@@ -32,8 +32,7 @@ public class DrivingOptionsScreen extends BaseMapScreen
new DrivingOption(RoadType.Dirty, R.string.avoid_unpaved), new DrivingOption(RoadType.Dirty, R.string.avoid_unpaved),
new DrivingOption(RoadType.Ferry, R.string.avoid_ferry), new DrivingOption(RoadType.Ferry, R.string.avoid_ferry),
new DrivingOption(RoadType.Motorway, R.string.avoid_motorways), new DrivingOption(RoadType.Motorway, R.string.avoid_motorways),
new DrivingOption(RoadType.Steps, R.string.avoid_steps), new DrivingOption(RoadType.Steps, R.string.avoid_steps)};
new DrivingOption(RoadType.Paved, R.string.avoid_paved)};
@NonNull @NonNull
private final Map<RoadType, Boolean> mInitialDrivingOptionsState = new HashMap<>(); private final Map<RoadType, Boolean> mInitialDrivingOptionsState = new HashMap<>();

View File

@@ -50,8 +50,8 @@ public final class IntentUtils
} }
// https://developer.android.com/reference/androidx/car/app/CarContext#startCarApp(android.content.Intent) // https://developer.android.com/reference/androidx/car/app/CarContext#startCarApp(android.content.Intent)
private static void processNavigationIntent(@NonNull CarContext carContext, @NonNull Renderer surfaceRenderer, private static void processNavigationIntent(@NonNull CarContext carContext,
@NonNull Intent intent) @NonNull Renderer surfaceRenderer, @NonNull Intent intent)
{ {
// TODO (AndrewShkrob): This logic will need to be revised when we introduce support for adding stops during // TODO (AndrewShkrob): This logic will need to be revised when we introduce support for adding stops during
// navigation or route planning. Skip navigation intents during navigation // navigation or route planning. Skip navigation intents during navigation

View File

@@ -31,7 +31,7 @@ public final class RoutingHelpers
default -> Distance.UNIT_METERS; default -> Distance.UNIT_METERS;
}; };
return Distance.create(distance.mDistance, displayUnit); return Distance.create(distance.mDistance, displayUnit);
} }
@NonNull @NonNull
@@ -52,7 +52,7 @@ public final class RoutingHelpers
default -> LaneDirection.SHAPE_UNKNOWN; default -> LaneDirection.SHAPE_UNKNOWN;
}; };
return LaneDirection.create(shape, isRecommended); return LaneDirection.create(shape, isRecommended);
} }
@NonNull @NonNull
@@ -77,7 +77,7 @@ public final class RoutingHelpers
case EXIT_HIGHWAY_TO_LEFT -> Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT; case EXIT_HIGHWAY_TO_LEFT -> Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT;
case EXIT_HIGHWAY_TO_RIGHT -> Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT; case EXIT_HIGHWAY_TO_RIGHT -> Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT;
}; };
final Maneuver.Builder builder = new Maneuver.Builder(maneuverType); final Maneuver.Builder builder = new Maneuver.Builder(maneuverType);
if (maneuverType == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW) if (maneuverType == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW)
builder.setRoundaboutExitNumber(roundaboutExitNum > 0 ? roundaboutExitNum : 1); builder.setRoundaboutExitNumber(roundaboutExitNum > 0 ? roundaboutExitNum : 1);
builder.setIcon(new CarIcon.Builder(createManeuverIcon(context, carDirection, roundaboutExitNum)).build()); builder.setIcon(new CarIcon.Builder(createManeuverIcon(context, carDirection, roundaboutExitNum)).build());
@@ -85,8 +85,7 @@ public final class RoutingHelpers
} }
@NonNull @NonNull
private static IconCompat createManeuverIcon(@NonNull final CarContext context, @NonNull CarDirection carDirection, private static IconCompat createManeuverIcon(@NonNull final CarContext context, @NonNull CarDirection carDirection, int roundaboutExitNum)
int roundaboutExitNum)
{ {
if (!CarDirection.isRoundAbout(carDirection) || roundaboutExitNum == 0) if (!CarDirection.isRoundAbout(carDirection) || roundaboutExitNum == 0)
{ {

View File

@@ -39,7 +39,8 @@ public final class UiHelpers
} }
@NonNull @NonNull
public static ActionStrip createMapActionStrip(@NonNull CarContext context, @NonNull Renderer surfaceRenderer) public static ActionStrip createMapActionStrip(@NonNull CarContext context,
@NonNull Renderer surfaceRenderer)
{ {
final CarIcon iconPlus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_plus)).build(); final CarIcon iconPlus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_plus)).build();
final CarIcon iconMinus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_minus)).build(); final CarIcon iconMinus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_minus)).build();
@@ -58,13 +59,15 @@ public final class UiHelpers
} }
@NonNull @NonNull
public static MapController createMapController(@NonNull CarContext context, @NonNull Renderer surfaceRenderer) public static MapController createMapController(@NonNull CarContext context,
@NonNull Renderer surfaceRenderer)
{ {
return new MapController.Builder().setMapActionStrip(createMapActionStrip(context, surfaceRenderer)).build(); return new MapController.Builder().setMapActionStrip(createMapActionStrip(context, surfaceRenderer)).build();
} }
@NonNull @NonNull
public static Action createSettingsAction(@NonNull BaseMapScreen mapScreen, @NonNull Renderer surfaceRenderer) public static Action createSettingsAction(@NonNull BaseMapScreen mapScreen,
@NonNull Renderer surfaceRenderer)
{ {
return createSettingsAction(mapScreen, surfaceRenderer, null); return createSettingsAction(mapScreen, surfaceRenderer, null);
} }
@@ -78,7 +81,8 @@ public final class UiHelpers
} }
@NonNull @NonNull
private static Action createSettingsAction(@NonNull BaseMapScreen mapScreen, @NonNull Renderer surfaceRenderer, private static Action createSettingsAction(@NonNull BaseMapScreen mapScreen,
@NonNull Renderer surfaceRenderer,
@Nullable OnScreenResultListener onScreenResultListener) @Nullable OnScreenResultListener onScreenResultListener)
{ {
final CarContext context = mapScreen.getCarContext(); final CarContext context = mapScreen.getCarContext();
@@ -119,7 +123,8 @@ public final class UiHelpers
return null; return null;
final Row.Builder builder = new Row.Builder(); final Row.Builder builder = new Row.Builder();
builder.setImage(new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_opening_hours)).build()); builder.setImage(
new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_opening_hours)).build());
if (isEmptyTT) if (isEmptyTT)
builder.setTitle(ohStr); builder.setTitle(ohStr);
@@ -172,7 +177,7 @@ public final class UiHelpers
{ {
case LocationState.PENDING_POSITION, LocationState.NOT_FOLLOW_NO_POSITION -> case LocationState.PENDING_POSITION, LocationState.NOT_FOLLOW_NO_POSITION ->
drawableRes = R.drawable.ic_location_off; drawableRes = R.drawable.ic_location_off;
case LocationState.NOT_FOLLOW -> drawableRes = R.drawable.ic_location_crosshair; case LocationState.NOT_FOLLOW -> drawableRes = R.drawable.ic_not_follow;
case LocationState.FOLLOW -> case LocationState.FOLLOW ->
{ {
drawableRes = R.drawable.ic_follow; drawableRes = R.drawable.ic_follow;

View File

@@ -125,8 +125,7 @@ public class EditTextDialogFragment extends BaseMwmDialogFragment
positiveButton.setOnClickListener(view -> { positiveButton.setOnClickListener(view -> {
final String result = mEtInput.getText().toString(); final String result = mEtInput.getText().toString();
if (validateInput(requireActivity(), result)) if (validateInput(requireActivity(), result)) {
{
processInput(result); processInput(result);
editTextDialog.dismiss(); editTextDialog.dismiss();
} }

View File

@@ -357,7 +357,7 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
private MenuBottomSheetItem getCancelMenuItem() private MenuBottomSheetItem getCancelMenuItem()
{ {
return new MenuBottomSheetItem(R.string.cancel, R.drawable.ic_close, () -> onCancelActionSelected(mSelectedItem)); return new MenuBottomSheetItem(R.string.cancel, R.drawable.ic_cancel, () -> onCancelActionSelected(mSelectedItem));
} }
private class ItemViewHolder extends BaseInnerViewHolder<CountryItem> private class ItemViewHolder extends BaseInnerViewHolder<CountryItem>

View File

@@ -26,10 +26,6 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
{ {
private static boolean sAutodownloadLocked; private static boolean sAutodownloadLocked;
private static final int HIDE_THRESHOLD = 2;
// Default bundles (e.g., world/coasts). Used to approximate “user-downloaded” count.
private static final int DEFAULT_MAP_BASELINE = 2;
private final MwmActivity mActivity; private final MwmActivity mActivity;
private final View mFrame; private final View mFrame;
private final MaterialTextView mParent; private final MaterialTextView mParent;
@@ -37,7 +33,6 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
private final MaterialTextView mSize; private final MaterialTextView mSize;
private final WheelProgressView mProgress; private final WheelProgressView mProgress;
private final MaterialButton mButton; private final MaterialButton mButton;
private final View mOfflineExplanation;
private int mStorageSubscriptionSlot; private int mStorageSubscriptionSlot;
@@ -49,10 +44,7 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
public void onStatusChanged(List<MapManager.StorageCallbackData> data) public void onStatusChanged(List<MapManager.StorageCallbackData> data)
{ {
if (mCurrentCountry == null) if (mCurrentCountry == null)
{
updateOfflineExplanationVisibility();
return; return;
}
for (MapManager.StorageCallbackData item : data) for (MapManager.StorageCallbackData item : data)
{ {
@@ -66,7 +58,7 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
{ {
mCurrentCountry.update(); mCurrentCountry.update();
updateProgressState(false); updateProgressState(false);
updateOfflineExplanationVisibility();
return; return;
} }
} }
@@ -109,15 +101,6 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
return enqueued || progress || applying; return enqueued || progress || applying;
} }
private void updateOfflineExplanationVisibility()
{
if (mOfflineExplanation == null)
return;
// hide once threshold reached; safe to call repeatedly.
app.organicmaps.util.UiUtils.showIf(MapManager.nativeGetDownloadedCount() < (DEFAULT_MAP_BASELINE + HIDE_THRESHOLD),
mOfflineExplanation);
}
private void updateProgressState(boolean shouldAutoDownload) private void updateProgressState(boolean shouldAutoDownload)
{ {
updateStateInternal(shouldAutoDownload); updateStateInternal(shouldAutoDownload);
@@ -125,8 +108,6 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
private void updateStateInternal(boolean shouldAutoDownload) private void updateStateInternal(boolean shouldAutoDownload)
{ {
updateOfflineExplanationVisibility();
boolean showFrame = boolean showFrame =
(mCurrentCountry != null && !mCurrentCountry.present && !RoutingController.get().isNavigating()); (mCurrentCountry != null && !mCurrentCountry.present && !RoutingController.get().isNavigating());
if (showFrame) if (showFrame)
@@ -210,9 +191,6 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
mProgress = controls.findViewById(R.id.wheel_downloader_progress); mProgress = controls.findViewById(R.id.wheel_downloader_progress);
mButton = controls.findViewById(R.id.downloader_button); mButton = controls.findViewById(R.id.downloader_button);
mOfflineExplanation = mFrame.findViewById(R.id.offline_explanation);
updateOfflineExplanationVisibility();
mProgress.setOnClickListener(v -> { mProgress.setOnClickListener(v -> {
if (mCurrentCountry == null) if (mCurrentCountry == null)
return; return;
@@ -269,7 +247,6 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
public void onResume() public void onResume()
{ {
updateOfflineExplanationVisibility();
if (mStorageSubscriptionSlot == 0) if (mStorageSubscriptionSlot == 0)
{ {
mStorageSubscriptionSlot = MapManager.nativeSubscribe(mStorageCallback); mStorageSubscriptionSlot = MapManager.nativeSubscribe(mStorageCallback);

View File

@@ -1,7 +1,5 @@
package app.organicmaps.editor; package app.organicmaps.editor;
import static android.view.View.INVISIBLE;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@@ -15,12 +13,14 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.GridLayout; import android.widget.GridLayout;
import android.widget.Toast;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes; import androidx.annotation.IdRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.widget.SwitchCompat;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R; import app.organicmaps.R;
@@ -42,9 +42,9 @@ import app.organicmaps.util.Graphics;
import app.organicmaps.util.InputUtils; import app.organicmaps.util.InputUtils;
import app.organicmaps.util.UiUtils; import app.organicmaps.util.UiUtils;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
@@ -115,9 +115,9 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
private MaterialTextView mPhone; private MaterialTextView mPhone;
private MaterialButton mEditPhoneLink; private MaterialButton mEditPhoneLink;
private MaterialTextView mCuisine; private MaterialTextView mCuisine;
private MaterialSwitch mWifi; private SwitchCompat mWifi;
private MaterialTextView mSelfService; private MaterialTextView mSelfService;
private MaterialSwitch mOutdoorSeating; private SwitchCompat mOutdoorSeating;
// Default Metadata entries. // Default Metadata entries.
private static final class MetadataEntry private static final class MetadataEntry
@@ -153,7 +153,6 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
private final Map<Metadata.MetadataType, View> mDetailsBlocks = new HashMap<>(); private final Map<Metadata.MetadataType, View> mDetailsBlocks = new HashMap<>();
private final Map<Metadata.MetadataType, View> mSocialMediaBlocks = new HashMap<>(); private final Map<Metadata.MetadataType, View> mSocialMediaBlocks = new HashMap<>();
private MaterialButton mReset; private MaterialButton mReset;
private MaterialButton mDisused;
private EditorHostFragment mParent; private EditorHostFragment mParent;
@@ -353,7 +352,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
{ {
hasChargeSockets = hasChargeSockets || (type == Metadata.MetadataType.FMD_CHARGE_SOCKETS.toInt()); hasChargeSockets = hasChargeSockets || (type == Metadata.MetadataType.FMD_CHARGE_SOCKETS.toInt());
} }
UiUtils.showIf(hasChargeSockets, mCardChargingStation); // Hide socket until https://codeberg.org/comaps/comaps/issues/2368 is fixed
//UiUtils.showIf(hasChargeSockets, mCardChargingStation);
setCardVisibility(mCardDetails, mDetailsBlocks, editableDetails); setCardVisibility(mCardDetails, mDetailsBlocks, editableDetails);
setCardVisibility(mCardSocialMedia, mSocialMediaBlocks, editableDetails); setCardVisibility(mCardSocialMedia, mSocialMediaBlocks, editableDetails);
@@ -395,26 +395,24 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
typeBtns.removeAllViews(); typeBtns.removeAllViews();
List<String> SOCKET_TYPES = Arrays.stream(getResources().getStringArray(R.array.charge_socket_types)).toList(); List<String> SOCKET_TYPES = Arrays.stream(getResources().getStringArray(R.array.charge_socket_types)).toList();
for (String socketType : SOCKET_TYPES) for (String socket : SOCKET_TYPES)
{ {
ChargeSocketDescriptor socket = new ChargeSocketDescriptor(socketType, 0, 0);
MaterialButton btn = (MaterialButton) inflater.inflate(R.layout.button_socket_type, typeBtns, false); MaterialButton btn = (MaterialButton) inflater.inflate(R.layout.button_socket_type, typeBtns, false);
btn.setTag(R.id.socket_type, socket.type()); btn.setTag(R.id.socket_type, socket);
// load SVG icon converted into VectorDrawable in res/drawable // load SVG icon converted into VectorDrawable in res/drawable
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
int resIconId = getResources().getIdentifier("ic_charge_socket_" + socket.visualType(), "drawable", int resIconId =
requireContext().getPackageName()); getResources().getIdentifier("ic_charge_socket_" + socket, "drawable", requireContext().getPackageName());
if (resIconId != 0) if (resIconId != 0)
{ {
btn.setIcon(getResources().getDrawable(resIconId)); btn.setIcon(getResources().getDrawable(resIconId));
} }
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
int resTypeId = getResources().getIdentifier("charge_socket_" + socket.visualType(), "string", int resTypeId =
requireContext().getPackageName()); getResources().getIdentifier("charge_socket_" + socket, "string", requireContext().getPackageName());
if (resTypeId != 0) if (resTypeId != 0)
{ {
btn.setText(getResources().getString(resTypeId)); btn.setText(getResources().getString(resTypeId));
@@ -462,16 +460,13 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
// Add a TextWatcher to validate on text change // Add a TextWatcher to validate on text change
countView.addTextChangedListener(new TextWatcher() { countView.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
{}
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) public void onTextChanged(CharSequence s, int start, int before, int count) {}
{}
@Override @Override
public void afterTextChanged(Editable s) public void afterTextChanged(Editable s) {
{
validatePositiveField(s.toString(), countInputLayout); validatePositiveField(s.toString(), countInputLayout);
} }
}); });
@@ -486,16 +481,13 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
// Add a TextWatcher to validate on text change // Add a TextWatcher to validate on text change
powerView.addTextChangedListener(new TextWatcher() { powerView.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
{}
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) public void onTextChanged(CharSequence s, int start, int before, int count) {}
{}
@Override @Override
public void afterTextChanged(Editable s) public void afterTextChanged(Editable s) {
{
validatePositiveField(s.toString(), powerInputLayout); validatePositiveField(s.toString(), powerInputLayout);
} }
}); });
@@ -503,82 +495,74 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
return new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog) return new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
.setTitle(R.string.editor_socket) .setTitle(R.string.editor_socket)
.setView(dialogView) .setView(dialogView)
.setPositiveButton( .setPositiveButton(R.string.save,
R.string.save, (dialog, which) -> {
(dialog, which) -> { String socketType = "";
String socketType = ""; for (MaterialButton b : buttonList)
for (MaterialButton b : buttonList) {
{ if (b.isChecked())
if (b.isChecked()) {
{ socketType = b.getTag(R.id.socket_type).toString();
socketType = b.getTag(R.id.socket_type).toString(); break;
break; }
} }
}
int countValue = 0; // 0 means 'unknown count' int countValue = 0; // 0 means 'unknown count'
try try
{ {
countValue = Integer.parseInt(countView.getText().toString()); countValue = Integer.parseInt(countView.getText().toString());
} }
catch (NumberFormatException ignored) catch (NumberFormatException ignored)
{ {
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString()); Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
} }
if (countValue < 0) if (countValue < 0)
{ {
countValue = 0; countValue = 0;
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString()); Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
} }
double powerValue = 0; // 0 means 'unknown power' double powerValue = 0; // 0 means 'unknown power'
try try
{ {
powerValue = Double.parseDouble(powerView.getText().toString()); powerValue = Double.parseDouble(powerView.getText().toString());
} }
catch (NumberFormatException ignored) catch (NumberFormatException ignored)
{ {
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString()); Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
} }
if (powerValue < 0) if (powerValue < 0)
{ {
powerValue = 0; powerValue = 0;
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString()); Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
} }
ChargeSocketDescriptor socket = new ChargeSocketDescriptor(socketType, countValue, powerValue); ChargeSocketDescriptor socket =
new ChargeSocketDescriptor(socketType, countValue, powerValue);
updateChargeSockets(socketIndex, socket); updateChargeSockets(socketIndex, socket);
}) })
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); .setNegativeButton(R.string.cancel, (dialog, which) -> { dialog.dismiss(); });
} }
// Helper method for validation logic // Helper method for validation logic
private boolean validatePositiveField(String text, TextInputLayout layout) private boolean validatePositiveField(String text, TextInputLayout layout) {
{ if (text.isEmpty()) {
if (text.isEmpty())
{
layout.setError(null); // No error if empty (assuming 0 is the default) layout.setError(null); // No error if empty (assuming 0 is the default)
return true; return true;
} }
try try {
{
double value = Double.parseDouble(text); double value = Double.parseDouble(text);
if (value < 0) if (value < 0) {
{
layout.setError(getString(R.string.error_value_must_be_positive)); layout.setError(getString(R.string.error_value_must_be_positive));
return false; return false;
} else {
layout.setError(null);
return true;
} }
else } catch (NumberFormatException e) {
{
layout.setError(null);
return true;
}
}
catch (NumberFormatException e)
{
layout.setError(getString(R.string.error_invalid_number)); layout.setError(getString(R.string.error_invalid_number));
return false; return false;
} }
@@ -598,8 +582,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
{ {
sockets[socketIndex] = socket; sockets[socketIndex] = socket;
} }
else else {
{
List<ChargeSocketDescriptor> list = new ArrayList<>(Arrays.asList(sockets)); List<ChargeSocketDescriptor> list = new ArrayList<>(Arrays.asList(sockets));
list.add(socket); list.add(socket);
sockets = list.toArray(new ChargeSocketDescriptor[0]); sockets = list.toArray(new ChargeSocketDescriptor[0]);
@@ -631,7 +614,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
// load SVG icon converted into VectorDrawable in res/drawable // load SVG icon converted into VectorDrawable in res/drawable
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
int resIconId = getResources().getIdentifier("ic_charge_socket_" + socket.visualType(), "drawable", int resIconId = getResources().getIdentifier("ic_charge_socket_" + socket.type(), "drawable",
requireContext().getPackageName()); requireContext().getPackageName());
if (resIconId != 0) if (resIconId != 0)
{ {
@@ -639,8 +622,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
} }
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
int resTypeId = getResources().getIdentifier("charge_socket_" + socket.visualType(), "string", int resTypeId =
requireContext().getPackageName()); getResources().getIdentifier("charge_socket_" + socket.type(), "string", requireContext().getPackageName());
if (resTypeId != 0) if (resTypeId != 0)
{ {
type.setText(resTypeId); type.setText(resTypeId);
@@ -651,24 +634,23 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
DecimalFormat df = new DecimalFormat("#.##"); DecimalFormat df = new DecimalFormat("#.##");
power.setText(getString(R.string.kw_label, df.format(socket.power()))); power.setText(getString(R.string.kw_label, df.format(socket.power())));
} }
else if (socket.ignorePower())
{
power.setVisibility(INVISIBLE);
}
if (socket.count() != 0) if (socket.count() != 0)
{ {
count.setText(getString(R.string.count_label, socket.count())); count.setText(getString(R.string.count_label, socket.count()));
} }
itemView.setOnClickListener( itemView.setOnClickListener(v -> {
v -> buildChargeSocketDialog(currentIndex, socket.type(), socket.count(), socket.power()).show()); buildChargeSocketDialog(currentIndex, socket.type(), socket.count(), socket.power()).show();
});
socketsGrid.addView(itemView); socketsGrid.addView(itemView);
} }
// add a 'new item' button at the end, to create new sockets // add a 'new item' button at the end, to create new sockets
View btnNewItemView = inflater.inflate(R.layout.button_new_item, socketsGrid, false); View btnNewItemView = inflater.inflate(R.layout.button_new_item, socketsGrid, false);
btnNewItemView.setOnClickListener(v -> buildChargeSocketDialog(-1, "unknown", -1, -1).show()); btnNewItemView.setOnClickListener(v -> {
buildChargeSocketDialog(-1, "unknown", -1, -1).show();
});
socketsGrid.addView(btnNewItemView); socketsGrid.addView(btnNewItemView);
} }
@@ -808,8 +790,9 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
View lineContactBlock = View lineContactBlock =
initBlock(view, Metadata.MetadataType.FMD_CONTACT_LINE, R.id.block_line, R.drawable.ic_line_white, initBlock(view, Metadata.MetadataType.FMD_CONTACT_LINE, R.id.block_line, R.drawable.ic_line_white,
R.string.editor_line_social_network, InputType.TYPE_TEXT_VARIATION_URI); R.string.editor_line_social_network, InputType.TYPE_TEXT_VARIATION_URI);
View blueskyContactBlock = initBlock(view, Metadata.MetadataType.FMD_CONTACT_BLUESKY, R.id.block_bluesky, View blueskyContactBlock =
R.drawable.ic_bluesky, R.string.bluesky, InputType.TYPE_TEXT_VARIATION_URI); initBlock(view, Metadata.MetadataType.FMD_CONTACT_BLUESKY, R.id.block_bluesky, R.drawable.ic_bluesky,
R.string.bluesky, InputType.TYPE_TEXT_VARIATION_URI);
View operatorBlock = initBlock(view, Metadata.MetadataType.FMD_OPERATOR, R.id.block_operator, View operatorBlock = initBlock(view, Metadata.MetadataType.FMD_OPERATOR, R.id.block_operator,
R.drawable.ic_operator, R.string.editor_operator, 0); R.drawable.ic_operator, R.string.editor_operator, 0);
@@ -844,8 +827,6 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
osmInfo.setMovementMethod(LinkMovementMethod.getInstance()); osmInfo.setMovementMethod(LinkMovementMethod.getInstance());
mReset = view.findViewById(R.id.reset); mReset = view.findViewById(R.id.reset);
mReset.setOnClickListener(this); mReset.setOnClickListener(this);
mDisused = view.findViewById(R.id.disused);
mDisused.setOnClickListener(this);
mDetailsBlocks.put(Metadata.MetadataType.FMD_OPEN_HOURS, blockOpeningHours); mDetailsBlocks.put(Metadata.MetadataType.FMD_OPEN_HOURS, blockOpeningHours);
mDetailsBlocks.put(Metadata.MetadataType.FMD_PHONE_NUMBER, blockPhone); mDetailsBlocks.put(Metadata.MetadataType.FMD_PHONE_NUMBER, blockPhone);
@@ -913,8 +894,6 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
mParent.addLanguage(); mParent.addLanguage();
else if (id == R.id.reset) else if (id == R.id.reset)
reset(); reset();
else if (id == R.id.disused)
placeDisused();
else if (id == R.id.block_outdoor_seating) else if (id == R.id.block_outdoor_seating)
mOutdoorSeating.toggle(); mOutdoorSeating.toggle();
} }
@@ -960,12 +939,9 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
if (mParent.addingNewObject()) if (mParent.addingNewObject())
{ {
UiUtils.hide(mReset); UiUtils.hide(mReset);
UiUtils.hide(mDisused);
return; return;
} }
mDisused.setVisibility(Editor.nativeCanMarkPlaceAsDisused() ? View.VISIBLE : View.GONE);
if (Editor.nativeIsMapObjectUploaded()) if (Editor.nativeIsMapObjectUploaded())
{ {
mReset.setText(R.string.editor_place_doesnt_exist); mReset.setText(R.string.editor_place_doesnt_exist);
@@ -1038,20 +1014,6 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
dialogFragment.setTextSaveListener(this::commitPlaceDoesntExists); dialogFragment.setTextSaveListener(this::commitPlaceDoesntExists);
} }
private void placeDisused()
{
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
.setTitle(R.string.editor_mark_business_vacant_title)
.setMessage(R.string.editor_mark_business_vacant_description)
.setPositiveButton(R.string.editor_submit,
(dlg, which) -> {
Editor.nativeMarkPlaceAsDisused();
mParent.processEditedFeatures();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void commitPlaceDoesntExists(@NonNull String text) private void commitPlaceDoesntExists(@NonNull String text)
{ {
Editor.nativePlaceDoesNotExist(text); Editor.nativePlaceDoesNotExist(text);

View File

@@ -358,7 +358,7 @@ public class EditorHostFragment
.show(); .show();
} }
public void processEditedFeatures() private void processEditedFeatures()
{ {
if (OsmOAuth.isAuthorized()) if (OsmOAuth.isAuthorized())
{ {

View File

@@ -1,19 +1,16 @@
package app.organicmaps.editor; package app.organicmaps.editor;
import android.content.res.ColorStateList;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.editor.data.FeatureCategory; import app.organicmaps.sdk.editor.data.FeatureCategory;
import app.organicmaps.sdk.util.StringUtils; import app.organicmaps.sdk.util.StringUtils;
import app.organicmaps.util.UiUtils; import app.organicmaps.util.UiUtils;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
@@ -68,7 +65,8 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter<RecyclerView.Vi
} }
case TYPE_FOOTER -> case TYPE_FOOTER ->
{ {
return new FooterViewHolder(inflater.inflate(R.layout.item_feature_category_footer, parent, false), mFragment); return new FooterViewHolder(inflater.inflate(R.layout.item_feature_category_footer, parent, false),
mFragment);
} }
default -> throw new IllegalArgumentException("Unsupported viewType: " + viewType); default -> throw new IllegalArgumentException("Unsupported viewType: " + viewType);
} }
@@ -121,7 +119,7 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter<RecyclerView.Vi
protected static class FooterViewHolder extends RecyclerView.ViewHolder protected static class FooterViewHolder extends RecyclerView.ViewHolder
{ {
private final TextInputEditText mNoteEditText; private final TextInputEditText mNoteEditText;
private final MaterialButton mSendNoteButton; private final View mSendNoteButton;
FooterViewHolder(@NonNull View itemView, @NonNull FooterListener listener) FooterViewHolder(@NonNull View itemView, @NonNull FooterListener listener)
{ {
@@ -131,24 +129,6 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter<RecyclerView.Vi
mNoteEditText = itemView.findViewById(R.id.note_edit_text); mNoteEditText = itemView.findViewById(R.id.note_edit_text);
mSendNoteButton = itemView.findViewById(R.id.send_note_button); mSendNoteButton = itemView.findViewById(R.id.send_note_button);
mSendNoteButton.setOnClickListener(v -> listener.onSendNoteClicked()); mSendNoteButton.setOnClickListener(v -> listener.onSendNoteClicked());
final ColorStateList bgButtonColor = new ColorStateList(
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {ContextCompat.getColor(mSendNoteButton.getContext(), R.color.base_accent),
ContextCompat.getColor(mSendNoteButton.getContext(), R.color.button_accent_disabled)});
final ColorStateList textButtonColor = new ColorStateList(
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {ContextCompat.getColor(mSendNoteButton.getContext(),
UiUtils.getStyledResourceId(mSendNoteButton.getContext(),
android.R.attr.textColorPrimaryInverse)),
ContextCompat.getColor(mSendNoteButton.getContext(), R.color.button_accent_text_disabled)});
mSendNoteButton.setBackgroundTintList(bgButtonColor);
mSendNoteButton.setTextColor(textButtonColor);
mNoteEditText.addTextChangedListener(new StringUtils.SimpleTextWatcher() { mNoteEditText.addTextChangedListener(new StringUtils.SimpleTextWatcher() {
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) public void onTextChanged(CharSequence s, int start, int before, int count)

View File

@@ -2,16 +2,19 @@ package app.organicmaps.editor;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.editor.data.HoursMinutes; import app.organicmaps.sdk.editor.data.HoursMinutes;
import app.organicmaps.sdk.util.DateUtils; import app.organicmaps.sdk.util.DateUtils;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
public class FromToTimePicker public class FromToTimePicker
{ {
@@ -29,11 +32,18 @@ public class FromToTimePicker
private boolean mIsFromTimePicked; private boolean mIsFromTimePicked;
private int mInputMode; private int mInputMode;
public static void pickTime(@NonNull Fragment fragment, @NonNull FromToTimePicker.OnPickListener listener, public static void pickTime(@NonNull Fragment fragment,
@NonNull HoursMinutes fromTime, @NonNull HoursMinutes toTime, int id, @NonNull FromToTimePicker.OnPickListener listener,
@NonNull HoursMinutes fromTime,
@NonNull HoursMinutes toTime,
int id,
boolean startWithToTime) boolean startWithToTime)
{ {
FromToTimePicker timePicker = new FromToTimePicker(fragment, listener, fromTime, toTime, id); FromToTimePicker timePicker = new FromToTimePicker(fragment,
listener,
fromTime,
toTime,
id);
if (startWithToTime) if (startWithToTime)
timePicker.showToTimePicker(); timePicker.showToTimePicker();
@@ -41,8 +51,11 @@ public class FromToTimePicker
timePicker.showFromTimePicker(); timePicker.showFromTimePicker();
} }
private FromToTimePicker(@NonNull Fragment fragment, @NonNull FromToTimePicker.OnPickListener listener, private FromToTimePicker(@NonNull Fragment fragment,
@NonNull HoursMinutes fromTime, @NonNull HoursMinutes toTime, int id) @NonNull FromToTimePicker.OnPickListener listener,
@NonNull HoursMinutes fromTime,
@NonNull HoursMinutes toTime,
int id)
{ {
mActivity = fragment.requireActivity(); mActivity = fragment.requireActivity();
mFragmentManager = fragment.getChildFragmentManager(); mFragmentManager = fragment.getChildFragmentManager();
@@ -87,12 +100,15 @@ public class FromToTimePicker
private MaterialTimePicker buildFromTimePicker() private MaterialTimePicker buildFromTimePicker()
{ {
MaterialTimePicker timePicker = buildTimePicker(mFromTime, mResources.getString(R.string.editor_time_from), MaterialTimePicker timePicker = buildTimePicker(mFromTime,
mResources.getString(R.string.next_button), null); mResources.getString(R.string.editor_time_from),
mResources.getString(R.string.next_button),
null);
timePicker.addOnNegativeButtonClickListener(view -> finishTimePicking(false)); timePicker.addOnNegativeButtonClickListener(view -> finishTimePicking(false));
timePicker.addOnPositiveButtonClickListener(view -> { timePicker.addOnPositiveButtonClickListener(view ->
{
mIsFromTimePicked = true; mIsFromTimePicked = true;
saveState(timePicker, true); saveState(timePicker, true);
mFromTimePicker = null; mFromTimePicker = null;
@@ -106,10 +122,13 @@ public class FromToTimePicker
private MaterialTimePicker buildToTimePicker() private MaterialTimePicker buildToTimePicker()
{ {
MaterialTimePicker timePicker = buildTimePicker(mToTime, mResources.getString(R.string.editor_time_to), null, MaterialTimePicker timePicker = buildTimePicker(mToTime,
mResources.getString(R.string.editor_time_to),
null,
mResources.getString(R.string.back)); mResources.getString(R.string.back));
timePicker.addOnNegativeButtonClickListener(view -> { timePicker.addOnNegativeButtonClickListener(view ->
{
saveState(timePicker, false); saveState(timePicker, false);
mToTimePicker = null; mToTimePicker = null;
if (mIsFromTimePicked) if (mIsFromTimePicked)
@@ -118,7 +137,8 @@ public class FromToTimePicker
finishTimePicking(false); finishTimePicking(false);
}); });
timePicker.addOnPositiveButtonClickListener(view -> { timePicker.addOnPositiveButtonClickListener(view ->
{
saveState(timePicker, false); saveState(timePicker, false);
finishTimePicking(true); finishTimePicking(true);
}); });
@@ -129,18 +149,18 @@ public class FromToTimePicker
} }
@NonNull @NonNull
private MaterialTimePicker buildTimePicker(@NonNull HoursMinutes time, @NonNull String title, private MaterialTimePicker buildTimePicker(@NonNull HoursMinutes time,
@NonNull String title,
@Nullable String positiveButtonTextOverride, @Nullable String positiveButtonTextOverride,
@Nullable String negativeButtonTextOverride) @Nullable String negativeButtonTextOverride)
{ {
MaterialTimePicker.Builder builder = MaterialTimePicker.Builder builder = new MaterialTimePicker.Builder()
new MaterialTimePicker.Builder() .setTitleText(title)
.setTitleText(title) .setTimeFormat(mIs24HourFormat ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H)
.setTimeFormat(mIs24HourFormat ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H) .setInputMode(mInputMode)
.setInputMode(mInputMode) .setTheme(R.style.MwmMain_MaterialTimePicker)
.setTheme(R.style.MwmTheme_MaterialTimePicker) .setHour((int) time.hours)
.setHour((int) time.hours) .setMinute((int) time.minutes);
.setMinute((int) time.minutes);
if (positiveButtonTextOverride != null) if (positiveButtonTextOverride != null)
builder.setPositiveButtonText(positiveButtonTextOverride); builder.setPositiveButtonText(positiveButtonTextOverride);

View File

@@ -1,10 +1,7 @@
package app.organicmaps.editor; package app.organicmaps.editor;
import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.os.ConfigurationCompat;
import androidx.core.os.LocaleListCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import app.organicmaps.base.BaseMwmRecyclerFragment; import app.organicmaps.base.BaseMwmRecyclerFragment;
import app.organicmaps.sdk.editor.Editor; import app.organicmaps.sdk.editor.Editor;
@@ -14,8 +11,6 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set; import java.util.Set;
public class LanguagesFragment extends BaseMwmRecyclerFragment<LanguagesAdapter> public class LanguagesFragment extends BaseMwmRecyclerFragment<LanguagesAdapter>
@@ -37,28 +32,10 @@ public class LanguagesFragment extends BaseMwmRecyclerFragment<LanguagesAdapter>
Set<String> existingLanguages = Set<String> existingLanguages =
args != null ? new HashSet<>(args.getStringArrayList(EXISTING_LOCALIZED_NAMES)) : new HashSet<>(); args != null ? new HashSet<>(args.getStringArrayList(EXISTING_LOCALIZED_NAMES)) : new HashSet<>();
Configuration config = requireContext().getResources().getConfiguration();
LocaleListCompat systemLocales = ConfigurationCompat.getLocales(config);
List<Language> languages = new ArrayList<>(); List<Language> languages = new ArrayList<>();
List<Language> systemLanguages = new ArrayList<>(systemLocales.size());
for (int i = 0; i < systemLocales.size(); i++)
systemLanguages.add(null);
for (Language lang : Editor.nativeGetSupportedLanguages(false)) for (Language lang : Editor.nativeGetSupportedLanguages(false))
{ {
// Separately extract system languages if (existingLanguages.contains(lang.code))
for (int i = 0; i < systemLocales.size(); i++)
{
Locale locale = systemLocales.get(i);
if (locale != null && locale.getLanguage().equals(lang.code))
{
systemLanguages.add(i, lang);
break;
}
}
if (existingLanguages.contains(lang.code) || systemLanguages.contains(lang))
continue; continue;
languages.add(lang); languages.add(lang);
@@ -66,8 +43,6 @@ public class LanguagesFragment extends BaseMwmRecyclerFragment<LanguagesAdapter>
Collections.sort(languages, Comparator.comparing(lhs -> lhs.name)); Collections.sort(languages, Comparator.comparing(lhs -> lhs.name));
languages.addAll(0, systemLanguages.stream().filter(Objects::nonNull).toList());
return new LanguagesAdapter(this, languages.toArray(new Language[languages.size()])); return new LanguagesAdapter(this, languages.toArray(new Language[languages.size()]));
} }

View File

@@ -6,6 +6,7 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
@@ -22,7 +23,6 @@ import app.organicmaps.util.Utils;
import app.organicmaps.util.WindowInsetUtils; import app.organicmaps.util.WindowInsetUtils;
import app.organicmaps.widget.StackedButtonDialogFragment; import app.organicmaps.widget.StackedButtonDialogFragment;
import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
import java.text.NumberFormat; import java.text.NumberFormat;
@@ -50,7 +50,7 @@ public class ProfileFragment extends BaseMwmToolbarFragment
private MaterialTextView mEditsSent; private MaterialTextView mEditsSent;
private MaterialTextView mProfileName; private MaterialTextView mProfileName;
private ShapeableImageView mProfileImage; private ShapeableImageView mProfileImage;
private CircularProgressIndicator mProfileInfoLoading; private ProgressBar mProfileInfoLoading;
@Nullable @Nullable
@Override @Override

View File

@@ -1,16 +1,16 @@
package app.organicmaps.editor; package app.organicmaps.editor;
import android.content.res.ColorStateList;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import androidx.annotation.IdRes; import androidx.annotation.IdRes;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R; import app.organicmaps.R;
@@ -23,7 +23,6 @@ import app.organicmaps.sdk.util.Utils;
import app.organicmaps.util.UiUtils; import app.organicmaps.util.UiUtils;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.checkbox.MaterialCheckBox; import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -121,14 +120,20 @@ class SimpleTimetableAdapter extends RecyclerView.Adapter<SimpleTimetableAdapter
notifyItemChanged(getItemCount() - 1); notifyItemChanged(getItemCount() - 1);
} }
private void pickTime(int position, @IntRange(from = ID_OPENING_TIME, to = ID_CLOSED_SPAN) int id, private void pickTime(int position,
@IntRange(from = ID_OPENING_TIME, to = ID_CLOSED_SPAN) int id,
boolean startWithToTime) boolean startWithToTime)
{ {
final Timetable data = mItems.get(position); final Timetable data = mItems.get(position);
mPickingPosition = position; mPickingPosition = position;
FromToTimePicker.pickTime(mFragment, this, data.workingTimespan.start, data.workingTimespan.end, id, FromToTimePicker.pickTime(mFragment,
startWithToTime); this,
data.workingTimespan.start,
data.workingTimespan.end,
id,
startWithToTime);
} }
@Override @Override
@@ -188,7 +193,7 @@ class SimpleTimetableAdapter extends RecyclerView.Adapter<SimpleTimetableAdapter
SparseArray<MaterialCheckBox> days = new SparseArray<>(7); SparseArray<MaterialCheckBox> days = new SparseArray<>(7);
View allday; View allday;
MaterialSwitch swAllday; SwitchCompat swAllday;
View schedule; View schedule;
View openClose; View openClose;
View open; View open;
@@ -376,24 +381,6 @@ class SimpleTimetableAdapter extends RecyclerView.Adapter<SimpleTimetableAdapter
final boolean enable = mComplementItem != null && mComplementItem.weekdays.length != 0; final boolean enable = mComplementItem != null && mComplementItem.weekdays.length != 0;
final String text = mFragment.getString(R.string.editor_time_add); final String text = mFragment.getString(R.string.editor_time_add);
mAdd.setEnabled(enable); mAdd.setEnabled(enable);
final ColorStateList bgButtonColor = new ColorStateList(
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {ContextCompat.getColor(mAdd.getContext(), R.color.base_accent),
ContextCompat.getColor(mAdd.getContext(), R.color.button_accent_disabled)});
final ColorStateList textButtonColor = new ColorStateList(
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {
ContextCompat.getColor(mAdd.getContext(), UiUtils.getStyledResourceId(
mAdd.getContext(), android.R.attr.textColorPrimaryInverse)),
ContextCompat.getColor(mAdd.getContext(), R.color.button_accent_text_disabled)});
mAdd.setBackgroundTintList(bgButtonColor);
mAdd.setTextColor(textButtonColor);
mAdd.setText(enable ? text + " (" + TimeFormatUtils.formatWeekdays(mComplementItem) + ")" : text); mAdd.setText(enable ? text + " (" + TimeFormatUtils.formatWeekdays(mComplementItem) + ")" : text);
} }
} }

View File

@@ -9,8 +9,8 @@ import androidx.annotation.Nullable;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.base.BaseMwmRecyclerFragment; import app.organicmaps.base.BaseMwmRecyclerFragment;
public class SimpleTimetableFragment public class SimpleTimetableFragment extends BaseMwmRecyclerFragment<SimpleTimetableAdapter>
extends BaseMwmRecyclerFragment<SimpleTimetableAdapter> implements TimetableProvider implements TimetableProvider
{ {
private SimpleTimetableAdapter mAdapter; private SimpleTimetableAdapter mAdapter;
@Nullable @Nullable

View File

@@ -2,12 +2,14 @@ package app.organicmaps.maplayer;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.adapter.OnItemClickListener; import app.organicmaps.adapter.OnItemClickListener;
import app.organicmaps.sdk.maplayer.Mode; import app.organicmaps.sdk.maplayer.Mode;
import app.organicmaps.util.ThemeUtils;
public class LayerBottomSheetItem public class LayerBottomSheetItem
{ {
@@ -35,29 +37,26 @@ public class LayerBottomSheetItem
@DrawableRes @DrawableRes
int drawableResId = 0; int drawableResId = 0;
@StringRes @StringRes
int buttonTextResource = switch (mode) int buttonTextResource = R.string.layers_title;
switch (mode)
{ {
case OUTDOORS -> case OUTDOORS:
{ drawableResId = R.drawable.ic_layers_outdoors;
drawableResId = R.drawable.ic_layers_outdoors; buttonTextResource = R.string.button_layer_outdoor;
yield R.string.button_layer_outdoor; break;
} case SUBWAY:
case SUBWAY -> drawableResId = R.drawable.ic_layers_subway;
{ buttonTextResource = R.string.subway;
drawableResId = R.drawable.ic_layers_subway; break;
yield R.string.subway; case ISOLINES:
} drawableResId = R.drawable.ic_layers_isoline;
case ISOLINES -> buttonTextResource = R.string.button_layer_isolines;
{ break;
drawableResId = R.drawable.ic_layers_isoline; case TRAFFIC:
yield R.string.button_layer_isolines; drawableResId = R.drawable.ic_layers_traffic;
} buttonTextResource = R.string.button_layer_traffic;
case TRAFFIC -> break;
{ }
drawableResId = R.drawable.ic_layers_traffic;
yield R.string.button_layer_traffic;
}
};
return new LayerBottomSheetItem(drawableResId, buttonTextResource, mode, layerItemClickListener); return new LayerBottomSheetItem(drawableResId, buttonTextResource, mode, layerItemClickListener);
} }

View File

@@ -5,9 +5,11 @@ import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.adapter.OnItemClickListener; import app.organicmaps.adapter.OnItemClickListener;
import com.google.android.material.textview.MaterialTextView;
class LayerHolder extends RecyclerView.ViewHolder class LayerHolder extends RecyclerView.ViewHolder
{ {

View File

@@ -395,7 +395,7 @@ public class MapButtonsController extends Fragment
0; 0;
// Allow offset tolerance for zoom buttons // Allow offset tolerance for zoom buttons
}; };
showButton(getViewTopOffset(translation, button) >= toleranceOffset, entry.getKey()); showButton(getViewTopOffset(translation, button) >= toleranceOffset, entry.getKey());
} }
} }
} }

View File

@@ -43,6 +43,7 @@ import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils; import app.organicmaps.util.Utils;
import app.organicmaps.widget.recycler.DotDividerItemDecoration; import app.organicmaps.widget.recycler.DotDividerItemDecoration;
import app.organicmaps.widget.recycler.MultilineLayoutManager; import app.organicmaps.widget.recycler.MultilineLayoutManager;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
@@ -122,9 +123,9 @@ final class RoutingBottomMenuController implements View.OnClickListener
@NonNull View timeElevationLine, @NonNull View transitFrame, @NonNull View timeElevationLine, @NonNull View transitFrame,
@NonNull MaterialTextView error, @NonNull MaterialButton start, @NonNull MaterialTextView error, @NonNull MaterialButton start,
@NonNull ShapeableImageView altitudeChart, @NonNull MaterialTextView time, @NonNull ShapeableImageView altitudeChart, @NonNull MaterialTextView time,
@NonNull MaterialTextView altitudeDifference, @NonNull MaterialTextView altitudeDifference, @NonNull MaterialTextView timeVehicle,
@NonNull MaterialTextView timeVehicle, @Nullable MaterialTextView arrival, @Nullable MaterialTextView arrival, @NonNull View actionFrame,
@NonNull View actionFrame, @Nullable RoutingBottomMenuListener listener) @Nullable RoutingBottomMenuListener listener)
{ {
mContext = context; mContext = context;
mAltitudeChartFrame = altitudeChartFrame; mAltitudeChartFrame = altitudeChartFrame;

View File

@@ -12,6 +12,9 @@ import androidx.annotation.IdRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.MwmApplication; import app.organicmaps.MwmApplication;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.Framework;
@@ -26,7 +29,6 @@ import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener;
import app.organicmaps.widget.RoutingToolbarButton; import app.organicmaps.widget.RoutingToolbarButton;
import app.organicmaps.widget.ToolbarController; import app.organicmaps.widget.ToolbarController;
import app.organicmaps.widget.WheelProgressView; import app.organicmaps.widget.WheelProgressView;
import com.google.android.material.textview.MaterialTextView;
public class RoutingPlanController extends ToolbarController public class RoutingPlanController extends ToolbarController
{ {
@@ -262,7 +264,7 @@ public class RoutingPlanController extends ToolbarController
default -> throw new IllegalArgumentException("unknown router: " + router); default -> throw new IllegalArgumentException("unknown router: " + router);
}; };
RoutingToolbarButton button = mRouterTypes.findViewById(mRouterTypes.getCheckedRadioButtonId()); RoutingToolbarButton button = mRouterTypes.findViewById(mRouterTypes.getCheckedRadioButtonId());
button.progress(); button.progress();
updateProgressLabels(); updateProgressLabels();

View File

@@ -14,10 +14,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.search.DisplayedCategories; import app.organicmaps.sdk.search.DisplayedCategories;
import app.organicmaps.sdk.util.Language; import app.organicmaps.sdk.util.Language;
import com.google.android.material.textview.MaterialTextView;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Locale; import java.util.Locale;

View File

@@ -5,8 +5,10 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import app.organicmaps.base.BaseMwmFragmentActivity; import app.organicmaps.base.BaseMwmFragmentActivity;
import app.organicmaps.util.ThemeUtils;
public class SearchActivity extends BaseMwmFragmentActivity public class SearchActivity extends BaseMwmFragmentActivity
{ {

View File

@@ -10,18 +10,17 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.search.SearchResult; import app.organicmaps.sdk.search.SearchResult;
import app.organicmaps.util.Graphics; import app.organicmaps.util.Graphics;
import app.organicmaps.util.ThemeUtils; import app.organicmaps.util.ThemeUtils;
import app.organicmaps.util.UiUtils; import app.organicmaps.util.UiUtils;
import com.google.android.material.textview.MaterialTextView;
class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHolder> class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHolder>
{ {
private static final int SHORT_HORIZON_CLOSE_MIN = 60;
private static final int SHORT_HORIZON_OPEN_MIN = 15;
private final SearchFragment mSearchFragment; private final SearchFragment mSearchFragment;
@Nullable @Nullable
private SearchResult[] mResults; private SearchResult[] mResults;
@@ -150,36 +149,41 @@ class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHol
{ {
final Resources resources = mSearchFragment.getResources(); final Resources resources = mSearchFragment.getResources();
if (result.description.openNow != SearchResult.OPEN_NOW_YES switch (result.description.openNow)
&& result.description.openNow != SearchResult.OPEN_NOW_NO)
{ {
// Hide if unknown opening hours state case SearchResult.OPEN_NOW_YES ->
UiUtils.hide(mOpen); {
return; if (result.description.minutesUntilClosed < 60) // less than 1 hour
{
final String time = result.description.minutesUntilClosed + " " + resources.getString(R.string.minute);
final String string = resources.getString(R.string.closes_in, time);
UiUtils.setTextAndShow(mOpen, string);
mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_yellow));
}
else
{
UiUtils.setTextAndShow(mOpen, resources.getString(R.string.editor_time_open));
mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_green));
}
} }
case SearchResult.OPEN_NOW_NO ->
final boolean isOpen = result.description.openNow == SearchResult.OPEN_NOW_YES;
final int minsToNextState = isOpen ? result.description.minutesUntilClosed : result.description.minutesUntilOpen;
final boolean shortHorizonClosing = isOpen && minsToNextState >= 0 && minsToNextState <= SHORT_HORIZON_CLOSE_MIN;
final boolean shortHorizonOpening = !isOpen && minsToNextState >= 0 && minsToNextState <= SHORT_HORIZON_OPEN_MIN;
if (shortHorizonClosing || shortHorizonOpening)
{ {
final String minsToChangeStr = resources.getQuantityString( if (result.description.minutesUntilOpen < 60) // less than 1 hour
R.plurals.minutes_short, Math.max(minsToNextState, 1), Math.max(minsToNextState, 1)); {
final String nextChangeFormatted = final String time = result.description.minutesUntilOpen + " " + resources.getString(R.string.minute);
resources.getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr); final String string = resources.getString(R.string.opens_in, time);
UiUtils.setTextAndShow(mOpen, nextChangeFormatted); UiUtils.setTextAndShow(mOpen, string);
mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_yellow)); mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_red));
}
else
{
UiUtils.setTextAndShow(mOpen, resources.getString(R.string.closed));
mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_red));
}
} }
else default -> UiUtils.hide(mOpen);
{
UiUtils.setTextAndShow(
mOpen, isOpen ? resources.getString(R.string.editor_time_open) : resources.getString(R.string.closed));
mOpen.setTextColor(
ContextCompat.getColor(mSearchFragment.getContext(), isOpen ? R.color.base_green : R.color.base_red));
} }
} }

View File

@@ -273,8 +273,7 @@ public class SearchFragment extends BaseMwmFragment implements SearchListener, C
RecyclerView mResults = mResultsFrame.findViewById(R.id.recycler); RecyclerView mResults = mResultsFrame.findViewById(R.id.recycler);
setRecyclerScrollListener(mResults); setRecyclerScrollListener(mResults);
mResultsPlaceholder = mResultsFrame.findViewById(R.id.placeholder); mResultsPlaceholder = mResultsFrame.findViewById(R.id.placeholder);
mResultsPlaceholder.setContent(R.string.search_not_found, R.string.search_not_found_query, mResultsPlaceholder.setContent(R.string.search_not_found, R.string.search_not_found_query, R.drawable.ic_search_fail);
R.drawable.ic_search_fail);
mSearchAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() mSearchAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver()
{ {

View File

@@ -5,13 +5,15 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.MwmApplication; import app.organicmaps.MwmApplication;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.routing.RoutingController;
import app.organicmaps.sdk.search.SearchRecents; import app.organicmaps.sdk.search.SearchRecents;
import app.organicmaps.util.Graphics; import app.organicmaps.util.Graphics;
import app.organicmaps.widget.SearchToolbarController; import app.organicmaps.widget.SearchToolbarController;
import com.google.android.material.textview.MaterialTextView;
class SearchHistoryAdapter extends RecyclerView.Adapter<SearchHistoryAdapter.ViewHolder> class SearchHistoryAdapter extends RecyclerView.Adapter<SearchHistoryAdapter.ViewHolder>
{ {

View File

@@ -8,12 +8,12 @@ import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.SwitchCompat;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.base.BaseMwmToolbarFragment; import app.organicmaps.base.BaseMwmToolbarFragment;
import app.organicmaps.sdk.routing.RoutingController; import app.organicmaps.sdk.routing.RoutingController;
import app.organicmaps.sdk.routing.RoutingOptions; import app.organicmaps.sdk.routing.RoutingOptions;
import app.organicmaps.sdk.settings.RoadType; import app.organicmaps.sdk.settings.RoadType;
import com.google.android.material.materialswitch.MaterialSwitch;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@@ -88,38 +88,30 @@ public class DrivingOptionsFragment extends BaseMwmToolbarFragment
private void initViews(@NonNull View root) private void initViews(@NonNull View root)
{ {
MaterialSwitch tollsBtn = root.findViewById(R.id.avoid_tolls_btn); SwitchCompat tollsBtn = root.findViewById(R.id.avoid_tolls_btn);
tollsBtn.setChecked(RoutingOptions.hasOption(RoadType.Toll)); tollsBtn.setChecked(RoutingOptions.hasOption(RoadType.Toll));
CompoundButton.OnCheckedChangeListener tollBtnListener = new ToggleRoutingOptionListener(RoadType.Toll, root); CompoundButton.OnCheckedChangeListener tollBtnListener = new ToggleRoutingOptionListener(RoadType.Toll);
tollsBtn.setOnCheckedChangeListener(tollBtnListener); tollsBtn.setOnCheckedChangeListener(tollBtnListener);
MaterialSwitch motorwaysBtn = root.findViewById(R.id.avoid_motorways_btn); SwitchCompat motorwaysBtn = root.findViewById(R.id.avoid_motorways_btn);
motorwaysBtn.setChecked(RoutingOptions.hasOption(RoadType.Motorway)); motorwaysBtn.setChecked(RoutingOptions.hasOption(RoadType.Motorway));
CompoundButton.OnCheckedChangeListener motorwayBtnListener = CompoundButton.OnCheckedChangeListener motorwayBtnListener = new ToggleRoutingOptionListener(RoadType.Motorway);
new ToggleRoutingOptionListener(RoadType.Motorway, root);
motorwaysBtn.setOnCheckedChangeListener(motorwayBtnListener); motorwaysBtn.setOnCheckedChangeListener(motorwayBtnListener);
MaterialSwitch ferriesBtn = root.findViewById(R.id.avoid_ferries_btn); SwitchCompat ferriesBtn = root.findViewById(R.id.avoid_ferries_btn);
ferriesBtn.setChecked(RoutingOptions.hasOption(RoadType.Ferry)); ferriesBtn.setChecked(RoutingOptions.hasOption(RoadType.Ferry));
CompoundButton.OnCheckedChangeListener ferryBtnListener = new ToggleRoutingOptionListener(RoadType.Ferry, root); CompoundButton.OnCheckedChangeListener ferryBtnListener = new ToggleRoutingOptionListener(RoadType.Ferry);
ferriesBtn.setOnCheckedChangeListener(ferryBtnListener); ferriesBtn.setOnCheckedChangeListener(ferryBtnListener);
MaterialSwitch dirtyRoadsBtn = root.findViewById(R.id.avoid_dirty_roads_btn); SwitchCompat dirtyRoadsBtn = root.findViewById(R.id.avoid_dirty_roads_btn);
dirtyRoadsBtn.setChecked(RoutingOptions.hasOption(RoadType.Dirty)); dirtyRoadsBtn.setChecked(RoutingOptions.hasOption(RoadType.Dirty));
dirtyRoadsBtn.setEnabled(!RoutingOptions.hasOption(RoadType.Paved) || RoutingOptions.hasOption(RoadType.Dirty)); CompoundButton.OnCheckedChangeListener dirtyBtnListener = new ToggleRoutingOptionListener(RoadType.Dirty);
CompoundButton.OnCheckedChangeListener dirtyBtnListener = new ToggleRoutingOptionListener(RoadType.Dirty, root);
dirtyRoadsBtn.setOnCheckedChangeListener(dirtyBtnListener); dirtyRoadsBtn.setOnCheckedChangeListener(dirtyBtnListener);
MaterialSwitch stepsBtn = root.findViewById(R.id.avoid_steps_btn); SwitchCompat stepsBtn = root.findViewById(R.id.avoid_steps_btn);
stepsBtn.setChecked(RoutingOptions.hasOption(RoadType.Steps)); stepsBtn.setChecked(RoutingOptions.hasOption(RoadType.Steps));
CompoundButton.OnCheckedChangeListener stepsBtnListener = new ToggleRoutingOptionListener(RoadType.Steps, root); CompoundButton.OnCheckedChangeListener stepsBtnListener = new ToggleRoutingOptionListener(RoadType.Steps);
stepsBtn.setOnCheckedChangeListener(stepsBtnListener); stepsBtn.setOnCheckedChangeListener(stepsBtnListener);
MaterialSwitch pavedBtn = root.findViewById(R.id.avoid_paved_roads_btn);
pavedBtn.setChecked(RoutingOptions.hasOption(RoadType.Paved));
pavedBtn.setEnabled(!RoutingOptions.hasOption(RoadType.Dirty) || RoutingOptions.hasOption(RoadType.Paved));
CompoundButton.OnCheckedChangeListener pavedBtnListener = new ToggleRoutingOptionListener(RoadType.Paved, root);
pavedBtn.setOnCheckedChangeListener(pavedBtnListener);
} }
private static class ToggleRoutingOptionListener implements CompoundButton.OnCheckedChangeListener private static class ToggleRoutingOptionListener implements CompoundButton.OnCheckedChangeListener
@@ -127,13 +119,9 @@ public class DrivingOptionsFragment extends BaseMwmToolbarFragment
@NonNull @NonNull
private final RoadType mRoadType; private final RoadType mRoadType;
@NonNull private ToggleRoutingOptionListener(@NonNull RoadType roadType)
private final View mRoot;
private ToggleRoutingOptionListener(@NonNull RoadType roadType, @NonNull View root)
{ {
mRoadType = roadType; mRoadType = roadType;
mRoot = root;
} }
@Override @Override
@@ -143,27 +131,6 @@ public class DrivingOptionsFragment extends BaseMwmToolbarFragment
RoutingOptions.addOption(mRoadType); RoutingOptions.addOption(mRoadType);
else else
RoutingOptions.removeOption(mRoadType); RoutingOptions.removeOption(mRoadType);
MaterialSwitch dirtyRoadsBtn = mRoot.findViewById(R.id.avoid_dirty_roads_btn);
MaterialSwitch pavedBtn = mRoot.findViewById(R.id.avoid_paved_roads_btn);
if (mRoadType == RoadType.Dirty)
{
pavedBtn.setEnabled(!isChecked);
if (isChecked)
{
pavedBtn.setChecked(false);
dirtyRoadsBtn.setEnabled(true);
}
}
else if (mRoadType == RoadType.Paved)
{
dirtyRoadsBtn.setEnabled(!isChecked);
if (isChecked)
{
dirtyRoadsBtn.setChecked(false);
pavedBtn.setEnabled(true);
}
}
} }
} }
} }

View File

@@ -0,0 +1,25 @@
package app.organicmaps.util;
import android.app.Activity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.text.TextUtilsCompat;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public class RtlUtils
{
private final static List<String> rtlLocalesWithTranslation = Arrays.asList("ar", "fa");
public static void manageRtl(@NonNull final Activity activity)
{
final String currentLanguage = Locale.getDefault().getLanguage();
final boolean isRTL =
TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
if (isRTL && rtlLocalesWithTranslation.contains(currentLanguage))
activity.getWindow().getDecorView().setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
else
activity.getWindow().getDecorView().setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
}
}

View File

@@ -90,7 +90,8 @@ public enum ThemeSwitcher
{ {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES); uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); else
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
if (RoutingController.get().isVehicleNavigation()) if (RoutingController.get().isVehicleNavigation())
style = MapStyle.VehicleDark; style = MapStyle.VehicleDark;
@@ -103,7 +104,8 @@ public enum ThemeSwitcher
{ {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO); uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); else
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
if (RoutingController.get().isVehicleNavigation()) if (RoutingController.get().isVehicleNavigation())
style = MapStyle.VehicleClear; style = MapStyle.VehicleClear;

View File

@@ -8,165 +8,165 @@ import android.graphics.Typeface;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
public abstract class BaseSignView extends View public abstract class BaseSignView extends View
{ {
private float mBorderWidthRatio = 0.1f; private float mBorderWidthRatio = 0.1f;
protected void setBorderWidthRatio(float ratio) protected void setBorderWidthRatio(float ratio) {
{ mBorderWidthRatio = ratio;
mBorderWidthRatio = ratio;
}
private float mBorderInsetRatio = 0f;
protected void setBorderInsetRatio(float ratio)
{
mBorderInsetRatio = ratio;
}
// colors
protected int mBackgroundColor;
protected int mBorderColor;
protected int mAlertColor;
protected int mTextColor;
protected int mTextAlertColor;
// paints
protected final Paint mBackgroundPaint;
protected final Paint mBorderPaint;
protected final Paint mTextPaint;
// geometry
protected float mWidth;
protected float mHeight;
protected float mRadius;
protected float mBorderWidth;
protected float mBorderRadius;
public BaseSignView(Context ctx, @Nullable AttributeSet attrs)
{
super(ctx, attrs);
mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint.setStyle(Paint.Style.STROKE);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
}
protected void setColors(int backgroundColor, int borderColor, int alertColor, int textColor, int textAlertColor)
{
mBackgroundColor = backgroundColor;
mBorderColor = borderColor;
mAlertColor = alertColor;
mTextColor = textColor;
mTextAlertColor = textAlertColor;
mBackgroundPaint.setColor(mBackgroundColor);
mBorderPaint.setColor(mBorderColor);
mTextPaint.setColor(mTextColor);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
super.onSizeChanged(width, height, oldWidth, oldHeight);
final float paddingX = getPaddingLeft() + getPaddingRight();
final float paddingY = getPaddingTop() + getPaddingBottom();
mWidth = width - paddingX;
mHeight = height - paddingY;
mRadius = Math.min(mWidth, mHeight) / 2f;
mBorderWidth = mRadius * mBorderWidthRatio;
// subtract half the stroke PLUS the extra inset
final float gap = mRadius * mBorderInsetRatio;
mBorderRadius = mRadius - (mBorderWidth / 2f) - gap;
configureTextSize();
}
@Override
protected void onDraw(@NonNull Canvas canvas)
{
super.onDraw(canvas);
final String str = getValueString();
if (str == null)
return;
final float cx = mWidth / 2f;
final float cy = mHeight / 2f;
// background & border
boolean alert = isAlert();
mBackgroundPaint.setColor(alert ? mAlertColor : mBackgroundColor);
canvas.drawCircle(cx, cy, mRadius, mBackgroundPaint);
if (!alert)
{
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(mBorderColor);
canvas.drawCircle(cx, cy, mBorderRadius, mBorderPaint);
} }
// text private float mBorderInsetRatio = 0f;
mTextPaint.setColor(alert ? mTextAlertColor : mTextColor); protected void setBorderInsetRatio(float ratio) {
drawValueString(canvas, cx, cy, str); mBorderInsetRatio = ratio;
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent e)
{
final float cx = mWidth / 2f, cy = mHeight / 2f;
final float dx = e.getX() - cx, dy = e.getY() - cy;
if ((dx * dx) + (dy * dy) <= (mRadius * mRadius))
{
performClick();
return true;
} }
return false;
}
@Override // colors
public boolean performClick() protected int mBackgroundColor;
{ protected int mBorderColor;
super.performClick(); protected int mAlertColor;
return false; protected int mTextColor;
} protected int mTextAlertColor;
private void drawValueString(Canvas c, float cx, float cy, String str) // paints
{ protected final Paint mBackgroundPaint;
Rect b = new Rect(); protected final Paint mBorderPaint;
mTextPaint.getTextBounds(str, 0, str.length(), b); protected final Paint mTextPaint;
final float y = cy - b.exactCenterY();
c.drawText(str, cx, y, mTextPaint);
}
void configureTextSize() // geometry
{ protected float mWidth;
String text = getValueString(); protected float mHeight;
if (text == null) protected float mRadius;
return; protected float mBorderWidth;
final float textRadius = mBorderRadius - mBorderWidth; protected float mBorderRadius;
final float maxTextSize = 2f * textRadius;
final float maxTextSize2 = maxTextSize * maxTextSize; public BaseSignView(Context ctx, @Nullable AttributeSet attrs)
float lo = 0f, hi = maxTextSize, sz = maxTextSize;
Rect b = new Rect();
while (lo <= hi)
{ {
sz = (lo + hi) / 2f; super(ctx, attrs);
mTextPaint.setTextSize(sz); mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.getTextBounds(text, 0, text.length(), b); mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
float area = b.width() * b.width() + b.height() * b.height(); mBorderPaint.setStyle(Paint.Style.STROKE);
if (area <= maxTextSize2) mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
lo = sz + 1f; mTextPaint.setTextAlign(Paint.Align.CENTER);
else mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
hi = sz - 1f;
} }
mTextPaint.setTextSize(Math.max(1f, sz));
}
/** child must return the string to draw, or null if nothing */ protected void setColors(int backgroundColor,
@Nullable int borderColor,
protected abstract String getValueString(); int alertColor,
int textColor,
int textAlertColor)
{
mBackgroundColor = backgroundColor;
mBorderColor = borderColor;
mAlertColor = alertColor;
mTextColor = textColor;
mTextAlertColor = textAlertColor;
/** child decides if this is in “alert” state */ mBackgroundPaint.setColor(mBackgroundColor);
protected abstract boolean isAlert(); mBorderPaint.setColor(mBorderColor);
mTextPaint.setColor(mTextColor);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
final float paddingX = getPaddingLeft() + getPaddingRight();
final float paddingY = getPaddingTop() + getPaddingBottom();
mWidth = width - paddingX;
mHeight = height - paddingY;
mRadius = Math.min(mWidth, mHeight) / 2f;
mBorderWidth = mRadius * mBorderWidthRatio;
// subtract half the stroke PLUS the extra inset
final float gap = mRadius * mBorderInsetRatio;
mBorderRadius = mRadius - (mBorderWidth / 2f) - gap;
configureTextSize();
}
@Override
protected void onDraw(@NonNull Canvas canvas)
{
super.onDraw(canvas);
final String str = getValueString();
if (str == null) return;
final float cx = mWidth / 2f;
final float cy = mHeight / 2f;
// background & border
boolean alert = isAlert();
mBackgroundPaint.setColor(alert ? mAlertColor : mBackgroundColor);
canvas.drawCircle(cx, cy, mRadius, mBackgroundPaint);
if (!alert)
{
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(mBorderColor);
canvas.drawCircle(cx, cy, mBorderRadius, mBorderPaint);
}
// text
mTextPaint.setColor(alert ? mTextAlertColor : mTextColor);
drawValueString(canvas, cx, cy, str);
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent e)
{
final float cx = mWidth / 2f, cy = mHeight / 2f;
final float dx = e.getX() - cx, dy = e.getY() - cy;
if ((dx * dx) + (dy * dy) <= (mRadius * mRadius))
{
performClick();
return true;
}
return false;
}
@Override
public boolean performClick()
{
super.performClick();
return false;
}
private void drawValueString(Canvas c, float cx, float cy, String str)
{
Rect b = new Rect();
mTextPaint.getTextBounds(str, 0, str.length(), b);
final float y = cy - b.exactCenterY();
c.drawText(str, cx, y, mTextPaint);
}
void configureTextSize()
{
String text = getValueString();
if (text == null) return;
final float textRadius = mBorderRadius - mBorderWidth;
final float maxTextSize = 2f * textRadius;
final float maxTextSize2 = maxTextSize * maxTextSize;
float lo = 0f, hi = maxTextSize, sz = maxTextSize;
Rect b = new Rect();
while (lo <= hi)
{
sz = (lo + hi) / 2f;
mTextPaint.setTextSize(sz);
mTextPaint.getTextBounds(text, 0, text.length(), b);
float area = b.width()*b.width() + b.height()*b.height();
if (area <= maxTextSize2)
lo = sz + 1f;
else
hi = sz - 1f;
}
mTextPaint.setTextSize(Math.max(1f, sz));
}
/** child must return the string to draw, or null if nothing */
@Nullable
protected abstract String getValueString();
/** child decides if this is in “alert” state */
protected abstract boolean isAlert();
} }

View File

@@ -4,7 +4,9 @@ import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.util.StringUtils; import app.organicmaps.sdk.util.StringUtils;
@@ -20,18 +22,18 @@ public class CurrentSpeedView extends BaseSignView
setBorderWidthRatio(0.1f); setBorderWidthRatio(0.1f);
setBorderInsetRatio(0.05f); setBorderInsetRatio(0.05f);
try (TypedArray a = ctx.getTheme().obtainStyledAttributes( try (TypedArray a = ctx.getTheme()
attrs, R.styleable.CurrentSpeedView /* reuse same attrs or define new */, 0, 0)) .obtainStyledAttributes(attrs, R.styleable.CurrentSpeedView /* reuse same attrs or define new */ , 0, 0))
{ {
int bg = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBackgroundColor, DefaultValues.BACKGROUND_COLOR); int bg = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBackgroundColor, DefaultValues.BACKGROUND_COLOR);
int bd = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBorderColor, DefaultValues.BORDER_COLOR); int bd = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBorderColor, DefaultValues.BORDER_COLOR);
int tc = a.getColor(R.styleable.CurrentSpeedView_currentSpeedTextColor, DefaultValues.TEXT_COLOR); int tc = a.getColor(R.styleable.CurrentSpeedView_currentSpeedTextColor, DefaultValues.TEXT_COLOR);
setColors(bg, bd, 0, tc, 0); setColors(bg, bd, 0, tc, 0);
if (isInEditMode()) if (isInEditMode())
{ {
mSpeedMps = a.getInt(R.styleable.CurrentSpeedView_currentSpeedEditModeCurrentSpeed, 50); mSpeedMps = a.getInt(R.styleable.CurrentSpeedView_currentSpeedEditModeCurrentSpeed, 50);
mSpeedStr = Integer.toString((int) mSpeedMps); mSpeedStr = Integer.toString((int)mSpeedMps);
} }
} }
} }
@@ -45,7 +47,7 @@ public class CurrentSpeedView extends BaseSignView
} }
else else
{ {
Pair<String, String> su = StringUtils.nativeFormatSpeedAndUnits(mps); Pair<String,String> su = StringUtils.nativeFormatSpeedAndUnits(mps);
mSpeedStr = su.first; mSpeedStr = su.first;
} }
requestLayout(); requestLayout();
@@ -68,8 +70,8 @@ public class CurrentSpeedView extends BaseSignView
private interface DefaultValues private interface DefaultValues
{ {
int BACKGROUND_COLOR = 0xFFFFFFFF; int BACKGROUND_COLOR = 0xFFFFFFFF;
int BORDER_COLOR = 0xFF000000; int BORDER_COLOR = 0xFF000000;
int TEXT_COLOR = 0xFF000000; int TEXT_COLOR = 0xFF000000;
} }
} }

View File

@@ -3,6 +3,7 @@ package app.organicmaps.widget;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -12,11 +13,13 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import app.organicmaps.R;
import app.organicmaps.util.UiUtils;
import com.google.android.material.imageview.ShapeableImageView; import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R;
import app.organicmaps.util.UiUtils;
public class PlaceholderView extends LinearLayout public class PlaceholderView extends LinearLayout
{ {
@SuppressWarnings("NullableProblems") @SuppressWarnings("NullableProblems")

View File

@@ -7,6 +7,7 @@ import androidx.annotation.DrawableRes;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatRadioButton; import androidx.appcompat.widget.AppCompatRadioButton;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.util.ThemeUtils;
public class RoutingToolbarButton extends AppCompatRadioButton public class RoutingToolbarButton extends AppCompatRadioButton
{ {

View File

@@ -5,14 +5,16 @@ import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.util.AttributeSet; import android.util.AttributeSet;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.organicmaps.R; import app.organicmaps.R;
public class SpeedLimitView extends BaseSignView public class SpeedLimitView extends BaseSignView
{ {
private int mSpeedLimit = -1; private int mSpeedLimit = -1;
private boolean mAlert = false; private boolean mAlert = false;
private String mSpeedStr = "-1"; private String mSpeedStr = "-1";
private final int unlimitedBorderColor; private final int unlimitedBorderColor;
private final int unlimitedStripeColor; private final int unlimitedStripeColor;
@@ -25,22 +27,15 @@ public class SpeedLimitView extends BaseSignView
try (TypedArray styleAttrs = ctx.getTheme().obtainStyledAttributes(attrs, R.styleable.SpeedLimitView, 0, 0)) try (TypedArray styleAttrs = ctx.getTheme().obtainStyledAttributes(attrs, R.styleable.SpeedLimitView, 0, 0))
{ {
final int bgColor = final int bgColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBackgroundColor, DefaultValues.BACKGROUND_COLOR);
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBackgroundColor, DefaultValues.BACKGROUND_COLOR); final int borderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBorderColor, DefaultValues.BORDER_COLOR);
final int borderColor = final int alertColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitAlertColor, DefaultValues.ALERT_COLOR);
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBorderColor, DefaultValues.BORDER_COLOR); final int textColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextColor, DefaultValues.TEXT_COLOR);
final int alertColor = final int txtAlertColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextAlertColor, DefaultValues.TEXT_ALERT_COLOR);
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitAlertColor, DefaultValues.ALERT_COLOR);
final int textColor =
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextColor, DefaultValues.TEXT_COLOR);
final int txtAlertColor =
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextAlertColor, DefaultValues.TEXT_ALERT_COLOR);
setColors(bgColor, borderColor, alertColor, textColor, txtAlertColor); setColors(bgColor, borderColor, alertColor, textColor, txtAlertColor);
unlimitedBorderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedBorderColor, unlimitedBorderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedBorderColor, DefaultValues.UNLIMITED_BORDER_COLOR);
DefaultValues.UNLIMITED_BORDER_COLOR); unlimitedStripeColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedStripeColor, DefaultValues.UNLIMITED_STRIPE_COLOR);
unlimitedStripeColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedStripeColor,
DefaultValues.UNLIMITED_STRIPE_COLOR);
if (isInEditMode()) if (isInEditMode())
{ {
@@ -56,7 +51,7 @@ public class SpeedLimitView extends BaseSignView
if (mSpeedLimit != limit) if (mSpeedLimit != limit)
{ {
mSpeedLimit = limit; mSpeedLimit = limit;
mSpeedStr = Integer.toString(limit); mSpeedStr = Integer.toString(limit);
requestLayout(); requestLayout();
} }
mAlert = alert; mAlert = alert;
@@ -80,7 +75,7 @@ public class SpeedLimitView extends BaseSignView
@Override @Override
protected void onDraw(Canvas canvas) protected void onDraw(Canvas canvas)
{ {
final float cx = mWidth / 2f, cy = mHeight / 2f; final float cx = mWidth/2f, cy = mHeight/2f;
if (mSpeedLimit == 0) // 0 means unlimited speed (maxspeed=none) if (mSpeedLimit == 0) // 0 means unlimited speed (maxspeed=none)
{ {
@@ -110,7 +105,7 @@ public class SpeedLimitView extends BaseSignView
stripe.setStrokeWidth(mBorderWidth * 0.4f); stripe.setStrokeWidth(mBorderWidth * 0.4f);
final float radius = mRadius * 0.8f; // Shorten to 80% of full radius final float radius = mRadius * 0.8f; // Shorten to 80% of full radius
final float diag = (float) (1 / Math.sqrt(2)); // 45 degrees final float diag = (float) (1/Math.sqrt(2)); // 45 degrees
final float dx = -diag, dy = +diag; final float dx = -diag, dy = +diag;
final float px = -dy, py = +dx; // Perpendicular final float px = -dy, py = +dx; // Perpendicular
final float step = radius * 0.15f; // Spacing final float step = radius * 0.15f; // Spacing
@@ -127,13 +122,14 @@ public class SpeedLimitView extends BaseSignView
} }
} }
private interface DefaultValues private interface DefaultValues
{ {
int BACKGROUND_COLOR = 0xFFFFFFFF; int BACKGROUND_COLOR = 0xFFFFFFFF;
int BORDER_COLOR = 0xFFFF0000; int BORDER_COLOR = 0xFFFF0000;
int ALERT_COLOR = 0xFFFF0000; int ALERT_COLOR = 0xFFFF0000;
int TEXT_COLOR = 0xFF000000; int TEXT_COLOR = 0xFF000000;
int TEXT_ALERT_COLOR = 0xFFFFFFFF; int TEXT_ALERT_COLOR = 0xFFFFFFFF;
int UNLIMITED_BORDER_COLOR = 0xFF000000; int UNLIMITED_BORDER_COLOR = 0xFF000000;
int UNLIMITED_STRIPE_COLOR = 0xFF000000; int UNLIMITED_STRIPE_COLOR = 0xFF000000;
} }

View File

@@ -67,12 +67,12 @@ public class MyPositionButton
{ {
case LocationState.PENDING_POSITION -> R.drawable.ic_menu_location_pending; case LocationState.PENDING_POSITION -> R.drawable.ic_menu_location_pending;
case LocationState.NOT_FOLLOW_NO_POSITION -> R.drawable.ic_location_off; case LocationState.NOT_FOLLOW_NO_POSITION -> R.drawable.ic_location_off;
case LocationState.NOT_FOLLOW -> R.drawable.ic_location_crosshair; case LocationState.NOT_FOLLOW -> R.drawable.ic_not_follow;
case LocationState.FOLLOW -> R.drawable.ic_follow; case LocationState.FOLLOW -> R.drawable.ic_follow;
case LocationState.FOLLOW_AND_ROTATE -> R.drawable.ic_follow_and_rotate; case LocationState.FOLLOW_AND_ROTATE -> R.drawable.ic_follow_and_rotate;
default -> throw new IllegalArgumentException("Invalid button mode: " + mode); default -> throw new IllegalArgumentException("Invalid button mode: " + mode);
}; };
image = ResourcesCompat.getDrawable(resources, drawableRes, context.getTheme()); image = ResourcesCompat.getDrawable(resources, drawableRes, context.getTheme());
mIcons.put(mode, image); mIcons.put(mode, image);
} }

View File

@@ -1,9 +1,9 @@
package app.organicmaps.widget.placepage; package app.organicmaps.widget.placepage;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.organicmaps.sdk.util.StringUtils; import app.organicmaps.sdk.util.StringUtils;
import com.github.mikephil.charting.charts.BarLineChartBase; import com.github.mikephil.charting.charts.BarLineChartBase;
import androidx.annotation.Nullable;
import com.github.mikephil.charting.components.AxisBase; import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.formatter.IAxisValueFormatter; import com.github.mikephil.charting.formatter.IAxisValueFormatter;

View File

@@ -105,8 +105,7 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View.
public EditBookmarkFragment() {} public EditBookmarkFragment() {}
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) public void onCreate(@Nullable Bundle savedInstanceState) {
{
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.MwmTheme_FullScreenDialog); setStyle(DialogFragment.STYLE_NORMAL, R.style.MwmTheme_FullScreenDialog);
} }
@@ -185,9 +184,10 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View.
{ {
super.onStart(); super.onStart();
Dialog dialog = getDialog(); Dialog dialog = getDialog();
if (dialog != null) if (dialog != null) {
{ dialog.getWindow().setLayout(
dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
} }
// Focus name and show keyboard for "Unknown Place" bookmarks // Focus name and show keyboard for "Unknown Place" bookmarks

View File

@@ -6,6 +6,9 @@ import android.widget.RelativeLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.widget.NestedScrollView; import androidx.core.widget.NestedScrollView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.ChartController; import app.organicmaps.ChartController;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.Framework;
@@ -14,7 +17,6 @@ import app.organicmaps.sdk.bookmarks.data.Track;
import app.organicmaps.sdk.bookmarks.data.TrackStatistics; import app.organicmaps.sdk.bookmarks.data.TrackStatistics;
import app.organicmaps.util.UiUtils; import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils; import app.organicmaps.util.Utils;
import com.google.android.material.textview.MaterialTextView;
import java.util.Objects; import java.util.Objects;
public class ElevationProfileViewRenderer implements PlacePageStateListener public class ElevationProfileViewRenderer implements PlacePageStateListener

View File

@@ -1,42 +0,0 @@
package app.organicmaps.widget.placepage;
import java.time.ZonedDateTime;
import java.time.format.TextStyle;
import java.util.Locale;
public class OpenStateTextFormatter
{
private OpenStateTextFormatter() {}
static String formatHoursMinutes(int hour, int minute, boolean use24h)
{
if (use24h)
return String.format(Locale.ROOT, "%02d:%02d", hour, minute);
int h = hour % 12;
if (h == 0)
h = 12;
String ampm = (hour < 12) ? "AM" : "PM";
return String.format(Locale.ROOT, "%d:%02d %s", h, minute, ampm);
}
static boolean isSameLocalDate(ZonedDateTime a, ZonedDateTime b)
{
return a.toLocalDate().isEqual(b.toLocalDate());
}
static String dayShort(ZonedDateTime t, Locale locale)
{
return t.getDayOfWeek().getDisplayName(TextStyle.SHORT, locale);
}
static String buildAtLabel(boolean opens, boolean isToday, String dayShort, String time, String opensAtLocalized,
String closesAtLocalized, String opensDayAtLocalized, String closesDayAtLocalized)
{
if (isToday)
return opens ? String.format(Locale.ROOT, opensAtLocalized, time) // Opens at %s
: String.format(Locale.ROOT, closesAtLocalized, time); // Closes at %s
return opens ? String.format(Locale.ROOT, opensDayAtLocalized, dayShort, time) // Opens %s at %s
: String.format(Locale.ROOT, closesDayAtLocalized, dayShort, time); // Closes %s at %s
}
}

View File

@@ -80,6 +80,6 @@ public class PlacePageButtonFactory
yield R.drawable.ic_more; yield R.drawable.ic_more;
} }
}; };
return new PlacePageButton(titleId, iconId, buttonType); return new PlacePageButton(titleId, iconId, buttonType);
} }
} }

View File

@@ -55,6 +55,7 @@ import app.organicmaps.sdk.downloader.MapManager;
import app.organicmaps.sdk.editor.Editor; import app.organicmaps.sdk.editor.Editor;
import app.organicmaps.sdk.editor.OhState; import app.organicmaps.sdk.editor.OhState;
import app.organicmaps.sdk.editor.OpeningHours; import app.organicmaps.sdk.editor.OpeningHours;
import app.organicmaps.sdk.editor.data.HoursMinutes;
import app.organicmaps.sdk.editor.data.Timetable; import app.organicmaps.sdk.editor.data.Timetable;
import app.organicmaps.sdk.location.LocationListener; import app.organicmaps.sdk.location.LocationListener;
import app.organicmaps.sdk.location.SensorListener; import app.organicmaps.sdk.location.SensorListener;
@@ -84,11 +85,9 @@ import com.google.android.material.textview.MaterialTextView;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.TextStyle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale;
public class PlacePageView extends Fragment public class PlacePageView extends Fragment
implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer<MapObject>, implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer<MapObject>,
@@ -106,9 +105,6 @@ public class PlacePageView extends Fragment
private static final String LINKS_FRAGMENT_TAG = "LINKS_FRAGMENT_TAG"; private static final String LINKS_FRAGMENT_TAG = "LINKS_FRAGMENT_TAG";
private static final String TRACK_SHARE_MENU_ID = "TRACK_SHARE_MENU_ID"; private static final String TRACK_SHARE_MENU_ID = "TRACK_SHARE_MENU_ID";
private static final int SHORT_HORIZON_CLOSE_MIN = 60;
private static final int SHORT_HORIZON_OPEN_MIN = 15;
private static final List<CoordinatesFormat> visibleCoordsFormat = private static final List<CoordinatesFormat> visibleCoordsFormat =
Arrays.asList(CoordinatesFormat.LatLonDMS, CoordinatesFormat.LatLonDecimal, CoordinatesFormat.OLCFull, Arrays.asList(CoordinatesFormat.LatLonDMS, CoordinatesFormat.LatLonDecimal, CoordinatesFormat.OLCFull,
CoordinatesFormat.UTM, CoordinatesFormat.MGRS, CoordinatesFormat.OSMLink); CoordinatesFormat.UTM, CoordinatesFormat.MGRS, CoordinatesFormat.OSMLink);
@@ -153,7 +149,6 @@ public class PlacePageView extends Fragment
private MaterialTextView mTvLastChecked; private MaterialTextView mTvLastChecked;
private View mEditPlace; private View mEditPlace;
private View mAddPlace; private View mAddPlace;
private View mMapTooOld;
private View mEditTopSpace; private View mEditTopSpace;
private ShapeableImageView mColorIcon; private ShapeableImageView mColorIcon;
private MaterialTextView mTvCategory; private MaterialTextView mTvCategory;
@@ -318,7 +313,6 @@ public class PlacePageView extends Fragment
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked); mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
mEditPlace = mFrame.findViewById(R.id.ll__place_editor); mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
mAddPlace = mFrame.findViewById(R.id.ll__place_add); 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); mEditTopSpace = mFrame.findViewById(R.id.edit_top_space);
latlon.setOnLongClickListener(this); latlon.setOnLongClickListener(this);
address.setOnLongClickListener(this); address.setOnLongClickListener(this);
@@ -685,72 +679,42 @@ public class PlacePageView extends Fragment
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning()) if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
{ {
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace, mMapTooOld); UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace);
} }
else else
{ {
UiUtils.showIf(Editor.nativeShouldShowEditPlace(), mEditPlace); UiUtils.showIf(Editor.nativeShouldShowEditPlace(), mEditPlace);
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace); UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
UiUtils.hide(mMapTooOld);
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor); MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add); MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
mTvEditPlace.setOnClickListener(this);
boolean shouldEnableEditPlace = Editor.nativeShouldEnableEditPlace(); mTvAddPlace.setOnClickListener(this);
mTvEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
if (shouldEnableEditPlace) mTvAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
{ final int editTextButtonColor =
mTvEditPlace.setOnClickListener(this); Editor.nativeShouldEnableEditPlace()
mTvAddPlace.setOnClickListener(this);
}
else
{
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); });
String countryId = MapManager.nativeGetSelectedCountry();
if (countryId != null)
{
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);
}
}
}
final int editButtonColor =
shouldEnableEditPlace
? ContextCompat.getColor( ? ContextCompat.getColor(
getContext(), getContext(),
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)) UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled); : ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
final ColorStateList editStrokeButtonColor = new ColorStateList(
mTvEditPlace.setTextColor(editButtonColor); new int[][]{
mTvAddPlace.setTextColor(editButtonColor); new int[]{android.R.attr.state_enabled}, // enabled
mTvEditPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor)); new int[]{-android.R.attr.state_enabled} // disabled
mTvAddPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor)); },
UiUtils.showIf(UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace), mEditTopSpace); 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);
UiUtils.showIf(
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
mEditTopSpace);
} }
updateLinksView(); updateLinksView();
updateOpeningHoursView(); updateOpeningHoursView();
@@ -833,122 +797,57 @@ public class PlacePageView extends Fragment
final String ohStr = mMapObject.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS); final String ohStr = mMapObject.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS);
final Timetable[] timetables = OpeningHours.nativeTimetablesFromString(ohStr); final Timetable[] timetables = OpeningHours.nativeTimetablesFromString(ohStr);
// No valid timetable if (timetables != null && timetables.length != 0)
if (timetables == null || timetables.length == 0)
{ {
UiUtils.hide(mTvOpenState); final Context context = requireContext();
return; final OhState poiState = OpeningHours.nativeCurrentState(timetables);
}
final Context context = requireContext(); // Ignore unknown rule state
final OhState poiState = OpeningHours.nativeCurrentState(timetables); if (poiState.state == OhState.State.Unknown)
// Ignore unknown rule state
if (poiState.state == OhState.State.Unknown)
{
UiUtils.hide(mTvOpenState);
return;
}
// Get colours
final ForegroundColorSpan colorGreen = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_green));
final ForegroundColorSpan colorYellow =
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_yellow));
final ForegroundColorSpan colorRed = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_red));
// Get next state info
final SpannableStringBuilder openStateString = new SpannableStringBuilder();
final boolean isOpen = (poiState.state == OhState.State.Open); // False == Closed due to early exit for Unknown
final long nextStateTime = isOpen ? poiState.nextTimeClosed : poiState.nextTimeOpen; // Unix time (seconds)
ZonedDateTime nextChangeLocal = null;
boolean hasFiniteNextChange = false;
final long nowSec = System.currentTimeMillis() / 1000;
final int minsToNextState = (int) ((nextStateTime - nowSec) / 60);
// Try to resolve a finite next-change time; handle 24/7 case
final boolean looksLike247 = "24/7".equals(ohStr.trim());
final int ONE_WEEK_MIN = 7 * 24 * 60;
final boolean noRealNextChange = looksLike247 || minsToNextState >= ONE_WEEK_MIN;
if (!noRealNextChange)
{
try
{ {
if (nextStateTime > 0 && nextStateTime < Long.MAX_VALUE / 2) UiUtils.hide(mTvOpenState);
{ return;
// NOTE: Timezone is currently device timezone. TODO: use feature-specific timezone.
nextChangeLocal = ZonedDateTime.ofInstant(Instant.ofEpochSecond(nextStateTime), ZoneId.systemDefault());
hasFiniteNextChange = true;
}
} }
catch (Throwable ignored)
{}
}
if (!hasFiniteNextChange) // No valid next change // Get colours
{ final ForegroundColorSpan colorGreen =
if (isOpen) new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_green));
final ForegroundColorSpan colorYellow =
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_yellow));
final ForegroundColorSpan colorRed = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_red));
// Get next state info
final SpannableStringBuilder openStateString = new SpannableStringBuilder();
final boolean isOpen = (poiState.state == OhState.State.Open); // False == Closed due to early exit for Unknown
final long nextStateTime = isOpen ? poiState.nextTimeClosed : poiState.nextTimeOpen; // Unix time (seconds)
final int minsToNextState = (int) ((nextStateTime - (System.currentTimeMillis() / 1000)) / 60);
if (minsToNextState <= 60) // POI opens/closes in 60 mins
{
final String minsToChangeStr = minsToNextState + " " + getString(R.string.minute);
final String nextChangeFormatted = getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr);
final ForegroundColorSpan nextChangeColor = isOpen ? colorYellow : colorRed;
// TODO: We should check closed/open time for specific feature's timezone.
ZonedDateTime time = ZonedDateTime.ofInstant(Instant.ofEpochSecond(nextStateTime), ZoneId.systemDefault());
String localizedTime =
new HoursMinutes(time.getHour(), time.getMinute(), DateUtils.is24HourFormat(context)).toString();
openStateString.append(nextChangeFormatted, nextChangeColor, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
.append("") // Add spacer
.append(getString(R.string.at, localizedTime));
}
else if (isOpen)
openStateString.append(getString(R.string.open_now), colorGreen, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); openStateString.append(getString(R.string.open_now), colorGreen, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
else // TODO: Add "Closes at 18:00" etc
else // Closed
openStateString.append(getString(R.string.closed_now), colorRed, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); openStateString.append(getString(R.string.closed_now), colorRed, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// TODO: Add "Opens at 18:00" etc
UiUtils.setTextAndHideIfEmpty(mTvOpenState, openStateString); UiUtils.setTextAndHideIfEmpty(mTvOpenState, openStateString);
return; return;
} }
// No valid timetable
String localizedTimeString = OpenStateTextFormatter.formatHoursMinutes( UiUtils.hide(mTvOpenState);
nextChangeLocal.getHour(), nextChangeLocal.getMinute(), DateUtils.is24HourFormat(context));
final boolean shortHorizonClosing = isOpen && minsToNextState >= 0 && minsToNextState <= SHORT_HORIZON_CLOSE_MIN;
final boolean shortHorizonOpening = !isOpen && minsToNextState >= 0 && minsToNextState <= SHORT_HORIZON_OPEN_MIN;
if (shortHorizonClosing || shortHorizonOpening) // POI Opens/Closes in 60 mins • at 18:00
{
final String minsToChangeStr = getResources().getQuantityString(
R.plurals.minutes_short, Math.max(minsToNextState, 1), Math.max(minsToNextState, 1));
final String nextChangeFormatted = getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr);
openStateString.append(nextChangeFormatted, colorYellow, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
.append("") // Add spacer
.append(getString(R.string.at, localizedTimeString));
}
else
{
final String opensAtStr = getString(R.string.opens_at); // "Opens at %s"
final String closesAtStr = getString(R.string.closes_at); // "Closes at %s"
final String opensDayAtStr = getString(R.string.opens_day_at); // "Opens %1$s at %2$s"
final String closesDayAtStr = getString(R.string.closes_day_at); // "Closes %1$s at %2$s"
final boolean isToday =
OpenStateTextFormatter.isSameLocalDate(nextChangeLocal, ZonedDateTime.now(nextChangeLocal.getZone()));
// Full weekday name per design feedback.
final String dayName = nextChangeLocal.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault());
if (isOpen) // > 60 minutes OR negative (safety). Show “Open now • Closes at 18:00”
{
openStateString.append(getString(R.string.open_now), colorGreen, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final String atLabel = OpenStateTextFormatter.buildAtLabel(
false, isToday, dayName, localizedTimeString, opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
if (!TextUtils.isEmpty(atLabel))
openStateString.append("").append(atLabel);
}
else // Closed
{
openStateString.append(getString(R.string.closed_now), colorRed, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final String atLabel = OpenStateTextFormatter.buildAtLabel(
true, isToday, dayName, localizedTimeString, opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
if (!TextUtils.isEmpty(atLabel))
openStateString.append("").append(atLabel);
}
}
UiUtils.setTextAndHideIfEmpty(mTvOpenState, openStateString);
} }
private void addPlace() private void addPlace()

View File

@@ -3,7 +3,9 @@ package app.organicmaps.widget.placepage;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.bookmarks.data.MapObject;
import java.util.List; import java.util.List;
public class PlacePageViewModel extends ViewModel public class PlacePageViewModel extends ViewModel

Some files were not shown because too many files have changed in this diff Show More