Compare commits

...
21 Commits
Author SHA1 Message Date
konggd c97c7c24a3 fix: typo in satellite 2026-09-01 03:20:31 +02:00
konggd 88a3e2c980 Change internal style filesystem to an internal registry and introduce style options concept 2026-09-01 03:19:44 +02:00
konggd 001f2958f2 Update README.md 2026-08-20 19:10:19 +00:00
konggd c580c1b7d4 Update README.md 2026-08-20 19:04:07 +00:00
konggd 4d0b347f1d Wrap buttons based on ConstraintLayout into MaterialCardView 2026-08-16 03:53:35 +02:00
konggd 5a608faf24 Make new style menu design consistent with style details menu 2026-08-12 11:53:32 +02:00
konggd f38bedd8cc Reduce margins on search fragment 2026-08-01 23:37:10 +02:00
konggd 8ef177089d Use custom shape for Map controls layout 2026-08-01 23:11:55 +02:00
konggd a9557f3bf4 Hide filename for Built-in styles 2026-07-13 03:51:55 +02:00
konggd cd12a52400 Inline items 2026-07-13 03:44:40 +02:00
konggd a2ed72c541 Preparation skeleton for Offline maps implementation 2026-07-13 00:59:11 +02:00
konggd 5cacd51415 Don't show editor button on Built-In styles, advise user to copy instead 2026-07-13 00:54:54 +02:00
konggd 4480366ffc Improve some UIs 2026-07-12 17:37:18 +02:00
konggd fafa1b4e8b Reload styles in askUserOverwriteFile 2026-07-07 01:52:21 +02:00
konggd fc5d5ec8e2 New style UI 2026-07-07 01:39:27 +02:00
konggd 6d7a391e28 Rework MapRenderer abstraction 2026-07-06 14:19:38 +02:00
konggd a64788f023 (gl js) Actually use bundled dist 2026-06-08 23:44:10 +02:00
konggd ebdf264626 MapLibre GL JS first stub 2026-06-07 17:31:17 +02:00
konggd d65f51aeb0 Basic functional settings screen 2026-06-02 02:22:08 +02:00
konggd cc49d4e536 Implement main screen dropdown menu 2026-05-24 01:17:20 +02:00
konggd ee6f391820 Make activity authoritative for contentView and implement a basic Settings skeleton 2026-05-23 07:16:39 +02:00
88 changed files with 3288 additions and 569 deletions
+7 -4
View File
@@ -1,8 +1,11 @@
## Strike Maps Project Android Client ## Strike Maps
Strike Maps is a project hoping to create a multi-platform, <br> Strike Maps is a FOSS mapping app for Android,
user-facing, complete FOSS mapping stack, focusing on customization <br> focusing on customization and offline features
and offline features. <br>
It is developed as part of a broader project, hoping to create a multi-platform,
user-facing, complete FOSS mapping stack
<br> <br>
This Android app has been in development since around January 2026 This Android app has been in development since around January 2026
+4 -3
View File
@@ -28,8 +28,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_15
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_15
} }
kotlin { kotlin {
@@ -46,6 +46,8 @@ dependencies {
implementation(libs.constraintlayout) implementation(libs.constraintlayout)
implementation(libs.viewpager2) 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("org.maplibre.gl:android-sdk:11.13.0")
implementation("com.github.mapsforge.vtm:vtm:0.27.0") implementation("com.github.mapsforge.vtm:vtm:0.27.0")
implementation("com.github.mapsforge.vtm:vtm-android:0.27.0") implementation("com.github.mapsforge.vtm:vtm-android:0.27.0")
@@ -60,7 +62,6 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0")
implementation("org.apache.commons:commons-lang3:3.14.0") implementation("org.apache.commons:commons-lang3:3.14.0")
implementation(libs.core.ktx)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.ext.junit)

Before

Width:  |  Height:  |  Size: 768 KiB

After

Width:  |  Height:  |  Size: 768 KiB

@@ -1,7 +1,7 @@
{ {
"version": 8, "version": 8,
"name": "Satelite", "name": "Satellite",
"icon": "assets://satelite.bmp", "icon": "assets://satellite.bmp",
"sources": { "sources": {
"satelite": { "satelite": {
"type": "raster", "type": "raster",
@@ -0,0 +1,125 @@
#
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 subject to 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
@@ -0,0 +1,29 @@
<!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>
@@ -1,8 +1,5 @@
package eu.konggdev.strikemaps; package eu.konggdev.strikemaps;
import static org.maplibre.android.style.layers.PropertyFactory.lineColor;
import static org.maplibre.android.style.layers.PropertyFactory.lineWidth;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import android.os.Bundle; import android.os.Bundle;
@@ -18,6 +15,7 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
app = new AppController(this); app = new AppController(this);
setContentView(R.layout.view_main);
app.init(); app.init();
} }
@@ -28,4 +26,10 @@ public class MainActivity extends AppCompatActivity {
public void logcat(String log) { public void logcat(String log) {
Log.i("LogcatGeneric", log); Log.i("LogcatGeneric", log);
} }
@Override
public void onBackPressed() {
if (!app.getUi().back())
super.onBackPressed();
}
} }
@@ -4,42 +4,57 @@ import android.content.SharedPreferences;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import eu.konggdev.strikemaps.MainActivity; import eu.konggdev.strikemaps.MainActivity;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
public class AppController { public class AppController {
private final MainActivity appActivity; private final MainActivity appActivity;
private MapComponent map; private MapComponent map;
private UIComponent ui; private UIComponent ui;
public AppController(MainActivity refActivity) {
appActivity = refActivity; private RegistryStorageComponent registry;
}
public AppController(MainActivity appActivity) { this.appActivity = appActivity;}
public void logcat(String log) { public void logcat(String log) {
appActivity.logcat(log); appActivity.logcat(log);
} }
public UIComponent getUi() { public UIComponent getUi() {
if (ui == null) init(); if (ui == null) init();
return ui; return ui;
} }
public MapComponent getMap() { public MapComponent getMap() {
if (map == null) init(); if (map == null) init();
return map; return map;
} }
public RegistryStorageComponent getRegistry() {
if (registry == null) init();
return registry;
}
public SharedPreferences getPrefs() { public SharedPreferences getPrefs() {
return getActivity().getSharedPreferences("user_prefs", MODE_PRIVATE); return getActivity().getSharedPreferences("user_prefs", MODE_PRIVATE);
} }
public AppCompatActivity getActivity() { return appActivity; } public AppCompatActivity getActivity() { return appActivity; }
public void init() { public void init() {
if (getActivity().getSupportActionBar() != null) if (getActivity().getSupportActionBar() != null)
getActivity().getSupportActionBar().hide(); getActivity().getSupportActionBar().show();
if(registry == null) registry = new RegistryStorageComponent(this);
if(map == null) map = new MapComponent(this); if(map == null) map = new MapComponent(this);
if(ui == null) { if(ui == null) {
ui = new UIComponent(this, map); ui = new UIComponent(this, map);
ui.swapScreen(R.layout.screen_main); //Initial ui.swapScreen(DefinedScreen.MAIN); //Initial
} }
} }
} }
@@ -0,0 +1,93 @@
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();
}
}
@@ -1,40 +0,0 @@
package eu.konggdev.strikemaps.data.helper;
import android.content.SharedPreferences;
public final class UserPrefsHelper {
private UserPrefsHelper() {} // prevent instantiation
//Keys
private static final String KEY_STARTUP_MAP_STYLE = "startupMapStyle";
private static final String KEY_MAP_RENDERER = "mapRenderer";
private static final String KEY_PERSIST_LOCATION_ENABLED = "persistLocationEnabled";
private static final String KEY_LAST_LOCATION_ENABLED = "lastLocationEnabled";
//Defaults
private static final String DEFAULT_MAP_STYLE = "bundled/style/classic.style.json";
private static final 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 String mapRenderer(SharedPreferences prefs) {
return prefs.getString(KEY_MAP_RENDERER, DEFAULT_MAP_RENDERER);
}
public static boolean persistLocationEnabled(SharedPreferences prefs) {
return prefs.getBoolean(KEY_PERSIST_LOCATION_ENABLED, DEFAULT_PERSIST_LOCATION_ENABLED);
}
public static boolean lastLocationEnabled(SharedPreferences prefs) {
return prefs.getBoolean(KEY_LAST_LOCATION_ENABLED, DEFAULT_LAST_LOCATION_ENABLED);
}
public static boolean lastLocationEnabled(SharedPreferences prefs, boolean status) {
return prefs.edit().putBoolean(KEY_LAST_LOCATION_ENABLED, status).commit();
}
}
@@ -1,17 +1,29 @@
package eu.konggdev.strikemaps.data.helper; package eu.konggdev.strikemaps.helper;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.os.Environment;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
//FIXME: Ugly //FIXME: Ugly
public final class FileHelper { public final class FileHelper {
public static Bitmap getIcon(String iconLocator, AppController app) {
switch (iconLocator.split("//")[0]) {
//TODO: https
case "assets:":
return BitmapFactory.decodeStream(FileHelper.openAssetStream("bundled/icon/" + iconLocator.split("//")[1], app));
default:
app.logcat("Unimplemented icon locator space: " + iconLocator);
return null;
}
}
public static String loadStringFromAssetFile(String filePath, AppController app) { public static String loadStringFromAssetFile(String filePath, AppController app) {
try (InputStream is = app.getActivity().getAssets().open(filePath)) { try (InputStream is = app.getActivity().getAssets().open(filePath)) {
int size = is.available(); int size = is.available();
@@ -24,17 +36,26 @@ public final class FileHelper {
} }
} }
public static String loadStringFromUserFile(String filePath) { public static void writeUserFile(String path, String fileName, String content, AppController app) throws IOException {
File file = new File(filePath); try {
File userDirectory = new File(app.getActivity().getExternalFilesDir(null), path);
try (FileInputStream fis = new FileInputStream(file)) { if (!userDirectory.exists() && !userDirectory.mkdirs()) {
int size = fis.available(); app.logcat("Failed to create directory: " + userDirectory.getAbsolutePath());
byte[] buffer = new byte[size]; return;
fis.read(buffer); }
return new String(buffer, StandardCharsets.UTF_8);
} catch (IOException ex) { File file = new File(userDirectory, fileName);
ex.printStackTrace();
return null; try (FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
writer.write(content);
writer.flush();
}
} catch (IOException e) {
throw e;
} }
} }
@@ -77,33 +98,4 @@ public final class FileHelper {
} }
} }
public static String[] getUserFiles(String path, String fileExt, AppController app) {
String packageName = app.getActivity().getPackageName();
File userDirectory = new File(Environment.getExternalStorageDirectory(), "Android/data/" + packageName + "/" + path);
if (!userDirectory.exists() || !userDirectory.isDirectory())
return new String[0];
File[] files = userDirectory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if (fileExt == null || fileExt.isEmpty()) {
return true;
}
return filename.toLowerCase().endsWith(fileExt.toLowerCase());
}
});
if (files == null || files.length == 0) {
return new String[0];
}
List<String> fileList = new ArrayList<>();
for (File file : files) {
fileList.add(file.getAbsolutePath());
}
return fileList.toArray(new String[0]);
}
} }
@@ -0,0 +1,127 @@
package eu.konggdev.strikemaps.helper;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.source.MapSource.MapSourceContractType;
import eu.konggdev.strikemaps.map.source.model.TileSource;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public final class UserPrefsHelper {
private UserPrefsHelper() {} // prevent instantiation
//Keys
private static final String KEY_STARTUP_MAP_STYLE = "startupMapStyle";
private static final String KEY_MAP_RENDERER = "mapRenderer";
private static final String KEY_PERSIST_LOCATION_ENABLED = "persistLocationEnabled";
private static final String KEY_LAST_LOCATION_ENABLED = "lastLocationEnabled";
private static final String KEY_STYLES = "styles";
private static final String KEY_SOURCES = "sources";
//Defaults
private static final Integer DEFAULT_MAP_STYLE = 0;
private static final Integer DEFAULT_MAP_RENDERER = 0;
private static final boolean DEFAULT_PERSIST_LOCATION_ENABLED = true;
private static final boolean DEFAULT_LAST_LOCATION_ENABLED = false;
public static Map<Integer, MapStyle> DEFAULT_STYLES(AppController app) {
Map<Integer, MapStyle> styles = new HashMap<>();
String[] styleAssets = FileHelper.getAssetFiles("bundled/style", ".style.json", app);
for (int i = 0; i < styleAssets.length; i++) { styles.put( i,
new MapStyle(
FileHelper.loadStringFromAssetFile(styleAssets[i], app),
new StyleOptions(),
new StyleManagementMetadata()
));
}
return styles;
} //Built-in Styles
private static final Map<Integer, MapSource> DEFAULT_SOURCES = Map.of(
0, new MapSource(
MapSourceContractType.DEFINITION,
"Strike Maps Planet",
new TileSource("https://tiles.strikemaps.eu/planet"),
"vector",
"smts"
),
1, new MapSource(
MapSourceContractType.DEFINITION,
"ArcGIS Imagery",
new TileSource(new String[]{"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"}),
"raster",
"raster"
)
); //Built-in Sources
public static Integer startupMapStyle(SharedPreferences prefs) {
return prefs.getInt(KEY_STARTUP_MAP_STYLE, DEFAULT_MAP_STYLE);
}
public static boolean startupMapStyle(SharedPreferences prefs, Integer updated) {
return prefs.edit().putInt(KEY_STARTUP_MAP_STYLE, updated).commit();
}
public static Integer mapRenderer(SharedPreferences prefs) {
return prefs.getInt(KEY_MAP_RENDERER, DEFAULT_MAP_RENDERER);
}
public static boolean mapRenderer(SharedPreferences prefs, Integer updated) {
return prefs.edit().putInt(KEY_MAP_RENDERER, updated).commit();
}
public static boolean persistLocationEnabled(SharedPreferences prefs) {
return prefs.getBoolean(KEY_PERSIST_LOCATION_ENABLED, DEFAULT_PERSIST_LOCATION_ENABLED);
}
public static boolean lastLocationEnabled(SharedPreferences prefs) {
return prefs.getBoolean(KEY_LAST_LOCATION_ENABLED, DEFAULT_LAST_LOCATION_ENABLED);
}
public static boolean lastLocationEnabled(SharedPreferences prefs, boolean status) {
return prefs.edit().putBoolean(KEY_LAST_LOCATION_ENABLED, status).commit();
}
public static Map<Integer, MapStyle> styles(SharedPreferences prefs, AppController app) {
String json = prefs.getString(KEY_STYLES, null);
if (json == null) return DEFAULT_STYLES(app);
Type type = new TypeToken<Map<Integer, MapStyle.StoredRepresentation>>() {}.getType();
Map<Integer, MapStyle.StoredRepresentation> stored =
new Gson().fromJson(json, type);
Map<Integer, MapStyle> result = new HashMap<>();
for (var entry : stored.entrySet())
result.put(entry.getKey(), entry.getValue().restore());
return result;
}
public static boolean styles(SharedPreferences prefs, Map<Integer, MapStyle> updated) {
Map<Integer, MapStyle.StoredRepresentation> stored = new HashMap<>();
for (var entry : updated.entrySet())
stored.put(entry.getKey(), entry.getValue().makeStoredRepresentation());
return prefs.edit()
.putString(KEY_STYLES, new Gson().toJson(stored))
.commit();
}
public static Map<Integer, MapSource> sources(SharedPreferences prefs) {
String json = prefs.getString(KEY_SOURCES, null);
if (json == null) return DEFAULT_SOURCES;
Type type = new TypeToken<Map<Integer, MapSource>>() {}.getType();
return new Gson().fromJson(json, type);
}
public static boolean sources(SharedPreferences prefs, Map<Integer, MapSource> updated) {
return prefs.edit()
.putString(KEY_SOURCES, new Gson().toJson(updated))
.commit();
}
}
@@ -1,11 +1,14 @@
package eu.konggdev.strikemaps.map; package eu.konggdev.strikemaps.map;
import java.util.*; import java.util.*;
import android.widget.Toast;
import eu.konggdev.strikemaps.Component; import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory; import eu.konggdev.strikemaps.map.renderer.implementation.MapLibreGLJSRenderer;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer;
import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
@@ -16,20 +19,27 @@ import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentMap; import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentMap;
public class MapComponent implements Component { public class MapComponent implements Component {
MapRenderer mapRenderer; private final MapRenderer mapRenderer;
AppController app; private final AppController app;
public MapStyle style; public MapStyle style;
public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>(); public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>();
public MapComponent(AppController ref) { public MapComponent(AppController ref) {
this.app = ref; this.app = ref;
switch(UserPrefsHelper.mapRenderer(app.getPrefs())) { switch(UserPrefsHelper.mapRenderer(app.getPrefs())) {
case "vtm": case 0:
this.mapRenderer = new MapLibreGLJSRenderer(app, this);
break;
case 1:
this.mapRenderer = new MapLibreNativeRenderer(app, this);
break;
case 2:
this.mapRenderer = new VtmRenderer(app, this); this.mapRenderer = new VtmRenderer(app, this);
break; break;
case "mapLibre":
default: //This shouldn't happen default: //This shouldn't happen
this.mapRenderer = new MapLibreNativeRenderer(app, this); 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);
break; break;
}; };
} }
@@ -40,13 +50,13 @@ public class MapComponent implements Component {
public void setStyle(MapStyle style) { public void setStyle(MapStyle style) {
this.style = style; this.style = style;
mapRenderer.reload(); mapRenderer.styleUpdate(style.effectiveDocument());
} }
public void switchOverlay(MapOverlay overlay) { public void switchOverlay(MapOverlay overlay) {
if (hasOverlay(overlay)) overlays.remove(overlay.getClass()); if (hasOverlay(overlay)) overlays.remove(overlay.getClass());
else overlays.put(overlay.getClass(), overlay); else overlays.put(overlay.getClass(), overlay);
update(); overlayUpdate(overlay);
} }
public boolean hasOverlay(MapOverlay overlay) { public boolean hasOverlay(MapOverlay overlay) {
@@ -57,18 +67,14 @@ public class MapComponent implements Component {
return overlays.containsKey(overlay); return overlays.containsKey(overlay);
} }
public void overlayUpdate(MapOverlay in) {
mapRenderer.overlayUpdate(in);
}
public void selectPoint(Feature selection) { public void selectPoint(Feature selection) {
//FIXME: Put back FragmentPointPreviewPopup (private code atm) //FIXME: Put back FragmentPointPreviewPopup (private code atm)
} }
public void onOverlayUpdate() {
update();
}
public void update() {
if(mapRenderer != null && style != null) mapRenderer.reload();
}
public boolean onMapClick(LatLng point) { public boolean onMapClick(LatLng point) {
List<Feature> features = mapRenderer.featuresAtPoint(point); List<Feature> features = mapRenderer.featuresAtPoint(point);
@@ -93,4 +99,12 @@ public class MapComponent implements Component {
//TODO: Likely Nonfeature(?) point selection //TODO: Likely Nonfeature(?) point selection
return true; return true;
} }
public void onMapInit() {
setStyle(
app.getRegistry().getStyle(
UserPrefsHelper.startupMapStyle(app.getPrefs())
)
);
}
} }
@@ -1,11 +0,0 @@
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;
}
}
@@ -1,16 +0,0 @@
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;
}
}
@@ -0,0 +1,10 @@
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;
}
}
@@ -0,0 +1,22 @@
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; package eu.konggdev.strikemaps.map.overlay;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer; import com.fasterxml.jackson.databind.JsonNode;
/* More or less a data-driven layer factory */ /* More or less a data-driven layer factory */
public interface MapOverlay { public interface MapOverlay {
public SourcedMapLayer makeLayer(); public JsonNode makePatch();
} }
@@ -1,19 +1,14 @@
package eu.konggdev.strikemaps.map.overlay.implementation; package eu.konggdev.strikemaps.map.overlay.implementation;
import android.graphics.Color;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.data.provider.LocationDataProvider; 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.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -33,34 +28,38 @@ public class LocationOverlay implements MapOverlay, LocationListener {
} }
@Override @Override
public SourcedMapLayer makeLayer() { public JsonNode makePatch() {
MapSource source = new MapSource();
source.type = "geojson";
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
try {
ObjectNode data = mapper.createObjectNode();
data.put("type", "Feature");
if(currentLocation != null) { ObjectNode root = mapper.createObjectNode();
ObjectNode sources = mapper.createObjectNode();
ObjectNode location = mapper.createObjectNode();
ObjectNode data = mapper.createObjectNode();
ObjectNode geometry = mapper.createObjectNode(); ObjectNode geometry = mapper.createObjectNode();
geometry.put("type", "Point"); ObjectNode properties = mapper.createObjectNode();
ArrayNode coordinates = mapper.createArrayNode(); ArrayNode coordinates = mapper.createArrayNode();
coordinates.add(currentLocation.getLongitude()); coordinates.add(currentLocation.getLongitude());
coordinates.add(currentLocation.getLatitude()); coordinates.add(currentLocation.getLatitude());
geometry.put("type", "Point");
geometry.set("coordinates", coordinates); geometry.set("coordinates", coordinates);
data.set("geometry", geometry);
data.set("properties", mapper.createObjectNode());
}
source.data = data;
} catch (Exception e) {
e.printStackTrace();
}
data.put("type", "Feature");
data.set("geometry", geometry);
data.set("properties", properties);
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(); ObjectNode layer = mapper.createObjectNode();
layer.put("id", "location"); layer.put("id", "location");
layer.put("type", "circle"); layer.put("type", "circle");
layer.put("source", "location"); layer.put("source", "location");
@@ -68,25 +67,21 @@ public class LocationOverlay implements MapOverlay, LocationListener {
ObjectNode paint = mapper.createObjectNode(); ObjectNode paint = mapper.createObjectNode();
paint.put("circle-radius", 5); paint.put("circle-radius", 5);
paint.put("circle-color", "#1E88E5"); paint.put("circle-color", "#1E88E5");
paint.put("circle-stroke-color", "#FFFFFF"); paint.put("circle-stroke-color", "#FFFFFF");
paint.put("circle-stroke-width", 1.5); paint.put("circle-stroke-width", 1.5);
layer.set("paint", paint); layer.set("paint", paint);
ObjectNode layout = mapper.createObjectNode();
layout.put("circle-pitch-alignment", "map");
layer.set("layout", layout);
ArrayNode layers = mapper.createArrayNode();
layers.add(layer); layers.add(layer);
root.set("layers", layers);
return new SourcedMapLayer("location", source, layers); return root;
} }
@Override @Override
public void onLocationChanged(@NonNull Location location) { public void onLocationChanged(@NonNull Location location) {
this.currentLocation = location; this.currentLocation = location;
map.onOverlayUpdate(); map.overlayUpdate(this);
} }
} }
@@ -1,12 +1,15 @@
package eu.konggdev.strikemaps.map.overlay.implementation; package eu.konggdev.strikemaps.map.overlay.implementation;
import eu.konggdev.strikemaps.map.layer.MapLayer; import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.overlay.MapOverlay;
public class PointSelectionOverlay implements MapOverlay { public class PointSelectionOverlay implements MapOverlay {
AppController app;
MapComponent map;
@Override @Override
public SourcedMapLayer makeLayer() { public JsonNode makePatch() {
return null; return null;
} }
} }
@@ -1,17 +1,18 @@
package eu.konggdev.strikemaps.map.renderer; package eu.konggdev.strikemaps.map.renderer;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import eu.konggdev.strikemaps.map.layer.MapLayer; import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import org.maplibre.android.style.layers.Layer; import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
import java.util.List; import java.util.List;
public interface MapRenderer { public interface MapRenderer {
void reload(); void styleUpdate(StyleDocument style);
void overlayUpdate(MapOverlay overlay);
View getView(); View getView();
@@ -0,0 +1,149 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.annotation.SuppressLint;
import android.view.View;
import android.view.ViewGroup;
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.source.MapSource;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import org.maplibre.android.geometry.LatLng;
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(StyleDocument 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)
for (MapSource source : style.sources)
sources.set(source.name, source.makeJson());
//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();
}
static class Bridge { }
}
@@ -5,14 +5,15 @@ import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper; import eu.konggdev.strikemaps.app.util.JsonPatcher;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; 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.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import org.maplibre.android.MapLibre; import org.maplibre.android.MapLibre;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.android.maps.MapLibreMap; import org.maplibre.android.maps.MapLibreMap;
@@ -27,10 +28,11 @@ import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
AppController app; @NonNull AppController app;
MapComponent controller; @NonNull MapComponent controller;
MapLibreMap map; MapLibreMap map;
final MapView mapView; final MapView mapView;
private JsonNode origin;
public MapLibreNativeRenderer(AppController app, MapComponent controller) { public MapLibreNativeRenderer(AppController app, MapComponent controller) {
this.app = app; this.app = app;
@@ -42,37 +44,72 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
} }
@Override @Override
public void reload() { public void styleUpdate(StyleDocument style) {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
MapStyle style = controller.style;
//Thanks to this, styleUpdate(null) can be used as an origin reload
if (style != null) {
try { try {
/* Take metadata from MapStyle
everything outside sources, layers */
ObjectNode root = style.metadata.deepCopy(); ObjectNode root = style.metadata.deepCopy();
//Sources //Sources
ObjectNode sources = mapper.createObjectNode(); ObjectNode sources = mapper.createObjectNode();
style.sources.forEach((k, v) -> sources.set(k, mapper.valueToTree(v))); if (style.sources != null)
for (MapSource source : style.sources)
sources.set(source.name, source.makeJson());
//Layers //Layers
ArrayNode layers = mapper.createArrayNode(); ArrayNode layers = mapper.createArrayNode();
if (style.layerDefinitions != null)
layers.addAll((ArrayNode) style.layerDefinitions); 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 //Set all to root
root.set("sources", sources); root.set("sources", sources);
root.set("layers", layers); root.set("layers", layers);
this.origin = root;
map.setStyle(new Style.Builder().fromJson(mapper.writeValueAsString(root)));
} catch (Exception e) { } catch (Exception e) {
app.logcat("Failed to reload Map"); 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());
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -91,7 +128,7 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
public void onMapReady(@NonNull MapLibreMap maplibreMap) { public void onMapReady(@NonNull MapLibreMap maplibreMap) {
this.map = maplibreMap; this.map = maplibreMap;
controller.setStyle(MapStyle.fromMapLibreJsonFile(UserPrefsHelper.startupMapStyle(app.getPrefs()), app)); controller.onMapInit();
//I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :) //I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :)
map.getUiSettings().setLogoEnabled(false); map.getUiSettings().setLogoEnabled(false);
@@ -3,7 +3,9 @@ package eu.konggdev.strikemaps.map.renderer.implementation;
import android.view.View; import android.view.View;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.renderer.MapRenderer; import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
@@ -16,6 +18,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
public class VtmRenderer implements MapRenderer { public class VtmRenderer implements MapRenderer {
AppController app; AppController app;
MapComponent controller; MapComponent controller;
@@ -30,7 +33,12 @@ public class VtmRenderer implements MapRenderer {
} }
@Override @Override
public void reload() { public void overlayUpdate(MapOverlay overlay) {
//TODO
}
@Override
public void styleUpdate(StyleDocument style) {
//TODO //TODO
OkHttpClient.Builder builder = new OkHttpClient.Builder(); OkHttpClient.Builder builder = new OkHttpClient.Builder();
OSciMap4TileSource tileSource = OSciMap4TileSource.builder().httpFactory(new OkHttpEngine.OkHttpFactory(builder)).build(); OSciMap4TileSource tileSource = OSciMap4TileSource.builder().httpFactory(new OkHttpEngine.OkHttpFactory(builder)).build();
@@ -1,21 +1,139 @@
package eu.konggdev.strikemaps.map.source; package eu.konggdev.strikemaps.map.source;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.map.source.model.TileSource;
public class MapSource { public class MapSource {
public String url; public enum MapSourceContractType {
public JsonNode data; REQUEST,
DEFINITION
}
/*
* Contract describes the "purpose" of the source.
*
* This exists because we effectively have two types
* of sources:
* - Sources that are the style asking for a specific type of source
* and defining a fallback, which is also used as a default for
*
* and
* - Sources that are the actual source
*/
@NonNull
public final MapSourceContractType contract;
/*
* For a request contract:
* name is the internal key used by the style.
*
* For a definition contract:
* name is the user-facing source name
* (e.g. "ArcGIS Imagery").
*
* When converting a request into a definition
* (when importing a style whose requests
* cannot be satisfied), we look for a name
* field and fall back to normalizing the key instead
*/
public String name;
/*
* For a request contract, this is the fallback
* for when we absolutely cannot satisfy the requirement,
* or the defaults for converting into a definition contract.
*/
public TileSource tileSource;
public String type; public String type;
public String schema; public String schema;
public JsonNode tiles;
public int minzoom; public int minzoom;
public int maxzoom; public int maxzoom;
public String scheme; public String scheme;
public int tileSize; public int tileSize;
public String attribution;
public String encoding; public String encoding;
public MapSource() { }
public MapSource(@NonNull MapSourceContractType contract, String name, TileSource tileSource, String type, String schema) {
this.contract = contract;
this.name = name;
this.tileSource = tileSource;
this.type = type;
this.schema = schema;
}
private MapSource(@NonNull MapSourceContractType contract) {
this.contract = contract;
}
public static MapSource fromJson(MapSourceContractType contract, String key, JsonNode sourceNode) {
MapSource result = new MapSource(contract);
result.name = key;
result.schema = sourceNode.path("schema").asText(null);
result.scheme = sourceNode.path("scheme").asText(null);
result.encoding = sourceNode.path("encoding").asText(null);
result.type = sourceNode.path("type").asText(null);
result.minzoom = sourceNode.path("minZoom").asInt(0);
result.maxzoom = sourceNode.path("maxZoom").asInt(24);
result.tileSize = sourceNode.path("tileSize").asInt(256);
result.tileSource = handleJsonTileSource(contract, sourceNode);
return result;
}
private static TileSource handleJsonTileSource(MapSourceContractType contract, JsonNode sourceNode) {
// By design, a source must use either "url" or "tiles", never both
// In case both are present, we prefer URL over tiles... because I don't know, we just do, m'kay?
if (sourceNode.has("url"))
return new TileSource(sourceNode.get("url").asText());
if (sourceNode.has("tiles")) {
String[] tiles = new String[0];
ObjectMapper mapper = new ObjectMapper();
try {
tiles = mapper.treeToValue(sourceNode.get("tiles"), String[].class);
} catch (Exception e) { // If we can't parse it, lets just keep it an empty array
e.printStackTrace();
}
return new TileSource(tiles);
}
if (contract == MapSourceContractType.REQUEST) //No tile source is only acceptable for a request contract
//TODO: Define an empty tile source?
return null;
//TODO: Decide what to do when we have a definition that doesn't define the most important part - the tile source
//Maybe throwing some custom exception, catching it in fromJson calls and propagating it back to the user would be appropriate
return null;
}
public ObjectNode makeJson() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.createObjectNode();
try {
if (type != null) node.put("type", type);
if (schema != null) node.put("schema", schema);
if (minzoom != 0) node.put("minzoom", minzoom);
if (maxzoom != 24) node.put("maxzoom", maxzoom);
if (scheme != null) node.put("scheme", scheme);
if (tileSize != 256) node.put("tileSize", tileSize);
if (encoding != null) node.put("encoding", encoding);
if (tileSource != null) tileSource.makeJson(mapper, node);
} catch (Exception e) {
e.printStackTrace();
}
return node;
}
} }
@@ -0,0 +1,51 @@
package eu.konggdev.strikemaps.map.source.model;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class TileSource {
public enum TileSourceType {
URL,
TILES,
DATA
}
private TileSourceType type;
private String url;
private String[] tiles;
private JsonNode data;
public TileSource(String url) {
this.url = url;
this.type = TileSourceType.URL;
}
public TileSource(String[] tiles) {
this.tiles = tiles;
this.type = TileSourceType.TILES;
}
public TileSource(JsonNode data) {
this.data = data;
this.type = TileSourceType.DATA;
}
public void makeJson(ObjectMapper mapper, ObjectNode node) {
switch (type) {
case URL -> node.put("url", url);
case TILES -> {
ArrayNode tilesNode = mapper.createArrayNode();
for (String tile : tiles)
tilesNode.add(tile);
node.set("tiles", tilesNode);
}
case DATA -> node.set("data", data);
}
}
}
@@ -1,68 +1,64 @@
package eu.konggdev.strikemaps.map.style; package eu.konggdev.strikemaps.map.style;
import android.graphics.Bitmap; import androidx.annotation.NonNull;
import android.graphics.BitmapFactory; import androidx.annotation.Nullable;
import com.fasterxml.jackson.core.type.TypeReference; import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import com.fasterxml.jackson.databind.JsonNode; import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata;
import com.fasterxml.jackson.databind.ObjectMapper; import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.data.helper.FileHelper;
import eu.konggdev.strikemaps.map.source.MapSource;
import java.util.*;
public class MapStyle { public class MapStyle {
//Only local data public static final class StoredRepresentation {
public String name; public final String json;
public Bitmap icon; public final StyleOptions options;
public JsonNode metadata; // everything except layers + sources public final StyleManagementMetadata managementMetadata;
public Map<String, MapSource> sources;
public ArrayNode layerDefinitions; // the "layers" array
//FIXME public StoredRepresentation(String json, StyleOptions options, StyleManagementMetadata managementMetadata) {
public static MapStyle fromMapLibreJsonFile(String filename, AppController app) { this.json = json;
String styleContents; this.options = options;
if (filename.startsWith("/storage")) styleContents = FileHelper.loadStringFromUserFile(filename); this.managementMetadata = managementMetadata;
else styleContents = FileHelper.loadStringFromAssetFile(filename, app); }
ObjectMapper mapper = new ObjectMapper(); public MapStyle restore() {
try { MapStyle style = new MapStyle(
JsonNode root = mapper.readTree(styleContents); json,
options,
MapStyle style = new MapStyle(); managementMetadata
style.name = root.path("name").asText();
style.icon = getIcon(root.path("icon").asText(), app);
style.sources = mapper.convertValue(
root.path("sources"),
new TypeReference<Map<String, MapSource>>() {}
); );
style.layerDefinitions = root.withArray("layers");
ObjectNode metadata = root.deepCopy();
metadata.remove("layers");
metadata.remove("sources");
style.metadata = metadata;
return style; return style;
} catch (Exception e) {
e.printStackTrace();
} }
return null;
} }
public static Bitmap getIcon(String iconLocator, AppController app) { @NonNull public final StyleDocument document;
switch(iconLocator.split("//")[0]) { @NonNull public final StyleOptions options;
//TODO: https
case "assets:": // Null when the style is not managed
return BitmapFactory.decodeStream(FileHelper.openAssetStream("bundled/icon/" + iconLocator.split("//")[1], app)); @Nullable public StyleManagementMetadata managementMetadata;
default:
app.logcat("Unimplemented icon locator space: " + iconLocator); // Original json representation of the style document, as we got it
return null; @NonNull public final String json;
public MapStyle(@NonNull String json, @NonNull StyleOptions styleOptions, @Nullable StyleManagementMetadata managementMetadata) {
this.json = json;
this.document = new StyleDocument(json);
this.options = styleOptions;
this.managementMetadata = managementMetadata;
} }
public MapStyle(@NonNull String json, @NonNull StyleDocument style, @NonNull StyleOptions styleOptions, @Nullable StyleManagementMetadata managementMetadata) {
this.json = json;
this.document = style;
this.options = styleOptions;
this.managementMetadata = managementMetadata;
}
public StoredRepresentation makeStoredRepresentation() {
return new StoredRepresentation(json, options, managementMetadata);
}
public StyleDocument effectiveDocument() {
return document.effectiveDocument(options);
} }
} }
@@ -0,0 +1,88 @@
package eu.konggdev.strikemaps.map.style.document;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.FileHelper;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import java.util.ArrayList;
import java.util.List;
public class StyleDocument {
//Only local data
public String name;
public String icon;
public JsonNode metadata; // everything except layers + sources
public List<MapSource> sources;
public ArrayNode layerDefinitions; // "layers" array
// Json constructor
public StyleDocument(String json) {
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode root = mapper.readTree(json);
this.name = root.path("name").asText();
this.icon = root.path("icon").asText();
JsonNode jsonSources = root.path("sources");
List<MapSource> sources = new ArrayList<>();
jsonSources.fields().forEachRemaining(entry -> {
sources.add(MapSource.fromJson(MapSource.MapSourceContractType.REQUEST, entry.getKey(), entry.getValue()));
});
this.sources = sources;
this.layerDefinitions = root.withArray("layers");
ObjectNode metadata = root.deepCopy();
metadata.remove("layers");
metadata.remove("sources");
this.metadata = metadata;
} catch (Exception e) {
throw new IllegalArgumentException("Invalid style document", e);
}
}
// Copy constructor
public StyleDocument(StyleDocument other) {
this.name = other.name;
this.icon = other.icon;
this.metadata = other.metadata.deepCopy();
this.sources = new ArrayList<>(other.sources);
this.layerDefinitions = other.layerDefinitions.deepCopy();
}
// The style that is presented to the renderer, with its options applied
public StyleDocument effectiveDocument(StyleOptions options) {
StyleDocument result = new StyleDocument(this); //Copy
for (JsonNode layer : result.layerDefinitions) {
JsonNode option = layer.get("option");
if (option == null)
continue;
if ("enable".equals(option.path("type").asText())) {
String id = layer.path("id").asText();
boolean enabled = options.getBoolean(
id,
option.path("default").asBoolean(true)
);
}
}
return result;
}
}
@@ -0,0 +1,20 @@
package eu.konggdev.strikemaps.map.style.management;
public class StyleManagementMetadata {
public boolean modified;
public boolean doUpdates;
public boolean autoUpdate;
public String source;
public String sourceHash;
public StyleManagementMetadata() { }
public StyleManagementMetadata(boolean modified, boolean doUpdates, boolean autoUpdate, String source, String sourceHash) {
this.modified = modified;
this.doUpdates = doUpdates;
this.autoUpdate = autoUpdate;
this.source = source;
this.sourceHash = sourceHash;
}
}
@@ -0,0 +1,30 @@
package eu.konggdev.strikemaps.map.style.options;
import eu.konggdev.strikemaps.map.source.MapSource;
import java.util.HashMap;
import java.util.Map;
public class StyleOptions {
private Map<String, Object> values;
public StyleOptions() {
this.values = new HashMap<>();
}
public StyleOptions(Map<String, Object> values) {
this.values = values;
}
public boolean getBoolean(String id, boolean defaultValue) {
Object value = values.get(id);
return value instanceof Boolean ? (Boolean) value : defaultValue;
}
public int getInteger(String id, int defaultValue) {
Object value = values.get(id);
return value instanceof Number
? ((Number) value).intValue()
: defaultValue;
}
}
@@ -0,0 +1,74 @@
package eu.konggdev.strikemaps.storage;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import java.util.Map;
public class RegistryStorageComponent implements Component {
private AppController app;
private Map<Integer, MapStyle> styles;
private Map<Integer, MapSource> sources;
public RegistryStorageComponent(AppController app) {
this.app = app;
styles();
}
private Map<Integer, MapSource> sources() {
if (sources == null) sources = UserPrefsHelper.sources(app.getPrefs());
return sources;
}
public Map<Integer, MapSource> getSources() {
return sources();
}
private Map<Integer, MapStyle> styles() {
if (styles == null) styles = UserPrefsHelper.styles(app.getPrefs(), app);
return styles;
}
public Map<Integer, MapStyle> getStyles() {
return styles();
}
public MapStyle getStyle(Integer id) {
return styles().get(id);
}
public int addStyle(MapStyle style) {
Map<Integer, MapStyle> styles = styles();
int id = styles.keySet().stream()
.mapToInt(Integer::intValue)
.max()
.orElse(-1) + 1;
styles.put(id, style);
save();
return id;
}
public void updateStyle(int id, MapStyle style) {
styles().put(id, style);
save();
}
public void deleteStyle(int id) {
styles().remove(id);
save();
}
private void save() {
UserPrefsHelper.styles(app.getPrefs(), styles);
}
public void checkForUpdates() {
//
}
}
@@ -3,64 +3,83 @@ package eu.konggdev.strikemaps.ui;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.common.collect.BiMap; import androidx.appcompat.widget.Toolbar;
import eu.konggdev.strikemaps.Component; import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.ui.element.UIRegion; import eu.konggdev.strikemaps.ui.element.region.content.MainContentRegion;
import eu.konggdev.strikemaps.ui.element.region.UIRegion;
import eu.konggdev.strikemaps.ui.fragment.layout.FragmentLayoutControls; 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.fragment.layout.content.main.FragmentLayoutContentSettings;
import eu.konggdev.strikemaps.ui.screen.Screen; import eu.konggdev.strikemaps.ui.screen.Screen;
import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
import java.util.HashMap; import java.util.ArrayDeque;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
public class UIComponent implements Component { public class UIComponent implements Component {
@NonNull AppController app; @NonNull AppController app;
private Map<Integer, Screen> screens; MapComponent map;
private Integer currentScreen;
private final ArrayDeque<Screen> screenStack = new ArrayDeque<>();
public UIComponent(AppController app, MapComponent map) { public UIComponent(AppController app, MapComponent map) {
this.app = app; this.app = app;
this.screens = Map.of( this.map = map;
}
public Map<DefinedScreen, Screen> getScreens(MapComponent map) {
return Map.of(
//Main screen //Main screen
R.layout.screen_main, new Screen( DefinedScreen.MAIN, new Screen(
//App reference //App reference
app, app,
//Map view
map.toFragment(), //FragmentLayoutContentMap
//Main screen init regions definition //Main screen init regions definition
Map.of(R.id.bottomUi, new UIRegion(new FragmentLayoutControls(app, R.id.bottomUi), R.id.bottomUi)), //TODO: Probably stop referencing layout 3(!) times everytime Map.of(
//Layout R.id.mainContentView, new MainContentRegion(map.toFragment(), R.id.mainContentView),
R.layout.screen_main //TODO: Define this for the Screen without duplicating the reference 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
), ),
//Settings screen //Settings screen
R.layout.screen_settings, new Screen( DefinedScreen.SETTINGS, new Screen(
app, app,
//Settings //Just the settings content fragment
new FragmentLayoutContentSettings(), Map.of(
/* No regions defined in settings R.id.mainContentView, new MainContentRegion(new FragmentLayoutContentSettings(app), R.id.mainContentView)
Entire screen is just the main view */ )
new HashMap<>(), ),
//Layout //Offline maps screen
R.layout.screen_settings DefinedScreen.OFFLINE, new Screen(
app,
Map.of(
R.id.mainContentView, new MainContentRegion(new FragmentLayoutContentOfflineMaps(app), R.id.mainContentView)
)
) )
); );
} }
public void swapScreen(Integer screen) { public void swapScreen(DefinedScreen screenKey) {
currentScreen = screen; if (!screenStack.isEmpty()) getCurrentScreen().detachAll();
screenStack.add(getScreens(map).get(screenKey));
getCurrentScreen().attachAll(); getCurrentScreen().attachAll();
} }
public Screen getCurrentScreen() { public boolean back() {
return getScreen(currentScreen); if (screenStack.size() <= 1) return false;
getCurrentScreen().detachAll();
screenStack.removeLast();
getCurrentScreen().attachAll();
return true;
} }
public Screen getScreen(Integer screen) { public Screen getCurrentScreen() {
return screens.get(screen); return screenStack.getLast();
} }
public void alert(AlertDialog dialog) { public void alert(AlertDialog dialog) {
@@ -7,14 +7,17 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.FileHelper;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.UIComponent;
public class GenericItem implements UIItem { public class GenericItem implements UIItem {
@NonNull public String name; @NonNull public String name;
public Bitmap image; public Bitmap image;
public Runnable onClick; public Runnable onClick;
public Runnable onLongClick;
boolean hasImage; boolean hasImage;
public GenericItem(String refName) { public GenericItem(String refName) {
@@ -26,6 +29,12 @@ public class GenericItem implements UIItem {
this.onClick = onClick; this.onClick = onClick;
hasImage = false; 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) { public GenericItem(String refName, Bitmap refImage) {
this.name = refName; this.name = refName;
this.image = refImage; this.image = refImage;
@@ -38,18 +47,35 @@ public class GenericItem implements UIItem {
hasImage = true; hasImage = true;
} }
public final static GenericItem fromStyle(MapStyle style, MapComponent map) { 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 static GenericItem fromStyle(StyleDocument style, AppController app, Runnable onClick) {
if(style == null) return new GenericItem("Unknown"); if(style == null) return new GenericItem("Unknown");
if(style.icon != null) if(style.icon != null)
return new GenericItem(style.name, style.icon, () -> map.setStyle(style)); return new GenericItem(style.name, FileHelper.getIcon(style.icon, app), onClick);
return new GenericItem(style.name, () -> map.setStyle(style)); return new GenericItem(style.name, onClick);
} }
public static GenericItem fromStyle(StyleDocument style, AppController app, Runnable onClick, Runnable onLongClick) {
if(style == null) return new GenericItem("Unknown");
if(style.icon != null)
return new GenericItem(style.name, FileHelper.getIcon(style.icon, app), onClick, onLongClick);
return new GenericItem(style.name, onClick, onLongClick);
}
@Override
public View makeView(UIComponent spawner) { public View makeView(UIComponent spawner) {
View v = spawner.inflateUi(R.layout.item_generic); View v = spawner.inflateUi(R.layout.item_generic);
//FIXME: These shouldn't be casted like that! //FIXME: These shouldn't be casted like that!
((TextView) v.findViewById(R.id.name)).setText(name); ((TextView) v.findViewById(R.id.name)).setText(name);
if(image != null) ((ImageButton) v.findViewById(R.id.image)).setImageBitmap(image); if(image != null) ((ImageButton) v.findViewById(R.id.image)).setImageBitmap(image);
if(onClick != null) v.findViewById(R.id.image).setOnClickListener(click(onClick)); if(onClick != null) v.findViewById(R.id.image).setOnClickListener(click(onClick));
if(onLongClick != null) v.findViewById(R.id.image).setOnLongClickListener(longClick(onLongClick));
return v; return v;
} }
@@ -0,0 +1,38 @@
package eu.konggdev.strikemaps.ui.element.item;
import android.view.View;
import android.widget.TextView;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.ui.UIComponent;
public class InlineItem implements UIItem {
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;
}
public static InlineItem fromSource(MapSource source) {
return new InlineItem(source.name);
}
public static InlineItem fromSource(MapSource source, Runnable onClick) {
return new InlineItem(source.name, onClick);
}
@Override
public View makeView(UIComponent spawner) {
View view = spawner.inflateUi(R.layout.item_inline);
((TextView) view.findViewById(R.id.inline)).setText(text);
if(onClick != null) view.findViewById(R.id.inline).setOnClickListener(click(onClick));
return view;
}
}
@@ -4,35 +4,41 @@ import android.graphics.Bitmap;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.UIComponent;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
public class PreviewItem implements UIItem { public class PreviewItem implements UIItem {
public String name; @NonNull public String name;
public String type; public String details;
public Bitmap image; public Bitmap image;
boolean hasImage; boolean hasImage;
public PreviewItem(String refName, String refType) { public PreviewItem(String refName, String refType) {
this.name = refName; this.name = refName;
this.type = refType; this.details = refType;
hasImage = false; hasImage = false;
} }
public PreviewItem(String refName, String refType, Bitmap refImage) { public PreviewItem(String refName, String refType, Bitmap refImage) {
this.name = refName; this.name = refName;
this.type = refType; this.details = refType;
this.image = refImage; this.image = refImage;
hasImage = true; hasImage = true;
} }
public static PreviewItem fromFeature(Feature feature) { public static PreviewItem fromFeature(Feature feature) {
return new PreviewItem(feature.getStringProperty("name"), feature.getStringProperty("class")); return new PreviewItem(feature.getStringProperty("name"), feature.getStringProperty("class"));
} }
@Override
public View makeView(UIComponent spawner) { public View makeView(UIComponent spawner) {
View view = spawner.inflateUi(R.layout.item_preview); View view = spawner.inflateUi(R.layout.item_preview);
((TextView) view.findViewById(R.id.choiceName)).setText(name); ((TextView) view.findViewById(R.id.choiceName)).setText(name);
((TextView) view.findViewById(R.id.type)).setText(type); if (details != null)
((TextView) view.findViewById(R.id.details)).setText(details);
return view; return view;
} }
public View makeView(UIComponent spawner, View.OnClickListener onClick) { public View makeView(UIComponent spawner, View.OnClickListener onClick) {
View view = makeView(spawner); View view = makeView(spawner);
view.setOnClickListener(onClick); view.setOnClickListener(onClick);
@@ -0,0 +1,12 @@
package eu.konggdev.strikemaps.ui.element.item;
import android.view.View;
import eu.konggdev.strikemaps.ui.UIComponent;
public class SelectionItem implements UIItem {
@Override
public View makeView(UIComponent spawner) {
return null;
}
}
@@ -12,5 +12,4 @@ public interface UIItem {
} }
default View.OnLongClickListener longClick(Runnable action) { return v -> { action.run(); return true; };} default View.OnLongClickListener longClick(Runnable action) { return v -> { action.run(); return true; };}
} }
@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.ui.element; package eu.konggdev.strikemaps.ui.element.region;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -40,5 +40,4 @@ public class UIRegion {
currentFragment = stockFragment; currentFragment = stockFragment;
} }
} }
} }
@@ -0,0 +1,24 @@
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,20 +1,135 @@
package eu.konggdev.strikemaps.ui.factory; package eu.konggdev.strikemaps.ui.factory;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
import android.os.Looper;
import android.view.View; import android.view.View;
import android.widget.LinearLayout; import android.widget.*;
import android.widget.ScrollView; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.app.util.JsonPatcher;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.element.item.PreviewItem; import eu.konggdev.strikemaps.ui.element.item.PreviewItem;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
//FIXME: Move Item functions into specific classes for specific types - e.g. StyleItem
//FIXME: Cleaner architecture would be having a class for each AlertDialog type
public final class AlertDialogFactory { public final class AlertDialogFactory {
public static AlertDialog copyBuiltInStyle(AppController app, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup) {
//TODO: Use an UI element that's supposed to be vertical, instead of GenericItem
LinearLayout container = new LinearLayout(app.getActivity());
container.setOrientation(LinearLayout.VERTICAL);
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");
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);
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();
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);
}
app.getRegistry().addStyle(new MapStyle(
mapper.writeValueAsString(root),
new StyleOptions(),
null
));
dialog.dismiss();
} catch (Exception e) {
Toast.makeText(app.getActivity(), "Failed to create", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
mapChangePopup.reloadStyles();
});
});
return dialog;
}
public static AlertDialog pointSelector(AppController app, List<Feature> features, Consumer<Feature> callback) { public static AlertDialog pointSelector(AppController app, List<Feature> features, Consumer<Feature> callback) {
LinearLayout layout = new LinearLayout(app.getActivity()); LinearLayout layout = new LinearLayout(app.getActivity());
layout.setOrientation(LinearLayout.VERTICAL); layout.setOrientation(LinearLayout.VERTICAL);
@@ -30,7 +145,7 @@ public final class AlertDialogFactory {
for (Feature feature : features) { for (Feature feature : features) {
View itemView = PreviewItem.fromFeature(feature).makeView(app.getUi(), v -> { View itemView = PreviewItem.fromFeature(feature).makeView(app.getUi(), v -> {
dialog.dismiss(); dialog.dismiss();
new android.os.Handler(android.os.Looper.getMainLooper()) new Handler(Looper.getMainLooper())
.post(() -> callback.accept(feature)); .post(() -> callback.accept(feature));
}); });
layout.addView(itemView); layout.addView(itemView);
@@ -38,4 +153,34 @@ public final class AlertDialogFactory {
return dialog; 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,7 +4,6 @@ import android.view.MotionEvent;
import android.view.View; import android.view.View;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.element.UIRegion;
public interface ContainerFragment { public interface ContainerFragment {
abstract public Integer getRegion(); abstract public Integer getRegion();
@@ -0,0 +1,142 @@
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 androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.card.MaterialCardView;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
import java.io.*;
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
)
);
} else {
app.getRegistry().addStyle(
new MapStyle(
content,
new StyleOptions(),
null
)
);
mapChangePopup.reloadStyles();
}
}
}
}
} 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);
MaterialCardView builtInBtn = view.findViewById(R.id.buttonFromBuiltInStyle);
MaterialCardView fileBtn = view.findViewById(R.id.buttonFromFile);
MaterialCardView 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;
}
}
@@ -0,0 +1,125 @@
package eu.konggdev.strikemaps.ui.fragment.dialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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 com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.card.MaterialCardView;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.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;
public class StyleDetailsBottomSheet extends BottomSheetDialogFragment {
@NonNull
AppController app;
@NonNull
MapComponent map;
@NonNull
UIComponent ui;
@NonNull
final FragmentMapChangePopup mapChangePopup;
private final MapStyle style;
private final Integer id;
private ActivityResultLauncher<Intent> exportLauncher;
void deleteStyle() {
app.getRegistry().deleteStyle(id);
mapChangePopup.reloadStyles();
dismiss();
}
private void showExportDialog() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/json");
exportLauncher.launch(intent);
}
public StyleDetailsBottomSheet(AppController app, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup, MapStyle style, Integer id) {
this.app = app;
this.map = map;
this.ui = ui;
this.mapChangePopup = mapChangePopup;
this.style = style;
this.id = id;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exportLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == android.app.Activity.RESULT_OK
&& result.getData() != null) {
Uri uri = result.getData().getData();
try (OutputStream out =
requireContext()
.getContentResolver()
.openOutputStream(uri)) {
if (out != null) {
out.write(style.json.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_style_details, container, false);
TextView styleNameView = view.findViewById(R.id.styleName);
TextView styleTypeView = view.findViewById(R.id.styleType);
TextView builtInStyleAlert = view.findViewById(R.id.builtInStyleAlert);
MaterialCardView editButtonLayout = view.findViewById(R.id.editButton);
MaterialCardView copyButtonLayout = view.findViewById(R.id.copyButton);
MaterialCardView exportButtonLayout = view.findViewById(R.id.exportButton);
MaterialCardView deleteButtonLayout = view.findViewById(R.id.deleteButton);
MaterialCardView closeButtonLayout = view.findViewById(R.id.closeButton);
styleNameView.setText(style.document.name);
if (style.managementMetadata != null) {
styleTypeView.setText("Built-In Style");
} 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, style.json, mapChangePopup)));
exportButtonLayout.setOnClickListener(v -> showExportDialog());
deleteButtonLayout.setOnClickListener(v -> deleteStyle());
closeButtonLayout.setOnClickListener(v -> dismiss());
return view;
}
}
@@ -15,7 +15,7 @@ import android.widget.Toast;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper; import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.implementation.LocationOverlay; import eu.konggdev.strikemaps.map.overlay.implementation.LocationOverlay;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup; import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
@@ -0,0 +1,86 @@
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);
});
}));
}
}
@@ -2,13 +2,11 @@ package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.element.UIRegion;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
public class FragmentLayoutContentMap extends Fragment implements MainContentLayout { public class FragmentLayoutContentMap extends Fragment implements MainContentLayout {
@@ -28,6 +26,7 @@ public class FragmentLayoutContentMap extends Fragment implements MainContentLay
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
LinearLayout layout = (LinearLayout) view; LinearLayout layout = (LinearLayout) view;
if(mapView.getParent() != null) ((ViewGroup) mapView.getParent()).removeView(mapView);
layout.addView(mapView); layout.addView(mapView);
} }
} }
@@ -0,0 +1,32 @@
package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
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);
// LinearLayout sourcesLayout = view.findViewById(R.id.llDownloadContainer);
// for (MapSource source : sources)
// sourcesLayout.addView(new InlineItem(source.name, () -> Toast.makeText(app.getActivity(), "Work in progress", Toast.LENGTH_SHORT).show()).makeView(app.getUi()));
}
}
@@ -1,10 +1,60 @@
package eu.konggdev.strikemaps.ui.fragment.layout.content.main; package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
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.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
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 @Override
public Fragment toFragment() { public Fragment toFragment() {
return null; 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) {}
}
);
} }
} }
@@ -1,25 +1,29 @@
package eu.konggdev.strikemaps.ui.fragment.popup; package eu.konggdev.strikemaps.ui.fragment.popup;
import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.data.helper.FileHelper;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.ui.UIComponent; 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 eu.konggdev.strikemaps.ui.element.item.GenericItem;
import java.util.ArrayList; import java.util.Map;
import java.util.Arrays;
import java.util.List;
public class FragmentMapChangePopup extends Fragment implements Popup { public class FragmentMapChangePopup extends Fragment implements Popup {
@NonNull AppController app; @NonNull AppController app;
@@ -28,6 +32,68 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
private final Integer region; private final Integer region;
private View view;
Map<Integer, Integer> tabs = Map.of(
R.id.stylesTab, R.id.styles,
R.id.optionsTab, R.id.options,
R.id.overlaysTab, R.id.overlays
);
Map<Integer, Runnable> tabLoadActions = Map.of(
R.id.stylesTab, this::reloadStyles,
R.id.optionsTab, this::loadStyleOptions,
R.id.overlaysTab, this::reloadOverlays
);
public void reloadStyles() {
LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout);
stylesLayout.removeAllViews();
app.getRegistry().getStyles().forEach((id, style) ->
stylesLayout.addView(GenericItem.fromStyle(style.document, app,
() -> map.setStyle(style),
() -> this.styleDetails(style, id)).makeView(ui))
);
Bitmap addNewIcon = BitmapFactory.decodeResource(app.getActivity().getResources(), android.R.drawable.ic_menu_add);
stylesLayout.addView(new GenericItem("",
addNewIcon,
this::newStyleFlow).makeView(ui));
}
public void loadStyleOptions() {
LinearLayout optionsLayout = view.findViewById(R.id.optionsLayout);
// optionsLayout.removeAllViews();
// TextView sources = new AppCompatTextView(requireContext()) {{
// setText("Sources");
// setTextSize(16);
// setLayoutParams(new ViewGroup.MarginLayoutParams(
// ViewGroup.LayoutParams.WRAP_CONTENT,
// ViewGroup.LayoutParams.WRAP_CONTENT
// ) {{ leftMargin = 16; }});
// }};
// optionsLayout.addView(sources);
// for (MapSource source : map.style.sources) {
// LinearLayout sourceSelectionLayout = new LinearLayout(requireContext());
// TextView sourceName = new TextView(requireContext());
// sourceName.setText(source.name);
// sourceSelectionLayout.addView(sourceName);
// optionsLayout.addView(sourceSelectionLayout);
// }
}
public void reloadOverlays() {
}
void newStyleFlow() {
new NewStyleBottomSheet(app, map, ui, this).show(app.getActivity().getSupportFragmentManager(), "NewStyleBottomSheet");
}
void styleDetails(MapStyle entry, Integer id) {
new StyleDetailsBottomSheet(app, map, ui, this, entry, id).show(app.getActivity().getSupportFragmentManager(), "StyleDetailsBottomSheet");
}
public FragmentMapChangePopup(AppController app, Integer region) { public FragmentMapChangePopup(AppController app, Integer region) {
super(R.layout.popup_map_change); super(R.layout.popup_map_change);
this.app = app; this.app = app;
@@ -46,16 +112,30 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
return this; return this;
} }
@SuppressLint("UseCompatTextViewDrawableApis")
void switchTab(int target) {
tabs.forEach((button, content) -> {
view.findViewById(content).setVisibility(button == target ? View.VISIBLE : View.GONE);
int color = (button == target
? Color.WHITE
: Color.parseColor("#888888"));
((TextView) view.findViewById(button)).setTextColor(color);
((TextView) view.findViewById(button)).setCompoundDrawableTintList(ColorStateList.valueOf(color));
});
tabLoadActions.get(target).run();
}
@Override @Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
//FIXME //FIXME
this.view = view;
tabs.keySet().forEach(button -> {
view.findViewById(button).setOnClickListener(v -> switchTab(button));
});
switchTab(R.id.stylesTab); // Show styles
setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup())); setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup()));
setupDragHandle(view, view, () -> ui.getCurrentScreen().closePopup()); setupDragHandle(view, view, () -> ui.getCurrentScreen().closePopup());
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,31 +1,25 @@
package eu.konggdev.strikemaps.ui.screen; package eu.konggdev.strikemaps.ui.screen;
import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.appcompat.widget.Toolbar;
import java.util.List;
import java.util.Map; import java.util.Map;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.fragment.ContainerFragment; import eu.konggdev.strikemaps.ui.fragment.ContainerFragment;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.MainContentLayout; import eu.konggdev.strikemaps.ui.fragment.FragmentEmptyPlaceholder;
import eu.konggdev.strikemaps.ui.fragment.popup.Popup; import eu.konggdev.strikemaps.ui.fragment.popup.Popup;
import eu.konggdev.strikemaps.ui.element.UIRegion; import eu.konggdev.strikemaps.ui.element.region.UIRegion;
public class Screen { public class Screen {
@NonNull AppController app; @NonNull AppController app;
public Screen(AppController app, MainContentLayout mainContent, Map<Integer, UIRegion> regions, Integer layout) { Toolbar toolbar;
public Screen(AppController app, Map<Integer, UIRegion> regions) {
this.app = app; this.app = app;
this.layout = layout;
this.mainContent = mainContent;
this.uiRegions = regions; this.uiRegions = regions;
} }
private final Integer layout;
private MainContentLayout mainContent;
Map<Integer, UIRegion> uiRegions; Map<Integer, UIRegion> uiRegions;
public Integer popup; public Integer popup;
@@ -68,10 +62,10 @@ public class Screen {
} }
public void attachAll() { public void attachAll() {
app.getActivity().setContentView(layout); for (UIRegion region : uiRegions.values()) setFragment(region, region.getFragment());
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());
} }
} }
@@ -0,0 +1,6 @@
package eu.konggdev.strikemaps.ui.screen.definition;
public enum DefinedScreen {
MAIN,
SETTINGS,
OFFLINE
}
+16
View File
@@ -0,0 +1,16 @@
The following files included inside the /res/drawable folder are derived from Google Material Icons and are subject to the Apache License, Version 2.0:
* `ic_sliders.xml`
* `ic_bubble.xml`
* `ic_stack.xml`
* `ic_clarify.xml`
*
The following files included inside the /res/drawable folder are copies of Google Material Icons and are subject to the Apache License, Version 2.0:
* `ic_file.xml`
* `ic_offline.xml`
* `ic_arrow_forward.xml`
* `ic_hide.bmp`
* `ic_dropdown.xml`
Unless otherwise noted in this file, all other files included in the `res` folder are subject to the standard license of this project
@@ -0,0 +1,9 @@
<?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>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="4dp"
android:insetTop="4dp"
android:insetRight="4dp"
android:insetBottom="4dp">
<ripple android:color="#33FFFFFF">
<item>
<shape android:shape="rectangle">
<solid android:color="#000000" />
<corners android:radius="3dp" />
</shape>
</item>
</ripple>
</inset>
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M400,680L400,280L600,480L400,680Z"/>
</vector>
+5
View File
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="18dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="18dp">
<path android:fillColor="@android:color/white" android:pathData="M619.5,148.5Q685,177 734,226Q783,275 811.5,340.5Q840,406 840,480Q840,554 811.5,619.5Q783,685 734,734Q685,783 619.5,811.5Q554,840 480,840Q439,840 401,831Q363,822 325,805L386,744Q409,752 432.5,756Q456,760 480,760Q596,760 678,678Q760,596 760,480Q760,364 678,282Q596,200 480,200Q364,200 282,282Q200,364 200,480Q200,504 204,527.5Q208,551 216,574L156,634Q138,598 129,559.5Q120,521 120,480Q120,406 148.5,340.5Q177,275 226,226Q275,177 340.5,148.5Q406,120 480,120Q554,120 619.5,148.5ZM520,640L520,496L176,840L120,784L464,440L320,440L320,360L600,360L600,640L520,640Z"/>
</vector>
+17
View File
@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:tint="#000000"
android:viewportWidth="960"
android:viewportHeight="960">
<group
android:translateY="-40">
<path
android:fillColor="@android:color/white"
android:pathData="M240,680L520,680L520,600L240,600L240,680ZM640,680L720,680L720,280L640,280L640,680ZM240,520L520,520L520,440L240,440L240,520ZM240,360L520,360L520,280L240,280L240,360ZM160,840Q127,840 103.5,816.5Q80,793 80,760L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,760Q880,793 856.5,816.5Q833,840 800,840L160,840ZM160,760L800,760Q800,760 800,760Q800,760 800,760L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760ZM160,760L160,760Q160,760 160,760Q160,760 160,760L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760Z" />
</group>
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

