This commit is contained in:
2026-04-13 20:19:02 +02:00
commit 31c92aeae9
77 changed files with 4610 additions and 0 deletions

1
app/.gitignore vendored Executable file
View File

@@ -0,0 +1 @@
/build

68
app/build.gradle.kts Executable file
View File

@@ -0,0 +1,68 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "eu.konggdev.strikemaps"
compileSdk = 36
defaultConfig {
applicationId = "eu.konggdev.strikemaps"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
}
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
implementation(libs.viewpager2)
implementation("org.maplibre.gl:android-sdk:11.13.0")
implementation("com.github.mapsforge.vtm:vtm:0.27.0")
implementation("com.github.mapsforge.vtm:vtm-android:0.27.0")
implementation("com.github.mapsforge.vtm:vtm-http:0.27.0")
implementation ("com.github.mapsforge.vtm:vtm-android:0.27.0@jar")
runtimeOnly ("com.github.mapsforge.vtm:vtm-android:0.27.0:natives-armeabi-v7a@jar")
runtimeOnly("com.github.mapsforge.vtm:vtm-android:0.27.0:natives-arm64-v8a@jar")
runtimeOnly("com.github.mapsforge.vtm:vtm-android:0.27.0:natives-x86@jar")
runtimeOnly("com.github.mapsforge.vtm:vtm-android:0.27.0:natives-x86_64@jar")
implementation("com.google.guava:guava:33.2.1-android")
implementation("com.caverock:androidsvg:1.4")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0")
implementation("org.apache.commons:commons-lang3:3.14.0")
implementation(libs.core.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
}

21
app/proguard-rules.pro vendored Executable file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,26 @@
package eu.konggdev.strikemaps;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("eu.konggdev.strikemaps", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.StrikeMaps"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
{
"version": 8,
"name": "Satelite",
"icon": "assets://satelite.bmp",
"sources": {
"satelite": {
"type": "raster",
"tiles": [
"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
],
"minzoom": 0,
"maxzoom": 18,
"scheme": "xyz",
"tileSize": 512
}
},
"sprite": "",
"glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
"layers": [
{
"id": "satelite",
"type": "raster",
"source": "satelite"
}
],
"id": "satellite"
}

View File

@@ -0,0 +1,5 @@
package eu.konggdev.strikemaps;
public interface Component {
//TODO: Implement base component methods
}

View File

@@ -0,0 +1,31 @@
package eu.konggdev.strikemaps;
import static org.maplibre.android.style.layers.PropertyFactory.lineColor;
import static org.maplibre.android.style.layers.PropertyFactory.lineWidth;
import eu.konggdev.strikemaps.app.AppController;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
AppController app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = new AppController(this);
app.init();
}
public void logcat(String tag, String log) {
Log.i(tag, log);
}
public void logcat(String log) {
Log.i("LogcatGeneric", log);
}
}

View File

@@ -0,0 +1,45 @@
package eu.konggdev.strikemaps.app;
import android.content.SharedPreferences;
import androidx.appcompat.app.AppCompatActivity;
import eu.konggdev.strikemaps.MainActivity;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.ui.UIComponent;
import static android.content.Context.MODE_PRIVATE;
public class AppController {
private final MainActivity appActivity;
private MapComponent map;
private UIComponent ui;
public AppController(MainActivity refActivity) {
appActivity = refActivity;
}
public void logcat(String log) {
appActivity.logcat(log);
}
public UIComponent getUi() {
if (ui == null) init();
return ui;
}
public MapComponent getMap() {
if (map == null) init();
return map;
}
public SharedPreferences getPrefs() {
return getActivity().getSharedPreferences("user_prefs", MODE_PRIVATE);
}
public AppCompatActivity getActivity() { return appActivity; }
public void init() {
if (getActivity().getSupportActionBar() != null)
getActivity().getSupportActionBar().hide();
if(map == null) map = new MapComponent(this);
if(ui == null) {
ui = new UIComponent(this, map);
ui.swapScreen(R.layout.screen_main); //Initial
}
}
}

View File

@@ -0,0 +1,41 @@
package eu.konggdev.strikemaps.factory;
import android.app.AlertDialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.element.item.PreviewItem;
import org.maplibre.geojson.Feature;
import java.util.List;
import java.util.function.Consumer;
//FIXME: Move Item functions into specific classes for specific types - e.g. StyleItem
public final class AlertDialogFactory {
public static AlertDialog pointSelector(AppController app, List<Feature> features, Consumer<Feature> callback) {
LinearLayout layout = new LinearLayout(app.getActivity());
layout.setOrientation(LinearLayout.VERTICAL);
ScrollView scrollView = new ScrollView(app.getActivity());
scrollView.addView(layout);
AlertDialog dialog = new AlertDialog.Builder(app.getActivity())
.setView(scrollView)
.create();
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.parseColor("#000000")));
for (Feature feature : features) {
View itemView = PreviewItem.fromFeature(feature).makeView(app.getUi(), v -> {
dialog.dismiss();
new android.os.Handler(android.os.Looper.getMainLooper())
.post(() -> callback.accept(feature));
});
layout.addView(itemView);
}
return dialog;
}
}

