Compare commits

..
8 Commits
Author SHA1 Message Date
konggd 2fb62a46fb Abstract style model further from MapLibre 2026-04-16 17:42:30 +02:00
konggd 5715a76c9e Update classic style 2026-04-16 17:42:30 +02:00
konggd 044855ea93 Fix raster sources
Fix typo
2026-04-16 17:42:30 +02:00
konggd c851830bf1 Update README.md 2026-04-16 17:42:30 +02:00
konggd f4c9374563 Small fixes 2026-04-16 17:42:30 +02:00
konggd 1583437b6e Data namespace 2026-04-16 17:42:29 +02:00
konggd 22af6a14bc Refractor UI factories into UI namespace 2026-04-16 17:42:26 +02:00
konggd 63d41d14d1 Furhter work on MapStyle 2026-04-16 17:42:25 +02:00
64 changed files with 323 additions and 2353 deletions
+3 -4
View File
@@ -28,8 +28,8 @@ android {
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_15
targetCompatibility = JavaVersion.VERSION_15
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
@@ -46,8 +46,6 @@ dependencies {
implementation(libs.constraintlayout)
implementation(libs.viewpager2)
implementation("androidx.core:core-ktx:1.17.0")
implementation("org.osmdroid:osmdroid-android:6.1.20")
implementation("org.maplibre.gl:android-sdk:11.13.0")
implementation("com.github.mapsforge.vtm:vtm:0.27.0")
implementation("com.github.mapsforge.vtm:vtm-android:0.27.0")
@@ -62,6 +60,7 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0")
implementation("org.apache.commons:commons-lang3:3.14.0")
implementation(libs.core.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
@@ -1,126 +0,0 @@
#
Builds included inside /dist correspond to MapLibre GL JS version 5.24.0 <br>
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
-------------------------------------------------------------------------------
Copyright (c) 2023, MapLibre contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of MapLibre GL JS nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Contains code from mapbox-gl-js v1.13 and earlier
Version v1.13 of mapbox-gl-js and earlier are licensed under a BSD-3-Clause license
Copyright (c) 2020, Mapbox
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Mapbox GL JS nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Contains code from glfx.js
Copyright (C) 2011 by Evan Wallace
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--------------------------------------------------------------------------------
Contains a portion of d3-color https://github.com/d3/d3-color
Copyright 2010-2016 Mike Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of contributors may be used to
endorse or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="dist/maplibre-gl.js"></script>
<link href="dist/maplibre-gl.css" rel="stylesheet"/>
<style>
html, body, #map {
margin: 0;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="map" style="width: 100%; height: 100%"></div>
<script>
const map = new maplibregl.Map({
center: [13.388, 52.517],
zoom: 9.5,
container: 'map',
attributionControl: false
})
</script>
</body>
</html>
@@ -18,7 +18,6 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
app = new AppController(this);
setContentView(R.layout.view_main);
app.init();
}
@@ -29,10 +28,4 @@ public class MainActivity extends AppCompatActivity {
public void logcat(String log) {
Log.i("LogcatGeneric", log);
}
@Override
public void onBackPressed() {
if (!app.getUi().back())
super.onBackPressed();
}
}
@@ -7,7 +7,6 @@ import eu.konggdev.strikemaps.MainActivity;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
import static android.content.Context.MODE_PRIVATE;
public class AppController {
@@ -35,11 +34,12 @@ public class AppController {
public AppCompatActivity getActivity() { return appActivity; }
public void init() {
if (getActivity().getSupportActionBar() != null)
getActivity().getSupportActionBar().show();
getActivity().getSupportActionBar().hide();
if(map == null) map = new MapComponent(this);
if(ui == null) {
ui = new UIComponent(this, map);
ui.swapScreen(DefinedScreen.MAIN); //Initial
ui.swapScreen(R.layout.screen_main); //Initial
}
}
}
@@ -1,93 +0,0 @@
package eu.konggdev.strikemaps.app.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.stream.StreamSupport;
public final class JsonPatcher {
//Takes a json and a patch and applies it
public static JsonNode patch(JsonNode current, JsonNode patch) throws Exception {
if (current.isObject()) {
ObjectNode result = current.deepCopy();
patch.fields().forEachRemaining(entry -> { //For each
String key = entry.getKey();
JsonNode patchValue = entry.getValue();
if(!result.has(key)) { //Not at all in current
result.set(key, patchValue); //Just append it to result
} else if (result.has(key) && !patchValue.isEmpty()) { //Deeper depth possible
try {
result.set(key, patch(result.get(key), patchValue)); //Patch iteration with patch's value (traverse deeper)
} catch (Exception e) {
throw new RuntimeException(e);
}
} else if (result.has(key) && patchValue.isEmpty() && result.get(key).isEmpty()) { //Max depth match
result.set(key, patchValue); //Replace with patch's value
}
});
return result;
}
if (current.isArray()) {
ArrayNode result = current.deepCopy();
for (JsonNode entry : patch) {
if (StreamSupport.stream(result.spliterator(), false)
.noneMatch(node -> node.equals(entry))) { //If no exact entry, add entry (prevents duplicates)
result.add(entry);
}
}
return result;
}
throw new Exception();
}
//Takes an original json, a patched json and a patch and removes that specific patch's changes from it
public static JsonNode unpatch(JsonNode original, JsonNode current, JsonNode patch) throws Exception {
if (current.isObject()) {
ObjectNode result = current.deepCopy(); //Current contains the patch, deepcopy it and operate on the copy
current.fields().forEachRemaining(entry -> {
String key = entry.getKey();
JsonNode value = entry.getValue();
if (!original.has(key) && patch.has(key)) { //Value is in patch and current but was not at all in the original
result.remove(key); //Just remove the value
} else if (!value.isEmpty() && patch.has(key)) { //Depper depth possible
try {
result.set(key, unpatch(original.get(key), result.get(key), patch.get(key))); //Replace with further unpatch of deeper level
} catch (Exception e) {
throw new RuntimeException(e);
}
} else if (value.isEmpty() && patch.has(key)) { //Max depth match
result.set(key, original.get(key)); //Replace with original unpatched value
}
});
return result;
}
if (current.isArray()) {
ArrayNode result = current.deepCopy();
for (int i = 0; i < result.size(); i++) {
JsonNode entry = result.get(i);
if (StreamSupport.stream(original.spliterator(), false)
.noneMatch(on -> on.equals(entry)) //If not in original
&&
StreamSupport.stream(patch.spliterator(), false)
.noneMatch(rn -> rn.equals(entry))) { //But also in patch
result.remove(i); //Remove from result
}
}
return result;
}
throw new Exception();
}
}
@@ -26,6 +26,7 @@ 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];
@@ -37,39 +38,6 @@ public final class FileHelper {
}
}
public static void writeUserFile(String path, String fileName, String content, AppController app) throws IOException {
try {
File userDirectory = new File(app.getActivity().getExternalFilesDir(null), path);
if (!userDirectory.exists() && !userDirectory.mkdirs()) {
app.logcat("Failed to create directory: " + userDirectory.getAbsolutePath());
return;
}
File file = new File(userDirectory, fileName);
try (FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
writer.write(content);
writer.flush();
}
} catch (IOException e) {
throw e;
}
}
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,20 +77,9 @@ 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);
String packageName = app.getActivity().getPackageName();
File userDirectory = new File(Environment.getExternalStorageDirectory(), "Android/data/" + packageName + "/" + path);
if (!userDirectory.exists() || !userDirectory.isDirectory())
return new String[0];
@@ -13,24 +13,17 @@ public final class UserPrefsHelper {
//Defaults
private static final String DEFAULT_MAP_STYLE = "bundled/style/classic.style.json";
private static final Integer DEFAULT_MAP_RENDERER = 0;
private static final String DEFAULT_MAP_RENDERER = "mapLibre";
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 String mapRenderer(SharedPreferences prefs) {
return prefs.getString(KEY_MAP_RENDERER, DEFAULT_MAP_RENDERER);
}
public static boolean persistLocationEnabled(SharedPreferences prefs) {
@@ -1,10 +1,7 @@
package eu.konggdev.strikemaps.map;
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;
@@ -24,22 +21,15 @@ public class MapComponent implements Component {
public MapStyle style;
public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>();
public MapComponent(AppController ref) {
this.app = ref;
switch(UserPrefsHelper.mapRenderer(app.getPrefs())) {
case 0:
this.mapRenderer = new MapLibreGLJSRenderer(app, this);
break;
case 1:
this.mapRenderer = new MapLibreNativeRenderer(app, this);
break;
case 2:
case "vtm":
this.mapRenderer = new VtmRenderer(app, this);
break;
case "mapLibre":
default: //This shouldn't happen
Toast.makeText(app.getActivity(), "Invalid renderer value in preferences\nFalling back to MapLibre GL JS", Toast.LENGTH_SHORT).show();
this.mapRenderer = new MapLibreGLJSRenderer(app, this);
this.mapRenderer = new MapLibreNativeRenderer(app, this);
break;
};
}
@@ -50,13 +40,13 @@ public class MapComponent implements Component {
public void setStyle(MapStyle style) {
this.style = style;
mapRenderer.styleUpdate(style);
mapRenderer.reload();
}
public void switchOverlay(MapOverlay overlay) {
if (hasOverlay(overlay)) overlays.remove(overlay.getClass());
else overlays.put(overlay.getClass(), overlay);
overlayUpdate(overlay);
update();
}
public boolean hasOverlay(MapOverlay overlay) {
@@ -71,8 +61,12 @@ public class MapComponent implements Component {
//FIXME: Put back FragmentPointPreviewPopup (private code atm)
}
public void overlayUpdate(MapOverlay in) {
mapRenderer.overlayUpdate(in);
public void onOverlayUpdate() {
update();
}
public void update() {
if(mapRenderer != null && style != null) mapRenderer.reload();
}
public boolean onMapClick(LatLng point) {
@@ -0,0 +1,11 @@
package eu.konggdev.strikemaps.map.layer;
import com.fasterxml.jackson.databind.JsonNode;
public class MapLayer {
public JsonNode layer;
public MapLayer(JsonNode layer) {
this.layer = layer;
}
}
@@ -0,0 +1,16 @@
package eu.konggdev.strikemaps.map.layer;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.map.source.MapSource;
public class SourcedMapLayer {
public String key;
public MapSource source;
public JsonNode layer;
public SourcedMapLayer(String key, MapSource source, JsonNode layer) {
this.key = key;
this.source = source;
this.layer = layer;
}
}
@@ -1,10 +0,0 @@
package eu.konggdev.strikemaps.map.offline;
import java.io.IOException;
public final class OfflineTileResolver {
public byte[] resolve(String url) throws IOException {
//byte[] data = getOfflineTile();
return null;
}
}
@@ -1,22 +0,0 @@
package eu.konggdev.strikemaps.map.offline.download;
import eu.konggdev.strikemaps.map.source.MapSource;
import java.net.URI;
public final class OfflineDownloader {
public static String[] fetchAvailableExports(MapSource source) {
URI uri = URI.create(source.url);
String host = uri.getHost();
return new String[0];
}
public static void download(MapSource source, String export) {
//TODO
}
public static void download(MapSource source, int[] bounds) {
//TODO
}
}
@@ -1,8 +1,8 @@
package eu.konggdev.strikemaps.map.overlay;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
/* More or less a data-driven layer factory */
public interface MapOverlay {
public JsonNode makePatch();
public SourcedMapLayer makeLayer();
}
@@ -1,15 +1,19 @@
package eu.konggdev.strikemaps.map.overlay.implementation;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationListener;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.data.provider.LocationDataProvider;
import org.maplibre.geojson.Feature;
import org.maplibre.geojson.FeatureCollection;
import org.maplibre.geojson.Point;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -29,38 +33,34 @@ public class LocationOverlay implements MapOverlay, LocationListener {
}
@Override
public JsonNode makePatch() {
public SourcedMapLayer makeLayer() {
MapSource source = new MapSource();
source.type = "geojson";
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
ObjectNode sources = mapper.createObjectNode();
ObjectNode location = mapper.createObjectNode();
try {
ObjectNode data = mapper.createObjectNode();
data.put("type", "Feature");
if(currentLocation != null) {
ObjectNode geometry = mapper.createObjectNode();
ObjectNode properties = mapper.createObjectNode();
geometry.put("type", "Point");
ArrayNode coordinates = mapper.createArrayNode();
coordinates.add(currentLocation.getLongitude());
coordinates.add(currentLocation.getLatitude());
geometry.put("type", "Point");
geometry.set("coordinates", coordinates);
data.put("type", "Feature");
data.set("geometry", geometry);
data.set("properties", properties);
data.set("properties", mapper.createObjectNode());
}
source.data = data;
} catch (Exception e) {
e.printStackTrace();
}
location.put("type", "geojson");
location.set("data", data);
sources.set("location", location);
root.set("sources", sources);
// layers
ArrayNode layers = mapper.createArrayNode();
ObjectNode layer = mapper.createObjectNode();
layer.put("id", "location");
layer.put("type", "circle");
layer.put("source", "location");
@@ -68,21 +68,25 @@ public class LocationOverlay implements MapOverlay, LocationListener {
ObjectNode paint = mapper.createObjectNode();
paint.put("circle-radius", 5);
paint.put("circle-color", "#1E88E5");
paint.put("circle-stroke-color", "#FFFFFF");
paint.put("circle-stroke-width", 1.5);
layer.set("paint", paint);
layers.add(layer);
root.set("layers", layers);
ObjectNode layout = mapper.createObjectNode();
layout.put("circle-pitch-alignment", "map");
return root;
layer.set("layout", layout);
ArrayNode layers = mapper.createArrayNode();
layers.add(layer);
return new SourcedMapLayer("location", source, layers);
}
@Override
public void onLocationChanged(@NonNull Location location) {
this.currentLocation = location;
map.overlayUpdate(this);
map.onOverlayUpdate();
}
}
@@ -1,15 +1,12 @@
package eu.konggdev.strikemaps.map.overlay.implementation;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
public class PointSelectionOverlay implements MapOverlay {
AppController app;
MapComponent map;
@Override
public JsonNode makePatch() {
public SourcedMapLayer makeLayer() {
return null;
}
}
@@ -1,18 +1,17 @@
package eu.konggdev.strikemaps.map.renderer;
import android.view.View;
import android.view.ViewGroup;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import java.util.List;
public interface MapRenderer {
void styleUpdate(MapStyle style);
void overlayUpdate(MapOverlay overlay);
void reload();
View getView();
@@ -1,149 +0,0 @@
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;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.annotation.JsonInclude;
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.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 org.maplibre.android.geometry.LatLng;
import org.maplibre.android.maps.Style;
import org.maplibre.geojson.Feature;
import java.util.Collections;
import java.util.List;
//Stub for now
public class MapLibreGLJSRenderer implements MapRenderer {
@NonNull AppController app;
@NonNull MapComponent controller;
final WebView webView;
private JsonNode origin;
@SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled"})
public MapLibreGLJSRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
webView = new WebView(app.getActivity());
webView.setLayoutParams(
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setAllowFileAccess(true);
webView.setWebChromeClient(new WebChromeClient());
webView.addJavascriptInterface(new Bridge(), "AndroidBridge");
webView.loadUrl("file:///android_asset/library/maplibre/gl-js/index.html");
}
@Override
public void styleUpdate(MapStyle style) {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
//Thanks to this, styleUpdate(null) can be used as an origin reload
if (style != null) {
try {
ObjectNode root = style.metadata.deepCopy();
//Sources
ObjectNode sources = mapper.createObjectNode();
if (style.sources != null)
style.sources.forEach((k, v) -> sources.set(k, mapper.valueToTree(v)));
//Layers
ArrayNode layers = mapper.createArrayNode();
if (style.layerDefinitions != null)
layers.addAll((ArrayNode) style.layerDefinitions);
//Set all to root
root.set("sources", sources);
root.set("layers", layers);
this.origin = root;
} catch (Exception e) {
app.logcat("Failed to parse style: " + style.name);
e.printStackTrace();
}
}
try {
final String mapped = mapper.writeValueAsString(origin);
webView.evaluateJavascript("map.setStyle(" + mapped + ", { diff: false });", null);
webView.evaluateJavascript("map.redraw()", null); //Force redraw to make the style change visible
} catch (Exception e) {
app.logcat("Failed to set style: " + style.name);
e.printStackTrace();
}
//Since we just annihilated all overlays from the face of the earth, lets repatch them
if (controller.overlays != null) {
for(MapOverlay overlay : controller.overlays.values())
overlayUpdate(overlay);
}
}
@Override
public void overlayUpdate(MapOverlay overlay) {
ObjectMapper mapper = new ObjectMapper();
webView.evaluateJavascript(
"JSON.stringify(map.getStyle())",
style -> {
try {
JsonNode current = mapper.readTree(style);
JsonNode merged;
if (controller.hasOverlay(overlay)) {
merged = JsonPatcher.patch(current, overlay.makePatch());
} else {
merged = JsonPatcher.unpatch(origin, current, overlay.makePatch());
}
final String mapped = mapper.writeValueAsString(merged);
webView.evaluateJavascript("map.setStyle(" + mapped + ", { diff: false });", null);
webView.evaluateJavascript("map.redraw()", null); //Force redraw to make the style change visible
} catch (Exception e) {
app.logcat("Failed to patch overlay: " + overlay.toString());
e.printStackTrace();
}
}
);
}
@Override
public View getView() {
return webView;
}
@Override
public List<Feature> featuresAtPoint(LatLng point) {
return Collections.emptyList();
}
class Bridge { }
}
@@ -5,13 +5,12 @@ import android.view.View;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.annotation.JsonInclude;
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.util.JsonPatcher;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.style.MapStyle;
import org.maplibre.android.MapLibre;
@@ -28,13 +27,11 @@ import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
@NonNull AppController app;
@NonNull MapComponent controller;
AppController app;
MapComponent controller;
MapLibreMap map;
final MapView mapView;
private JsonNode origin;
public MapLibreNativeRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
@@ -45,71 +42,37 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
}
@Override
public void styleUpdate(MapStyle style) {
public void reload() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
//Thanks to this, styleUpdate(null) can be used as an origin reload
if (style != null) {
MapStyle style = controller.style;
try {
/* Take metadata from MapStyle
everything outside sources, layers */
ObjectNode root = style.metadata.deepCopy();
//Sources
ObjectNode sources = mapper.createObjectNode();
if (style.sources != null)
style.sources.forEach((k, v) -> sources.set(k, mapper.valueToTree(v)));
//Layers
ArrayNode layers = mapper.createArrayNode();
if (style.layerDefinitions != null)
layers.addAll((ArrayNode) style.layerDefinitions);
//Overlays
for (MapOverlay overlay : controller.overlays.values()) {
SourcedMapLayer overlayLayer = overlay.makeLayer();
sources.set(overlayLayer.key, mapper.valueToTree(overlayLayer.source));
layers.addAll((ArrayNode) overlayLayer.layer);
}
//Set all to root
root.set("sources", sources);
root.set("layers", layers);
this.origin = root;
map.setStyle(new Style.Builder().fromJson(mapper.writeValueAsString(root)));
} catch (Exception e) {
app.logcat("Failed to parse style: " + style.name);
e.printStackTrace();
}
}
try {
map.setStyle(new Style.Builder().fromJson(mapper.writeValueAsString(origin)));
} catch (Exception e) {
app.logcat("Failed to set style: " + style.name);
e.printStackTrace();
}
//Since we just annihilated all overlays from the face of the earth, lets repatch them
if (controller.overlays != null) {
for(MapOverlay overlay : controller.overlays.values())
overlayUpdate(overlay);
}
}
@Override
public void overlayUpdate(MapOverlay overlay) {
if(map == null) return;
Style style = map.getStyle();
ObjectMapper mapper = new ObjectMapper();
if(style == null) return;
try {
JsonNode current = mapper.readTree(style.getJson());
JsonNode merged;
if (controller.hasOverlay(overlay)) {
merged = JsonPatcher.patch(current, overlay.makePatch());
} else {
merged = JsonPatcher.unpatch(origin, current, overlay.makePatch());
}
map.setStyle(new Style.Builder().fromJson(mapper.writeValueAsString(merged)));
} catch (Exception e) {
app.logcat("Failed to patch overlay: " + overlay.toString());
app.logcat("Failed to reload Map");
e.printStackTrace();
}
}
@@ -128,7 +91,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.setStyle(MapStyle.fromMapLibreJsonFile(UserPrefsHelper.startupMapStyle(app.getPrefs()), app));
//I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :)
map.getUiSettings().setLogoEnabled(false);
@@ -3,9 +3,7 @@ package eu.konggdev.strikemaps.map.renderer.implementation;
import android.view.View;
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 okhttp3.OkHttpClient;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
@@ -18,7 +16,6 @@ import java.util.Collections;
import java.util.List;
public class VtmRenderer implements MapRenderer {
AppController app;
MapComponent controller;
@@ -33,12 +30,7 @@ public class VtmRenderer implements MapRenderer {
}
@Override
public void overlayUpdate(MapOverlay overlay) {
//TODO
}
@Override
public void styleUpdate(MapStyle style) {
public void reload() {
//TODO
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OSciMap4TileSource tileSource = OSciMap4TileSource.builder().httpFactory(new OkHttpEngine.OkHttpFactory(builder)).build();
@@ -20,10 +20,10 @@ public class MapStyle {
public JsonNode metadata; // everything except layers + sources
public Map<String, MapSource> sources;
public ArrayNode layerDefinitions; // "layers" array
public ArrayNode layerDefinitions; // the "layers" array
//FIXME
public static MapStyle fromFile(String filename, AppController app) {
public static MapStyle fromMapLibreJsonFile(String filename, AppController app) {
String styleContents;
if (filename.startsWith("/storage")) styleContents = FileHelper.loadStringFromUserFile(filename);
else styleContents = FileHelper.loadStringFromAssetFile(filename, app);
@@ -3,83 +3,64 @@ package eu.konggdev.strikemaps.ui;
import android.app.AlertDialog;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import com.google.common.collect.BiMap;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.ui.element.region.content.MainContentRegion;
import eu.konggdev.strikemaps.ui.element.region.UIRegion;
import eu.konggdev.strikemaps.ui.element.UIRegion;
import eu.konggdev.strikemaps.ui.fragment.layout.FragmentLayoutControls;
import eu.konggdev.strikemaps.ui.fragment.layout.FragmentLayoutSearch;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentOfflineMaps;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentSettings;
import eu.konggdev.strikemaps.ui.screen.Screen;
import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class UIComponent implements Component {
@NonNull AppController app;
MapComponent map;
private final ArrayDeque<Screen> screenStack = new ArrayDeque<>();
private Map<Integer, Screen> screens;
private Integer currentScreen;
public UIComponent(AppController app, MapComponent map) {
this.app = app;
this.map = map;
}
public Map<DefinedScreen, Screen> getScreens(MapComponent map) {
return Map.of(
this.screens = Map.of(
//Main screen
DefinedScreen.MAIN, new Screen(
R.layout.screen_main, new Screen(
//App reference
app,
//Map view
map.toFragment(), //FragmentLayoutContentMap
//Main screen init regions definition
Map.of(
R.id.mainContentView, new MainContentRegion(map.toFragment(), R.id.mainContentView),
R.id.bottomUi, new UIRegion(new FragmentLayoutControls(app, R.id.bottomUi), R.id.bottomUi),
R.id.topUi, new UIRegion(new FragmentLayoutSearch(app, R.id.topUi), R.id.topUi)
) //TODO: Probably stop referencing layout 3(!) times everytime
Map.of(R.id.bottomUi, new UIRegion(new FragmentLayoutControls(app, R.id.bottomUi), R.id.bottomUi)), //TODO: Probably stop referencing layout 3(!) times everytime
//Layout
R.layout.screen_main //TODO: Define this for the Screen without duplicating the reference
),
//Settings screen
DefinedScreen.SETTINGS, new Screen(
R.layout.screen_settings, new Screen(
app,
//Just the settings content fragment
Map.of(
R.id.mainContentView, new MainContentRegion(new FragmentLayoutContentSettings(app), R.id.mainContentView)
)
),
//Offline maps screen
DefinedScreen.OFFLINE, new Screen(
app,
Map.of(
R.id.mainContentView, new MainContentRegion(new FragmentLayoutContentOfflineMaps(app), R.id.mainContentView)
)
//Settings
new FragmentLayoutContentSettings(),
/* No regions defined in settings
Entire screen is just the main view */
new HashMap<>(),
//Layout
R.layout.screen_settings
)
);
}
public void swapScreen(DefinedScreen screenKey) {
if (!screenStack.isEmpty()) getCurrentScreen().detachAll();
screenStack.add(getScreens(map).get(screenKey));
public void swapScreen(Integer screen) {
currentScreen = screen;
getCurrentScreen().attachAll();
}
public boolean back() {
if (screenStack.size() <= 1) return false;
getCurrentScreen().detachAll();
screenStack.removeLast();
getCurrentScreen().attachAll();
return true;
}
public Screen getCurrentScreen() {
return screenStack.getLast();
return getScreen(currentScreen);
}
public Screen getScreen(Integer screen) {
return screens.get(screen);
}
public void alert(AlertDialog dialog) {
@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.ui.element.region;
package eu.konggdev.strikemaps.ui.element;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@@ -40,4 +40,5 @@ public class UIRegion {
currentFragment = stockFragment;
}
}
}
@@ -15,7 +15,6 @@ public class GenericItem implements UIItem {
@NonNull public String name;
public Bitmap image;
public Runnable onClick;
public Runnable onLongClick;
boolean hasImage;
public GenericItem(String refName) {
@@ -27,12 +26,6 @@ public class GenericItem implements UIItem {
this.onClick = onClick;
hasImage = false;
}
public GenericItem(String refName, Runnable onClick, Runnable onLongClick) {
this.name = refName;
this.onClick = onClick;
this.onLongClick = onLongClick;
hasImage = false;
}
public GenericItem(String refName, Bitmap refImage) {
this.name = refName;
this.image = refImage;
@@ -45,34 +38,18 @@ public class GenericItem implements UIItem {
hasImage = true;
}
public GenericItem(String refName, Bitmap refImage, Runnable onClick, Runnable onLongClick) {
this.name = refName;
this.image = refImage;
this.onClick = onClick;
this.onLongClick = onLongClick;
hasImage = true;
}
public final static GenericItem fromStyle(MapStyle style, MapComponent map, Runnable onClick) {
public final static GenericItem fromStyle(MapStyle style, MapComponent map) {
if(style == null) return new GenericItem("Unknown");
if(style.icon != null)
return new GenericItem(style.name, style.icon, onClick);
return new GenericItem(style.name, onClick);
return new GenericItem(style.name, style.icon, () -> map.setStyle(style));
return new GenericItem(style.name, () -> map.setStyle(style));
}
public final static GenericItem fromStyle(MapStyle style, MapComponent map, 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, onClick, onLongClick);
}
public View makeView(UIComponent spawner) {
View v = spawner.inflateUi(R.layout.item_generic);
//FIXME: These shouldn't be casted like that!
((TextView) v.findViewById(R.id.name)).setText(name);
if(image != null) ((ImageButton) v.findViewById(R.id.image)).setImageBitmap(image);
if(onClick != null) v.findViewById(R.id.image).setOnClickListener(click(onClick));
if(onLongClick != null) v.findViewById(R.id.image).setOnLongClickListener(longClick(onLongClick));
return v;
}
@@ -1,28 +0,0 @@
package eu.konggdev.strikemaps.ui.element.item;
import android.view.View;
import android.widget.TextView;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.ui.UIComponent;
public class InlineItem implements UIItem {
public String text;
public Runnable onClick;
public InlineItem(String refText) {
this.text = refText;
}
public InlineItem(String refText, Runnable onClick) {
this.text = refText;
this.onClick = onClick;
}
@Override
public View makeView(UIComponent spawner) {
View view = spawner.inflateUi(R.layout.item_inline);
((TextView) view.findViewById(R.id.inline)).setText(text);
if(onClick != null) view.findViewById(R.id.inline).setOnClickListener(click(onClick));
return view;
}
}
@@ -4,36 +4,33 @@ import android.graphics.Bitmap;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.ui.UIComponent;
import org.maplibre.geojson.Feature;
import eu.konggdev.strikemaps.R;
public class PreviewItem implements UIItem {
@NonNull public String name;
public String details;
public String name;
public String type;
public Bitmap image;
boolean hasImage;
public PreviewItem(String refName, String refType) {
this.name = refName;
this.details = refType;
this.type = refType;
hasImage = false;
}
public PreviewItem(String refName, String refType, Bitmap refImage) {
this.name = refName;
this.details = refType;
this.type = refType;
this.image = refImage;
hasImage = true;
}
public static PreviewItem fromFeature(Feature feature) {
return new PreviewItem(feature.getStringProperty("name"), feature.getStringProperty("class"));
}
public View makeView(UIComponent spawner) {
View view = spawner.inflateUi(R.layout.item_preview);
((TextView) view.findViewById(R.id.choiceName)).setText(name);
if (details != null)
((TextView) view.findViewById(R.id.details)).setText(details);
((TextView) view.findViewById(R.id.type)).setText(type);
return view;
}
public View makeView(UIComponent spawner, View.OnClickListener onClick) {
@@ -12,4 +12,5 @@ public interface UIItem {
}
default View.OnLongClickListener longClick(Runnable action) { return v -> { action.run(); return true; };}
}
@@ -1,24 +0,0 @@
package eu.konggdev.strikemaps.ui.element.region.content;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.element.region.UIRegion;
public class MainContentRegion extends UIRegion {
public Fragment fragment;
public Integer layoutId;
public MainContentRegion(@NonNull Fragment initFragment, Integer refLayoutId) {
super(initFragment, refLayoutId);
this.fragment = initFragment;
this.layoutId = refLayoutId;
}
public Fragment getFragment() {
return this.fragment;
}
public void setFragment(Fragment fragment) {
this.fragment = fragment;
}
}
@@ -1,257 +1,20 @@
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 android.widget.LinearLayout;
import android.widget.ScrollView;
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.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
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<String> styles = Arrays.asList(FileHelper.getAssetFiles("bundled/style", ".style.json", app));
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);
AlertDialog dialog = new AlertDialog.Builder(app.getActivity())
.setTitle("Copy from")
.setView(scrollView)
.setNegativeButton("Cancel", null)
.create();
return dialog;
}
public static AlertDialog createStyle(AppController app, String baseStyleContents, FragmentMapChangePopup mapChangePopup) {
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);
int padding = (int) (20 * app.getActivity().getResources().getDisplayMetrics().density);
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")
.setView(container)
.setPositiveButton("Create", null)
.setNegativeButton("Cancel", (d, w) -> d.dismiss())
.create();
dialog.setOnShowListener(d -> {
Button createButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
createButton.setOnClickListener(v -> {
ObjectMapper mapper = new ObjectMapper();
boolean nameEntryRequired = true;
String styleContentName = "";
if (baseStyleContents != null) {
try {
JsonNode root = mapper.readTree(baseStyleContents);
if (!root.path("name").asText().isEmpty()) {
nameEntryRequired = false; //We can take the name from the style
styleContentName = root.path("name").asText();
}
} catch (Exception e) {
e.printStackTrace();
}
}
String name = nameInput.getText().toString().trim();
if (name.isEmpty() && nameEntryRequired) {
nameInput.setError("Name required");
return;
} else if (name.isEmpty() && !nameEntryRequired) {
name = styleContentName;
}
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;
if(baseStyleContents != null) {
if(!baseStyleContents.isEmpty()) {
root = mapper.readTree(baseStyleContents);
} else {
root = mapper.createObjectNode();
}
} else {
root = mapper.createObjectNode();
}
if (!root.path("name").asText().isEmpty()) {
ObjectNode node = mapper.createObjectNode();
node.put("name", name);
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();
} catch (Exception e) {
Toast.makeText(app.getActivity(), "Failed to create", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
mapChangePopup.reloadStyles();
});
});
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<Feature> features, Consumer<Feature> callback) {
LinearLayout layout = new LinearLayout(app.getActivity());
layout.setOrientation(LinearLayout.VERTICAL);
@@ -267,7 +30,7 @@ public final class AlertDialogFactory {
for (Feature feature : features) {
View itemView = PreviewItem.fromFeature(feature).makeView(app.getUi(), v -> {
dialog.dismiss();
new Handler(Looper.getMainLooper())
new android.os.Handler(android.os.Looper.getMainLooper())
.post(() -> callback.accept(feature));
});
layout.addView(itemView);
@@ -275,34 +38,4 @@ public final class AlertDialogFactory {
return dialog;
}
public static AlertDialog searchSettings(AppController app) {
return new AlertDialog.Builder(app.getActivity())
.setTitle("Configure Search")
.setPositiveButton("OK", null)
.create();
}
public static AlertDialog restartDialog(AppController app) {
return new AlertDialog.Builder(app.getActivity())
.setTitle("Restart required")
.setMessage("Restart the app to apply changes.")
.setCancelable(false)
.setNegativeButton("Cancel", (d, w) -> {
Toast.makeText(app.getActivity(),
"Changes will be applied on next restart",
Toast.LENGTH_SHORT).show();
d.dismiss();
})
.setPositiveButton("Restart", (d, w) -> {
Intent i = app.getActivity().getPackageManager()
.getLaunchIntentForPackage(app.getActivity().getPackageName());
if (i != null) {
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
app.getActivity().startActivity(i);
}
Runtime.getRuntime().exit(0);
})
.create();
}
}
@@ -4,6 +4,7 @@ import android.view.MotionEvent;
import android.view.View;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.element.UIRegion;
public interface ContainerFragment {
abstract public Integer getRegion();
@@ -1,148 +0,0 @@
package eu.konggdev.strikemaps.ui.fragment.dialog;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiContext;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
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.ui.UIComponent;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
import java.io.*;
import java.util.stream.Collectors;
public class NewStyleBottomSheet extends BottomSheetDialogFragment {
private final String styleBase = "{\"name\":\"None\"}";
@NonNull AppController app;
@NonNull MapComponent map;
@NonNull UIComponent ui;
@NonNull
FragmentMapChangePopup mapChangePopup;
private final ActivityResultLauncher<Intent> importLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == android.app.Activity.RESULT_OK
&& result.getData() != null) {
Uri uri = result.getData().getData();
try (InputStream in = requireContext()
.getContentResolver()
.openInputStream(uri)) {
try (Cursor cursor = requireContext()
.getContentResolver()
.query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (nameIndex >= 0) {
String fileName = cursor.getString(nameIndex);
BufferedReader reader = new BufferedReader(
new InputStreamReader(in)
);
StringBuilder contentBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
contentBuilder.append(line).append(System.lineSeparator());
}
reader.close();
String content = contentBuilder.toString();
if (fileName != null && !fileName.endsWith(".style.json")) {
app.getUi().alert(
AlertDialogFactory.createStyle(
app,
content,
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));
}
mapChangePopup.reloadStyles();
return;
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
);
private void showImportDialog() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("application/json");
intent.addCategory(Intent.CATEGORY_OPENABLE);
importLauncher.launch(intent);
}
public NewStyleBottomSheet(AppController app, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup) {
this.app = app;
this.map = map;
this.ui = ui;
this.mapChangePopup = mapChangePopup;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_new_style, container, false);
Button builtInBtn = view.findViewById(R.id.buttonFromBuiltInStyle);
Button fileBtn = view.findViewById(R.id.buttonFromFile);
Button emptyBtn = view.findViewById(R.id.buttonCreateEmpty);
builtInBtn.setOnClickListener(v -> {
app.getUi().alert(AlertDialogFactory.copyBuiltInStyle(app, map, ui, mapChangePopup));
});
fileBtn.setOnClickListener(v -> {
showImportDialog();
});
emptyBtn.setOnClickListener(v -> {
app.getUi().alert(AlertDialogFactory.createStyle(app, styleBase, mapChangePopup));
});
return view;
}
}
@@ -1,156 +0,0 @@
package eu.konggdev.strikemaps.ui.fragment.dialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.button.MaterialButton;
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;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
import static android.media.MediaExtractor.MetricsConstants.MIME_TYPE;
public class StyleDetailsBottomSheet extends BottomSheetDialogFragment {
@NonNull
AppController app;
@NonNull
MapComponent map;
@NonNull
UIComponent ui;
@NonNull
FragmentMapChangePopup mapChangePopup;
private final MapStyle style;
private final String stylePath;
private final ActivityResultLauncher<Intent> 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)) {
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;
}
mapChangePopup.reloadStyles();
dismiss();
}
private void showExportDialog(String fileName) {
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) {
this.app = app;
this.map = map;
this.ui = ui;
this.mapChangePopup = mapChangePopup;
this.style = style;
this.stylePath = stylePath;
}
@Nullable
@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);
ConstraintLayout editButtonLayout = view.findViewById(R.id.editButton);
ConstraintLayout copyButtonLayout = view.findViewById(R.id.copyButton);
ConstraintLayout exportButtonLayout = view.findViewById(R.id.exportButton);
ConstraintLayout deleteButtonLayout = view.findViewById(R.id.deleteButton);
ConstraintLayout closeButtonLayout = view.findViewById(R.id.closeButton);
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) {
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));
deleteButtonLayout.setOnClickListener(v -> deleteStyle());
closeButtonLayout.setOnClickListener(v -> dismiss());
return view;
}
}
@@ -1,125 +0,0 @@
package eu.konggdev.strikemaps.ui.fragment.layout;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupWindow;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
public class FragmentLayoutSearch extends Fragment implements Layout {
AppController app;
private final Integer region;
public FragmentLayoutSearch(AppController app, Integer region) {
super(R.layout.fragment_search);
this.app = app;
this.region = region;
}
@Override
public Integer getRegion() {
return region;
}
@Override
public Fragment toFragment() {
return this;
}
@Override
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);
PopupWindow popupWindow = new PopupWindow(
menuView,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true
);
popupWindow.setOutsideTouchable(true);
popupWindow.setFocusable(true);
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
setupButton(menuView, R.id.menuSearchSettings, click(() -> {
app.getUi().alert(AlertDialogFactory.searchSettings(app));
}));
setupButton(menuView, R.id.menuSettings, click(() -> {
popupWindow.dismiss();
app.getUi().swapScreen(DefinedScreen.SETTINGS);
}));
menuView.findViewById(R.id.menuSearchSettings).setOnClickListener(v -> popupWindow.dismiss());
menuView.findViewById(R.id.menuAbout).setOnClickListener(v -> popupWindow.dismiss());
View anchor = view.findViewById(R.id.searchContainer);
anchor.post(() -> {
menuView.measure(
View.MeasureSpec.UNSPECIFIED,
View.MeasureSpec.UNSPECIFIED
);
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);
});
}));
}
}
@@ -2,11 +2,13 @@ package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.element.UIRegion;
import eu.konggdev.strikemaps.R;
public class FragmentLayoutContentMap extends Fragment implements MainContentLayout {
@@ -26,7 +28,6 @@ public class FragmentLayoutContentMap extends Fragment implements MainContentLay
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
LinearLayout layout = (LinearLayout) view;
if(mapView.getParent() != null) ((ViewGroup) mapView.getParent()).removeView(mapView);
layout.addView(mapView);
}
}
@@ -1,60 +0,0 @@
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;
public FragmentLayoutContentOfflineMaps(AppController app) {
super(R.layout.fragment_offline_maps);
this.app = app;
}
@Override
public Fragment toFragment() {
return this;
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
List<MapSource> sources = new ArrayList<>();
List<String> 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<MapSource> 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()));
}
}
}
@@ -1,66 +1,10 @@
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;
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.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;
public FragmentLayoutContentSettings(AppController app) {
super(R.layout.fragment_settings);
this.app = app;
}
public class FragmentLayoutContentSettings implements MainContentLayout {
@Override
public Fragment toFragment() {
return this;
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
Spinner mapRendererSelector = view.findViewById(R.id.mapRendererSelector);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
view.getContext(),
R.array.map_renderers,
android.R.layout.simple_spinner_item
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mapRendererSelector.setAdapter(adapter);
mapRendererSelector.setSelection(UserPrefsHelper.mapRenderer(app.getPrefs()));
final boolean[] ignoreFirst = {true};
mapRendererSelector.setOnItemSelectedListener(
new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (ignoreFirst[0]) {
ignoreFirst[0] = false;
return;
}
UserPrefsHelper.mapRenderer(app.getPrefs(), position);
app.getUi().alert(AlertDialogFactory.restartDialog(app));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
}
);
return null;
}
}
@@ -1,7 +1,5 @@
package eu.konggdev.strikemaps.ui.fragment.popup;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
@@ -16,8 +14,6 @@ import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.ui.UIComponent;
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;
@@ -32,19 +28,6 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
private final Integer region;
private View view;
// Action definitions
//*//
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");
}
//*//
public FragmentMapChangePopup(AppController app, Integer region) {
super(R.layout.popup_map_change);
this.app = app;
@@ -63,30 +46,16 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
return this;
}
public void reloadStyles() {
List<String> 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));
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
//FIXME
setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup()));
setupDragHandle(view, view, () -> ui.getCurrentScreen().closePopup());
this.view = view;
reloadStyles();
List<String> 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);
for (String style : stylePaths)
stylesLayout.addView(GenericItem.fromStyle(MapStyle.fromMapLibreJsonFile(style, app), map).makeView(ui));
}
}
@@ -1,25 +1,31 @@
package eu.konggdev.strikemaps.ui.screen;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.appcompat.widget.Toolbar;
import java.util.List;
import java.util.Map;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.fragment.ContainerFragment;
import eu.konggdev.strikemaps.ui.fragment.FragmentEmptyPlaceholder;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.MainContentLayout;
import eu.konggdev.strikemaps.ui.fragment.popup.Popup;
import eu.konggdev.strikemaps.ui.element.region.UIRegion;
import eu.konggdev.strikemaps.ui.element.UIRegion;
public class Screen {
@NonNull AppController app;
Toolbar toolbar;
public Screen(AppController app, Map<Integer, UIRegion> regions) {
public Screen(AppController app, MainContentLayout mainContent, Map<Integer, UIRegion> regions, Integer layout) {
this.app = app;
this.layout = layout;
this.mainContent = mainContent;
this.uiRegions = regions;
}
private final Integer layout;
private MainContentLayout mainContent;
Map<Integer, UIRegion> uiRegions;
public Integer popup;
@@ -62,10 +68,10 @@ public class Screen {
}
public void attachAll() {
for (UIRegion region : uiRegions.values()) setFragment(region, region.getFragment());
app.getActivity().setContentView(layout);
fragmentTransaction(R.id.mainContentView, mainContent.toFragment());
for (UIRegion region : uiRegions.values()) {
setFragment(region, region.getFragment());
}
public void detachAll() {
for (UIRegion region : uiRegions.values()) fragmentTransaction(region.layoutId, new FragmentEmptyPlaceholder());
}
}
@@ -1,6 +0,0 @@
package eu.konggdev.strikemaps.ui.screen.definition;
public enum DefinedScreen {
MAIN,
SETTINGS,
OFFLINE
}
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#2A2100" />
<corners android:radius="10dp" />
<stroke
android:width="1dp"
android:color="#4D3D00" />
</shape>
@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z" />
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 214 B

-5
View File
@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10s10,-4.49 10,-10S17.51,2 12,2zM11,10V6h2v4h3l-4,4l-4,-4H11zM17,17H7v-2h10V17z"/>
</vector>
@@ -1,60 +0,0 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#000000"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New style"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="6dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonFromBuiltInStyle"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="From built-in style"
android:textAllCaps="false"
android:textColor="#FFFFFF"
app:backgroundTint="#1A1A1A"
app:cornerRadius="24dp" />
<Space
android:layout_width="match_parent"
android:layout_height="4dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonFromFile"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="From file"
android:textAllCaps="false"
android:textColor="#FFFFFF"
app:backgroundTint="#1A1A1A"
app:cornerRadius="24dp" />
<Space
android:layout_width="match_parent"
android:layout_height="4dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonCreateEmpty"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="Create empty"
android:textAllCaps="false"
android:textColor="#FFFFFF"
app:backgroundTint="#1A1A1A"
app:cornerRadius="24dp" />
</LinearLayout>
@@ -1,250 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="#000000"
android:padding="12dp">
<TextView
android:id="@+id/styleName"
android:layout_width="164dp"
android:layout_height="34dp"
android:gravity="center"
android:text="Style Name"
android:textColor="#FFFFFF"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/fileName"
android:layout_width="wrap_content"
android:layout_height="27dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="\?.style.json"
android:textColor="#BDBDBD"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/styleName" />
<TextView
android:id="@+id/styleType"
android:layout_width="112dp"
android:layout_height="27dp"
android:layout_marginTop="4dp"
android:text="Style Type?"
android:textColor="#FDFDFD"
android:textSize="18sp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/fileName" />
<TextView
android:id="@+id/builtInStyleAlert"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:paddingHorizontal="14dp"
android:paddingVertical="12dp"
android:background="@drawable/bg_alert_warning"
android:text="Built-in styles can't be edited directly\nMake a copy to customize it"
android:textColor="#FFD54F"
android:textSize="14sp"
android:lineSpacingExtra="2dp"
android:gravity="center"
android:drawablePadding="8dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/styleType"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/editButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="2dp"
android:background="#1B1A1A"
app:layout_constraintTop_toBottomOf="@id/builtInStyleAlert"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/hideIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_edit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/hideBtnText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Edit style"
android:textColor="#FFFFFF"
android:textSize="20sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/copyButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="2dp"
android:background="#1B1A1A"
app:layout_constraintTop_toBottomOf="@id/editButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/copyIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_copy"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/copyBtnText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Copy style"
android:textColor="#FFFFFF"
android:textSize="20sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/exportButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="2dp"
android:background="#1B1A1A"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/copyButton">
<ImageView
android:id="@+id/exportIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_save"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/exportBtnText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Export style"
android:textColor="#FFFFFF"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/deleteButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="5dp"
android:background="#1B1A1A"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/exportButton"
app:layout_constraintVertical_bias="0.081">
<ImageView
android:id="@+id/deleteIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_delete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/deleteText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Delete style"
android:textColor="#FFFFFF"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/closeButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="10dp"
android:background="#1B1A1A"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/deleteButton"
tools:layout_editor_absoluteY="251dp">
<ImageView
android:id="@+id/closeIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_more"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/closeText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Close"
android:textColor="#FFFFFF"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
-52
View File
@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="200dp"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="12dp"
app:cardBackgroundColor="#000000">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/menuSearchSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="Configure Search"
android:textColor="#FFFFFF"
android:background="?android:attr/selectableItemBackground"/>
<TextView
android:id="@+id/menuSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="Settings"
android:textColor="#FFFFFF"
android:background="?android:attr/selectableItemBackground"/>
<TextView
android:id="@+id/menuAbout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="About"
android:textColor="#FFFFFF"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="200dp"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="12dp"
app:cardBackgroundColor="#000000">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/offlineMaps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="Offline Maps"
android:textColor="#FFFFFF"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
+1
View File
@@ -2,4 +2,5 @@
<android.widget.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.widget.LinearLayout>
@@ -1,41 +0,0 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<TextView
android:id="@+id/tvDownloadLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Download for source:"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:includeFontPadding="true"
android:paddingBottom="4dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollDownloadContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/tvDownloadLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="8dp"
android:fillViewport="true">
<LinearLayout
android:id="@+id/llDownloadContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp">
<LinearLayout
android:id="@+id/searchContainer"
android:layout_width="320dp"
android:layout_height="35dp"
android:layout_marginTop="2dp"
android:orientation="horizontal"
android:background="#000000"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<SearchView
android:id="@+id/searchView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:iconifiedByDefault="false"
android:queryHint="Search..."
android:background="@android:color/transparent" />
<ImageButton
android:id="@+id/offlineMapsButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:contentDescription="Offline Maps"
android:src="@drawable/ic_offline"
android:padding="6dp"
android:scaleType="fitCenter"
app:tint="#B0B0B0" />
<ImageButton
android:id="@+id/hamburgerButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:contentDescription="Menu"
android:src="@drawable/ic_hamburger_menu" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
android:background="@android:color/background_dark">
<TextView
android:id="@+id/mapRendererLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Map Renderer:"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="@id/mapRendererSelector"/>
<Spinner
android:id="@+id/mapRendererSelector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintStart_toEndOf="@id/mapRendererLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
-28
View File
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
android:foregroundTint="#FFFFFF">
<TextView
android:id="@+id/inline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:paddingVertical="12dp"
android:text=""
android:textColor="#FFFFFF"
android:textSize="16sp"
android:drawableEnd="@drawable/ic_arrow_forward"
android:drawablePadding="8dp"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:clickable="true"
android:focusable="true"/>
</androidx.constraintlayout.widget.ConstraintLayout>
+8 -7
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
android:background="#0E0000"
android:foregroundTint="#FFFFFF">
<TextView
@@ -15,7 +16,7 @@
android:layout_marginEnd="12dp"
android:text="Choice Item"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textSize="24sp"
android:ellipsize="end"
android:maxLines="1"
android:scrollHorizontally="false"
@@ -23,12 +24,12 @@
android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="24sp"
android:autoSizeStepGranularity="1sp"
app:layout_constraintStart_toEndOf="@+id/icon"
app:layout_constraintStart_toEndOf="@+id/poiIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/icon"
android:id="@+id/poiIcon"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginStart="7dp"
@@ -38,20 +39,20 @@
app:srcCompat="@drawable/maplibre_info_bg_selector" />
<TextView
android:id="@+id/details"
android:id="@+id/type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:text="Type"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:ellipsize="end"
android:singleLine="true"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintStart_toEndOf="@id/poiIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/choiceName"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
+80
View File
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" >
<LinearLayout
android:id="@+id/popupHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:id="@+id/mainContentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
</LinearLayout>
<FrameLayout
android:id="@+id/bottomUi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/devIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DEVELOPMENT BUILD!"
android:textColor="#000000"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/searchContainer"
android:layout_width="280dp"
android:layout_height="33dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
android:background="#000000"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<SearchView
android:id="@+id/searchView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:iconifiedByDefault="false"
android:queryHint="Search..."
android:background="@android:color/transparent" />
<ImageButton
android:id="@+id/hamburgerButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:contentDescription="Menu"
android:src="@drawable/ic_hamburger_menu" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
-41
View File
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" >
<LinearLayout
android:id="@+id/mainContentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/bottomUi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0" />
<FrameLayout
android:id="@+id/topUi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
+2 -1
View File
@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
@@ -11,5 +11,6 @@
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
+1 -7
View File
@@ -1,11 +1,5 @@
<resources>
<string name="app_name">Strike Maps</string>
<string name="settings_title">Settings</string>
<string name="attribution_title"> Attribution </string>
<string name="shipped_attribution">Map Rendering libraries Included;\n - MapLibre developed by MapLibre Organization\n - Vtm developed by Mapsforge\nStrike Maps made with &lt;3 by konggdev</string>
<string-array name="map_renderers">
<item>MapLibre GL JS</item>
<item>MapLibre Native</item>
<item>VTM</item>
</string-array>
<string name="shipped_attribution">Earth map data Included;\n - © OpenStreetMap Contributors\nMap Rendering libraries Included;\n - MapLibre developed by MapLibre Organization\n - Vtm developed by Mapsforge\nStrike Maps made with &lt;3 by konggdev</string>
</resources>
+3 -2
View File
@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryVariant">@color/black</item>
@@ -11,5 +11,6 @@
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
+3
View File
@@ -10,6 +10,7 @@ constraintlayout = "2.1.4"
playServicesMaps = "19.0.0"
viewpager2 = "1.1.0"
kotlin = "2.3.10"
coreKtx = "1.18.0"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -19,7 +20,9 @@ appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "a
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" }
viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }