Seperate style source fields from MapSource

This commit is contained in:
2026-09-10 03:25:59 +02:00
parent c97c7c24a3
commit 19d0a768fd
14 changed files with 174 additions and 138 deletions
+1
View File
@@ -61,6 +61,7 @@ dependencies {
implementation("com.caverock:androidsvg:1.4") implementation("com.caverock:androidsvg:1.4")
implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0")
implementation("commons-codec:commons-codec:1.17.2")
implementation("org.apache.commons:commons-lang3:3.14.0") implementation("org.apache.commons:commons-lang3:3.14.0")
testImplementation(libs.junit) testImplementation(libs.junit)
@@ -5,12 +5,11 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.source.MapSource; import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.source.MapSource.MapSourceContractType; import eu.konggdev.strikemaps.map.source.tiles.SourceTiles;
import eu.konggdev.strikemaps.map.source.model.TileSource;
import eu.konggdev.strikemaps.map.style.MapStyle; 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.management.StyleManagementMetadata;
import eu.konggdev.strikemaps.map.style.options.StyleOptions; import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import org.apache.commons.codec.digest.DigestUtils;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashMap; import java.util.HashMap;
@@ -36,11 +35,15 @@ public final class UserPrefsHelper {
public static Map<Integer, MapStyle> DEFAULT_STYLES(AppController app) { public static Map<Integer, MapStyle> DEFAULT_STYLES(AppController app) {
Map<Integer, MapStyle> styles = new HashMap<>(); Map<Integer, MapStyle> styles = new HashMap<>();
String[] styleAssets = FileHelper.getAssetFiles("bundled/style", ".style.json", app); String[] styleAssets = FileHelper.getAssetFiles("bundled/style", ".style.json", app);
for (int i = 0; i < styleAssets.length; i++) { styles.put( i, for (int i = 0; i < styleAssets.length; i++) {
String styleContents = FileHelper.loadStringFromAssetFile(styleAssets[i], app);
styles.put( i,
new MapStyle( new MapStyle(
FileHelper.loadStringFromAssetFile(styleAssets[i], app), styleContents,
new StyleOptions(), new StyleOptions(),
new StyleManagementMetadata() new StyleManagementMetadata( //Set the style to automatically update from assets
false, true, true,
"assets://" + styleAssets[i], DigestUtils.sha256Hex(styleContents))
)); ));
} }
return styles; return styles;
@@ -48,16 +51,14 @@ public final class UserPrefsHelper {
private static final Map<Integer, MapSource> DEFAULT_SOURCES = Map.of( private static final Map<Integer, MapSource> DEFAULT_SOURCES = Map.of(
0, new MapSource( 0, new MapSource(
MapSourceContractType.DEFINITION,
"Strike Maps Planet", "Strike Maps Planet",
new TileSource("https://tiles.strikemaps.eu/planet"), new SourceTiles("https://tiles.strikemaps.eu/planet"),
"vector", "vector",
"smts" "smts"
), ),
1, new MapSource( 1, new MapSource(
MapSourceContractType.DEFINITION,
"ArcGIS Imagery", "ArcGIS Imagery",
new TileSource(new String[]{"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"}), new SourceTiles(new String[]{"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"}),
"raster", "raster",
"raster" "raster"
) )
@@ -22,7 +22,8 @@ public class MapComponent implements Component {
private final MapRenderer mapRenderer; private final MapRenderer mapRenderer;
private final AppController app; private final AppController app;
public MapStyle style; public Integer styleId;
public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>(); public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>();
public MapComponent(AppController ref) { public MapComponent(AppController ref) {
@@ -48,9 +49,8 @@ public class MapComponent implements Component {
return new FragmentLayoutContentMap(mapRenderer.getView()); return new FragmentLayoutContentMap(mapRenderer.getView());
} }
public void setStyle(MapStyle style) { public void setStyle(Integer id) {
this.style = style; styleId = id;
mapRenderer.styleUpdate(style.effectiveDocument());
} }
public void switchOverlay(MapOverlay overlay) { public void switchOverlay(MapOverlay overlay) {
@@ -102,9 +102,7 @@ public class MapComponent implements Component {
public void onMapInit() { public void onMapInit() {
setStyle( setStyle(
app.getRegistry().getStyle( UserPrefsHelper.startupMapStyle(app.getPrefs())
UserPrefsHelper.startupMapStyle(app.getPrefs())
)
); );
} }
} }
@@ -17,8 +17,8 @@ import eu.konggdev.strikemaps.app.util.JsonPatcher;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.renderer.MapRenderer; import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.style.document.StyleDocument; import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.style.source.StyleSource;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
@@ -72,8 +72,9 @@ public class MapLibreGLJSRenderer implements MapRenderer {
//Sources //Sources
ObjectNode sources = mapper.createObjectNode(); ObjectNode sources = mapper.createObjectNode();
if (style.sources != null) if (style.sources != null)
for (MapSource source : style.sources) for (StyleSource source : style.sources)
sources.set(source.name, source.makeJson()); sources.set(source.key,
source.current.makeJson());
//Layers //Layers
ArrayNode layers = mapper.createArrayNode(); ArrayNode layers = mapper.createArrayNode();
@@ -10,16 +10,19 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import eu.konggdev.strikemaps.app.util.JsonPatcher; import eu.konggdev.strikemaps.app.util.JsonPatcher;
import eu.konggdev.strikemaps.map.offline.OfflineTileResolver;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.renderer.MapRenderer; import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.style.document.StyleDocument; import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.style.source.StyleSource;
import org.maplibre.android.MapLibre; import org.maplibre.android.MapLibre;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.android.maps.MapLibreMap; import org.maplibre.android.maps.MapLibreMap;
import org.maplibre.android.maps.MapView; import org.maplibre.android.maps.MapView;
import org.maplibre.android.maps.OnMapReadyCallback; import org.maplibre.android.maps.OnMapReadyCallback;
import org.maplibre.android.maps.Style; import org.maplibre.android.maps.Style;
import org.maplibre.android.module.http.HttpRequestUtil;
import org.maplibre.android.offline.OfflineManager;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
import java.util.List; import java.util.List;
@@ -39,7 +42,9 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
this.controller = controller; this.controller = controller;
MapLibre.getInstance(app.getActivity()); MapLibre.getInstance(app.getActivity());
this.mapView = new MapView(app.getActivity()); this.mapView = new MapView(app.getActivity());
//TODO: Investigate if we might want to restore any savedInstanceState for MapLibre
mapView.onCreate(null); mapView.onCreate(null);
//We need this to get the onMapReady callback
mapView.getMapAsync(this); mapView.getMapAsync(this);
} }
@@ -56,8 +61,9 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
//Sources //Sources
ObjectNode sources = mapper.createObjectNode(); ObjectNode sources = mapper.createObjectNode();
if (style.sources != null) if (style.sources != null)
for (MapSource source : style.sources) for (StyleSource source : style.sources)
sources.set(source.name, source.makeJson()); sources.set(source.key,
source.current.makeJson());
//Layers //Layers
ArrayNode layers = mapper.createArrayNode(); ArrayNode layers = mapper.createArrayNode();
@@ -1,52 +1,14 @@
package eu.konggdev.strikemaps.map.source; package eu.konggdev.strikemaps.map.source;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.map.source.model.TileSource; import eu.konggdev.strikemaps.map.source.tiles.SourceTiles;
public class MapSource { public class MapSource {
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; public String name;
/* public SourceTiles tileSource;
* 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 type;
public String schema; public String schema;
@@ -59,41 +21,33 @@ public class MapSource {
public String encoding; public String encoding;
public MapSource(@NonNull MapSourceContractType contract, String name, TileSource tileSource, String type, String schema) { public MapSource(String name, SourceTiles tileSource, String type, String schema) {
this.contract = contract;
this.name = name; this.name = name;
this.tileSource = tileSource; this.tileSource = tileSource;
this.type = type; this.type = type;
this.schema = schema; this.schema = schema;
} }
private MapSource(@NonNull MapSourceContractType contract) { public MapSource(String key, JsonNode sourceNode) {
this.contract = contract; this.name = key;
this.type = sourceNode.path("type").asText(null);
this.schema = sourceNode.path("schema").asText(null);
this.scheme = sourceNode.path("scheme").asText(null);
this.encoding = sourceNode.path("encoding").asText(null);
this.minzoom = sourceNode.path("minZoom").asInt(0);
this.maxzoom = sourceNode.path("maxZoom").asInt(24);
this.tileSize = sourceNode.path("tileSize").asInt(256);
this.tileSource = handleJsonTileSource(sourceNode);
} }
public static MapSource fromJson(MapSourceContractType contract, String key, JsonNode sourceNode) { private static SourceTiles handleJsonTileSource(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 // 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? // In case both are present, we prefer URL over tiles... because I don't know, we just do, m'kay?
if (sourceNode.has("url")) if (sourceNode.has("url"))
return new TileSource(sourceNode.get("url").asText()); return new SourceTiles(sourceNode.get("url").asText());
if (sourceNode.has("tiles")) { if (sourceNode.has("tiles")) {
String[] tiles = new String[0]; String[] tiles = new String[0];
@@ -105,15 +59,12 @@ public class MapSource {
e.printStackTrace(); e.printStackTrace();
} }
return new TileSource(tiles); return new SourceTiles(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 //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 //Maybe throwing some custom exception and propagating it back to the user would be appropriate
return null; return null;
} }
@@ -0,0 +1,14 @@
package eu.konggdev.strikemaps.map.source.properties;
import com.fasterxml.jackson.databind.JsonNode;
public class SourceProperties {
public String type;
public String schema;
// Json constructor
public SourceProperties(JsonNode sourceNode) {
this.type = sourceNode.path("type").asText(null);
this.schema = sourceNode.path("schema").asText(null);
}
}
@@ -1,35 +1,36 @@
package eu.konggdev.strikemaps.map.source.model; package eu.konggdev.strikemaps.map.source.tiles;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
public class TileSource { public class SourceTiles {
public enum TileSourceType { public enum TilesType {
URL, URL,
TILES, TILES,
DATA DATA
} }
private TileSourceType type;
private final TilesType type;
private String url; private String url;
private String[] tiles; private String[] tiles;
private JsonNode data; private JsonNode data;
public TileSource(String url) { public SourceTiles(String url) {
this.url = url; this.url = url;
this.type = TileSourceType.URL; this.type = TilesType.URL;
} }
public TileSource(String[] tiles) { public SourceTiles(String[] tiles) {
this.tiles = tiles; this.tiles = tiles;
this.type = TileSourceType.TILES; this.type = TilesType.TILES;
} }
public TileSource(JsonNode data) { public SourceTiles(JsonNode data) {
this.data = data; this.data = data;
this.type = TileSourceType.DATA; this.type = TilesType.DATA;
} }
public void makeJson(ObjectMapper mapper, ObjectNode node) { public void makeJson(ObjectMapper mapper, ObjectNode node) {
@@ -2,9 +2,11 @@ package eu.konggdev.strikemaps.map.style;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.style.document.StyleDocument; import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata; import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata;
import eu.konggdev.strikemaps.map.style.options.StyleOptions; import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
public class MapStyle { public class MapStyle {
public static final class StoredRepresentation { public static final class StoredRepresentation {
@@ -20,13 +22,11 @@ public class MapStyle {
} }
public MapStyle restore() { public MapStyle restore() {
MapStyle style = new MapStyle( return new MapStyle(
json, json,
options, options,
managementMetadata managementMetadata
); );
return style;
} }
} }
@@ -39,7 +39,6 @@ public class MapStyle {
// Original json representation of the style document, as we got it // Original json representation of the style document, as we got it
@NonNull public final String json; @NonNull public final String json;
public MapStyle(@NonNull String json, @NonNull StyleOptions styleOptions, @Nullable StyleManagementMetadata managementMetadata) { public MapStyle(@NonNull String json, @NonNull StyleOptions styleOptions, @Nullable StyleManagementMetadata managementMetadata) {
this.json = json; this.json = json;
this.document = new StyleDocument(json); this.document = new StyleDocument(json);
@@ -58,7 +57,7 @@ public class MapStyle {
return new StoredRepresentation(json, options, managementMetadata); return new StoredRepresentation(json, options, managementMetadata);
} }
public StyleDocument effectiveDocument() { public StyleDocument effectiveDocument(RegistryStorageComponent registry) {
return document.effectiveDocument(options); return document.effectiveDocument(options, registry);
} }
} }
@@ -1,27 +1,25 @@
package eu.konggdev.strikemaps.map.style.document; 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.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.FileHelper;
import eu.konggdev.strikemaps.map.source.MapSource; import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.style.options.StyleOptions; import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.map.style.source.StyleSource;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class StyleDocument { public class StyleDocument {
//Only local data //Only local data
public String name; public String name;
public String icon; public String icon;
public JsonNode metadata; // everything except layers + sources public JsonNode metadata; // everything except layers + sources
public List<MapSource> sources; public List<StyleSource> sources;
public ArrayNode layerDefinitions; // "layers" array public ArrayNode layerDefinitions; // "layers" array
// Json constructor // Json constructor
@@ -35,9 +33,9 @@ public class StyleDocument {
this.icon = root.path("icon").asText(); this.icon = root.path("icon").asText();
JsonNode jsonSources = root.path("sources"); JsonNode jsonSources = root.path("sources");
List<MapSource> sources = new ArrayList<>(); List<StyleSource> sources = new ArrayList<>();
jsonSources.fields().forEachRemaining(entry -> { jsonSources.fields().forEachRemaining(entry -> {
sources.add(MapSource.fromJson(MapSource.MapSourceContractType.REQUEST, entry.getKey(), entry.getValue())); sources.add(new StyleSource(entry.getKey(), entry.getValue()));
}); });
this.sources = sources; this.sources = sources;
@@ -47,8 +45,8 @@ public class StyleDocument {
metadata.remove("layers"); metadata.remove("layers");
metadata.remove("sources"); metadata.remove("sources");
this.metadata = metadata; this.metadata = metadata;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException("Invalid style document", e); throw new IllegalArgumentException("Invalid style document", e);
} }
} }
@@ -63,8 +61,16 @@ public class StyleDocument {
} }
// The style that is presented to the renderer, with its options applied // The style that is presented to the renderer, with its options applied
public StyleDocument effectiveDocument(StyleOptions options) { public StyleDocument effectiveDocument(StyleOptions options, RegistryStorageComponent registry) {
StyleDocument result = new StyleDocument(this); //Copy StyleDocument result = new StyleDocument(this); //Copy
for (int i = 0; i < sources.size(); i++) {
StyleSource source = sources.get(i);
MapSource effectiveSource = registry.getSource(
options.getInteger(source.key, 0)
);
if (effectiveSource != null) source.current = effectiveSource;
}
for (JsonNode layer : result.layerDefinitions) { for (JsonNode layer : result.layerDefinitions) {
JsonNode option = layer.get("option"); JsonNode option = layer.get("option");
@@ -0,0 +1,28 @@
package eu.konggdev.strikemaps.map.style.source;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.source.properties.SourceProperties;
// Stores a source currently used by a style
// Along with requirements of what source the style needs here
public class StyleSource {
public String key;
// Requirement properties
public SourceProperties properties;
// In an effective style, this is the actual source matched to the
// style's requirements according to user's preferences (StyleOptions).
// Otherwise, this is the fallback source.
// It also preserves the default source defined by the JSON for non-effective style documents
@NonNull public MapSource current;
// Json constructor
public StyleSource(String key, JsonNode sourceNode) {
this.key = key;
this.properties = new SourceProperties(sourceNode);
this.current = new MapSource(key, sourceNode);
}
}
@@ -1,5 +1,6 @@
package eu.konggdev.strikemaps.storage; package eu.konggdev.strikemaps.storage;
import androidx.annotation.Nullable;
import eu.konggdev.strikemaps.Component; import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.source.MapSource; import eu.konggdev.strikemaps.map.source.MapSource;
@@ -8,6 +9,9 @@ import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import java.util.Map; import java.util.Map;
import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata;
import org.apache.commons.codec.digest.DigestUtils;
public class RegistryStorageComponent implements Component { public class RegistryStorageComponent implements Component {
private AppController app; private AppController app;
@@ -29,6 +33,10 @@ public class RegistryStorageComponent implements Component {
return sources(); return sources();
} }
@Nullable public MapSource getSource(Integer id) {
return sources().get(id);
}
private Map<Integer, MapStyle> styles() { private Map<Integer, MapStyle> styles() {
if (styles == null) styles = UserPrefsHelper.styles(app.getPrefs(), app); if (styles == null) styles = UserPrefsHelper.styles(app.getPrefs(), app);
return styles; return styles;
@@ -38,7 +46,7 @@ public class RegistryStorageComponent implements Component {
return styles(); return styles();
} }
public MapStyle getStyle(Integer id) { @Nullable public MapStyle getStyle(Integer id) {
return styles().get(id); return styles().get(id);
} }
@@ -6,6 +6,8 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
@@ -18,6 +20,7 @@ import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory; import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup; import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
@@ -33,15 +36,16 @@ public class StyleDetailsBottomSheet extends BottomSheetDialogFragment {
@NonNull @NonNull
UIComponent ui; UIComponent ui;
@NonNull @NonNull
RegistryStorageComponent registry;
@NonNull
final FragmentMapChangePopup mapChangePopup; final FragmentMapChangePopup mapChangePopup;
private final MapStyle style; private final Integer styleId;
private final Integer id;
private ActivityResultLauncher<Intent> exportLauncher; private ActivityResultLauncher<Intent> exportLauncher;
void deleteStyle() { void deleteStyle() {
app.getRegistry().deleteStyle(id); registry.deleteStyle(styleId);
mapChangePopup.reloadStyles(); mapChangePopup.reloadStyles();
dismiss(); dismiss();
@@ -54,13 +58,13 @@ public class StyleDetailsBottomSheet extends BottomSheetDialogFragment {
exportLauncher.launch(intent); exportLauncher.launch(intent);
} }
public StyleDetailsBottomSheet(AppController app, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup, MapStyle style, Integer id) { public StyleDetailsBottomSheet(AppController app, FragmentMapChangePopup mapChangePopup, Integer styleId) {
this.app = app; this.app = app;
this.map = map; this.map = app.getMap();
this.ui = ui; this.ui = app.getUi();
this.registry = app.getRegistry();
this.mapChangePopup = mapChangePopup; this.mapChangePopup = mapChangePopup;
this.style = style; this.styleId = styleId;
this.id = id;
} }
@Override @Override
@@ -81,7 +85,8 @@ public class StyleDetailsBottomSheet extends BottomSheetDialogFragment {
.openOutputStream(uri)) { .openOutputStream(uri)) {
if (out != null) { if (out != null) {
out.write(style.json.getBytes()); String styleJson = registry.getStyle(styleId).json;
if (styleJson != null) out.write(styleJson.getBytes());
} }
} catch (IOException e) { } catch (IOException e) {
@@ -96,20 +101,27 @@ public class StyleDetailsBottomSheet extends BottomSheetDialogFragment {
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_style_details, container, false); View view = inflater.inflate(R.layout.dialog_style_details, container, false);
MapStyle style = registry.getStyle(styleId);
TextView styleNameView = view.findViewById(R.id.styleName); TextView styleNameView = view.findViewById(R.id.styleName);
LinearLayout styleManagementInfo = view.findViewById(R.id.styleManagementInfo);
TextView styleTypeView = view.findViewById(R.id.styleType); TextView styleTypeView = view.findViewById(R.id.styleType);
ImageView managedStyleIndicator = view.findViewById(R.id.managedStyleIndicator);
TextView builtInStyleAlert = view.findViewById(R.id.builtInStyleAlert);
MaterialCardView editButtonLayout = view.findViewById(R.id.editButton); MaterialCardView editButtonLayout = view.findViewById(R.id.editButton);
MaterialCardView copyButtonLayout = view.findViewById(R.id.copyButton); MaterialCardView copyButtonLayout = view.findViewById(R.id.copyButton);
MaterialCardView exportButtonLayout = view.findViewById(R.id.exportButton); MaterialCardView exportButtonLayout = view.findViewById(R.id.exportButton);
MaterialCardView deleteButtonLayout = view.findViewById(R.id.deleteButton); MaterialCardView deleteButtonLayout = view.findViewById(R.id.deleteButton);
MaterialCardView closeButtonLayout = view.findViewById(R.id.closeButton);
styleNameView.setText(style.document.name); styleNameView.setText(style.document.name);
if (style.managementMetadata != null) { if (style.managementMetadata != null) {
styleTypeView.setText("Built-In Style"); styleTypeView.setText("Built-In Style");
managedStyleIndicator.setVisibility(View.VISIBLE);
styleManagementInfo.setOnClickListener(v ->
ui.alert(
AlertDialogFactory.styleManagementOptions(app, style.managementMetadata)
)
);
} else { } else {
styleTypeView.setText("User Style"); styleTypeView.setText("User Style");
} }
@@ -118,7 +130,6 @@ public class StyleDetailsBottomSheet extends BottomSheetDialogFragment {
copyButtonLayout.setOnClickListener(v -> ui.alert(AlertDialogFactory.createStyle(app, style.json, mapChangePopup))); copyButtonLayout.setOnClickListener(v -> ui.alert(AlertDialogFactory.createStyle(app, style.json, mapChangePopup)));
exportButtonLayout.setOnClickListener(v -> showExportDialog()); exportButtonLayout.setOnClickListener(v -> showExportDialog());
deleteButtonLayout.setOnClickListener(v -> deleteStyle()); deleteButtonLayout.setOnClickListener(v -> deleteStyle());
closeButtonLayout.setOnClickListener(v -> dismiss());
return view; return view;
} }
@@ -18,17 +18,20 @@ import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.fragment.dialog.NewStyleBottomSheet; import eu.konggdev.strikemaps.ui.fragment.dialog.NewStyleBottomSheet;
import eu.konggdev.strikemaps.ui.fragment.dialog.StyleDetailsBottomSheet; import eu.konggdev.strikemaps.ui.fragment.dialog.StyleDetailsBottomSheet;
import eu.konggdev.strikemaps.ui.element.item.GenericItem; import eu.konggdev.strikemaps.ui.element.item.GenericItem;
import java.util.Map; import java.util.Map;
import java.util.Objects;
public class FragmentMapChangePopup extends Fragment implements Popup { public class FragmentMapChangePopup extends Fragment implements Popup {
@NonNull AppController app; @NonNull AppController app;
@NonNull MapComponent map; @NonNull MapComponent map;
@NonNull UIComponent ui; @NonNull UIComponent ui;
@NonNull RegistryStorageComponent registry;
private final Integer region; private final Integer region;
@@ -49,11 +52,13 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
public void reloadStyles() { public void reloadStyles() {
LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout); LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout);
stylesLayout.removeAllViews(); stylesLayout.removeAllViews();
app.getRegistry().getStyles().forEach((id, style) -> app.getRegistry().getStyles().forEach((id, style) -> {
stylesLayout.addView(GenericItem.fromStyle(style.document, app, View item = new GenericItem(
() -> map.setStyle(style), style.document,
() -> this.styleDetails(style, id)).makeView(ui)) app,
); () -> this.setStyle(id),
() -> this.styleDetails(id)).makeView(ui);
});
Bitmap addNewIcon = BitmapFactory.decodeResource(app.getActivity().getResources(), android.R.drawable.ic_menu_add); Bitmap addNewIcon = BitmapFactory.decodeResource(app.getActivity().getResources(), android.R.drawable.ic_menu_add);
stylesLayout.addView(new GenericItem("", stylesLayout.addView(new GenericItem("",
addNewIcon, addNewIcon,
@@ -90,8 +95,13 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
new NewStyleBottomSheet(app, map, ui, this).show(app.getActivity().getSupportFragmentManager(), "NewStyleBottomSheet"); new NewStyleBottomSheet(app, map, ui, this).show(app.getActivity().getSupportFragmentManager(), "NewStyleBottomSheet");
} }
void styleDetails(MapStyle entry, Integer id) { void styleDetails(Integer id) {
new StyleDetailsBottomSheet(app, map, ui, this, entry, id).show(app.getActivity().getSupportFragmentManager(), "StyleDetailsBottomSheet"); new StyleDetailsBottomSheet(app, this, id).show(app.getActivity().getSupportFragmentManager(), "StyleDetailsBottomSheet");
}
void setStyle(Integer id) {
map.setStyle(id);
reloadStyles();
} }
public FragmentMapChangePopup(AppController app, Integer region) { public FragmentMapChangePopup(AppController app, Integer region) {
@@ -99,6 +109,7 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
this.app = app; this.app = app;
this.map = app.getMap(); this.map = app.getMap();
this.ui = app.getUi(); this.ui = app.getUi();
this.registry = app.getRegistry();
this.region = region; this.region = region;
} }