View File

@@ -0,0 +1,109 @@
package eu.konggdev.strikemaps.helper;
import android.content.res.AssetManager;
import android.os.Environment;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import eu.konggdev.strikemaps.app.AppController;
//FIXME: Ugly
public final class FileHelper {
public static String loadStringFromAssetFile(String filePath, AppController app) {
try (InputStream is = app.getActivity().getAssets().open(filePath)) {
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
return new String(buffer, StandardCharsets.UTF_8);
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
}
public static String loadStringFromUserFile(String filePath) {
File file = new File(filePath);
try (FileInputStream fis = new FileInputStream(file)) {
int size = fis.available();
byte[] buffer = new byte[size];
fis.read(buffer);
return new String(buffer, StandardCharsets.UTF_8);
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
}
public static String[] getAssetFiles(String path, String fileExt, AppController app) {
AssetManager assetManager = app.getActivity().getAssets();
try {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
String fullPath = (path == null || path.isEmpty()) ? "" : path;
String[] files = assetManager.list(fullPath);
if (files == null) return new String[0];
if (fileExt == null || fileExt.isEmpty())
return files;
List<String> filtered = new ArrayList<>();
for (String file : files) {
if (file.toLowerCase().endsWith(fileExt.toLowerCase())) {
filtered.add((fullPath.isEmpty() ? "" : fullPath + "/") + file);
}
}
return filtered.toArray(new String[0]);
} catch (IOException e) {
e.printStackTrace();
return new String[0];
}
}
public static InputStream openAssetStream(String path, AppController app) {
try {
return app.getActivity().getAssets().open(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
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]);
}
}

View File

@@ -0,0 +1,40 @@
package eu.konggdev.strikemaps.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();
}
}

View File

@@ -0,0 +1,95 @@
package eu.konggdev.strikemaps.map;
import java.util.*;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.renderer.implementation.MapLibreNativeRenderer;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentMap;
public class MapComponent implements Component {
MapRenderer mapRenderer;
AppController app;
public String style;
public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>();
public MapComponent(AppController ref) {
this.app = ref;
switch(UserPrefsHelper.mapRenderer(app.getPrefs())) {
case "vtm":
this.mapRenderer = new VtmRenderer(app, this);
break;
case "mapLibre":
default: //This shouldn't happen
this.mapRenderer = new MapLibreNativeRenderer(app, this);
break;
};
}
public FragmentLayoutContentMap toFragment() {
return new FragmentLayoutContentMap(mapRenderer.getView());
}
public void setStyle(String style) {
this.style = style;
mapRenderer.reload();
}
public void switchOverlay(MapOverlay overlay) {
if (hasOverlay(overlay)) overlays.remove(overlay.getClass());
else overlays.put(overlay.getClass(), overlay);
update();
}
public boolean hasOverlay(MapOverlay overlay) {
return overlays.containsKey(overlay.getClass());
}
public boolean hasOverlay(Class<? extends MapOverlay> overlay) {
return overlays.containsKey(overlay);
}
public void selectPoint(Feature selection) {
//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) {
List<Feature> features = mapRenderer.featuresAtPoint(point);
switch (features.size()) {
case 0:
//TODO: Implement point selection for no POI found (MIGHT be done on long click??)
//Maybe collapse UI? (Hide/show UI feature)... could be user configurable
break;
case 1:
selectPoint(features.get(0));
break;
default:
app.getUi().alert(
AlertDialogFactory.pointSelector(app, features, selectedItem -> {
selectPoint(selectedItem);
}));
}
return true;
}
public boolean onMapLongClick(LatLng point) {
//TODO: Likely Nonfeature(?) point selection
return true;
}
}

View File

@@ -0,0 +1,16 @@
package eu.konggdev.strikemaps.map.layer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.style.sources.GeoJsonSource;
import java.util.List;
//TOOD: Make not strictly MapLibre reliant
public class MapLayer {
public GeoJsonSource source;
public Layer layer;
public MapLayer(GeoJsonSource source, Layer layer) {
this.source = source;
this.layer = layer;
}
}

View File