+10
View File
@@ -0,0 +1,10 @@
<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="@android:color/white"
android:pathData="M16,1H4C2.9,1 2,1.9 2,3v14h2V3h12V1zM19,5H8C6.9,5 6,5.9 6,7v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2V7C21,5.9 20.1,5 19,5zM19,21H8V7h11V21z"/>
</vector>
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M480,600L280,400L680,400L480,600Z"/>
</vector>
+5
View File
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M720,630Q720,734 647,807Q574,880 470,880Q366,880 293,807Q220,734 220,630L220,260Q220,185 272.5,132.5Q325,80 400,80Q475,80 527.5,132.5Q580,185 580,260L580,610Q580,656 548,688Q516,720 470,720Q424,720 392,688Q360,656 360,610L360,240L440,240L440,610Q440,623 448.5,631.5Q457,640 470,640Q483,640 491.5,631.5Q500,623 500,610L500,260Q499,218 470.5,189Q442,160 400,160Q358,160 329,189Q300,218 300,260L300,630Q299,701 349,750.5Q399,800 470,800Q540,800 589,750.5Q638,701 640,630L640,240L720,240L720,630Z"/>
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

After

Width:  |  Height:  |  Size: 177 B

+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M11.07,12.85c0.77,-1.39 2.25,-2.21 3.11,-3.44 0.91,-1.29 0.4,-3.7 -2.18,-3.7 -1.69,0 -2.52,1.28 -2.87,2.34L6.54,6.96C7.25,4.83 9.18,3 11.99,3c2.35,0 3.96,1.07 4.78,2.41 0.7,1.15 1.11,3.3 0.03,4.9 -1.2,1.77 -2.35,2.31 -2.97,3.45 -0.25,0.46 -0.35,0.76 -0.35,2.24h-2.89C10.58,15.22 10.46,13.95 11.07,12.85zM14,20c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2 0.9,-2 2,-2 2,0.9 2,2z"/>
</vector>
+5
View File
@@ -0,0 +1,5 @@
<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>
+24
View File
@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#000000">
<group
android:pivotX="480"
android:pivotY="480"
android:scaleX="1.15"
android:scaleY="1.15"
android:translateX="-80"
android:translateY="40">
<path
android:fillColor="@android:color/white"
android:pathData="M300,600L360,600L360,440L300,440L300,490L240,490L240,550L300,550L300,600Z
M400,550L720,550L720,490L400,490L400,550Z
M600,440L660,440L660,390L720,390L660,330L660,280L600,280L600,440Z
M240,390L560,390L560,330L240,330L240,390Z" />
</group>
</vector>
+17
View File
@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:tint="#000000"
android:viewportWidth="960"
android:viewportHeight="960">
<group
android:translateY="-40">
<path
android:fillColor="@android:color/white"
android:pathData="M80,880L80,800L880,800L880,880L80,880ZM280,440L280,320L680,320L680,440L280,440ZM280,680L280,560L680,560L680,680L280,680Z" />
</group>
</vector>
@@ -0,0 +1,173 @@
<?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:padding="12dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="34dp"
android:gravity="center"
android:text="New style"
android:textColor="#FFFFFF"
android:textSize="24sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/buttonFromBuiltInStyle"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/builtInStyleIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_edit"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/builtInStyleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="From built-In style"
android:textColor="#FFFFFF"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/buttonFromFile"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/buttonFromBuiltInStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/fileIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_file"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/fileBtnText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="From file"
android:textColor="#FFFFFF"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/buttonCreateEmpty"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="10dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/buttonFromFile"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/emptyIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_create"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/emptyBtnText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Create empty"
android:textColor="#FFFFFF"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,305 @@
<?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: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/styleType"
android:layout_width="112dp"
android:layout_height="27dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="Style Type?"
android:textColor="#FDFDFD"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/styleName" />
<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" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/editButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/builtInStyleAlert"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/hideIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_edit"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="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>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/copyButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/editButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/copyIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_copy"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="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>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/exportButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/copyButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/exportIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_save"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="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_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/deleteButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/exportButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/deleteIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_delete"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="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_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/closeButton"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="10dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#1B1A1A"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:strokeColor="#292828"
app:strokeWidth="1dp"
app:layout_constraintTop_toBottomOf="@id/deleteButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/closeIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@android:drawable/ic_menu_more"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="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_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
+52
View File
@@ -0,0 +1,52 @@
<?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>
+17 -11
View File
@@ -8,33 +8,36 @@
<ImageButton <ImageButton
android:id="@+id/routeButton" android:id="@+id/routeButton"
android:layout_width="65dp" android:layout_width="64dp"
android:layout_height="42dp" android:layout_height="38dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginBottom="36dp" android:layout_marginBottom="34dp"
android:backgroundTint="#000000" android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
android:src="@android:drawable/ic_menu_directions" android:src="@android:drawable/ic_menu_directions"
app:layout_constraintBottom_toBottomOf="@+id/placesButton" app:layout_constraintBottom_toBottomOf="@+id/placesButton"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<ImageButton <ImageButton
android:id="@+id/placesButton" android:id="@+id/placesButton"
android:layout_width="65dp" android:layout_width="64dp"
android:layout_height="43dp" android:layout_height="38dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginBottom="36dp" android:layout_marginBottom="34dp"
android:backgroundTint="#000000" android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
app:layout_constraintBottom_toBottomOf="@+id/layersButton" app:layout_constraintBottom_toBottomOf="@+id/layersButton"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@android:drawable/ic_menu_myplaces" /> app:srcCompat="@android:drawable/ic_menu_myplaces" />
<ImageButton <ImageButton
android:id="@+id/layersButton" android:id="@+id/layersButton"
android:layout_width="65dp" android:layout_width="64dp"
android:layout_height="42dp" android:layout_height="38dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:backgroundTint="#000000" android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_styles_menu" /> app:srcCompat="@drawable/ic_styles_menu" />
@@ -42,10 +45,11 @@
<ImageButton <ImageButton
android:id="@+id/modeButton" android:id="@+id/modeButton"
android:layout_width="60dp" android:layout_width="60dp"
android:layout_height="36dp" android:layout_height="34dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="44dp" android:layout_marginBottom="44dp"
android:backgroundTint="#000000" android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
android:src="@android:drawable/ic_menu_manage" android:src="@android:drawable/ic_menu_manage"
app:layout_constraintBottom_toBottomOf="@+id/locationButtonContainer" app:layout_constraintBottom_toBottomOf="@+id/locationButtonContainer"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -54,11 +58,12 @@
<ImageButton <ImageButton
android:id="@+id/attributionButton" android:id="@+id/attributionButton"
android:layout_width="60dp" android:layout_width="60dp"
android:layout_height="31dp" android:layout_height="24dp"
android:layout_marginStart="272dp" android:layout_marginStart="272dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:backgroundTint="#000000" android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.375" app:layout_constraintHorizontal_bias="0.375"
@@ -69,7 +74,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="24dp" android:layout_marginBottom="21dp"
app:layout_constraintBottom_toBottomOf="@+id/attributionButton" app:layout_constraintBottom_toBottomOf="@+id/attributionButton"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent">
@@ -78,6 +83,7 @@
android:layout_width="60dp" android:layout_width="60dp"
android:layout_height="47dp" android:layout_height="47dp"
android:backgroundTint="#000000" android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
android:contentDescription="Location" android:contentDescription="Location"
app:srcCompat="@android:drawable/ic_menu_mylocation" /> app:srcCompat="@android:drawable/ic_menu_mylocation" />
-1
View File
@@ -2,5 +2,4 @@
<android.widget.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.widget.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
</android.widget.LinearLayout> </android.widget.LinearLayout>
@@ -0,0 +1,41 @@
<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>
@@ -0,0 +1,38 @@
<?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="6dp">
<LinearLayout
android:id="@+id/searchContainer"
android:layout_width="320dp"
android:layout_height="35dp"
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,30 @@
<?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
@@ -0,0 +1,28 @@
<?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>
+7 -8
View File
@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="#0E0000" android:background="#000000"
android:foregroundTint="#FFFFFF"> android:foregroundTint="#FFFFFF">
<TextView <TextView
@@ -16,7 +15,7 @@
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:text="Choice Item" android:text="Choice Item"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="24sp" android:textSize="20sp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:scrollHorizontally="false" android:scrollHorizontally="false"
@@ -24,12 +23,12 @@
android:autoSizeMinTextSize="12sp" android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="24sp" android:autoSizeMaxTextSize="24sp"
android:autoSizeStepGranularity="1sp" android:autoSizeStepGranularity="1sp"
app:layout_constraintStart_toEndOf="@+id/poiIcon" app:layout_constraintStart_toEndOf="@+id/icon"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/poiIcon" android:id="@+id/icon"
android:layout_width="55dp" android:layout_width="55dp"
android:layout_height="55dp" android:layout_height="55dp"
android:layout_marginStart="7dp" android:layout_marginStart="7dp"
@@ -39,20 +38,20 @@
app:srcCompat="@drawable/maplibre_info_bg_selector" /> app:srcCompat="@drawable/maplibre_info_bg_selector" />
<TextView <TextView
android:id="@+id/type" android:id="@+id/details"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="7dp" android:layout_marginStart="7dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:text="Type"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
app:layout_constraintStart_toEndOf="@id/poiIcon" app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/choiceName" app:layout_constraintTop_toBottomOf="@id/choiceName"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,25 @@
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/selectionName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/currentSelection"
android:drawableEnd="@drawable/ic_dropdown"
android:drawablePadding="4dp"
android:drawableTint="@android:color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
+157 -14
View File
@@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="#000000" android:background="#000000"
@@ -19,41 +20,183 @@
<ImageButton <ImageButton
android:id="@+id/closeButton" android:id="@+id/closeButton"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="27dp"
android:backgroundTint="#000000" android:backgroundTint="#000000"
app:srcCompat="@android:drawable/ic_menu_close_clear_cancel" app:srcCompat="@android:drawable/ic_menu_close_clear_cancel"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<TextView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/stylesLabel" android:id="@+id/tabs"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="28dp"
android:text="Styles"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginBottom="2dp"
app:layout_constraintTop_toBottomOf="@id/dragHandle"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/stylesTab"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="Style"
android:textAllCaps="false"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:clickable="true"
android:focusable="true"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_bubble"
android:drawablePadding="6dp"
android:drawableTint="@android:color/white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/optionsTab"
app:layout_constraintHorizontal_chainStyle="packed" />
<TextView
android:id="@+id/optionsTab"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="Options"
android:textAllCaps="false"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#888888"
android:clickable="true"
android:focusable="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_sliders"
android:drawablePadding="2dp"
android:drawableTint="#888888"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/stylesTab"
app:layout_constraintEnd_toStartOf="@id/overlaysTab" />
<TextView
android:id="@+id/overlaysTab"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="Overlays"
android:textAllCaps="false"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#888888"
android:clickable="true"
android:focusable="true"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_stack"
android:drawablePadding="4dp"
android:drawableTint="#888888"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/optionsTab"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<HorizontalScrollView </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/styles"
<FrameLayout
android:id="@+id/tabContent"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/stylesLabel" app:layout_constraintTop_toBottomOf="@id/tabs"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent" >
<!-- Styles tab -->
<LinearLayout
android:id="@+id/styles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<HorizontalScrollView
android:id="@+id/stylesScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:fillViewport="false">
<LinearLayout <LinearLayout
android:id="@+id/stylesLayout" android:id="@+id/stylesLayout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" /> android:orientation="horizontal" />
</HorizontalScrollView> </HorizontalScrollView>
<!-- Sources selection to be implemented here --> </LinearLayout>
<!-- Options tab -->
<ScrollView
android:id="@+id/options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<LinearLayout
android:id="@+id/optionsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sources"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/sourcesLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
<!-- Overlays tab -->
<ScrollView
android:id="@+id/overlays"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<LinearLayout
android:id="@+id/overlaysLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
-80
View File
@@ -1,80 +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/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>
@@ -1,6 +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">
</androidx.constraintlayout.widget.ConstraintLayout>
+41
View File
@@ -0,0 +1,41 @@
<?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>
+1 -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. --> <!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item> <item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/purple_700</item>
@@ -11,6 +11,5 @@
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style> </style>
</resources> </resources>
+7 -1
View File
@@ -1,5 +1,11 @@
<resources> <resources>
<string name="app_name">Strike Maps</string> <string name="app_name">Strike Maps</string>
<string name="settings_title">Settings</string>
<string name="attribution_title"> Attribution </string> <string name="attribution_title"> Attribution </string>
<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> <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>
</resources> </resources>
+2 -3
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. --> <!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/black</item> <item name="colorPrimary">@color/black</item>
<item name="colorPrimaryVariant">@color/black</item> <item name="colorPrimaryVariant">@color/black</item>
@@ -11,6 +11,5 @@
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style> </style>
</resources> </resources>
-3
View File
@@ -10,7 +10,6 @@ constraintlayout = "2.1.4"
playServicesMaps = "19.0.0" playServicesMaps = "19.0.0"
viewpager2 = "1.1.0" viewpager2 = "1.1.0"
kotlin = "2.3.10" kotlin = "2.3.10"
coreKtx = "1.18.0"
[libraries] [libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -20,9 +19,7 @@ appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "a
material = { group = "com.google.android.material", name = "material", version.ref = "material" } material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } 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" } viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }