From 88a3e2c980994d56eb1eb57bbe4b7acce6a1fede Mon Sep 17 00:00:00 2001 From: konggdev Date: Tue, 1 Sep 2026 03:15:38 +0200 Subject: [PATCH] Change internal style filesystem to an internal registry and introduce style options concept --- .../gl-js/dist/THIRD_PARTY_NOTICES.md | 3 +- .../eu/konggdev/strikemaps/MainActivity.java | 3 - .../strikemaps/app/AppController.java | 23 ++- .../data/helper/UserPrefsHelper.java | 47 ----- .../{data => }/helper/FileHelper.java | 79 ++------ .../strikemaps/helper/UserPrefsHelper.java | 127 ++++++++++++ .../konggdev/strikemaps/map/MapComponent.java | 28 ++- .../offline/download/OfflineDownloader.java | 4 +- .../implementation/LocationOverlay.java | 1 - .../strikemaps/map/renderer/MapRenderer.java | 4 +- .../implementation/MapLibreGLJSRenderer.java | 12 +- .../MapLibreNativeRenderer.java | 12 +- .../renderer/implementation/VtmRenderer.java | 4 +- .../strikemaps/map/source/MapSource.java | 130 ++++++++++++- .../map/source/model/TileSource.java | 51 +++++ .../strikemaps/map/style/MapStyle.java | 102 +++++----- .../map/style/document/StyleDocument.java | 88 +++++++++ .../management/StyleManagementMetadata.java | 20 ++ .../map/style/options/StyleOptions.java | 30 +++ .../storage/RegistryStorageComponent.java | 74 +++++++ .../ui/element/item/GenericItem.java | 13 +- .../ui/element/item/InlineItem.java | 10 + .../ui/element/item/PreviewItem.java | 3 + .../ui/element/item/SelectionItem.java | 12 ++ .../ui/factory/AlertDialogFactory.java | 140 +------------- .../fragment/dialog/NewStyleBottomSheet.java | 24 +-- .../dialog/StyleDetailsBottomSheet.java | 106 ++++------ .../layout/FragmentLayoutControls.java | 2 +- .../fragment/layout/FragmentLayoutSearch.java | 39 ---- .../FragmentLayoutContentOfflineMaps.java | 34 +--- .../main/FragmentLayoutContentSettings.java | 8 +- .../popup/FragmentMapChangePopup.java | 105 +++++++--- app/src/main/res/THIRD_PARTY_NOTICES.md | 16 ++ .../main/res/drawable/ic_arrow_forward.xml | 15 +- app/src/main/res/drawable/ic_bubble.xml | 5 + app/src/main/res/drawable/ic_clarify.xml | 17 ++ app/src/main/res/drawable/ic_copy.bmp | Bin 5322 -> 0 bytes app/src/main/res/drawable/ic_copy.xml | 10 + app/src/main/res/drawable/ic_dropdown.xml | 5 + app/src/main/res/drawable/ic_help.xml | 10 + app/src/main/res/drawable/ic_sliders.xml | 24 +++ app/src/main/res/drawable/ic_stack.xml | 17 ++ .../main/res/layout/dialog_style_details.xml | 15 +- app/src/main/res/layout/dropdown_offline.xml | 34 ---- app/src/main/res/layout/fragment_search.xml | 11 -- app/src/main/res/layout/item_selection.xml | 25 +++ app/src/main/res/layout/popup_map_change.xml | 181 ++++++++++++++++-- 47 files changed, 1105 insertions(+), 618 deletions(-) delete mode 100644 app/src/main/java/eu/konggdev/strikemaps/data/helper/UserPrefsHelper.java rename app/src/main/java/eu/konggdev/strikemaps/{data => }/helper/FileHelper.java (57%) create mode 100644 app/src/main/java/eu/konggdev/strikemaps/helper/UserPrefsHelper.java create mode 100644 app/src/main/java/eu/konggdev/strikemaps/map/source/model/TileSource.java create mode 100644 app/src/main/java/eu/konggdev/strikemaps/map/style/document/StyleDocument.java create mode 100644 app/src/main/java/eu/konggdev/strikemaps/map/style/management/StyleManagementMetadata.java create mode 100644 app/src/main/java/eu/konggdev/strikemaps/map/style/options/StyleOptions.java create mode 100644 app/src/main/java/eu/konggdev/strikemaps/storage/RegistryStorageComponent.java create mode 100644 app/src/main/java/eu/konggdev/strikemaps/ui/element/item/SelectionItem.java create mode 100644 app/src/main/res/THIRD_PARTY_NOTICES.md create mode 100644 app/src/main/res/drawable/ic_bubble.xml create mode 100644 app/src/main/res/drawable/ic_clarify.xml delete mode 100644 app/src/main/res/drawable/ic_copy.bmp create mode 100644 app/src/main/res/drawable/ic_copy.xml create mode 100644 app/src/main/res/drawable/ic_dropdown.xml create mode 100644 app/src/main/res/drawable/ic_help.xml create mode 100644 app/src/main/res/drawable/ic_sliders.xml create mode 100644 app/src/main/res/drawable/ic_stack.xml delete mode 100644 app/src/main/res/layout/dropdown_offline.xml create mode 100644 app/src/main/res/layout/item_selection.xml diff --git a/app/src/main/assets/library/maplibre/gl-js/dist/THIRD_PARTY_NOTICES.md b/app/src/main/assets/library/maplibre/gl-js/dist/THIRD_PARTY_NOTICES.md index 0887f82..68ff3aa 100644 --- a/app/src/main/assets/library/maplibre/gl-js/dist/THIRD_PARTY_NOTICES.md +++ b/app/src/main/assets/library/maplibre/gl-js/dist/THIRD_PARTY_NOTICES.md @@ -3,11 +3,10 @@ Builds included inside /dist correspond to MapLibre GL JS version 5.24.0
The source for these builds is available on https://github.com/maplibre/maplibre-gl-js # -All files inside the /dist folder are under the following license +All files inside the /dist folder are subject to the following license ------------------------------------------------------------------------------- - Copyright (c) 2023, MapLibre contributors All rights reserved. diff --git a/app/src/main/java/eu/konggdev/strikemaps/MainActivity.java b/app/src/main/java/eu/konggdev/strikemaps/MainActivity.java index aefacdd..a01c923 100755 --- a/app/src/main/java/eu/konggdev/strikemaps/MainActivity.java +++ b/app/src/main/java/eu/konggdev/strikemaps/MainActivity.java @@ -1,8 +1,5 @@ package eu.konggdev.strikemaps; -import static org.maplibre.android.style.layers.PropertyFactory.lineColor; -import static org.maplibre.android.style.layers.PropertyFactory.lineWidth; - import eu.konggdev.strikemaps.app.AppController; import android.os.Bundle; diff --git a/app/src/main/java/eu/konggdev/strikemaps/app/AppController.java b/app/src/main/java/eu/konggdev/strikemaps/app/AppController.java index 7cc5bb6..7ecbff5 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/app/AppController.java +++ b/app/src/main/java/eu/konggdev/strikemaps/app/AppController.java @@ -4,8 +4,8 @@ import android.content.SharedPreferences; import androidx.appcompat.app.AppCompatActivity; import eu.konggdev.strikemaps.MainActivity; -import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.map.MapComponent; +import eu.konggdev.strikemaps.storage.RegistryStorageComponent; import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen; @@ -14,28 +14,43 @@ public class AppController { private final MainActivity appActivity; private MapComponent map; + private UIComponent ui; - public AppController(MainActivity refActivity) { - appActivity = refActivity; - } + + private RegistryStorageComponent registry; + + + public AppController(MainActivity appActivity) { this.appActivity = appActivity;} + public void logcat(String log) { appActivity.logcat(log); } + public UIComponent getUi() { if (ui == null) init(); return ui; } + public MapComponent getMap() { if (map == null) init(); return map; } + + public RegistryStorageComponent getRegistry() { + if (registry == null) init(); + return registry; + } + public SharedPreferences getPrefs() { return getActivity().getSharedPreferences("user_prefs", MODE_PRIVATE); } + public AppCompatActivity getActivity() { return appActivity; } + public void init() { if (getActivity().getSupportActionBar() != null) getActivity().getSupportActionBar().show(); + if(registry == null) registry = new RegistryStorageComponent(this); if(map == null) map = new MapComponent(this); if(ui == null) { ui = new UIComponent(this, map); diff --git a/app/src/main/java/eu/konggdev/strikemaps/data/helper/UserPrefsHelper.java b/app/src/main/java/eu/konggdev/strikemaps/data/helper/UserPrefsHelper.java deleted file mode 100644 index ee16dd4..0000000 --- a/app/src/main/java/eu/konggdev/strikemaps/data/helper/UserPrefsHelper.java +++ /dev/null @@ -1,47 +0,0 @@ -package eu.konggdev.strikemaps.data.helper; - -import android.content.SharedPreferences; - -public final class UserPrefsHelper { - private UserPrefsHelper() {} // prevent instantiation - - //Keys - private static final String KEY_STARTUP_MAP_STYLE = "startupMapStyle"; - private static final String KEY_MAP_RENDERER = "mapRenderer"; - private static final String KEY_PERSIST_LOCATION_ENABLED = "persistLocationEnabled"; - private static final String KEY_LAST_LOCATION_ENABLED = "lastLocationEnabled"; - - //Defaults - private static final String DEFAULT_MAP_STYLE = "bundled/style/classic.style.json"; - private static final Integer DEFAULT_MAP_RENDERER = 0; - private static final boolean DEFAULT_PERSIST_LOCATION_ENABLED = true; - private static final boolean DEFAULT_LAST_LOCATION_ENABLED = false; - - public static String startupMapStyle(SharedPreferences prefs) { - return prefs.getString(KEY_STARTUP_MAP_STYLE, DEFAULT_MAP_STYLE); - } - - public static boolean startupMapStyle(SharedPreferences prefs, String updated) { - return prefs.edit().putString(KEY_STARTUP_MAP_STYLE, updated).commit(); - } - - public static Integer mapRenderer(SharedPreferences prefs) { - return prefs.getInt(KEY_MAP_RENDERER, DEFAULT_MAP_RENDERER); - } - - public static boolean mapRenderer(SharedPreferences prefs, Integer updated) { - return prefs.edit().putInt(KEY_MAP_RENDERER, updated).commit(); - } - - public static boolean persistLocationEnabled(SharedPreferences prefs) { - return prefs.getBoolean(KEY_PERSIST_LOCATION_ENABLED, DEFAULT_PERSIST_LOCATION_ENABLED); - } - - public static boolean lastLocationEnabled(SharedPreferences prefs) { - return prefs.getBoolean(KEY_LAST_LOCATION_ENABLED, DEFAULT_LAST_LOCATION_ENABLED); - } - - public static boolean lastLocationEnabled(SharedPreferences prefs, boolean status) { - return prefs.edit().putBoolean(KEY_LAST_LOCATION_ENABLED, status).commit(); - } -} diff --git a/app/src/main/java/eu/konggdev/strikemaps/data/helper/FileHelper.java b/app/src/main/java/eu/konggdev/strikemaps/helper/FileHelper.java similarity index 57% rename from app/src/main/java/eu/konggdev/strikemaps/data/helper/FileHelper.java rename to app/src/main/java/eu/konggdev/strikemaps/helper/FileHelper.java index 2ce6cc8..0763fe4 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/data/helper/FileHelper.java +++ b/app/src/main/java/eu/konggdev/strikemaps/helper/FileHelper.java @@ -1,17 +1,29 @@ -package eu.konggdev.strikemaps.data.helper; +package eu.konggdev.strikemaps.helper; import android.content.res.AssetManager; -import android.os.Environment; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import eu.konggdev.strikemaps.app.AppController; //FIXME: Ugly public final class FileHelper { + public static Bitmap getIcon(String iconLocator, AppController app) { + switch (iconLocator.split("//")[0]) { + //TODO: https + case "assets:": + return BitmapFactory.decodeStream(FileHelper.openAssetStream("bundled/icon/" + iconLocator.split("//")[1], app)); + default: + app.logcat("Unimplemented icon locator space: " + iconLocator); + return null; + } + } + public static String loadStringFromAssetFile(String filePath, AppController app) { try (InputStream is = app.getActivity().getAssets().open(filePath)) { int size = is.available(); @@ -24,19 +36,6 @@ public final class FileHelper { } } - public static String loadStringFromUserFile(String filePath) { - File file = new File(filePath); - try (FileInputStream fis = new FileInputStream(file)) { - int size = fis.available(); - byte[] buffer = new byte[size]; - fis.read(buffer); - return new String(buffer, StandardCharsets.UTF_8); - } catch (IOException ex) { - ex.printStackTrace(); - return null; - } - } - public static void writeUserFile(String path, String fileName, String content, AppController app) throws IOException { try { File userDirectory = new File(app.getActivity().getExternalFilesDir(null), path); @@ -60,16 +59,6 @@ public final class FileHelper { } } - public static boolean deleteFile(String filePath) { - File file = new File(filePath); - - if (!file.exists() || !file.isFile()) { - return false; - } - - return file.delete(); - } - public static String[] getAssetFiles(String path, String fileExt, AppController app) { AssetManager assetManager = app.getActivity().getAssets(); try { @@ -109,44 +98,4 @@ public final class FileHelper { } } - public static boolean userFileExists(String path, String fileName, AppController app) { - File userDirectory = new File(app.getActivity().getExternalFilesDir(null), path); - - if (!userDirectory.exists() || !userDirectory.isDirectory()) { - return false; - } - - File file = new File(userDirectory, fileName); - - return file.exists() && file.isFile(); - } - - public static String[] getUserFiles(String path, String fileExt, AppController app) { - File userDirectory = new File(app.getActivity().getExternalFilesDir(null), path); - - if (!userDirectory.exists() || !userDirectory.isDirectory()) - return new String[0]; - - File[] files = userDirectory.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String filename) { - if (fileExt == null || fileExt.isEmpty()) { - return true; - } - - return filename.toLowerCase().endsWith(fileExt.toLowerCase()); - } - }); - - if (files == null || files.length == 0) { - return new String[0]; - } - - List fileList = new ArrayList<>(); - for (File file : files) { - fileList.add(file.getAbsolutePath()); - } - - return fileList.toArray(new String[0]); - } } diff --git a/app/src/main/java/eu/konggdev/strikemaps/helper/UserPrefsHelper.java b/app/src/main/java/eu/konggdev/strikemaps/helper/UserPrefsHelper.java new file mode 100644 index 0000000..f6d16ad --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/helper/UserPrefsHelper.java @@ -0,0 +1,127 @@ +package eu.konggdev.strikemaps.helper; + +import android.content.SharedPreferences; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import eu.konggdev.strikemaps.app.AppController; +import eu.konggdev.strikemaps.map.source.MapSource; +import eu.konggdev.strikemaps.map.source.MapSource.MapSourceContractType; +import eu.konggdev.strikemaps.map.source.model.TileSource; +import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.style.document.StyleDocument; +import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata; +import eu.konggdev.strikemaps.map.style.options.StyleOptions; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public final class UserPrefsHelper { + private UserPrefsHelper() {} // prevent instantiation + + //Keys + private static final String KEY_STARTUP_MAP_STYLE = "startupMapStyle"; + private static final String KEY_MAP_RENDERER = "mapRenderer"; + private static final String KEY_PERSIST_LOCATION_ENABLED = "persistLocationEnabled"; + private static final String KEY_LAST_LOCATION_ENABLED = "lastLocationEnabled"; + private static final String KEY_STYLES = "styles"; + private static final String KEY_SOURCES = "sources"; + + //Defaults + private static final Integer DEFAULT_MAP_STYLE = 0; + private static final Integer DEFAULT_MAP_RENDERER = 0; + private static final boolean DEFAULT_PERSIST_LOCATION_ENABLED = true; + private static final boolean DEFAULT_LAST_LOCATION_ENABLED = false; + + public static Map DEFAULT_STYLES(AppController app) { + Map styles = new HashMap<>(); + String[] styleAssets = FileHelper.getAssetFiles("bundled/style", ".style.json", app); + for (int i = 0; i < styleAssets.length; i++) { styles.put( i, + new MapStyle( + FileHelper.loadStringFromAssetFile(styleAssets[i], app), + new StyleOptions(), + new StyleManagementMetadata() + )); + } + return styles; + } //Built-in Styles + + private static final Map DEFAULT_SOURCES = Map.of( + 0, new MapSource( + MapSourceContractType.DEFINITION, + "Strike Maps Planet", + new TileSource("https://tiles.strikemaps.eu/planet"), + "vector", + "smts" + ), + 1, new MapSource( + MapSourceContractType.DEFINITION, + "ArcGIS Imagery", + new TileSource(new String[]{"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"}), + "raster", + "raster" + ) + ); //Built-in Sources + + public static Integer startupMapStyle(SharedPreferences prefs) { + return prefs.getInt(KEY_STARTUP_MAP_STYLE, DEFAULT_MAP_STYLE); + } + + public static boolean startupMapStyle(SharedPreferences prefs, Integer updated) { + return prefs.edit().putInt(KEY_STARTUP_MAP_STYLE, updated).commit(); + } + + public static Integer mapRenderer(SharedPreferences prefs) { + return prefs.getInt(KEY_MAP_RENDERER, DEFAULT_MAP_RENDERER); + } + + public static boolean mapRenderer(SharedPreferences prefs, Integer updated) { + return prefs.edit().putInt(KEY_MAP_RENDERER, updated).commit(); + } + + public static boolean persistLocationEnabled(SharedPreferences prefs) { + return prefs.getBoolean(KEY_PERSIST_LOCATION_ENABLED, DEFAULT_PERSIST_LOCATION_ENABLED); + } + + public static boolean lastLocationEnabled(SharedPreferences prefs) { + return prefs.getBoolean(KEY_LAST_LOCATION_ENABLED, DEFAULT_LAST_LOCATION_ENABLED); + } + + public static boolean lastLocationEnabled(SharedPreferences prefs, boolean status) { + return prefs.edit().putBoolean(KEY_LAST_LOCATION_ENABLED, status).commit(); + } + + public static Map styles(SharedPreferences prefs, AppController app) { + String json = prefs.getString(KEY_STYLES, null); + if (json == null) return DEFAULT_STYLES(app); + Type type = new TypeToken>() {}.getType(); + Map stored = + new Gson().fromJson(json, type); + Map result = new HashMap<>(); + for (var entry : stored.entrySet()) + result.put(entry.getKey(), entry.getValue().restore()); + return result; + } + + public static boolean styles(SharedPreferences prefs, Map updated) { + Map stored = new HashMap<>(); + for (var entry : updated.entrySet()) + stored.put(entry.getKey(), entry.getValue().makeStoredRepresentation()); + return prefs.edit() + .putString(KEY_STYLES, new Gson().toJson(stored)) + .commit(); + } + + public static Map sources(SharedPreferences prefs) { + String json = prefs.getString(KEY_SOURCES, null); + if (json == null) return DEFAULT_SOURCES; + Type type = new TypeToken>() {}.getType(); + return new Gson().fromJson(json, type); + } + + public static boolean sources(SharedPreferences prefs, Map updated) { + return prefs.edit() + .putString(KEY_SOURCES, new Gson().toJson(updated)) + .commit(); + } +} diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java b/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java index bd89b05..4f5728a 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java @@ -5,10 +5,10 @@ import java.util.*; import android.widget.Toast; import eu.konggdev.strikemaps.Component; import eu.konggdev.strikemaps.map.renderer.implementation.MapLibreGLJSRenderer; -import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory; -import eu.konggdev.strikemaps.data.helper.UserPrefsHelper; -import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer; import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory; +import eu.konggdev.strikemaps.helper.UserPrefsHelper; +import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer; import org.maplibre.android.geometry.LatLng; import org.maplibre.geojson.Feature; @@ -19,8 +19,8 @@ import eu.konggdev.strikemaps.map.renderer.MapRenderer; import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentMap; public class MapComponent implements Component { - MapRenderer mapRenderer; - AppController app; + private final MapRenderer mapRenderer; + private final AppController app; public MapStyle style; public Map, MapOverlay> overlays = new HashMap<>(); @@ -50,7 +50,7 @@ public class MapComponent implements Component { public void setStyle(MapStyle style) { this.style = style; - mapRenderer.styleUpdate(style); + mapRenderer.styleUpdate(style.effectiveDocument()); } public void switchOverlay(MapOverlay overlay) { @@ -67,14 +67,14 @@ public class MapComponent implements Component { return overlays.containsKey(overlay); } - public void selectPoint(Feature selection) { - //FIXME: Put back FragmentPointPreviewPopup (private code atm) - } - public void overlayUpdate(MapOverlay in) { mapRenderer.overlayUpdate(in); } + public void selectPoint(Feature selection) { + //FIXME: Put back FragmentPointPreviewPopup (private code atm) + } + public boolean onMapClick(LatLng point) { List features = mapRenderer.featuresAtPoint(point); @@ -99,4 +99,12 @@ public class MapComponent implements Component { //TODO: Likely Nonfeature(?) point selection return true; } + + public void onMapInit() { + setStyle( + app.getRegistry().getStyle( + UserPrefsHelper.startupMapStyle(app.getPrefs()) + ) + ); + } } diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/offline/download/OfflineDownloader.java b/app/src/main/java/eu/konggdev/strikemaps/map/offline/download/OfflineDownloader.java index 9c857f6..00d5d0f 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/offline/download/OfflineDownloader.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/offline/download/OfflineDownloader.java @@ -7,8 +7,8 @@ import java.net.URI; public final class OfflineDownloader { public static String[] fetchAvailableExports(MapSource source) { - URI uri = URI.create(source.url); - String host = uri.getHost(); + //URI uri = URI.create(source.url); + //String host = uri.getHost(); return new String[0]; } diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/overlay/implementation/LocationOverlay.java b/app/src/main/java/eu/konggdev/strikemaps/map/overlay/implementation/LocationOverlay.java index ff19dde..63e49ed 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/overlay/implementation/LocationOverlay.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/overlay/implementation/LocationOverlay.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.JsonNode; import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.overlay.MapOverlay; -import eu.konggdev.strikemaps.map.source.MapSource; import eu.konggdev.strikemaps.data.provider.LocationDataProvider; diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/MapRenderer.java b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/MapRenderer.java index caad918..970f44f 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/MapRenderer.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/MapRenderer.java @@ -3,14 +3,14 @@ package eu.konggdev.strikemaps.map.renderer; import android.view.View; import eu.konggdev.strikemaps.map.overlay.MapOverlay; -import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.style.document.StyleDocument; import org.maplibre.android.geometry.LatLng; import org.maplibre.geojson.Feature; import java.util.List; public interface MapRenderer { - void styleUpdate(MapStyle style); + void styleUpdate(StyleDocument style); void overlayUpdate(MapOverlay overlay); diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreGLJSRenderer.java b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreGLJSRenderer.java index 7e311cb..f405a90 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreGLJSRenderer.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreGLJSRenderer.java @@ -3,7 +3,6 @@ package eu.konggdev.strikemaps.map.renderer.implementation; import android.annotation.SuppressLint; import android.view.View; import android.view.ViewGroup; -import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; @@ -18,9 +17,9 @@ import eu.konggdev.strikemaps.app.util.JsonPatcher; import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.renderer.MapRenderer; -import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.source.MapSource; +import eu.konggdev.strikemaps.map.style.document.StyleDocument; import org.maplibre.android.geometry.LatLng; -import org.maplibre.android.maps.Style; import org.maplibre.geojson.Feature; import java.util.Collections; @@ -61,7 +60,7 @@ public class MapLibreGLJSRenderer implements MapRenderer { } @Override - public void styleUpdate(MapStyle style) { + public void styleUpdate(StyleDocument style) { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); @@ -73,7 +72,8 @@ public class MapLibreGLJSRenderer implements MapRenderer { //Sources ObjectNode sources = mapper.createObjectNode(); if (style.sources != null) - style.sources.forEach((k, v) -> sources.set(k, mapper.valueToTree(v))); + for (MapSource source : style.sources) + sources.set(source.name, source.makeJson()); //Layers ArrayNode layers = mapper.createArrayNode(); @@ -145,5 +145,5 @@ public class MapLibreGLJSRenderer implements MapRenderer { return Collections.emptyList(); } - class Bridge { } + static class Bridge { } } diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java index a1d0160..4b1bd3e 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java @@ -10,10 +10,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ArrayNode; import eu.konggdev.strikemaps.app.util.JsonPatcher; -import eu.konggdev.strikemaps.data.helper.UserPrefsHelper; import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.renderer.MapRenderer; -import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.source.MapSource; +import eu.konggdev.strikemaps.map.style.document.StyleDocument; import org.maplibre.android.MapLibre; import org.maplibre.android.geometry.LatLng; import org.maplibre.android.maps.MapLibreMap; @@ -32,7 +32,6 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { @NonNull MapComponent controller; MapLibreMap map; final MapView mapView; - private JsonNode origin; public MapLibreNativeRenderer(AppController app, MapComponent controller) { @@ -45,7 +44,7 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { } @Override - public void styleUpdate(MapStyle style) { + public void styleUpdate(StyleDocument style) { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); @@ -57,7 +56,8 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { //Sources ObjectNode sources = mapper.createObjectNode(); if (style.sources != null) - style.sources.forEach((k, v) -> sources.set(k, mapper.valueToTree(v))); + for (MapSource source : style.sources) + sources.set(source.name, source.makeJson()); //Layers ArrayNode layers = mapper.createArrayNode(); @@ -128,7 +128,7 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { public void onMapReady(@NonNull MapLibreMap maplibreMap) { this.map = maplibreMap; - controller.setStyle(MapStyle.fromFile(UserPrefsHelper.startupMapStyle(app.getPrefs()), app)); + controller.onMapInit(); //I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :) map.getUiSettings().setLogoEnabled(false); diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/VtmRenderer.java b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/VtmRenderer.java index 9af0c9f..724bde5 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/VtmRenderer.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/VtmRenderer.java @@ -5,7 +5,7 @@ import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.renderer.MapRenderer; -import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.style.document.StyleDocument; import okhttp3.OkHttpClient; import org.maplibre.android.geometry.LatLng; import org.maplibre.geojson.Feature; @@ -38,7 +38,7 @@ public class VtmRenderer implements MapRenderer { } @Override - public void styleUpdate(MapStyle style) { + public void styleUpdate(StyleDocument style) { //TODO OkHttpClient.Builder builder = new OkHttpClient.Builder(); OSciMap4TileSource tileSource = OSciMap4TileSource.builder().httpFactory(new OkHttpEngine.OkHttpFactory(builder)).build(); diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java b/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java index f097a59..0f72383 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java @@ -1,21 +1,139 @@ package eu.konggdev.strikemaps.map.source; +import androidx.annotation.NonNull; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import eu.konggdev.strikemaps.map.source.model.TileSource; public class MapSource { - public String url; - public JsonNode data; + public enum MapSourceContractType { + REQUEST, + DEFINITION + } + + /* + * Contract describes the "purpose" of the source. + * + * This exists because we effectively have two types + * of sources: + * - Sources that are the style asking for a specific type of source + * and defining a fallback, which is also used as a default for + * + * and + * - Sources that are the actual source + */ + @NonNull + public final MapSourceContractType contract; + + /* + * For a request contract: + * name is the internal key used by the style. + * + * For a definition contract: + * name is the user-facing source name + * (e.g. "ArcGIS Imagery"). + * + * When converting a request into a definition + * (when importing a style whose requests + * cannot be satisfied), we look for a name + * field and fall back to normalizing the key instead + */ + public String name; + + /* + * For a request contract, this is the fallback + * for when we absolutely cannot satisfy the requirement, + * or the defaults for converting into a definition contract. + */ + public TileSource tileSource; + public String type; public String schema; - public JsonNode tiles; public int minzoom; public int maxzoom; + public String scheme; public int tileSize; - public String attribution; - public String encoding; - public MapSource() { } + + public MapSource(@NonNull MapSourceContractType contract, String name, TileSource tileSource, String type, String schema) { + this.contract = contract; + this.name = name; + this.tileSource = tileSource; + this.type = type; + this.schema = schema; + } + + private MapSource(@NonNull MapSourceContractType contract) { + this.contract = contract; + } + + public static MapSource fromJson(MapSourceContractType contract, String key, JsonNode sourceNode) { + MapSource result = new MapSource(contract); + result.name = key; + + result.schema = sourceNode.path("schema").asText(null); + result.scheme = sourceNode.path("scheme").asText(null); + result.encoding = sourceNode.path("encoding").asText(null); + result.type = sourceNode.path("type").asText(null); + + result.minzoom = sourceNode.path("minZoom").asInt(0); + result.maxzoom = sourceNode.path("maxZoom").asInt(24); + result.tileSize = sourceNode.path("tileSize").asInt(256); + + result.tileSource = handleJsonTileSource(contract, sourceNode); + + return result; + } + + private static TileSource handleJsonTileSource(MapSourceContractType contract, JsonNode sourceNode) { + // By design, a source must use either "url" or "tiles", never both + // In case both are present, we prefer URL over tiles... because I don't know, we just do, m'kay? + if (sourceNode.has("url")) + return new TileSource(sourceNode.get("url").asText()); + + if (sourceNode.has("tiles")) { + String[] tiles = new String[0]; + ObjectMapper mapper = new ObjectMapper(); + + try { + tiles = mapper.treeToValue(sourceNode.get("tiles"), String[].class); + } catch (Exception e) { // If we can't parse it, lets just keep it an empty array + e.printStackTrace(); + } + + return new TileSource(tiles); + } + + if (contract == MapSourceContractType.REQUEST) //No tile source is only acceptable for a request contract + //TODO: Define an empty tile source? + return null; + + //TODO: Decide what to do when we have a definition that doesn't define the most important part - the tile source + //Maybe throwing some custom exception, catching it in fromJson calls and propagating it back to the user would be appropriate + return null; + } + + public ObjectNode makeJson() { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode node = mapper.createObjectNode(); + + try { + if (type != null) node.put("type", type); + if (schema != null) node.put("schema", schema); + if (minzoom != 0) node.put("minzoom", minzoom); + if (maxzoom != 24) node.put("maxzoom", maxzoom); + if (scheme != null) node.put("scheme", scheme); + if (tileSize != 256) node.put("tileSize", tileSize); + if (encoding != null) node.put("encoding", encoding); + if (tileSource != null) tileSource.makeJson(mapper, node); + } catch (Exception e) { + e.printStackTrace(); + } + + return node; + } } diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/source/model/TileSource.java b/app/src/main/java/eu/konggdev/strikemaps/map/source/model/TileSource.java new file mode 100644 index 0000000..43d38f2 --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/map/source/model/TileSource.java @@ -0,0 +1,51 @@ +package eu.konggdev.strikemaps.map.source.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class TileSource { + public enum TileSourceType { + URL, + TILES, + DATA + } + private TileSourceType type; + private String url; + private String[] tiles; + + private JsonNode data; + + public TileSource(String url) { + this.url = url; + this.type = TileSourceType.URL; + } + + public TileSource(String[] tiles) { + this.tiles = tiles; + this.type = TileSourceType.TILES; + } + + public TileSource(JsonNode data) { + this.data = data; + this.type = TileSourceType.DATA; + } + + public void makeJson(ObjectMapper mapper, ObjectNode node) { + switch (type) { + case URL -> node.put("url", url); + + case TILES -> { + ArrayNode tilesNode = mapper.createArrayNode(); + + for (String tile : tiles) + tilesNode.add(tile); + + node.set("tiles", tilesNode); + } + + case DATA -> node.set("data", data); + } + } +} diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java b/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java index 0af52ba..e60ab80 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java @@ -1,68 +1,64 @@ package eu.konggdev.strikemaps.map.style; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.data.helper.FileHelper; -import eu.konggdev.strikemaps.map.source.MapSource; - -import java.util.*; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import eu.konggdev.strikemaps.map.style.document.StyleDocument; +import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata; +import eu.konggdev.strikemaps.map.style.options.StyleOptions; public class MapStyle { - //Only local data - public String name; - public Bitmap icon; + public static final class StoredRepresentation { + public final String json; + public final StyleOptions options; - public JsonNode metadata; // everything except layers + sources - public Map sources; - public ArrayNode layerDefinitions; // "layers" array + public final StyleManagementMetadata managementMetadata; - //FIXME - public static MapStyle fromFile(String filename, AppController app) { - String styleContents; - if (filename.startsWith("/storage")) styleContents = FileHelper.loadStringFromUserFile(filename); - else styleContents = FileHelper.loadStringFromAssetFile(filename, app); + public StoredRepresentation(String json, StyleOptions options, StyleManagementMetadata managementMetadata) { + this.json = json; + this.options = options; + this.managementMetadata = managementMetadata; + } - ObjectMapper mapper = new ObjectMapper(); - try { - JsonNode root = mapper.readTree(styleContents); - - MapStyle style = new MapStyle(); - style.name = root.path("name").asText(); - style.icon = getIcon(root.path("icon").asText(), app); - - style.sources = mapper.convertValue( - root.path("sources"), - new TypeReference>() {} + public MapStyle restore() { + MapStyle style = new MapStyle( + json, + options, + managementMetadata ); - style.layerDefinitions = root.withArray("layers"); - - ObjectNode metadata = root.deepCopy(); - metadata.remove("layers"); - metadata.remove("sources"); - style.metadata = metadata; - return style; - } catch (Exception e) { - e.printStackTrace(); } - return null; } - public static Bitmap getIcon(String iconLocator, AppController app) { - switch(iconLocator.split("//")[0]) { - //TODO: https - case "assets:": - return BitmapFactory.decodeStream(FileHelper.openAssetStream("bundled/icon/" + iconLocator.split("//")[1], app)); - default: - app.logcat("Unimplemented icon locator space: " + iconLocator); - return null; - } + @NonNull public final StyleDocument document; + @NonNull public final StyleOptions options; + + // Null when the style is not managed + @Nullable public StyleManagementMetadata managementMetadata; + + // Original json representation of the style document, as we got it + @NonNull public final String json; + + + public MapStyle(@NonNull String json, @NonNull StyleOptions styleOptions, @Nullable StyleManagementMetadata managementMetadata) { + this.json = json; + this.document = new StyleDocument(json); + this.options = styleOptions; + this.managementMetadata = managementMetadata; + } + + public MapStyle(@NonNull String json, @NonNull StyleDocument style, @NonNull StyleOptions styleOptions, @Nullable StyleManagementMetadata managementMetadata) { + this.json = json; + this.document = style; + this.options = styleOptions; + this.managementMetadata = managementMetadata; + } + + public StoredRepresentation makeStoredRepresentation() { + return new StoredRepresentation(json, options, managementMetadata); + } + + public StyleDocument effectiveDocument() { + return document.effectiveDocument(options); } } diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/style/document/StyleDocument.java b/app/src/main/java/eu/konggdev/strikemaps/map/style/document/StyleDocument.java new file mode 100644 index 0000000..ed052d6 --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/map/style/document/StyleDocument.java @@ -0,0 +1,88 @@ +package eu.konggdev.strikemaps.map.style.document; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import eu.konggdev.strikemaps.app.AppController; +import eu.konggdev.strikemaps.helper.FileHelper; +import eu.konggdev.strikemaps.map.source.MapSource; +import eu.konggdev.strikemaps.map.style.options.StyleOptions; + +import java.util.ArrayList; +import java.util.List; + +public class StyleDocument { + + //Only local data + public String name; + public String icon; + + public JsonNode metadata; // everything except layers + sources + public List sources; + public ArrayNode layerDefinitions; // "layers" array + + // Json constructor + public StyleDocument(String json) { + ObjectMapper mapper = new ObjectMapper(); + + try { + JsonNode root = mapper.readTree(json); + + this.name = root.path("name").asText(); + this.icon = root.path("icon").asText(); + + JsonNode jsonSources = root.path("sources"); + List sources = new ArrayList<>(); + jsonSources.fields().forEachRemaining(entry -> { + sources.add(MapSource.fromJson(MapSource.MapSourceContractType.REQUEST, entry.getKey(), entry.getValue())); + }); + this.sources = sources; + + this.layerDefinitions = root.withArray("layers"); + + ObjectNode metadata = root.deepCopy(); + metadata.remove("layers"); + metadata.remove("sources"); + this.metadata = metadata; + + } catch (Exception e) { + throw new IllegalArgumentException("Invalid style document", e); + } + } + + // Copy constructor + public StyleDocument(StyleDocument other) { + this.name = other.name; + this.icon = other.icon; + this.metadata = other.metadata.deepCopy(); + this.sources = new ArrayList<>(other.sources); + this.layerDefinitions = other.layerDefinitions.deepCopy(); + } + + // The style that is presented to the renderer, with its options applied + public StyleDocument effectiveDocument(StyleOptions options) { + StyleDocument result = new StyleDocument(this); //Copy + for (JsonNode layer : result.layerDefinitions) { + JsonNode option = layer.get("option"); + + if (option == null) + continue; + + if ("enable".equals(option.path("type").asText())) { + String id = layer.path("id").asText(); + + boolean enabled = options.getBoolean( + id, + option.path("default").asBoolean(true) + ); + + + } + } + + return result; + } +} diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/style/management/StyleManagementMetadata.java b/app/src/main/java/eu/konggdev/strikemaps/map/style/management/StyleManagementMetadata.java new file mode 100644 index 0000000..338a2e2 --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/map/style/management/StyleManagementMetadata.java @@ -0,0 +1,20 @@ +package eu.konggdev.strikemaps.map.style.management; + +public class StyleManagementMetadata { + public boolean modified; + public boolean doUpdates; + public boolean autoUpdate; + + public String source; + public String sourceHash; + + public StyleManagementMetadata() { } + + public StyleManagementMetadata(boolean modified, boolean doUpdates, boolean autoUpdate, String source, String sourceHash) { + this.modified = modified; + this.doUpdates = doUpdates; + this.autoUpdate = autoUpdate; + this.source = source; + this.sourceHash = sourceHash; + } +} diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/style/options/StyleOptions.java b/app/src/main/java/eu/konggdev/strikemaps/map/style/options/StyleOptions.java new file mode 100644 index 0000000..19cdcb4 --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/map/style/options/StyleOptions.java @@ -0,0 +1,30 @@ +package eu.konggdev.strikemaps.map.style.options; + +import eu.konggdev.strikemaps.map.source.MapSource; + +import java.util.HashMap; +import java.util.Map; + +public class StyleOptions { + private Map values; + + public StyleOptions() { + this.values = new HashMap<>(); + } + + public StyleOptions(Map values) { + this.values = values; + } + + public boolean getBoolean(String id, boolean defaultValue) { + Object value = values.get(id); + return value instanceof Boolean ? (Boolean) value : defaultValue; + } + + public int getInteger(String id, int defaultValue) { + Object value = values.get(id); + return value instanceof Number + ? ((Number) value).intValue() + : defaultValue; + } +} diff --git a/app/src/main/java/eu/konggdev/strikemaps/storage/RegistryStorageComponent.java b/app/src/main/java/eu/konggdev/strikemaps/storage/RegistryStorageComponent.java new file mode 100644 index 0000000..598be48 --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/storage/RegistryStorageComponent.java @@ -0,0 +1,74 @@ +package eu.konggdev.strikemaps.storage; + +import eu.konggdev.strikemaps.Component; +import eu.konggdev.strikemaps.app.AppController; +import eu.konggdev.strikemaps.map.source.MapSource; +import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.helper.UserPrefsHelper; + +import java.util.Map; + +public class RegistryStorageComponent implements Component { + private AppController app; + + private Map styles; + + private Map sources; + + public RegistryStorageComponent(AppController app) { + this.app = app; + styles(); + } + + private Map sources() { + if (sources == null) sources = UserPrefsHelper.sources(app.getPrefs()); + return sources; + } + + public Map getSources() { + return sources(); + } + + private Map styles() { + if (styles == null) styles = UserPrefsHelper.styles(app.getPrefs(), app); + return styles; + } + + public Map getStyles() { + return styles(); + } + + public MapStyle getStyle(Integer id) { + return styles().get(id); + } + + public int addStyle(MapStyle style) { + Map styles = styles(); + int id = styles.keySet().stream() + .mapToInt(Integer::intValue) + .max() + .orElse(-1) + 1; + + styles.put(id, style); + save(); + return id; + } + + public void updateStyle(int id, MapStyle style) { + styles().put(id, style); + save(); + } + + public void deleteStyle(int id) { + styles().remove(id); + save(); + } + + private void save() { + UserPrefsHelper.styles(app.getPrefs(), styles); + } + + public void checkForUpdates() { + // + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java index 7227f83..4a66cb2 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java @@ -7,8 +7,10 @@ import android.widget.TextView; import androidx.annotation.NonNull; import eu.konggdev.strikemaps.R; +import eu.konggdev.strikemaps.app.AppController; +import eu.konggdev.strikemaps.helper.FileHelper; import eu.konggdev.strikemaps.map.MapComponent; -import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.style.document.StyleDocument; import eu.konggdev.strikemaps.ui.UIComponent; public class GenericItem implements UIItem { @@ -53,19 +55,20 @@ public class GenericItem implements UIItem { hasImage = true; } - public final static GenericItem fromStyle(MapStyle style, MapComponent map, Runnable onClick) { + public static GenericItem fromStyle(StyleDocument style, AppController app, Runnable onClick) { if(style == null) return new GenericItem("Unknown"); if(style.icon != null) - return new GenericItem(style.name, style.icon, onClick); + return new GenericItem(style.name, FileHelper.getIcon(style.icon, app), onClick); return new GenericItem(style.name, onClick); } - public final static GenericItem fromStyle(MapStyle style, MapComponent map, Runnable onClick, Runnable onLongClick) { + public static GenericItem fromStyle(StyleDocument style, AppController app, Runnable onClick, Runnable onLongClick) { if(style == null) return new GenericItem("Unknown"); if(style.icon != null) - return new GenericItem(style.name, style.icon, onClick, onLongClick); + return new GenericItem(style.name, FileHelper.getIcon(style.icon, app), onClick, onLongClick); return new GenericItem(style.name, onClick, onLongClick); } + @Override public View makeView(UIComponent spawner) { View v = spawner.inflateUi(R.layout.item_generic); //FIXME: These shouldn't be casted like that! diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/InlineItem.java b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/InlineItem.java index d63e586..a963216 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/InlineItem.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/InlineItem.java @@ -3,6 +3,7 @@ package eu.konggdev.strikemaps.ui.element.item; import android.view.View; import android.widget.TextView; import eu.konggdev.strikemaps.R; +import eu.konggdev.strikemaps.map.source.MapSource; import eu.konggdev.strikemaps.ui.UIComponent; public class InlineItem implements UIItem { @@ -18,6 +19,15 @@ public class InlineItem implements UIItem { this.onClick = onClick; } + public static InlineItem fromSource(MapSource source) { + return new InlineItem(source.name); + } + + public static InlineItem fromSource(MapSource source, Runnable onClick) { + return new InlineItem(source.name, onClick); + } + + @Override public View makeView(UIComponent spawner) { View view = spawner.inflateUi(R.layout.item_inline); diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/PreviewItem.java b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/PreviewItem.java index 329d87c..d8abf37 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/PreviewItem.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/PreviewItem.java @@ -29,6 +29,8 @@ public class PreviewItem implements UIItem { public static PreviewItem fromFeature(Feature feature) { return new PreviewItem(feature.getStringProperty("name"), feature.getStringProperty("class")); } + + @Override public View makeView(UIComponent spawner) { View view = spawner.inflateUi(R.layout.item_preview); ((TextView) view.findViewById(R.id.choiceName)).setText(name); @@ -36,6 +38,7 @@ public class PreviewItem implements UIItem { ((TextView) view.findViewById(R.id.details)).setText(details); return view; } + public View makeView(UIComponent spawner, View.OnClickListener onClick) { View view = makeView(spawner); view.setOnClickListener(onClick); diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/SelectionItem.java b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/SelectionItem.java new file mode 100644 index 0000000..5c612ec --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/SelectionItem.java @@ -0,0 +1,12 @@ +package eu.konggdev.strikemaps.ui.element.item; + +import android.view.View; +import eu.konggdev.strikemaps.ui.UIComponent; + +public class SelectionItem implements UIItem { + + @Override + public View makeView(UIComponent spawner) { + return null; + } +} diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/factory/AlertDialogFactory.java b/app/src/main/java/eu/konggdev/strikemaps/ui/factory/AlertDialogFactory.java index 0409ef5..f6a9741 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/factory/AlertDialogFactory.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/factory/AlertDialogFactory.java @@ -1,64 +1,38 @@ package eu.konggdev.strikemaps.ui.factory; import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; -import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.KeyEvent; import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.util.JsonPatcher; -import eu.konggdev.strikemaps.data.helper.FileHelper; import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.style.options.StyleOptions; import eu.konggdev.strikemaps.ui.UIComponent; -import eu.konggdev.strikemaps.ui.element.item.GenericItem; import eu.konggdev.strikemaps.ui.element.item.PreviewItem; import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup; import org.maplibre.geojson.Feature; -import java.io.IOException; -import java.util.Arrays; import java.util.List; -import java.util.Locale; import java.util.function.Consumer; -import static androidx.core.content.ContextCompat.getSystemService; -//FIXME: Move Item functions into specific classes for specific types - e.g. StyleItem +//FIXME: Cleaner architecture would be having a class for each AlertDialog type public final class AlertDialogFactory { public static AlertDialog copyBuiltInStyle(AppController app, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup) { - //TODO: Use an UI element thats supposed to be vertical, instead of GenericItem - List styles = Arrays.asList(FileHelper.getAssetFiles("bundled/style", ".style.json", app)); + //TODO: Use an UI element that's supposed to be vertical, instead of GenericItem LinearLayout container = new LinearLayout(app.getActivity()); container.setOrientation(LinearLayout.VERTICAL); - for (String style : styles) { - View itemView = GenericItem - .fromStyle( - MapStyle.fromFile(style, app), - map, - () -> ui.alert(AlertDialogFactory.createStyle(app, FileHelper.loadStringFromAssetFile(style, app), mapChangePopup)) - ) - .makeView(ui); - - container.addView(itemView); - } - ScrollView scrollView = new ScrollView(app.getActivity()); scrollView.addView(container); @@ -75,15 +49,6 @@ public final class AlertDialogFactory { final EditText nameInput = new EditText(app.getActivity()); nameInput.setHint("Name"); - final CheckBox inferFileName = new CheckBox(app.getActivity()); - inferFileName.setText("Infer filename automatically"); - inferFileName.setChecked(true); - - final EditText fileInput = new EditText(app.getActivity()); - fileInput.setHint("Filename"); - - final TextView inferedFileName = new TextView(app.getActivity()); - inferedFileName.setPadding(0, 2, 0, 0); LinearLayout container = new LinearLayout(app.getActivity()); container.setOrientation(LinearLayout.VERTICAL); @@ -91,38 +56,6 @@ public final class AlertDialogFactory { container.setPadding(padding, padding, padding, 0); container.addView(nameInput); - container.addView(inferFileName); - container.addView(fileInput); - container.addView(inferedFileName); - - inferFileName.setOnCheckedChangeListener((buttonView, isChecked) -> { - fileInput.setVisibility(isChecked ? View.GONE : View.VISIBLE); - inferedFileName.setVisibility(isChecked ? View.VISIBLE : View.GONE); - }); - - fileInput.setVisibility(inferFileName.isChecked() ? View.GONE : View.VISIBLE); - inferedFileName.setVisibility(inferFileName.isChecked() ? View.VISIBLE : View.GONE); - - nameInput.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - String nameText = ""; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - if (!s.isEmpty() && s != null) { - nameText = "File name will be: " + s.toString().toLowerCase(Locale.ROOT) + ".style.json"; - } - } else if (s != null) { - nameText = "File name will be: " + s.toString().toLowerCase(Locale.ROOT) + ".style.json"; - } - - inferedFileName.setText(nameText); - } - @Override - public void afterTextChanged(Editable s) {} - }); AlertDialog dialog = new AlertDialog.Builder(app.getActivity()) .setTitle("Create") @@ -160,21 +93,6 @@ public final class AlertDialogFactory { assert !name.isEmpty(); - String fileName; - if (inferFileName.isChecked()) { - fileName = name.toLowerCase(Locale.ROOT) + ".style.json"; - } else { - fileName = fileInput.getText().toString(); - if (fileName.isEmpty()) { - fileInput.setError("File name required"); - return; - } - - if (!fileName.endsWith(".style.json")) { - fileInput.setError("File must end with .style.json"); - return; - } - } try { JsonNode root; @@ -194,12 +112,12 @@ public final class AlertDialogFactory { root = JsonPatcher.patch(root, node); } - if (FileHelper.userFileExists("style", fileName, app)) { - app.getUi().alert(askUserOverwriteFile(app, fileName, "style", mapper.writeValueAsString(root), dialog, mapChangePopup)); - } else { - FileHelper.writeUserFile("style", fileName, mapper.writeValueAsString(root), app); - } - dialog.dismiss(); + app.getRegistry().addStyle(new MapStyle( + mapper.writeValueAsString(root), + new StyleOptions(), + null + )); + dialog.dismiss(); } catch (Exception e) { Toast.makeText(app.getActivity(), "Failed to create", Toast.LENGTH_SHORT).show(); e.printStackTrace(); @@ -212,46 +130,6 @@ public final class AlertDialogFactory { return dialog; } - public static AlertDialog askUserOverwriteFile(AppController app, String fileName, String path, String content, FragmentMapChangePopup mapChangePopup) { - return new AlertDialog.Builder(app.getActivity()) - .setMessage("Style of filename " + fileName + " already exists, do you wish to overwrite it?") - .setPositiveButton("Yes", (dialog, which) -> { - try { - FileHelper.writeUserFile(path, fileName, content, app); - } catch (Exception e) { - e.printStackTrace(); - } - mapChangePopup.reloadStyles(); - }) - .setNegativeButton("No", null) - .create(); - } - - public static AlertDialog askUserOverwriteFile(AppController app, String fileName, String path, String content, AlertDialog originDialog, FragmentMapChangePopup mapChangePopup) { - return new AlertDialog.Builder(app.getActivity()) - .setMessage("Style of filename: " + fileName + " already exists, do you wish to overwrite it?") - .setPositiveButton("Yes", (dialog, which) -> { - try { - FileHelper.writeUserFile(path, fileName, content, app); - } catch (IOException e) { - e.printStackTrace(); - } - - if (originDialog != null) { - originDialog.dismiss(); - } - mapChangePopup.reloadStyles(); - dialog.dismiss(); - }) - .setNegativeButton("No", (dialog, which) -> { - if (originDialog != null) { - originDialog.dismiss(); - } - dialog.dismiss(); - }) - .create(); - } - public static AlertDialog pointSelector(AppController app, List features, Consumer callback) { LinearLayout layout = new LinearLayout(app.getActivity()); layout.setOrientation(LinearLayout.VERTICAL); diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/NewStyleBottomSheet.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/NewStyleBottomSheet.java index f19328b..2172442 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/NewStyleBottomSheet.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/NewStyleBottomSheet.java @@ -18,8 +18,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.card.MaterialCardView; import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.app.util.helper.FileHelper; import eu.konggdev.strikemaps.map.MapComponent; +import eu.konggdev.strikemaps.map.style.MapStyle; +import eu.konggdev.strikemaps.map.style.options.StyleOptions; import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory; import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup; @@ -35,7 +36,6 @@ public class NewStyleBottomSheet extends BottomSheetDialogFragment { @NonNull FragmentMapChangePopup mapChangePopup; - private final ActivityResultLauncher importLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), @@ -79,17 +79,15 @@ public class NewStyleBottomSheet extends BottomSheetDialogFragment { mapChangePopup ) ); - - return; } else { - if (!FileHelper.userFileExists("style", fileName, app)) { - FileHelper.writeUserFile("style", fileName, content, app); - } else { - app.getUi().alert(AlertDialogFactory.askUserOverwriteFile(app, fileName, "style", content, mapChangePopup)); - } - + app.getRegistry().addStyle( + new MapStyle( + content, + new StyleOptions(), + null + ) + ); mapChangePopup.reloadStyles(); - return; } } } @@ -119,9 +117,7 @@ public class NewStyleBottomSheet extends BottomSheetDialogFragment { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.dialog_new_style, container, false); diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/StyleDetailsBottomSheet.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/StyleDetailsBottomSheet.java index f367f54..118fb0f 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/StyleDetailsBottomSheet.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/dialog/StyleDetailsBottomSheet.java @@ -16,7 +16,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.card.MaterialCardView; import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.app.util.helper.FileHelper; import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.ui.UIComponent; @@ -25,7 +24,6 @@ import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup; import java.io.IOException; import java.io.OutputStream; -import java.util.Objects; public class StyleDetailsBottomSheet extends BottomSheetDialogFragment { @NonNull @@ -35,82 +33,70 @@ public class StyleDetailsBottomSheet extends BottomSheetDialogFragment { @NonNull UIComponent ui; @NonNull - FragmentMapChangePopup mapChangePopup; + final FragmentMapChangePopup mapChangePopup; private final MapStyle style; - private final String stylePath; + private final Integer id; - private final ActivityResultLauncher exportLauncher = - registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { + private ActivityResultLauncher exportLauncher; - if (result.getResultCode() == android.app.Activity.RESULT_OK - && result.getData() != null) { - - Uri uri = result.getData().getData(); - - try (OutputStream out = - requireContext() - .getContentResolver() - .openOutputStream(uri)) { - - out.write(getContents().getBytes()); - - } catch (IOException e) { - e.printStackTrace(); - } - } - } - ); - - //Action definitions - //*// void deleteStyle() { - boolean deleted = FileHelper.deleteFile(stylePath); - if (!deleted) { - Toast.makeText(requireContext(), "Failed deleting style", Toast.LENGTH_SHORT).show(); - return; - } + app.getRegistry().deleteStyle(id); mapChangePopup.reloadStyles(); dismiss(); } - - private void showExportDialog(String fileName) { + private void showExportDialog() { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.setType("application/json"); - intent.putExtra(Intent.EXTRA_TITLE, fileName); exportLauncher.launch(intent); } - //*// - String getContents() { - if (stylePath.startsWith("/storage")) return FileHelper.loadStringFromUserFile(stylePath); - else return FileHelper.loadStringFromAssetFile(stylePath, app); - } - - public StyleDetailsBottomSheet(AppController app, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup, MapStyle style, String stylePath) { + public StyleDetailsBottomSheet(AppController app, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup, MapStyle style, Integer id) { this.app = app; this.map = map; this.ui = ui; this.mapChangePopup = mapChangePopup; this.style = style; - this.stylePath = stylePath; + this.id = id; } - @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + exportLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == android.app.Activity.RESULT_OK + && result.getData() != null) { + + Uri uri = result.getData().getData(); + + try (OutputStream out = + requireContext() + .getContentResolver() + .openOutputStream(uri)) { + + if (out != null) { + out.write(style.json.getBytes()); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + } + ); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.dialog_style_details, container, false); TextView styleNameView = view.findViewById(R.id.styleName); - TextView fileNameView = view.findViewById(R.id.fileName); TextView styleTypeView = view.findViewById(R.id.styleType); TextView builtInStyleAlert = view.findViewById(R.id.builtInStyleAlert); @@ -120,27 +106,17 @@ public class StyleDetailsBottomSheet extends BottomSheetDialogFragment { MaterialCardView deleteButtonLayout = view.findViewById(R.id.deleteButton); MaterialCardView closeButtonLayout = view.findViewById(R.id.closeButton); + styleNameView.setText(style.document.name); - styleNameView.setText(style.name); - - String[] pathSplit = stylePath.split("/"); - String fileName = pathSplit[pathSplit.length - 1]; - - fileNameView.setText(fileName); - - if (Objects.equals(pathSplit[0], "bundled") && pathSplit.length > 1) { + if (style.managementMetadata != null) { styleTypeView.setText("Built-In Style"); - fileNameView.setVisibility(View.GONE); - editButtonLayout.setVisibility(View.GONE); - builtInStyleAlert.setVisibility(View.VISIBLE); - deleteButtonLayout.setVisibility(View.GONE); } else { styleTypeView.setText("User Style"); } - editButtonLayout.setOnClickListener(v -> Toast.makeText(requireContext(), "Editor not implemented yet\nWait for release", Toast.LENGTH_SHORT).show()); - copyButtonLayout.setOnClickListener(v -> ui.alert(AlertDialogFactory.createStyle(app, getContents(), mapChangePopup))); - exportButtonLayout.setOnClickListener(v -> showExportDialog(fileName)); + editButtonLayout.setOnClickListener(v -> Toast.makeText(requireContext(), "Editor not implemented yet\nWait for release", Toast.LENGTH_SHORT).show()); + copyButtonLayout.setOnClickListener(v -> ui.alert(AlertDialogFactory.createStyle(app, style.json, mapChangePopup))); + exportButtonLayout.setOnClickListener(v -> showExportDialog()); deleteButtonLayout.setOnClickListener(v -> deleteStyle()); closeButtonLayout.setOnClickListener(v -> dismiss()); diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutControls.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutControls.java index 6200045..6537655 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutControls.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutControls.java @@ -15,7 +15,7 @@ import android.widget.Toast; import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.data.helper.UserPrefsHelper; +import eu.konggdev.strikemaps.helper.UserPrefsHelper; import eu.konggdev.strikemaps.map.overlay.implementation.LocationOverlay; import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup; diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutSearch.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutSearch.java index 7b0f5b6..ecd22ae 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutSearch.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/FragmentLayoutSearch.java @@ -39,8 +39,6 @@ public class FragmentLayoutSearch extends Fragment implements Layout { public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - - setupButton(view, R.id.hamburgerButton, click(() -> { View menuView = getLayoutInflater().inflate(R.layout.dropdown_main, null); @@ -80,43 +78,6 @@ public class FragmentLayoutSearch extends Fragment implements Layout { int popupWidth = menuView.getMeasuredWidth(); int containerWidth = anchor.getWidth(); - int xOffset = containerWidth - popupWidth; - popupWindow.showAsDropDown(anchor, xOffset, 1); - }); - - })); - - setupButton(view, R.id.offlineMapsButton, click(() -> { - View offlineMapsPopupView = getLayoutInflater().inflate(R.layout.dropdown_offline, null); - - PopupWindow popupWindow = new PopupWindow( - offlineMapsPopupView, - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - true - ); - - popupWindow.setOutsideTouchable(true); - popupWindow.setFocusable(true); - popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - - View anchor = view.findViewById(R.id.searchContainer); - - setupButton(offlineMapsPopupView, R.id.offlineMaps, click(() -> { - popupWindow.dismiss(); - app.getUi().swapScreen(DefinedScreen.OFFLINE); - })); - - anchor.post(() -> { - - offlineMapsPopupView.measure( - View.MeasureSpec.UNSPECIFIED, - View.MeasureSpec.UNSPECIFIED - ); - - int popupWidth = offlineMapsPopupView.getMeasuredWidth(); - int containerWidth = anchor.getWidth(); - int xOffset = containerWidth - popupWidth; popupWindow.showAsDropDown(anchor, xOffset, 1); }); diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentOfflineMaps.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentOfflineMaps.java index d37e30f..191e5ce 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentOfflineMaps.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentOfflineMaps.java @@ -2,23 +2,10 @@ package eu.konggdev.strikemaps.ui.fragment.layout.content.main; import android.os.Bundle; import android.view.View; -import android.widget.LinearLayout; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.data.helper.FileHelper; -import eu.konggdev.strikemaps.map.source.MapSource; -import eu.konggdev.strikemaps.map.style.MapStyle; -import eu.konggdev.strikemaps.ui.element.item.GenericItem; -import eu.konggdev.strikemaps.ui.element.item.InlineItem; -import eu.konggdev.strikemaps.ui.element.item.PreviewItem; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; public class FragmentLayoutContentOfflineMaps extends Fragment implements MainContentLayout { @NonNull AppController app; @@ -37,24 +24,9 @@ public class FragmentLayoutContentOfflineMaps extends Fragment implements MainCo public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - List sources = new ArrayList<>(); - List stylePaths = new ArrayList<>(); - stylePaths.addAll(Arrays.asList(FileHelper.getAssetFiles("bundled/style", ".style.json", app))); - stylePaths.addAll(Arrays.asList(FileHelper.getUserFiles("style", ".style.json", app))); - /* Parsing an entire MapStyle is absolutely unnecessary and resource wasteful - TODO: A method should be implemented to parse a List directly from the JSON */ - for (String stylePath : stylePaths) { - MapStyle parsedStyle = MapStyle.fromFile(stylePath, app); - sources.addAll(parsedStyle.sources.values()); - } - - LinearLayout sourcesLayout = view.findViewById(R.id.llDownloadContainer); - for (MapSource source : sources) { - if (!Objects.equals(source.type, "raster")) - sourcesLayout.addView(new InlineItem(source.url, () -> Toast.makeText(app.getActivity(), "Work in progress", Toast.LENGTH_SHORT).show()).makeView(app.getUi())); - else - sourcesLayout.addView(new InlineItem(source.tiles.toString(), () -> Toast.makeText(app.getActivity(), "Work in progress", Toast.LENGTH_SHORT).show()).makeView(app.getUi())); - } +// LinearLayout sourcesLayout = view.findViewById(R.id.llDownloadContainer); +// for (MapSource source : sources) +// sourcesLayout.addView(new InlineItem(source.name, () -> Toast.makeText(app.getActivity(), "Work in progress", Toast.LENGTH_SHORT).show()).makeView(app.getUi())); } } \ No newline at end of file diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentSettings.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentSettings.java index 39373bd..236b8a9 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentSettings.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/layout/content/main/FragmentLayoutContentSettings.java @@ -1,6 +1,5 @@ package eu.konggdev.strikemaps.ui.fragment.layout.content.main; -import android.app.ActionBar; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; @@ -8,17 +7,12 @@ import android.widget.Spinner; import android.widget.ArrayAdapter; import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; -import com.google.android.material.appbar.MaterialToolbar; import eu.konggdev.strikemaps.R; -import eu.konggdev.strikemaps.data.helper.UserPrefsHelper; +import eu.konggdev.strikemaps.helper.UserPrefsHelper; import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory; -import java.util.Arrays; - public class FragmentLayoutContentSettings extends Fragment implements MainContentLayout { @NonNull AppController app; diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java index 433d172..954a94b 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java @@ -1,17 +1,20 @@ package eu.konggdev.strikemaps.ui.fragment.popup; +import android.annotation.SuppressLint; +import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.os.Bundle; import android.view.View; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import android.widget.LinearLayout; import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.data.helper.FileHelper; import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.style.MapStyle; @@ -20,10 +23,7 @@ import eu.konggdev.strikemaps.ui.fragment.dialog.NewStyleBottomSheet; import eu.konggdev.strikemaps.ui.fragment.dialog.StyleDetailsBottomSheet; import eu.konggdev.strikemaps.ui.element.item.GenericItem; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - +import java.util.Map; public class FragmentMapChangePopup extends Fragment implements Popup { @NonNull AppController app; @@ -34,16 +34,65 @@ public class FragmentMapChangePopup extends Fragment implements Popup { private View view; - // Action definitions - //*// + Map tabs = Map.of( + R.id.stylesTab, R.id.styles, + R.id.optionsTab, R.id.options, + R.id.overlaysTab, R.id.overlays + ); + + Map tabLoadActions = Map.of( + R.id.stylesTab, this::reloadStyles, + R.id.optionsTab, this::loadStyleOptions, + R.id.overlaysTab, this::reloadOverlays + ); + + public void reloadStyles() { + LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout); + stylesLayout.removeAllViews(); + app.getRegistry().getStyles().forEach((id, style) -> + stylesLayout.addView(GenericItem.fromStyle(style.document, app, + () -> map.setStyle(style), + () -> this.styleDetails(style, id)).makeView(ui)) + ); + Bitmap addNewIcon = BitmapFactory.decodeResource(app.getActivity().getResources(), android.R.drawable.ic_menu_add); + stylesLayout.addView(new GenericItem("", + addNewIcon, + this::newStyleFlow).makeView(ui)); + } + + public void loadStyleOptions() { + LinearLayout optionsLayout = view.findViewById(R.id.optionsLayout); +// optionsLayout.removeAllViews(); +// TextView sources = new AppCompatTextView(requireContext()) {{ +// setText("Sources"); +// setTextSize(16); +// setLayoutParams(new ViewGroup.MarginLayoutParams( +// ViewGroup.LayoutParams.WRAP_CONTENT, +// ViewGroup.LayoutParams.WRAP_CONTENT +// ) {{ leftMargin = 16; }}); +// }}; +// optionsLayout.addView(sources); + +// for (MapSource source : map.style.sources) { +// LinearLayout sourceSelectionLayout = new LinearLayout(requireContext()); +// TextView sourceName = new TextView(requireContext()); +// sourceName.setText(source.name); +// sourceSelectionLayout.addView(sourceName); +// optionsLayout.addView(sourceSelectionLayout); +// } + } + + public void reloadOverlays() { + + } + void newStyleFlow() { new NewStyleBottomSheet(app, map, ui, this).show(app.getActivity().getSupportFragmentManager(), "NewStyleBottomSheet"); } - void styleDetails(MapStyle style, String stylePath) { - new StyleDetailsBottomSheet(app, map, ui, this, style, stylePath).show(app.getActivity().getSupportFragmentManager(), "StyleDetailsBottomSheet"); + void styleDetails(MapStyle entry, Integer id) { + new StyleDetailsBottomSheet(app, map, ui, this, entry, id).show(app.getActivity().getSupportFragmentManager(), "StyleDetailsBottomSheet"); } - //*// public FragmentMapChangePopup(AppController app, Integer region) { super(R.layout.popup_map_change); @@ -63,30 +112,30 @@ public class FragmentMapChangePopup extends Fragment implements Popup { return this; } - public void reloadStyles() { - List stylePaths = new ArrayList<>(); - stylePaths.addAll(Arrays.asList(FileHelper.getAssetFiles("bundled/style", ".style.json", app))); - stylePaths.addAll(Arrays.asList(FileHelper.getUserFiles("style", ".style.json", app))); - LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout); - stylesLayout.removeAllViews(); - for (String stylePath : stylePaths) { - MapStyle parsedStyle = MapStyle.fromFile(stylePath, app); - stylesLayout.addView(GenericItem.fromStyle(parsedStyle, map, - () -> map.setStyle(parsedStyle), - () -> this.styleDetails(parsedStyle, stylePath)).makeView(ui)); - } - Bitmap addNewIcon = BitmapFactory.decodeResource(app.getActivity().getResources(), android.R.drawable.ic_menu_add); - stylesLayout.addView(new GenericItem("", - addNewIcon, - this::newStyleFlow).makeView(ui)); + @SuppressLint("UseCompatTextViewDrawableApis") + void switchTab(int target) { + tabs.forEach((button, content) -> { + view.findViewById(content).setVisibility(button == target ? View.VISIBLE : View.GONE); + int color = (button == target + ? Color.WHITE + : Color.parseColor("#888888")); + ((TextView) view.findViewById(button)).setTextColor(color); + ((TextView) view.findViewById(button)).setCompoundDrawableTintList(ColorStateList.valueOf(color)); + }); + tabLoadActions.get(target).run(); } @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { //FIXME + this.view = view; + + tabs.keySet().forEach(button -> { + view.findViewById(button).setOnClickListener(v -> switchTab(button)); + }); + + switchTab(R.id.stylesTab); // Show styles setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup())); setupDragHandle(view, view, () -> ui.getCurrentScreen().closePopup()); - this.view = view; - reloadStyles(); } } diff --git a/app/src/main/res/THIRD_PARTY_NOTICES.md b/app/src/main/res/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..ba5c52d --- /dev/null +++ b/app/src/main/res/THIRD_PARTY_NOTICES.md @@ -0,0 +1,16 @@ +The following files included inside the /res/drawable folder are derived from Google Material Icons and are subject to the Apache License, Version 2.0: + +* `ic_sliders.xml` +* `ic_bubble.xml` +* `ic_stack.xml` +* `ic_clarify.xml` +* +The following files included inside the /res/drawable folder are copies of Google Material Icons and are subject to the Apache License, Version 2.0: + +* `ic_file.xml` +* `ic_offline.xml` +* `ic_arrow_forward.xml` +* `ic_hide.bmp` +* `ic_dropdown.xml` + +Unless otherwise noted in this file, all other files included in the `res` folder are subject to the standard license of this project \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_forward.xml b/app/src/main/res/drawable/ic_arrow_forward.xml index 91637db..8437cde 100644 --- a/app/src/main/res/drawable/ic_arrow_forward.xml +++ b/app/src/main/res/drawable/ic_arrow_forward.xml @@ -1,10 +1,5 @@ - - - \ No newline at end of file + + + + + diff --git a/app/src/main/res/drawable/ic_bubble.xml b/app/src/main/res/drawable/ic_bubble.xml new file mode 100644 index 0000000..04fbb16 --- /dev/null +++ b/app/src/main/res/drawable/ic_bubble.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_clarify.xml b/app/src/main/res/drawable/ic_clarify.xml new file mode 100644 index 0000000..bd2fd65 --- /dev/null +++ b/app/src/main/res/drawable/ic_clarify.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_copy.bmp b/app/src/main/res/drawable/ic_copy.bmp deleted file mode 100644 index 322fad853e8bddbce2e1df97abd3724d76c84615..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5322 zcmZ?rJte{b23kl0LmZcgJ2*R3jQ-NKoE%k-^o3wxc|^sjldmp zqH?XT8I6sNh11f~)PR_6=FFL#wp`blHDdaWV7fsx3o>Vjvj6}8&jQ4zKwLO!(xm^C zV4xW_KpYRmyyON4P_OI4S+oCx@Z!ZwD8ewm0L`3CuA8Xg4s?Hj+yw;jlXxlyyi3$nGIoGnxiuvp{S#X`;BuW)P(Tk0NBVM%{xMP(wL6O>M>Y%D4s`v<>PFpz9y$yRqv3;^M$toPG<-(G z2R%)I_{ix3#KxuuUEOH;gBeQ5;W!#T!zX+|@k$8>r3F}ez}KE9HvPcNqm%~O2LuV2 zF$EF9=N_OwS6G-(!yQn!Oa+Ti(DG$22g)F0iRnyF2v^^>^`8a x=wNdXdKi%#AL#lh2}SfUqBQM-^i$#c)ZK7#p$LibQDAHm{{688)Sy#VBny%qof diff --git a/app/src/main/res/drawable/ic_copy.xml b/app/src/main/res/drawable/ic_copy.xml new file mode 100644 index 0000000..349c6ff --- /dev/null +++ b/app/src/main/res/drawable/ic_copy.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dropdown.xml b/app/src/main/res/drawable/ic_dropdown.xml new file mode 100644 index 0000000..1dc5231 --- /dev/null +++ b/app/src/main/res/drawable/ic_dropdown.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml new file mode 100644 index 0000000..834c1bc --- /dev/null +++ b/app/src/main/res/drawable/ic_help.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_sliders.xml b/app/src/main/res/drawable/ic_sliders.xml new file mode 100644 index 0000000..b53cdec --- /dev/null +++ b/app/src/main/res/drawable/ic_sliders.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_stack.xml b/app/src/main/res/drawable/ic_stack.xml new file mode 100644 index 0000000..d65626d --- /dev/null +++ b/app/src/main/res/drawable/ic_stack.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_style_details.xml b/app/src/main/res/layout/dialog_style_details.xml index d161eac..b7259a4 100644 --- a/app/src/main/res/layout/dialog_style_details.xml +++ b/app/src/main/res/layout/dialog_style_details.xml @@ -20,19 +20,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - + app:layout_constraintTop_toBottomOf="@id/styleName" /> - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index ae356e6..0e4a736 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -25,17 +25,6 @@ android:queryHint="Search..." android:background="@android:color/transparent" /> - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_map_change.xml b/app/src/main/res/layout/popup_map_change.xml index 01905d4..49bc82e 100644 --- a/app/src/main/res/layout/popup_map_change.xml +++ b/app/src/main/res/layout/popup_map_change.xml @@ -2,6 +2,7 @@ - + app:layout_constraintEnd_toEndOf="parent"> - + + + + + + + + + app:layout_constraintBottom_toBottomOf="parent" > + + - + android:orientation="vertical"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file