@@ -0,0 +1,8 @@
package eu.konggdev.strikemaps.map.overlay;
import eu.konggdev.strikemaps.map.layer.MapLayer;
/* More or less a data-driven layer factory */
public interface MapOverlay {
public MapLayer makeLayer();
}

View File

@@ -0,0 +1,62 @@
package eu.konggdev.strikemaps.map.overlay.overlay;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationListener;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.provider.LocationDataProvider;
import org.maplibre.android.style.layers.CircleLayer;
import org.maplibre.android.style.layers.Property;
import org.maplibre.android.style.sources.GeoJsonSource;
import org.maplibre.geojson.Feature;
import org.maplibre.geojson.FeatureCollection;
import org.maplibre.geojson.Point;
import static org.maplibre.android.style.layers.PropertyFactory.*;
public class LocationOverlay implements MapOverlay, LocationListener {
LocationDataProvider locationDataProvider;
AppController app;
MapComponent map;
public Location currentLocation = null;
public LocationOverlay(AppController app) {
this.app = app;
this.map = app.getMap();
this.locationDataProvider = new LocationDataProvider(app.getActivity(), this);
}
@Override
public MapLayer makeLayer() {
GeoJsonSource source = new GeoJsonSource(
"location",
FeatureCollection.fromFeatures(new Feature[]{}) // empty
);
if (currentLocation != null)
source.setGeoJson(Feature.fromGeometry(Point.fromLngLat(currentLocation.getLongitude(), currentLocation.getLatitude())));
CircleLayer layer = new CircleLayer("location", "location");
layer.setProperties(
circleRadius(5f),
circleColor(Color.parseColor("#1E88E5")),
circleStrokeColor(Color.WHITE),
circleStrokeWidth(1.5f),
circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP)
);
return new MapLayer(source, layer);
}
@Override
public void onLocationChanged(@NonNull Location location) {
this.currentLocation = location;
map.onOverlayUpdate();
}
}

View File

@@ -0,0 +1,11 @@
package eu.konggdev.strikemaps.map.overlay.overlay;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
public class PointSelectionOverlay implements MapOverlay {
@Override
public MapLayer makeLayer() {
return null;
}
}

View File

@@ -0,0 +1,19 @@
package eu.konggdev.strikemaps.map.renderer;
import android.view.View;
import android.view.ViewGroup;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import java.util.List;
public interface MapRenderer {
void reload();
View getView();
List<Feature> featuresAtPoint(LatLng point);
}

View File

@@ -0,0 +1,78 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.view.View;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.helper.FileHelper;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import org.maplibre.android.MapLibre;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.android.maps.MapLibreMap;
import org.maplibre.android.maps.MapView;
import org.maplibre.android.maps.OnMapReadyCallback;
import org.maplibre.android.maps.Style;
import org.maplibre.geojson.Feature;
import java.util.List;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
AppController app;
MapComponent controller;
MapLibreMap map;
final MapView mapView;
public MapLibreNativeRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
MapLibre.getInstance(app.getActivity());
this.mapView = new MapView(app.getActivity());
mapView.onCreate(null);
mapView.getMapAsync(this);
}
void passLayer(MapLayer layer) {
map.getStyle().addSource(layer.source);
map.getStyle().addLayer(layer.layer);
}
@Override
public void reload() {
map.setStyle(new Style.Builder().fromJson(controller.style), style -> {
for(MapOverlay overlay : controller.overlays.values()) {
passLayer(overlay.makeLayer());
}
});
}
@Override
public View getView() {
return mapView;
}
@Override
public List<Feature> featuresAtPoint(LatLng point) {
return map.queryRenderedFeatures(map.getProjection().toScreenLocation(point));
}
@Override
public void onMapReady(@NonNull MapLibreMap maplibreMap) {
this.map = maplibreMap;
controller.style = FileHelper.loadStringFromAssetFile(UserPrefsHelper.startupMapStyle(app.getPrefs()), app);
//I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :)
map.getUiSettings().setLogoEnabled(false);
map.getUiSettings().setAttributionEnabled(false);
map.addOnMapClickListener(point -> controller.onMapClick(point));
map.addOnMapLongClickListener(point -> controller.onMapLongClick(point));
this.reload();
}
}

View File

