Maps: Make map theme configurable

This commit is contained in:
José Rebelo
2025-04-13 09:16:16 +01:00
parent cb7914892e
commit ee218d7a65
8 changed files with 183 additions and 83 deletions
@@ -31,16 +31,12 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.Dimension;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.core.util.LatLongUtils;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.overlay.Polyline;
import org.mapsforge.map.model.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,7 +50,6 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.maps.MapsSettingsFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.maps.MapsTrackActivity;
@@ -73,20 +68,20 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesGpsFragment.class);
private MapView mapView;
private TextView gpsWarning;
private File inputFile;
private MapsManager mapsManager;
private Polyline polyline;
private Paint paint;
private boolean mapValid = true;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (MapsSettingsFragment.ACTION_SETTING_CHANGE.equals(intent.getAction())) {
final int trackColor = GBApplication.getPrefs().getInt(MapsManager.PREF_TRACK_COLOR, getResources().getColor(R.color.hrv_status_low));
paint.setColor(trackColor);
polyline.setPaintStroke(paint);
polyline.requestRedraw();
if (!isResumed()) {
mapValid = false;
} else {
mapsManager.reload();
}
}
}
};
@@ -95,12 +90,12 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_gps, container, false);
gpsWarning = rootView.findViewById(R.id.gpsWarning);
TextView gpsWarning = rootView.findViewById(R.id.gpsWarning);
mapView = rootView.findViewById(R.id.activitygpsview);
mapView.setBuiltInZoomControls(false);
mapsManager = new MapsManager(requireContext());
mapsManager.loadMaps(mapView);
mapsManager = new MapsManager(requireContext(), mapView);
mapsManager.loadMaps();
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MapsSettingsFragment.ACTION_SETTING_CHANGE);
@@ -116,6 +111,14 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
return rootView;
}
@Override
public void onResume() {
super.onResume();
if (!mapValid) {
mapsManager.reload();
}
}
@Override
public void onDestroyView() {
LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver);
@@ -185,19 +188,7 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
.map(p -> new LatLong(p.getLatitude(), p.getLongitude()))
.collect(Collectors.toList());
if (paint == null) {
paint = AndroidGraphicFactory.INSTANCE.createPaint();
final int trackColor = GBApplication.getPrefs().getInt(MapsManager.PREF_TRACK_COLOR, getResources().getColor(R.color.hrv_status_low));
paint.setColor(trackColor);
paint.setStrokeWidth(8);
paint.setStyle(Style.STROKE);
}
if (polyline == null) {
polyline = new Polyline(paint, AndroidGraphicFactory.INSTANCE);
mapView.addLayer(polyline);
}
polyline.setPoints(latlongs);
mapView.getLayerManager().redrawLayers();
mapsManager.setTrack(latlongs);
final Model model = mapView.getModel();
// FIXME: We need to offset the min latitude so the track gets centered - not sure why
@@ -1,4 +1,4 @@
/* Copyright (C) 2024 José Rebelo
/* Copyright (C) 2025 José Rebelo
This file is part of Gadgetbridge.
@@ -23,14 +23,12 @@ import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.Preference;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
import nodomain.freeyourgadget.gadgetbridge.util.maps.MapsManager;
@@ -39,6 +37,7 @@ public class MapsSettingsFragment extends AbstractPreferenceFragment {
static final String FRAGMENT_TAG = "MAP_SETTINGS_FRAGMENT";
public static final String ACTION_SETTING_CHANGE = "nodomain.freeyourgadget.gadgetbridge.maps.setting_change";
public static final String EXTRA_SETTING_KEY = "nodomain.freeyourgadget.gadgetbridge.maps_setting_key";
@Override
public void onCreatePreferences(@Nullable final Bundle savedInstanceState, @Nullable final String rootKey) {
@@ -70,7 +69,7 @@ public class MapsSettingsFragment extends AbstractPreferenceFragment {
.putString(MapsManager.PREF_MAPS_FOLDER, localUri.toString())
.apply();
prefFolder.setSummary(localUri.toString());
broadcastPreferenceChange();
broadcastPreferenceChange(MapsManager.PREF_MAPS_FOLDER);
}
}
);
@@ -81,16 +80,23 @@ public class MapsSettingsFragment extends AbstractPreferenceFragment {
return true;
});
final Preference prefMapTheme = Objects.requireNonNull(findPreference(MapsManager.PREF_MAP_THEME));
prefMapTheme.setOnPreferenceChangeListener((preference, newValue) -> {
broadcastPreferenceChange(MapsManager.PREF_MAP_THEME);
return true;
});
final Preference prefTrackColor = Objects.requireNonNull(findPreference(MapsManager.PREF_TRACK_COLOR));
prefTrackColor.setOnPreferenceChangeListener((preference, newValue) -> {
broadcastPreferenceChange();
broadcastPreferenceChange(MapsManager.PREF_TRACK_COLOR);
return true;
});
}
private void broadcastPreferenceChange() {
private void broadcastPreferenceChange(final String key) {
final Intent intent = new Intent();
intent.setAction(ACTION_SETTING_CHANGE);
intent.putExtra(EXTRA_SETTING_KEY, key);
LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent);
}
}
@@ -29,15 +29,11 @@ import androidx.annotation.NonNull;
import androidx.core.view.MenuProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.Dimension;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.util.LatLongUtils;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.overlay.Polyline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,7 +43,6 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesGpsFragment;
@@ -63,17 +58,14 @@ public class MapsTrackActivity extends AbstractGBActivity implements MenuProvide
public static boolean isActivityOpen = false;
private MapsManager mapsManager;
private Polyline polyline;
private Paint paint;
private boolean mapValid = true;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (MapsSettingsFragment.ACTION_SETTING_CHANGE.equals(intent.getAction())) {
final int trackColor = GBApplication.getPrefs().getInt(MapsManager.PREF_TRACK_COLOR, getResources().getColor(R.color.hrv_status_low));
paint.setColor(trackColor);
polyline.setPaintStroke(paint);
polyline.requestRedraw();
mapValid = false;
}
}
};
@@ -92,8 +84,8 @@ public class MapsTrackActivity extends AbstractGBActivity implements MenuProvide
addMenuProvider(this);
mapView = findViewById(R.id.activitygpsview);
mapsManager = new MapsManager(this);
mapsManager.loadMaps(mapView);
mapsManager = new MapsManager(this, mapView);
mapsManager.loadMaps();
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MapsSettingsFragment.ACTION_SETTING_CHANGE);
@@ -117,19 +109,7 @@ public class MapsTrackActivity extends AbstractGBActivity implements MenuProvide
.map(p -> new LatLong(p.getLatitude(), p.getLongitude()))
.collect(Collectors.toList());
if (paint == null) {
paint = AndroidGraphicFactory.INSTANCE.createPaint();
final int trackColor = GBApplication.getPrefs().getInt(MapsManager.PREF_TRACK_COLOR, getResources().getColor(R.color.hrv_status_low));
paint.setColor(trackColor);
paint.setStrokeWidth(8);
paint.setStyle(Style.STROKE);
}
if (polyline == null) {
polyline = new Polyline(paint, AndroidGraphicFactory.INSTANCE);
polyline.setPoints(latlongs);
mapView.addLayer(polyline);
}
mapView.getLayerManager().redrawLayers();
mapsManager.setTrack(latlongs);
mapView.setCenter(new LatLong(minLat + (maxLat - minLat) / 2, minLon + (maxLon - minLon) / 2));
byte zoom = LatLongUtils.zoomForBounds(new Dimension(this.getResources().getDisplayMetrics().widthPixels, this.getResources().getDisplayMetrics().heightPixels), new BoundingBox(minLat, minLon, maxLat, maxLon), mapView.getModel().displayModel.getTileSize());
@@ -167,6 +147,14 @@ public class MapsTrackActivity extends AbstractGBActivity implements MenuProvide
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
}
@Override
protected void onResume() {
super.onResume();
if (!mapValid) {
mapsManager.reload();
}
}
@Override
public void onCreateMenu(@NonNull final Menu menu, @NonNull final MenuInflater menuInflater) {
menuInflater.inflate(R.menu.maps_track_menu, menu);
@@ -9,6 +9,7 @@ import java.io.InputStream;
public enum MapTheme implements XmlRenderTheme {
DEFAULT("/assets/mapsforge/default.xml"),
OSMARENDER("/assets/mapsforge/osmarender.xml"),
MOTORIDER("/assets/mapsforge/motorider.xml"),
;
private XmlRenderThemeMenuCallback menuCallback;
@@ -47,4 +48,8 @@ public enum MapTheme implements XmlRenderTheme {
public void setResourceProvider(final XmlThemeResourceProvider resourceProvider) {
}
public String getPath() {
return path;
}
}
@@ -22,22 +22,33 @@ import android.net.Uri;
import androidx.documentfile.provider.DocumentFile;
import org.mapsforge.core.graphics.Canvas;
import org.mapsforge.core.graphics.GraphicFactory;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.Point;
import org.mapsforge.core.model.Rotation;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.datastore.MapDataStore;
import org.mapsforge.map.datastore.MultiMapDataStore;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.overlay.Polyline;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.model.MapViewPosition;
import org.mapsforge.map.reader.MapFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
public final class MapsManager {
@@ -45,20 +56,35 @@ public final class MapsManager {
public static final String PREF_MAPS_FOLDER = "maps_folder";
public static final String PREF_TRACK_COLOR = "maps_track_color";
public static final String PREF_MAP_THEME = "maps_theme";
private final Context mContext;
private final MapView mapView;
private Polyline polyline;
private TileRendererLayer tileRendererLayer;
private boolean isMapLoaded = false;
public MapsManager(final Context context) {
public MapsManager(final Context context, final MapView mapView) {
this.mContext = context;
this.mapView = mapView;
}
public void loadMaps(final MapView mapView) {
AndroidGraphicFactory.createInstance(GBApplication.app());
GBPrefs prefs = GBApplication.getPrefs();
public void loadMaps() {
if (tileRendererLayer != null) {
mapView.getLayerManager().getLayers().remove(tileRendererLayer);
tileRendererLayer.onDestroy();
tileRendererLayer.getTileCache().purge();
tileRendererLayer = null;
}
String folderUri = prefs.getString(PREF_MAPS_FOLDER, "");
isMapLoaded = false;
AndroidGraphicFactory.createInstance(GBApplication.app());
final GBPrefs prefs = GBApplication.getPrefs();
final String folderUri = prefs.getString(PREF_MAPS_FOLDER, "");
if (folderUri.isEmpty()) {
return;
}
@@ -68,7 +94,7 @@ public final class MapsManager {
return;
}
MultiMapDataStore multiMapDataStore = new MultiMapDataStore(MultiMapDataStore.DataPolicy.RETURN_ALL);
final MultiMapDataStore multiMapDataStore = new MultiMapDataStore(MultiMapDataStore.DataPolicy.RETURN_ALL);
final DocumentFile[] documentFiles = folder.listFiles();
@@ -86,7 +112,10 @@ public final class MapsManager {
LOG.debug("Loading {}", documentFile.getName());
try {
FileInputStream inputStream = (FileInputStream) mContext.getContentResolver().openInputStream(documentFile.getUri());
final FileInputStream inputStream = (FileInputStream) mContext.getContentResolver().openInputStream(documentFile.getUri());
if (inputStream == null) {
throw new IOException("Failed to open input stream for " + documentFile.getName());
}
final MapFile mapFile = new MapFile(inputStream, 0, null);
multiMapDataStore.addMapDataStore(mapFile, true, true);
isMapLoaded = true;
@@ -95,27 +124,83 @@ public final class MapsManager {
}
}
final TileCache tileCache = AndroidUtil.createTileCache(mContext, "mapcache",
mapView.getModel().displayModel.getTileSize(), 1f,
mapView.getModel().frameBufferModel.getOverdrawFactor());
final TileCache tileCache = AndroidUtil.createTileCache(
mContext,
"mapcache",
mapView.getModel().displayModel.getTileSize(),
1f,
mapView.getModel().frameBufferModel.getOverdrawFactor()
);
final TileRendererLayer tileRendererLayer = new TileRendererLayer(
tileCache, multiMapDataStore,
mapView.getModel().mapViewPosition, true, false, false, AndroidGraphicFactory.INSTANCE) {
@Override
public void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas, Point topLeftPoint, Rotation rotation) {
if (!isMapLoaded) {
canvas.fillColor(GBApplication.getWindowBackgroundColor(mapView.getContext()));
}
super.draw(boundingBox, zoomLevel, canvas, topLeftPoint, rotation);
}
};
tileRendererLayer.setXmlRenderTheme(MapTheme.DEFAULT);
tileRendererLayer = new MyTileRendererLayer(
tileCache,
multiMapDataStore,
mapView.getModel().mapViewPosition,
true,
false,
false,
AndroidGraphicFactory.INSTANCE
);
mapView.getLayerManager().getLayers().add(tileRendererLayer);
final String themePrefValue = prefs.getString(PREF_MAP_THEME, "default").toUpperCase(Locale.ROOT);
MapTheme theme;
try {
theme = MapTheme.valueOf(themePrefValue);
} catch (final Exception e) {
LOG.error("Failed to find theme {}", themePrefValue, e);
theme = MapTheme.DEFAULT;
}
tileRendererLayer.setXmlRenderTheme(theme);
mapView.getLayerManager().getLayers().add(0, tileRendererLayer);
}
public boolean isMapLoaded() {
return isMapLoaded;
}
public void setTrack(final List<LatLong> points) {
if (polyline == null) {
final Paint paint = AndroidGraphicFactory.INSTANCE.createPaint();
final int trackColor = GBApplication.getPrefs().getInt(MapsManager.PREF_TRACK_COLOR, mContext.getResources().getColor(R.color.map_track_default));
paint.setColor(trackColor);
paint.setStrokeWidth(8);
paint.setStyle(Style.STROKE);
polyline = new Polyline(paint, AndroidGraphicFactory.INSTANCE);
mapView.addLayer(polyline);
}
polyline.setPoints(points);
mapView.getLayerManager().redrawLayers();
}
public void reload() {
if (polyline != null) {
final int trackColor = GBApplication.getPrefs().getInt(MapsManager.PREF_TRACK_COLOR, mContext.getResources().getColor(R.color.map_track_default));
polyline.getPaintStroke().setColor(trackColor);
polyline.requestRedraw();
}
loadMaps();
}
private class MyTileRendererLayer extends TileRendererLayer {
public MyTileRendererLayer(final TileCache tileCache,
final MapDataStore mapDataStore,
final MapViewPosition mapViewPosition,
final boolean isTransparent,
final boolean renderLabels,
final boolean cacheLabels,
final GraphicFactory graphicFactory) {
super(tileCache, mapDataStore, mapViewPosition, isTransparent, renderLabels, cacheLabels, graphicFactory);
}
@Override
public void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas, Point topLeftPoint, Rotation rotation) {
if (!isMapLoaded) {
canvas.fillColor(GBApplication.getWindowBackgroundColor(mapView.getContext()));
}
super.draw(boundingBox, zoomLevel, canvas, topLeftPoint, rotation);
}
}
}
+14
View File
@@ -17,6 +17,20 @@
<item>@string/pref_theme_value_dynamic</item>
</string-array>
<!--
These must match nodomain.freeyourgadget.gadgetbridge.util.maps.MapTheme
-->
<string-array name="maps_themes_names">
<item>@string/pref_default</item>
<item>@string/map_theme_osmarender</item>
<item>@string/map_theme_motorider</item>
</string-array>
<string-array name="maps_themes_values">
<item>default</item>
<item>osmarender</item>
<item>motorider</item>
</string-array>
<!-- https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code -->
<string-array name="pref_language_options">
<item name="default">System Default</item>
+2
View File
@@ -249,6 +249,8 @@
<string name="pref_theme_dark">Dark</string>
<string name="pref_theme_system">System</string>
<string name="pref_theme_dynamic">Dynamic colors</string>
<string name="map_theme_osmarender">Osmarender</string>
<string name="map_theme_motorider">Motorider</string>
<string name="pref_theme_black_background">Use black background in Dark Theme</string>
<string name="pref_title_language">Language</string>
<string name="pref_title_minimize_priority">Hide the Gadgetbridge notification</string>
+9
View File
@@ -18,6 +18,15 @@
android:title="@string/pref_title_theme"
app:iconSpaceReserved="false">
<ListPreference
android:defaultValue="default"
android:entries="@array/maps_themes_names"
android:entryValues="@array/maps_themes_values"
android:icon="@drawable/ic_paint"
android:key="maps_theme"
android:title="@string/pref_title_theme"
app:useSimpleSummaryProvider="true" />
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
android:defaultValue="@color/map_track_default"
android:icon="@drawable/ic_paint"