@@ -0,0 +1,50 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.view.View;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import okhttp3.OkHttpClient;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import org.oscim.android.MapView;
import org.oscim.map.Map;
import org.oscim.tiling.source.OkHttpEngine;
import org.oscim.tiling.source.oscimap4.OSciMap4TileSource;
import java.util.Collections;
import java.util.List;
public class VtmRenderer implements MapRenderer {
AppController app;
MapComponent controller;
Map map;
final MapView mapView;
public VtmRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
this.mapView = new MapView(app.getActivity());
this.map = mapView.map();
}
@Override
public void reload() {
//TODO
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OSciMap4TileSource tileSource = OSciMap4TileSource.builder().httpFactory(new OkHttpEngine.OkHttpFactory(builder)).build();
map.setBaseMap(tileSource);
}
@Override
public View getView() {
return mapView;
}
@Override
public List<Feature> featuresAtPoint(LatLng point) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,12 @@
package eu.konggdev.strikemaps.map.source;
public class MapSource {
public final String url;
public final String type;
public final String schema;
public MapSource(String url, String type, String schema) {
this.url = url;
this.type = type;
this.schema = schema;
}
}

View File

@@ -0,0 +1,5 @@
package eu.konggdev.strikemaps.provider;
public class HttpDataProvider implements Provider {
//TODO
}

View File

@@ -0,0 +1,43 @@
package eu.konggdev.strikemaps.provider;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import java.util.List;
public class LocationDataProvider implements Provider {
private LocationManager locationManager;
private List<String> locationManagerProviders = List.of(LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER);
public LocationDataProvider(AppCompatActivity activity, LocationListener listener) {
locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
//TODO: Move permission request to UI
if(ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
Location initLocation = null;
for (String provider : locationManagerProviders) {
if(locationManager.isProviderEnabled(provider)) {
if (initLocation == null) {
initLocation = locationManager.getLastKnownLocation(provider);
if (initLocation != null)
listener.onLocationChanged(initLocation);
}
locationManager.requestLocationUpdates(
provider,
1000,
1,
listener
);
}
}
}
}
}

View File

@@ -0,0 +1,5 @@
package eu.konggdev.strikemaps.provider;
public interface Provider {
}

View File

@@ -0,0 +1,77 @@
package eu.konggdev.strikemaps.ui;
import android.app.AlertDialog;
import android.view.View;
import androidx.annotation.NonNull;
import com.google.common.collect.BiMap;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.ui.element.UIRegion;
import eu.konggdev.strikemaps.ui.fragment.layout.FragmentLayoutControls;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentSettings;
import eu.konggdev.strikemaps.ui.screen.Screen;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class UIComponent implements Component {
@NonNull AppController app;
private Map<Integer, Screen> screens;
private Integer currentScreen;
public UIComponent(AppController app, MapComponent map) {
this.app = app;
this.screens = Map.of(
//Main screen
R.layout.screen_main, new Screen(
//App reference
app,
//Map view
map.toFragment(), //FragmentLayoutContentMap
//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
//Layout
R.layout.screen_main //TODO: Define this for the Screen without duplicating the reference
),
//Settings screen
R.layout.screen_settings, new Screen(
app,
//Settings
new FragmentLayoutContentSettings(),
/* No regions defined in settings
Entire screen is just the main view */
new HashMap<>(),
//Layout
R.layout.screen_settings
)
);
}
public void swapScreen(Integer screen) {
currentScreen = screen;
getCurrentScreen().attachAll();
}
public Screen getCurrentScreen() {
return getScreen(currentScreen);
}
public Screen getScreen(Integer screen) {
return screens.get(screen);
}
public void alert(AlertDialog dialog) {
dialog.show();
}
public <T> void alert(AlertDialog dialog, Consumer<T> callback) {
dialog.show();
}
public View inflateUi(int layout) {
return app.getActivity().getLayoutInflater().inflate(layout, null);
}
}

View File

@@ -0,0 +1,44 @@
package eu.konggdev.strikemaps.ui.element;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.fragment.layout.Layout;
import java.util.ArrayDeque;
public class UIRegion {
private final ArrayDeque<Fragment> previousFragments = new ArrayDeque<>();
private Fragment stockFragment;
private Fragment currentFragment;
public Integer layoutId;
public UIRegion(@NonNull Fragment initFragment, Integer refLayoutId) {
this.currentFragment = initFragment;
this.stockFragment = initFragment;
this.layoutId = refLayoutId;
}
public Fragment getFragment() {
return currentFragment;
}
public void setFragment(Fragment fragment) {
previousFragments.add(currentFragment);
currentFragment = fragment;
}
public void overwriteStockFragment(Fragment fragment) {
stockFragment = fragment;
}
public void back() {
if (!previousFragments.isEmpty()) {
currentFragment = previousFragments.pop();
} else {
currentFragment = stockFragment;
}
}
}

View File

@@ -0,0 +1,77 @@
package eu.konggdev.strikemaps.ui.element.item;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.FileHelper;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.ui.UIComponent;
import org.json.JSONObject;
import java.io.InputStream;
public class GenericItem implements UIItem {
@NonNull public String name;
public Bitmap image;
public Runnable onClick;
boolean hasImage;
public GenericItem(String refName) {
this.name = refName;
hasImage = false;
}
public GenericItem(String refName, Runnable onClick) {
this.name = refName;
this.onClick = onClick;
hasImage = false;
}
public GenericItem(String refName, Bitmap refImage) {
this.name = refName;
this.image = refImage;
hasImage = true;
}
public GenericItem(String refName, Bitmap refImage, Runnable onClick) {
this.name = refName;
this.image = refImage;
this.onClick = onClick;
hasImage = true;
}
//FIXME: Ugly glue static constructor
public final static GenericItem fromStyle(String style, AppController app, MapComponent map) {
try {
JSONObject styleJson = new JSONObject(style);
String name = "Unknown"; //Fallback name
if (styleJson.has("name")) name = styleJson.getString("name");
if (styleJson.has("icon")) {
switch(styleJson.getString("icon").split("//")[0]) {
//TODO: https
case "assets:":
Bitmap icon = BitmapFactory.decodeStream(FileHelper.openAssetStream("bundled/icon/" + styleJson.getString("icon").split("//")[1], app));
return new GenericItem(name, icon, () -> map.setStyle(style));
default:
app.logcat("Unimplemented icon source requested in style: " + name);
return new GenericItem(name, () -> map.setStyle(style));
}
}
return new GenericItem(name, () -> map.setStyle(style));
} catch (Exception e) {
e.printStackTrace();
return new GenericItem("Exception!", () -> map.setStyle(style));
}
}
public View makeView(UIComponent spawner) {
View v = spawner.inflateUi(R.layout.item_generic);
//FIXME: These shouldn't be casted like that!
((TextView) v.findViewById(R.id.name)).setText(name);
if(image != null) ((ImageButton) v.findViewById(R.id.image)).setImageBitmap(image);
if(onClick != null) v.findViewById(R.id.image).setOnClickListener(click(onClick));
return v;
}
}

View File

@@ -0,0 +1,41 @@
package eu.konggdev.strikemaps.ui.element.item;
import android.graphics.Bitmap;
import android.view.View;
import android.widget.TextView;
import eu.konggdev.strikemaps.ui.UIComponent;
import org.maplibre.geojson.Feature;
import eu.konggdev.strikemaps.R;
public class PreviewItem implements UIItem {
public String name;
public String type;
public Bitmap image;
boolean hasImage;
public PreviewItem(String refName, String refType) {
this.name = refName;
this.type = refType;
hasImage = false;
}
public PreviewItem(String refName, String refType, Bitmap refImage) {
this.name = refName;
this.type = refType;
this.image = refImage;
hasImage = true;
}
public static PreviewItem fromFeature(Feature feature) {
return new PreviewItem(feature.getStringProperty("name"), feature.getStringProperty("class"));
}
public View makeView(UIComponent spawner) {
View view = spawner.inflateUi(R.layout.item_preview);
((TextView) view.findViewById(R.id.choiceName)).setText(name);
((TextView) view.findViewById(R.id.type)).setText(type);
return view;
}
public View makeView(UIComponent spawner, View.OnClickListener onClick) {
View view = makeView(spawner);
view.setOnClickListener(onClick);
return view;
}
}

View File

@@ -0,0 +1,16 @@
package eu.konggdev.strikemaps.ui.element.item;
import android.view.View;
import eu.konggdev.strikemaps.ui.UIComponent;
public interface UIItem {
abstract View makeView(UIComponent spawner);
default View.OnClickListener click(Runnable action) {
return v -> action.run();
}
default View.OnLongClickListener longClick(Runnable action) { return v -> { action.run(); return true; };}
}

View File

@@ -0,0 +1,90 @@
package eu.konggdev.strikemaps.ui.fragment;
import android.view.MotionEvent;
import android.view.View;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.element.UIRegion;
public interface ContainerFragment {
abstract public Integer getRegion();
abstract public Fragment toFragment();
//Helper methods (ugly)
//FIXME
default void setupButton(View view, int button, View.OnClickListener onClick) {
view.findViewById(button)
.setOnClickListener(onClick);
}
default void setupButton(View view, int button, View.OnClickListener onClick, View.OnLongClickListener onLongClick) {
View buttonView = view.findViewById(button);
buttonView.setOnClickListener(onClick);
buttonView.setOnLongClickListener(onLongClick);
}
default void setupButton(View view, int button, View.OnLongClickListener onLongClick) {
view.findViewById(button)
.setOnLongClickListener(onLongClick);
}
default View.OnClickListener click(Runnable action) {
return v -> action.run();
}
default View.OnLongClickListener longClick(Runnable action) { return v -> { action.run(); return true; };}
//TODO: Make animation less wonky
default void setupDragHandle(View dragHandle, View layout, Runnable closeAction) {
final float[] dY = new float[1];
dragHandle.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
dY[0] = event.getRawY() - layout.getY();
return true;
case MotionEvent.ACTION_MOVE:
float newY = event.getRawY() - dY[0];
if (newY >= 0) {
layout.setY(newY);
}
return true;
case MotionEvent.ACTION_UP:
if (layout.getY() > layout.getHeight() / 4) {
layout.animate()
.scaleX(0f)
.scaleY(0f)
.alpha(0f)
.setDuration(300)
.withEndAction(new Runnable() {
@Override
public void run() {
layout.setVisibility(View.GONE);
closeAction.run();
layout.setScaleX(1f);
layout.setScaleY(1f);
layout.setAlpha(1f);
layout.setY(0f);
}
})
.start();
} else {
layout.animate()
.translationY(0f)
.setDuration(200)
.start();
}
return true;
default:
return false;
}
}
});
}
}

View File

@@ -0,0 +1,7 @@
package eu.konggdev.strikemaps.ui.fragment;
import androidx.fragment.app.Fragment;
public class FragmentEmptyPlaceholder extends Fragment {
//:)
}

View File

@@ -0,0 +1,120 @@
package eu.konggdev.strikemaps.ui.fragment.layout;
import android.Manifest;
import android.app.AlertDialog;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import android.widget.TextView;
import android.widget.Toast;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.overlay.LocationOverlay;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
public class FragmentLayoutControls extends Fragment implements Layout {
AppController app;
View rootView;
private final Integer region;
// Action definitions
//*//
public void notImplemented() { //Should never be called in release
Toast.makeText(requireContext(), "Not implemented yet\nWait for release", Toast.LENGTH_SHORT).show();
}
public void toggleLocationService() {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
} else {
app.getMap().switchOverlay(new LocationOverlay(app));
setupView();
}
}
public void zoomToLocation() {
if(!app.getMap().hasOverlay(LocationOverlay.class)) {
Toast.makeText(requireContext(), "Hold to enable location", Toast.LENGTH_SHORT).show();
return;
}
}
public void attributtionDialog() {
AlertDialog dialog = new AlertDialog.Builder(app.getActivity())
.setTitle(app.getActivity().getString(R.string.attribution_title))
.setMessage(app.getActivity().getString(R.string.shipped_attribution))
.setPositiveButton("OK", null).show();
}
//*//
public FragmentLayoutControls(AppController app, Integer region) {
super(R.layout.fragment_controls);
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);
this.rootView = view;
/* Restores location enabled status from user prefs,
TODO: Should be moved out of UI code in the future */
if(UserPrefsHelper.persistLocationEnabled(app.getPrefs()) && UserPrefsHelper.lastLocationEnabled(app.getPrefs()) && !app.getMap().hasOverlay(LocationOverlay.class))
toggleLocationService();
this.setupView();
}
public void setupView() {
if (rootView == null) return;
setupButton(rootView, R.id.layersButton, click(() -> app.getUi().getCurrentScreen().open(new FragmentMapChangePopup(app, R.id.bottomUi))));
setupButton(rootView, R.id.attributionButton, click(this::attributtionDialog));
setupButton(rootView, R.id.locationButton, click(this::zoomToLocation), longClick(this::toggleLocationService));
//TODO
setupButton(rootView, R.id.placesButton, click(this::notImplemented));
setupButton(rootView, R.id.placesButton, click(this::notImplemented));
setupButton(rootView, R.id.routeButton, click(this::notImplemented));
setupButton(rootView, R.id.modeButton, click(this::notImplemented));
TextView locationServiceStatusIndicator = rootView.findViewById(R.id.locationServiceStatusIndicator);
if (app.getMap().hasOverlay(LocationOverlay.class)) {
locationServiceStatusIndicator.setBackgroundColor(Color.parseColor("#00FF00")); //green
} else {
locationServiceStatusIndicator.setBackgroundColor(Color.parseColor("#FB0303")); //red
}
if(UserPrefsHelper.persistLocationEnabled(app.getPrefs()))
UserPrefsHelper.lastLocationEnabled(app.getPrefs(), app.getMap().hasOverlay(LocationOverlay.class));
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) toggleLocationService();
else Toast.makeText(requireContext(), "You need to grant location permission", Toast.LENGTH_SHORT).show();
}
}
}

View File

@@ -0,0 +1,8 @@
package eu.konggdev.strikemaps.ui.fragment.layout;
import eu.konggdev.strikemaps.ui.fragment.ContainerFragment;
public interface Layout extends ContainerFragment {
}

View File

@@ -0,0 +1,6 @@
package eu.konggdev.strikemaps.ui.fragment.layout.content;
import eu.konggdev.strikemaps.ui.fragment.layout.Layout;
public interface ContentLayout extends Layout {
}

View File

@@ -0,0 +1,33 @@
package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.element.UIRegion;
import eu.konggdev.strikemaps.R;
public class FragmentLayoutContentMap extends Fragment implements MainContentLayout {
View mapView;
public FragmentLayoutContentMap(View refMapView) {
super(R.layout.fragment_map);
this.mapView = refMapView;
}
@Override
public Fragment toFragment() {
return this;
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
LinearLayout layout = (LinearLayout) view;
layout.addView(mapView);
}
}

View File

@@ -0,0 +1,10 @@
package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
import androidx.fragment.app.Fragment;
public class FragmentLayoutContentSettings implements MainContentLayout {
@Override
public Fragment toFragment() {
return null;
}
}

View File

@@ -0,0 +1,10 @@
package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.ui.fragment.layout.content.ContentLayout;
public interface MainContentLayout extends ContentLayout {
default Integer getRegion() {
return R.id.mainContentView;
}
}

View File

@@ -0,0 +1,61 @@
package eu.konggdev.strikemaps.ui.fragment.popup;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.widget.LinearLayout;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.FileHelper;
import eu.konggdev.strikemaps.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.element.item.GenericItem;
import org.apache.commons.lang3.ArrayUtils;
import java.util.ArrayList;
import java.util.List;
public class FragmentMapChangePopup extends Fragment implements Popup {
@NonNull AppController app;
@NonNull MapComponent map;
@NonNull UIComponent ui;
private final Integer region;
public FragmentMapChangePopup(AppController app, Integer region) {
super(R.layout.popup_map_change);
this.app = app;
this.map = app.getMap();
this.ui = app.getUi();
this.region = region;
}
@Override
public Integer getRegion() {
return region;
}
@Override
public Fragment toFragment() {
return this;
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
//FIXME
setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup()));
setupDragHandle(view, view, () -> ui.getCurrentScreen().closePopup());
String[] stylePaths = ArrayUtils.addAll(FileHelper.getAssetFiles("bundled/style", ".style.json", app), FileHelper.getUserFiles("style", ".style.json", app));
List<View> views = new ArrayList<>();
LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout);
for(String i : stylePaths) {
if(i.startsWith("/storage")) stylesLayout.addView(GenericItem.fromStyle(FileHelper.loadStringFromUserFile(i), app, map).makeView(ui));
else stylesLayout.addView(GenericItem.fromStyle(FileHelper.loadStringFromAssetFile(i, app), app, map).makeView(ui));
}
}
}

View File

@@ -0,0 +1,8 @@
package eu.konggdev.strikemaps.ui.fragment.popup;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.ui.fragment.ContainerFragment;
public interface Popup extends ContainerFragment {
}

View File

@@ -0,0 +1,77 @@
package eu.konggdev.strikemaps.ui.screen;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import java.util.List;
import java.util.Map;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.fragment.ContainerFragment;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.MainContentLayout;
import eu.konggdev.strikemaps.ui.fragment.popup.Popup;
import eu.konggdev.strikemaps.ui.element.UIRegion;
public class Screen {
@NonNull AppController app;
public Screen(AppController app, MainContentLayout mainContent, Map<Integer, UIRegion> regions, Integer layout) {
this.app = app;
this.layout = layout;
this.mainContent = mainContent;
this.uiRegions = regions;
}
private final Integer layout;
private MainContentLayout mainContent;
Map<Integer, UIRegion> uiRegions;
public Integer popup;
public void open(ContainerFragment fragment) {
if(fragment instanceof Popup && popup != null) return;
if(fragment instanceof Popup)
popup = fragment.getRegion();
setFragment(uiRegions.get(fragment.getRegion()), fragment.toFragment());
}
public void closePopup() {
if(popup != null) {
UIRegion popupRegion = uiRegions.get(popup);
popupRegion.back();
/* If newFragment is still a popup, assign the current popup value to the new fragment
otherwise, set the current popup value to null */
if(popupRegion.getFragment() instanceof Popup) {
popup = popupRegion.layoutId;
} else {
popup = null;
}
setFragment(popupRegion, popupRegion.getFragment());
}
}
public void setFragment(UIRegion region, Fragment fragment) {
if (region == null) return;
region.setFragment(fragment);
fragmentTransaction(region.layoutId, fragment);
}
public void fragmentTransaction(int layoutId, Fragment fragment) {
app.getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(layoutId, fragment)
.commit();
}
public void attachAll() {
app.getActivity().setContentView(layout);
fragmentTransaction(R.id.mainContentView, mainContent.toFragment());
for (UIRegion region : uiRegions.values()) {
setFragment(region, region.getFragment());
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
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">
<ImageButton
android:id="@+id/routeButton"
android:layout_width="65dp"
android:layout_height="42dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="36dp"
android:backgroundTint="#000000"
android:src="@android:drawable/ic_menu_directions"
app:layout_constraintBottom_toBottomOf="@+id/placesButton"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/placesButton"
android:layout_width="65dp"
android:layout_height="43dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="36dp"
android:backgroundTint="#000000"
app:layout_constraintBottom_toBottomOf="@+id/layersButton"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@android:drawable/ic_menu_myplaces" />
<ImageButton
android:id="@+id/layersButton"
android:layout_width="65dp"
android:layout_height="42dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
android:backgroundTint="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_styles_menu" />
<ImageButton
android:id="@+id/modeButton"
android:layout_width="60dp"
android:layout_height="36dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="44dp"
android:backgroundTint="#000000"
android:src="@android:drawable/ic_menu_manage"
app:layout_constraintBottom_toBottomOf="@+id/locationButtonContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" />
<ImageButton
android:id="@+id/attributionButton"
android:layout_width="60dp"
android:layout_height="31dp"
android:layout_marginStart="272dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:backgroundTint="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.375"
app:srcCompat="@android:drawable/ic_menu_info_details" />
<FrameLayout
android:id="@+id/locationButtonContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="@+id/attributionButton"
app:layout_constraintEnd_toEndOf="parent">
<ImageButton
android:id="@+id/locationButton"
android:layout_width="60dp"
android:layout_height="47dp"
android:backgroundTint="#000000"
android:contentDescription="Location"
app:srcCompat="@android:drawable/ic_menu_mylocation" />
<TextView
android:id="@+id/locationServiceStatusIndicator"
android:layout_width="30dp"
android:layout_height="1dp"
android:layout_gravity="center|bottom"
android:gravity="center"
android:layout_marginBottom="7dp"
android:background="#FB0303"
android:textSize="5sp" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<android.widget.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.widget.LinearLayout>

View File

@@ -0,0 +1,27 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="8dp">
<ImageButton
android:id="@+id/image"
android:layout_width="54dp"
android:layout_height="47dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Style Image"
android:src="@android:drawable/ic_menu_gallery" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="Item Name"
android:textColor="#FFFFFF"
android:textSize="14sp" />
</LinearLayout>

View File

@@ -0,0 +1,58 @@
<?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="#0E0000"
android:foregroundTint="#FFFFFF">
<TextView
android:id="@+id/choiceName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="12dp"
android:text="Choice Item"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:ellipsize="end"
android:maxLines="1"
android:scrollHorizontally="false"
android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="24sp"
android:autoSizeStepGranularity="1sp"
app:layout_constraintStart_toEndOf="@+id/poiIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/poiIcon"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginStart="7dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/maplibre_info_bg_selector" />
<TextView
android:id="@+id/type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:text="Type"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:ellipsize="end"
android:singleLine="true"
app:layout_constraintStart_toEndOf="@id/poiIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/choiceName"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,59 @@
<?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="16dp">
<View
android:id="@+id/dragHandle"
android:layout_width="170dp"
android:layout_height="5dp"
android:background="#979797"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageButton
android:id="@+id/closeButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:backgroundTint="#000000"
app:srcCompat="@android:drawable/ic_menu_close_clear_cancel"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/stylesLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Styles"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<HorizontalScrollView
android:id="@+id/styles"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/stylesLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:id="@+id/stylesLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</HorizontalScrollView>
<!-- Sources selection to be implemented here -->
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" >
<LinearLayout
android:id="@+id/popupHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:id="@+id/mainContentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
</LinearLayout>
<FrameLayout
android:id="@+id/bottomUi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/devIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DEVELOPMENT BUILD!"
android:textColor="#000000"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/searchContainer"
android:layout_width="280dp"
android:layout_height="33dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
android:background="#000000"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<SearchView
android:id="@+id/searchView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:iconifiedByDefault="false"
android:queryHint="Search..."
android:background="@android:color/transparent" />
<ImageButton
android:id="@+id/hamburgerButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:contentDescription="Menu"
android:src="@drawable/ic_hamburger_menu" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<string name="app_name">Strike Maps</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>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryVariant">@color/black</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@@ -0,0 +1,17 @@
package eu.konggdev.strikemaps;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}