Compare commits

..
2 Commits
88 changed files with 645 additions and 3342 deletions

No files matched your search

+4 -7
View File
@@ -1,11 +1,8 @@
## Strike Maps
## Strike Maps Project Android Client
Strike Maps is a FOSS mapping app for Android,
focusing on customization 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
Strike Maps is a project hoping to create a multi-platform, <br>
user-facing, complete FOSS mapping stack, focusing on customization <br>
and offline features.
<br>
This Android app has been in development since around January 2026
+5 -7
View File
@@ -9,8 +9,8 @@ android {
defaultConfig {
applicationId = "eu.konggdev.strikemaps"
minSdk = 23
targetSdk = 36
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
@@ -28,8 +28,8 @@ android {
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_15
targetCompatibility = JavaVersion.VERSION_15
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
@@ -46,8 +46,6 @@ dependencies {
implementation(libs.constraintlayout)
implementation(libs.viewpager2)
implementation("androidx.core:core-ktx:1.17.0")
implementation("org.osmdroid:osmdroid-android:6.1.20")
implementation("org.maplibre.gl:android-sdk:11.13.0")
implementation("com.github.mapsforge.vtm:vtm:0.27.0")
implementation("com.github.mapsforge.vtm:vtm-android:0.27.0")
@@ -61,8 +59,8 @@ dependencies {
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("commons-codec:commons-codec:1.17.2")
implementation("org.apache.commons:commons-lang3:3.14.0")
implementation(libs.core.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
+3 -2
View File
@@ -9,9 +9,10 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.StrikeMaps">
android:theme="@style/Theme.StrikeMaps"
tools:targetApi="31">
<activity
android:name=".activity.MainActivity"
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -1,9 +1,9 @@
{
"version": 8,
"name": "Satellite",
"icon": "assets://satellite.bmp",
"name": "Satelite",
"icon": "assets://satelite.bmp",
"sources": {
"imagery": {
"satelite": {
"type": "raster",
"schema" : "raster",
"tiles": [
@@ -17,9 +17,9 @@
},
"layers": [
{
"id": "background",
"id": "satelite",
"type": "raster",
"source": "imagery"
"source": "satelite"
}
],
"id": "satellite"
@@ -1,125 +0,0 @@
#
Builds included inside /dist correspond to MapLibre GL JS version 5.24.0 <br>
The source for these builds is available on https://github.com/maplibre/maplibre-gl-js
#
All files inside the /dist folder are 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.
@@ -1 +0,0 @@
.maplibregl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:rgb(0 0 0/0)}.maplibregl-canvas{left:0;position:absolute;top:0}.maplibregl-map:fullscreen{height:100%;width:100%}.maplibregl-ctrl-group button.maplibregl-ctrl-compass{touch-action:none}.maplibregl-canvas-container.maplibregl-interactive,.maplibregl-ctrl-group button.maplibregl-ctrl-compass{cursor:grab;-webkit-user-select:none;-moz-user-select:none;user-select:none}.maplibregl-canvas-container.maplibregl-interactive.maplibregl-track-pointer{cursor:pointer}.maplibregl-canvas-container.maplibregl-interactive:active,.maplibregl-ctrl-group button.maplibregl-ctrl-compass:active{cursor:grabbing}.maplibregl-canvas-container.maplibregl-touch-zoom-rotate,.maplibregl-canvas-container.maplibregl-touch-zoom-rotate .maplibregl-canvas{touch-action:pan-x pan-y}.maplibregl-canvas-container.maplibregl-touch-drag-pan,.maplibregl-canvas-container.maplibregl-touch-drag-pan .maplibregl-canvas{touch-action:pinch-zoom}.maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan,.maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan .maplibregl-canvas{touch-action:none}.maplibregl-canvas-container.maplibregl-touch-drag-pan.maplibregl-cooperative-gestures,.maplibregl-canvas-container.maplibregl-touch-drag-pan.maplibregl-cooperative-gestures .maplibregl-canvas{touch-action:pan-x pan-y}.maplibregl-ctrl-bottom-left,.maplibregl-ctrl-bottom-right,.maplibregl-ctrl-top-left,.maplibregl-ctrl-top-right{pointer-events:none;position:absolute;z-index:2}.maplibregl-ctrl-top-left{left:0;top:0}.maplibregl-ctrl-top-right{right:0;top:0}.maplibregl-ctrl-bottom-left{bottom:0;left:0}.maplibregl-ctrl-bottom-right{bottom:0;right:0}.maplibregl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.maplibregl-ctrl-top-left .maplibregl-ctrl{float:left;margin:10px 0 0 10px}.maplibregl-ctrl-top-right .maplibregl-ctrl{float:right;margin:10px 10px 0 0}.maplibregl-ctrl-bottom-left .maplibregl-ctrl{float:left;margin:0 0 10px 10px}.maplibregl-ctrl-bottom-right .maplibregl-ctrl{float:right;margin:0 10px 10px 0}.maplibregl-ctrl-group{background:#fff;border-radius:4px}.maplibregl-ctrl-group:not(:empty){box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (forced-colors:active){.maplibregl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.maplibregl-ctrl-group button{background-color:transparent;border:0;box-sizing:border-box;cursor:pointer;display:block;height:29px;outline:none;padding:0;width:29px}.maplibregl-ctrl-group button+button{border-top:1px solid #ddd}.maplibregl-ctrl button .maplibregl-ctrl-icon{background-position:50%;background-repeat:no-repeat;display:block;height:100%;width:100%}@media (forced-colors:active){.maplibregl-ctrl-icon{background-color:transparent}.maplibregl-ctrl-group button+button{border-top:1px solid ButtonText}}.maplibregl-ctrl button::-moz-focus-inner{border:0;padding:0}.maplibregl-ctrl-attrib-button:focus,.maplibregl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.maplibregl-ctrl button:disabled{cursor:not-allowed}.maplibregl-ctrl button:disabled .maplibregl-ctrl-icon{opacity:.25}@media (hover:hover){.maplibregl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}}.maplibregl-ctrl button:not(:disabled):active{background-color:rgba(0,0,0,.05)}.maplibregl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.maplibregl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.maplibregl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.maplibregl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.maplibregl-ctrl-group button:focus:only-child{border-radius:inherit}.maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E")}.maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5'/%3E%3C/svg%3E")}@media (forced-colors:active){.maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E")}.maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon{background-image:url("data:image/svLine truncated
@@ -1,59 +0,0 @@
/**
* MapLibre GL JS
* @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v5.24.0/LICENSE.txt
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.maplibregl = factory());
})(this, (function () { 'use strict';
/* eslint-disable */
var maplibregl = {};
var modules = {};
function define(moduleName, _dependencies, moduleFactory) {
modules[moduleName] = moduleFactory;
// to get the list of modules see generated dist/maplibre-gl-dev.js file (look for `define(` calls)
if (moduleName !== 'index') {
return;
}
// we assume that when an index module is initializing then other modules are loaded already
var workerBundleString = 'var sharedModule = {}; (' + modules.shared + ')(sharedModule); (' + modules.worker + ')(sharedModule);'
var sharedModule = {};
// the order of arguments of a module factory depends on rollup (it decides who is whose dependency)
// to check the correct order, see dist/maplibre-gl-dev.js file (look for `define(` calls)
// we assume that for our 3 chunks it will generate 3 modules and their order is predefined like the following
modules.shared(sharedModule);
modules.index(maplibregl, sharedModule);
if (typeof window !== 'undefined') {
maplibregl.setWorkerUrl(window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })));
}
return maplibregl;
};
define("shared",["exports"],(function(t){"use strict";function e(t,e,n,r){return new(n||(n=Promise))((function(i,s){function o(t){try{l(r.next(t));}catch(t){s(t);}}function a(t){try{l(r.throw(t));}catch(t){s(t);}}function l(t){var e;t.done?i(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e);}))).then(o,a);}l((r=r.apply(t,e||[])).next());}))}function n(t,e){this.x=t,this.y=e;}function r(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var i,s;"function"==typeof SuppressedError&&SuppressedError,n.prototype={clone(){return new n(this.x,this.y)},add(t){return this.clone()._add(t)},sub(t){return this.clone()._sub(t)},multByPoint(t){return this.clone()._multByPoint(t)},divByPoint(t){return this.clone()._divByPoint(t)},mult(t){return this.clone()._mult(t)},div(t){return this.clone()._div(t)},rotate(t){return this.clone()._rotate(t)},rotateAround(t,e){return this.clone()._rotateAround(t,e)},matMult(t){return this.clone()._matMult(t)},unit(){return this.clone()._unit()},perp(){return this.clone()._perp()},round(){return this.clone()._round()},mag(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals(t){return this.x===t.x&&this.y===t.y},dist(t){return Math.sqrt(this.distSqr(t))},distSqr(t){const e=t.x-this.x,n=t.y-this.y;return e*e+n*n},angle(){return Math.atan2(this.y,this.x)},angleTo(t){return Math.atan2(this.y-t.y,this.x-t.x)},angleWith(t){return this.angleWithSep(t.x,t.y)},angleWithSep(t,e){return Math.atan2(this.x*e-this.y*t,this.x*t+this.y*e)},_matMult(t){const e=t[2]*this.x+t[3]*this.y;return this.x=t[0]*this.x+t[1]*this.y,this.y=e,this},_add(t){return this.x+=t.x,this.y+=t.y,this},_sub(t){return this.x-=t.x,this.y-=t.y,this},_mult(t){return this.x*=t,this.y*=t,this},_div(t){return this.x/=t,this.y/=t,this},_multByPoint(t){return this.x*=t.x,this.y*=t.y,this},_divByPoint(t){return this.x/=t.x,this.y/=t.y,this},_unit(){return this._div(this.mag()),this},_perp(){const t=this.y;return this.y=this.x,this.x=-t,this},_rotate(t){const e=Math.cos(t),n=Math.sin(t),r=n*this.x+e*this.y;return this.x=e*this.x-n*this.y,this.y=r,this},_rotateAround(t,e){const n=Math.cos(t),r=Math.sin(t),i=e.y+r*(this.x-e.x)+n*(this.y-e.y);return this.x=e.x+n*(this.x-e.x)-r*(this.y-e.y),this.y=i,this},_round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},constructor:n},n.convert=function(t){if(t instanceof n)return t;if(Array.isArray(t))return new n(+t[0],+t[1]);if(void 0!==t.x&&void 0!==t.y)return new n(+t.x,+t.y);throw new Error("Expected [x, y] or {x, y} point format")};var o=function(){if(s)return i;function t(t,e,n,r){this.cx=3*t,this.bx=3*(n-t)-this.cx,this.ax=1-this.cx-this.bx,this.cy=3*e,this.by=3*(r-e)-this.cy,this.ay=1-this.cy-this.by,this.p1x=t,this.p1y=e,this.p2x=n,this.p2y=r;}return s=1,i=t,t.prototype={sampleCurveX:function(t){return ((this.ax*t+this.bx)*t+this.cx)*t},sampleCurveY:function(t){return ((this.ay*t+this.by)*t+this.cy)*t},sampleCurveDerivativeX:function(t){return (3*this.ax*t+2*this.bx)*t+this.cx},solveCurveX:function(t,e){if(void 0===e&&(e=1e-6),t<0)return 0;if(t>1)return 1;for(var n=t,r=0;r<8;r++){var i=this.sampleCurveX(n)-t;if(Math.abs(i)<e)return n;var s=this.sampleCurveDerivativeX(n);if(Math.abs(s)<1e-6)break;n-=i/s;}var o=0,a=1;for(n=t,r=0;r<20&&(i=this.sampleCurveX(n),!(Math.abs(i-t)<e));r++)t>i?o=n:a=n,n=.5*(a-o)+o;return n},solve:function(t,e){return this.sampleCurveY(this.solveCurveX(t,e))}},i}(),a=r(o);let l,u;function c(){return null!=l||(l="undefined"!=typeof OffscreenCanvas&&new OffscreenCanvas(1,1).getContext("2d")&&"function"==typeof createImageBitmap),l}function h(){if(null==u&&(u=!1,c())){const t=5,e=new OffscreenCanvas(t,t).getContext("2d",{willReadFrequently:!0});if(e){for(let n=0;n<t*t;n++){const r=4*n;e.fillStyle=`rgb(${r},${r+1},${r+2})`,e.fillRect(n%t,Math.floor(n/t),1,1);}const n=e.getImageData(0,0,t,t).data;for(let e=0;e<t*t*4;e++)if(e%4!=3&&n[e]!==e){u=!0;break}}}return u||!1}var p=1e-6,f="undefined"!=typeof Float32Array?Float32Array:Array;function d(){var t=new f(9);return f!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[5]=0,t[6]=0,t[7]=0),t[0]=1,t[4]=1,t[8]=1,t}function y(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t}function m(){var t=new f(3);return f!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t}function g(t){var e=t[0],n=t[1],r=t[2];return Math.sqrt(e*e+n*n+r*r)}function x(t,e,n){var r=new f(3);return r[0]=t,r[1]=e,r[2]=n,r}function v(t,e,n){return t[0]=e[0]+n[0],t[1]=e[1]+n[1],t[2]=e[2]+n[2],t}function b(t,e,n){return t[0]=e[0]*n,t[1]=e[1]*n,t[2]=e[2]*n,t}function w(t,e,n){var r=e[0],i=e[1],s=e[2],o=n[0],a=n[1],l=n[2];return t[0]=i*l-s*a,t[1]=s*o-r*l,t[2]=r*a-i*o,t}var _,S=g;function A(t,e,n){var r=e[0],i=e[1],s=e[2],o=e[3];return t[0]=n[0]*r+n[4]*i+n[8]*s+n[12]*o,t[1]=n[1]*r+n[5]*i+n[9]*s+n[13]*o,t[2]=n[2]*r+n[6]*i+n[10]*s+n[14]*o,t[3]=n[3]*r+n[7]*i+n[11]*s+n[15]*o,t}function M(){var t=new f(4);return f!=Float32Array&&(t[0]=0,t[1]=0,t[2]Line truncated
define("worker",["./shared"],(function(e){"use strict";class t{constructor(e,t){this.keyCache={},e&&this.replace(e,t);}replace(e,t){this._layerConfigs={},this._layers={},this.update(e,[],t);}update(t,i,s){for(const i of t){this._layerConfigs[i.id]=i;const t=this._layers[i.id]=e.bT(i,s);t._featureFilter=e.ah(t.filter,s),this.keyCache[i.id]&&delete this.keyCache[i.id];}for(const e of i)delete this.keyCache[e],delete this._layerConfigs[e],delete this._layers[e];this.familiesBySource={};const r=e.cJ(Object.values(this._layerConfigs),this.keyCache);for(const t of r){const i=t.map((e=>this._layers[e.id])),s=i[0];if(s.isHidden())continue;const r=s.source||"";let o=this.familiesBySource[r];o||(o=this.familiesBySource[r]={});const n=s.sourceLayer||e.ag;let a=o[n];a||(a=o[n]=[]),a.push(i);}}}class i{constructor(t){const i={},s=[];for(const e in t){const r=t[e],o=i[e]={};for(const e in r){const t=r[+e];if(!t||0===t.bitmap.width||0===t.bitmap.height)continue;const i={x:0,y:0,w:t.bitmap.width+2,h:t.bitmap.height+2};s.push(i),o[e]={rect:i,metrics:t.metrics};}}const{w:r,h:o}=e.p(s),n=new e.t({width:r||1,height:o||1});for(const s in t){const r=t[s];for(const t in r){const o=r[+t];if(!o||0===o.bitmap.width||0===o.bitmap.height)continue;const a=i[s][t].rect;e.t.copy(o.bitmap,n,{x:0,y:0},{x:a.x+1,y:a.y+1},o.bitmap);}}this.image=n,this.positions=i;}}e.cK("GlyphAtlas",i);class s{constructor(t){this.tileID=new e.a3(t.tileID.overscaledZ,t.tileID.wrap,t.tileID.canonical.z,t.tileID.canonical.x,t.tileID.canonical.y),this.uid=t.uid,this.zoom=t.zoom,this.pixelRatio=t.pixelRatio,this.tileSize=t.tileSize,this.source=t.source,this.overscaling=this.tileID.overscaleFactor(),this.showCollisionBoxes=t.showCollisionBoxes,this.collectResourceTiming=!!t.collectResourceTiming,this.returnDependencies=!!t.returnDependencies,this.promoteId=t.promoteId,this.inFlightDependencies=[];}parse(t,s,o,n,a){return e._(this,void 0,void 0,(function*(){this.status="parsing",this.data=t,this.collisionBoxArray=new e.ae;const l=new e.cL(Object.keys(t.layers).sort()),c=new e.cM(this.tileID,this.promoteId);c.bucketLayerIDs=[];const h={},d={featureIndex:c,iconDependencies:{},patternDependencies:{},glyphDependencies:{},dashDependencies:{},availableImages:o,subdivisionGranularity:a},u=s.familiesBySource[this.source];for(const i in u){const s=t.layers[i];if(!s)continue;1===s.version&&e.w(`Vector tile source "${this.source}" layer "${i}" does not use vector tile spec v2 and therefore may have some rendering errors.`);const n=l.encode(i),a=[];for(let e=0;e<s.length;e++){const t=s.feature(e),r=c.getId(t,i);a.push({feature:t,id:r,index:e,sourceLayerIndex:n});}for(const t of u[i]){const i=t[0];i.source!==this.source&&e.w(`layer.source = ${i.source} does not equal this.source = ${this.source}`),i.isHidden(this.zoom,!0)||(r(t,this.zoom,o),(h[i.id]=i.createBucket({index:c.bucketLayerIDs.length,layers:t,zoom:this.zoom,pixelRatio:this.pixelRatio,overscaling:this.overscaling,collisionBoxArray:this.collisionBoxArray,sourceLayerIndex:n,sourceID:this.source})).populate(a,d,this.tileID.canonical),c.bucketLayerIDs.push(t.map((e=>e.id))));}}const g=e.bY(d.glyphDependencies,(e=>Object.keys(e).map(Number)));for(const e of this.inFlightDependencies)null==e||e.abort();this.inFlightDependencies=[];let p=Promise.resolve({});if(Object.keys(g).length){const e=new AbortController;this.inFlightDependencies.push(e),p=n.sendAsync({type:"GG",data:{stacks:g,source:this.source,tileID:this.tileID,type:"glyphs"}},e);}const f=Object.keys(d.iconDependencies);let y=Promise.resolve({});if(f.length){const e=new AbortController;this.inFlightDependencies.push(e),y=n.sendAsync({type:"GI",data:{icons:f,source:this.source,tileID:this.tileID,type:"icons"}},e);}const v=Object.keys(d.patternDependencies);let m=Promise.resolve({});if(v.length){const e=new AbortController;this.inFlightDependencies.push(e),m=n.sendAsync({type:"GI",data:{icons:v,source:this.source,tileID:this.tileID,type:"patterns"}},e);}const _=d.dashDependencies;let S=Promise.resolve({});if(Object.keys(_).length){const e=new AbortController;this.inFlightDependencies.push(e),S=n.sendAsync({type:"GDA",data:{dashes:_}},e);}const[w,b,I,T]=yield Promise.all([p,y,m,S]),k=new i(w),x=new e.cN(b,I);for(const t in h){const i=h[t];i instanceof e.af?(r(i.layers,this.zoom,o),e.cO({bucket:i,glyphMap:w,glyphPositions:k.positions,imageMap:b,imagePositions:x.iconPositions,showCollisionBoxes:this.showCollisionBoxes,canonical:this.tileID.canonical,subdivisionGranularity:d.subdivisionGranularity})):i.hasDependencies&&(i instanceof e.cP||i instanceof e.cQ||i instanceof e.cR)&&(r(i.layers,this.zoom,o),i.addFeatures(d,this.tileID.canonical,x.patternPositions,T));}return this.status="done",{buckets:Object.values(h).filter((e=>!e.isEmpty())),featureIndex:c,collisionBoxArray:this.collisionBoxArray,glyphAtlasImage:k.image,imageAtlas:x,dashPositions:T,glyphMap:this.returnDependencies?w:null,iconMap:this.returnDependencies?b:null,glyphPositions:this.returnDependencies?k.positionLine truncated
define("index",["exports","./shared"],(function(e,t){"use strict";var i="5.24.0";function o(){var e=new t.A(4);return t.A!=Float32Array&&(e[1]=0,e[2]=0),e[0]=1,e[3]=1,e}let a,r,s;const n={frame(e,i,o,a){const r=a||window,s=r.requestAnimationFrame((e=>{n(),i(e);})),{unsubscribe:n}=t.s(e.signal,"abort",(()=>{n(),r.cancelAnimationFrame(s),o(new t.a(e.signal.reason));}),!1);},frameAsync(e,t){return new Promise(((i,o)=>{this.frame(e,i,o,t);}))},getImageData(e,t=0){return this.getImageCanvasContext(e).getImageData(-t,-t,e.width+2*t,e.height+2*t)},getImageCanvasContext(e){const t=window.document.createElement("canvas"),i=t.getContext("2d",{willReadFrequently:!0});if(!i)throw new Error("failed to create canvas 2d context");return t.width=e.width,t.height=e.height,i.drawImage(e,0,0,e.width,e.height),i},resolveURL:e=>(a||(a=document.createElement("a")),a.href=e,a.href),hardwareConcurrency:"undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,get prefersReducedMotion(){return void 0!==s?s:!!matchMedia&&(null!=r||(r=matchMedia("(prefers-reduced-motion: reduce)")),r.matches)},set prefersReducedMotion(e){s=e;}},l=new class{constructor(){this._frozenAt=null;}getCurrentTime(){return null!==this._frozenAt?this._frozenAt:performance.now()}setNow(e){this._frozenAt=e;}restoreNow(){this._frozenAt=null;}isFrozen(){return null!==this._frozenAt}};function c(){return l.getCurrentTime()}var h,u;class d{static create(e,t,i){const o=window.document.createElement(e);return void 0!==t&&(o.className=t),i&&i.appendChild(o),o}static createNS(e,t){return window.document.createElementNS(e,t)}static disableDrag(){d.docStyle&&d.selectProp&&(d.userSelect=d.docStyle[d.selectProp],d.docStyle[d.selectProp]="none");}static enableDrag(){d.docStyle&&d.selectProp&&(d.docStyle[d.selectProp]=d.userSelect);}static suppressClickInternal(e){e.preventDefault(),e.stopPropagation(),window.removeEventListener("click",d.suppressClickInternal,!0);}static suppressClick(){window.addEventListener("click",d.suppressClickInternal,!0),window.setTimeout((()=>{window.removeEventListener("click",d.suppressClickInternal,!0);}),0);}static getScale(e){const t=e.getBoundingClientRect();return {x:t.width/e.offsetWidth||1,y:t.height/e.offsetHeight||1,boundingClientRect:t}}static getPoint(e,i,o){const a=i.boundingClientRect;return new t.P((o.clientX-a.left)/i.x-e.clientLeft,(o.clientY-a.top)/i.y-e.clientTop)}static mousePos(e,t){const i=d.getScale(e);return d.getPoint(e,i,t)}static touchPos(e,t){const i=[],o=d.getScale(e);for(const a of t)i.push(d.getPoint(e,o,a));return i}static sanitize(e){const t=(new DOMParser).parseFromString(e,"text/html").body||document.createElement("body"),i=t.querySelectorAll("script");for(const e of i)e.remove();return d.clean(t),t.innerHTML}static isPossiblyDangerous(e,t){const i=t.replace(/\s+/g,"").toLowerCase();return !(!["src","href","xlink:href"].includes(e)||!i.includes("javascript:")&&!i.includes("data:"))||!!e.startsWith("on")||void 0}static clean(e){const t=e.children;for(const e of t)d.removeAttributes(e),d.clean(e);}static removeAttributes(e){for(const{name:t,value:i}of e.attributes)d.isPossiblyDangerous(t,i)&&e.removeAttribute(t);}}d.docStyle="undefined"!=typeof window&&(null===(h=window.document)||void 0===h?void 0:h.documentElement.style),d.selectProp=!d.docStyle||"userSelect"in d.docStyle?"userSelect":"webkitUserSelect",function(e){let i,o,a,r;e.resetRequestQueue=()=>{i=[],o=0,a=0,r={};},e.addThrottleControl=e=>{const t=a++;return r[t]=e,t},e.removeThrottleControl=e=>{delete r[e],n();},e.getImage=(e,o,a=!0)=>new Promise(((r,s)=>{e.headers||(e.headers={}),e.headers.accept="image/webp,*/*",t.e(e,{type:"image"}),i.push({abortController:o,requestParameters:e,supportImageRefresh:a,state:"queued",onError:e=>{s(e);},onSuccess:e=>{r(e);}}),n();}));const s=e=>t._(this,void 0,void 0,(function*(){e.state="running";const{requestParameters:i,supportImageRefresh:a,onError:r,onSuccess:s,abortController:c}=e,h=!1===a&&!t.i(self)&&!t.g(i.url)&&(!i.headers||Object.keys(i.headers).reduce(((e,t)=>e&&"accept"===t),!0));o++;const u=h?l(i,c):t.m(i,c);try{const i=yield u;delete e.abortController,e.state="completed",i.data instanceof HTMLImageElement||t.b(i.data)?s(i):i.data&&s({data:yield(d=i.data,"function"==typeof createImageBitmap?t.h(d):t.j(d)),cacheControl:i.cacheControl,expires:i.expires});}catch(i){delete e.abortController,r(t.d(i));}finally{o--,n();}var d;})),n=()=>{const e=(()=>{for(const e of Object.keys(r))if(r[e]())return !0;return !1})()?t.c.MAX_PARALLEL_IMAGE_REQUESTS_PER_FRAME:t.c.MAX_PARALLEL_IMAGE_REQUESTS;for(let t=o;t<e&&i.length>0;t++){const e=i.shift();e.abortController.signal.aborted?t--:s(e);}},l=(e,i)=>new Promise(((o,a)=>{const r=new Image,s=e.url,n=e.credentials;n&&"include"===n?r.crossOrigin="use-credentials":(n&&"same-origin"===n||!t.f(s))&&(r.crossOrigin="anonymous"),i.signal.addEventListener("abort",(()=>{r.src="",a(new t.a(i.signal.reason));})),r.fetchPriority="high",r.onload=()=>{r.onerror=r.onload=null,o({data:r});Line truncated
//
// Our custom intro provides a specialized "define()" function, called by the
// AMD modules below, that sets up the worker blob URL and then executes the
// main module, storing its exported value as 'maplibregl'
var maplibregl$1 = maplibregl;
return maplibregl$1;
}));
//# sourceMappingURL=maplibre-gl.js.map
@@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="dist/maplibre-gl.js"></script>
<link href="dist/maplibre-gl.css" rel="stylesheet"/>
<style>
html, body, #map {
margin: 0;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="map" style="width: 100%; height: 100%"></div>
<script>
const map = new maplibregl.Map({
center: [13.388, 52.517],
zoom: 9.5,
container: 'map',
attributionControl: false
})
</script>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
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);
setContentView(R.layout.view_main);
app.init();
}
public void logcat(String tag, String log) {
Log.i(tag, log);
}
public void logcat(String log) {
Log.i("LogcatGeneric", log);
}
@Override
public void onBackPressed() {
if (!app.getUi().back()) {
super.onBackPressed(); // exits app
}
}
}
@@ -1,90 +0,0 @@
package eu.konggdev.strikemaps.activity;
import android.content.SharedPreferences;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import android.os.Bundle;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.element.region.UIRegion;
import eu.konggdev.strikemaps.ui.element.region.content.MainContentRegion;
import eu.konggdev.strikemaps.ui.fragment.layout.FragmentLayoutControls;
import eu.konggdev.strikemaps.ui.fragment.layout.FragmentLayoutSearch;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentOfflineMaps;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentSettings;
import eu.konggdev.strikemaps.ui.screen.Screen;
import java.util.List;
import java.util.Map;
public class MainActivity extends ComponentHolderActivity {
private List<Component> components;
private UIComponent ui;
private Map<String, Screen> initScreens(UIComponent ui, MapComponent map, RegistryStorageComponent registry, SharedPreferences userPrefs) {
return Map.of(
//Main screen
"main", new Screen(
this,
//Main screen init regions definition
Map.of(
R.id.mainContentView, new MainContentRegion(map.toFragment(), R.id.mainContentView),
R.id.bottomUi, new UIRegion(new FragmentLayoutControls(this, ui, map, registry, userPrefs, R.id.bottomUi), R.id.bottomUi),
R.id.topUi, new UIRegion(new FragmentLayoutSearch(this, ui, R.id.topUi), R.id.topUi)
) //TODO: Probably stop referencing layout 3(!) times everytime
),
//Settings screen
"settings", new Screen(
this,
//Just the settings content fragment
Map.of(
R.id.mainContentView, new MainContentRegion(new FragmentLayoutContentSettings(this, ui, userPrefs), R.id.mainContentView)
)
),
//Offline maps screen
"offline", new Screen(
this,
Map.of(
R.id.mainContentView, new MainContentRegion(new FragmentLayoutContentOfflineMaps(this), R.id.mainContentView)
)
)
);
}
@NonNull
private List<Component> initComponents() {
SharedPreferences userPrefs = getSharedPreferences("user_prefs", MODE_PRIVATE);
RegistryStorageComponent registry = new RegistryStorageComponent(this, userPrefs);
MapComponent map = new MapComponent(this, registry, userPrefs);
UIComponent ui = new UIComponent(this, map, registry, userPrefs);
ui.defineScreens(initScreens(ui, map, registry, userPrefs));
this.ui = ui;
return List.of(
map,
ui,
registry
);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.components = initComponents();
ui.swapScreen("main");
setContentView(R.layout.view_main);
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (!ui.back()) {
setEnabled(false);
getOnBackPressedDispatcher().onBackPressed();
}
}
});
}
}
@@ -0,0 +1,46 @@
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 eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
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(DefinedScreen.MAIN); //Initial
}
}
}
@@ -1,24 +0,0 @@
package eu.konggdev.strikemaps.app;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import eu.konggdev.strikemaps.Component;
import java.util.ArrayList;
import java.util.List;
public abstract class ComponentHolderActivity extends AppCompatActivity {
private List<Component> components = new ArrayList<>();
public final <T extends Component> T getComponent(Class<T> type) {
return components.stream()
.filter(type::isInstance)
.findFirst()
.map(type::cast)
.orElse(null);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
@@ -0,0 +1,109 @@
package eu.konggdev.strikemaps.data.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]);
}
}
@@ -0,0 +1,40 @@
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,129 +0,0 @@
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.ComponentHolderActivity;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.source.tiles.SourceTiles;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.util.file.FileTools;
import org.apache.commons.codec.digest.DigestUtils;
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(ComponentHolderActivity app) {
Map<Integer, MapStyle> styles = new HashMap<>();
String[] styleAssets = FileTools.getAssetFiles("bundled/style", ".style.json", app);
for (int i = 0; i < styleAssets.length; i++) {
String styleContents = FileTools.loadStringFromAssetFile(styleAssets[i], app);
styles.put( i,
new MapStyle(
styleContents,
new StyleOptions(),
new StyleManagementMetadata( //Set the style to automatically update from assets
false, true, true,
"assets://" + styleAssets[i], DigestUtils.sha256Hex(styleContents))
));
}
return styles;
} //Built-in Styles
private static final Map<Integer, MapSource> DEFAULT_SOURCES = Map.of(
0, new MapSource(
"Strike Maps Planet",
new SourceTiles("https://tiles.strikemaps.eu/planet"),
"vector",
"smts"
),
1, new MapSource(
"ArcGIS Imagery",
new SourceTiles(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, ComponentHolderActivity 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,61 +1,35 @@
package eu.konggdev.strikemaps.map;
import java.util.*;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.Toast;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.map.renderer.implementation.MapLibreGLJSRenderer;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer;
import eu.konggdev.strikemaps.map.style.MapStyle;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
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 {
private final String TAG = "MapComponent";
private final MapRenderer mapRenderer;
private final ComponentHolderActivity activity;
private final RegistryStorageComponent registry;
private final SharedPreferences userPrefs;
//Current style
public Integer styleId;
MapRenderer mapRenderer;
AppController app;
public MapStyle style;
public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>();
public MapComponent(ComponentHolderActivity activity, RegistryStorageComponent registry, SharedPreferences userPrefs) {
this.activity = activity;
this.registry = registry;
this.userPrefs = userPrefs;
switch(UserPrefsHelper.mapRenderer(userPrefs)) {
case 0:
this.mapRenderer = new MapLibreGLJSRenderer(activity, this);
break;
case 1:
this.mapRenderer = new MapLibreNativeRenderer(activity, this);
break;
case 2:
this.mapRenderer = new VtmRenderer(activity, this);
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
Toast.makeText(activity, "Invalid renderer value in preferences\nFalling back to MapLibre GL JS", Toast.LENGTH_SHORT).show();
Log.e(TAG, "Invalid renderer value in preferences\nFalling back to MapLibre GL JS");
this.mapRenderer = new MapLibreGLJSRenderer(activity, this);
this.mapRenderer = new MapLibreNativeRenderer(app, this);
break;
};
}
@@ -64,22 +38,15 @@ public class MapComponent implements Component {
return new FragmentLayoutContentMap(mapRenderer.getView());
}
public void setStyle(Integer id) {
styleId = id;
//Pass to renderer
StyleDocument document = registry.getStyle(styleId)
.effectiveDocument(registry);
if (document == null) {
Log.e(TAG, "Received null style from registry");
return;
}
mapRenderer.styleUpdate(document);
public void setStyle(MapStyle style) {
this.style = style;
mapRenderer.reload();
}
public void switchOverlay(MapOverlay overlay) {
if (hasOverlay(overlay)) overlays.remove(overlay.getClass());
else overlays.put(overlay.getClass(), overlay);
overlayUpdate(overlay);
update();
}
public boolean hasOverlay(MapOverlay overlay) {
@@ -90,14 +57,18 @@ public class MapComponent implements Component {
return overlays.containsKey(overlay);
}
public void overlayUpdate(MapOverlay in) {
mapRenderer.overlayUpdate(in);
}
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);
@@ -110,12 +81,10 @@ public class MapComponent implements Component {
selectPoint(features.get(0));
break;
default:
UIComponent ui = activity.getComponent(UIComponent.class);
if (ui != null) {
ui.alert(
AlertDialogFactory.pointSelector(activity, ui, features, this::selectPoint)
);
}
app.getUi().alert(
AlertDialogFactory.pointSelector(app, features, selectedItem -> {
selectPoint(selectedItem);
}));
}
return true;
}
@@ -124,10 +93,4 @@ public class MapComponent implements Component {
//TODO: Likely Nonfeature(?) point selection
return true;
}
public void onMapInit() {
setStyle(
UserPrefsHelper.startupMapStyle(userPrefs)
);
}
}
@@ -0,0 +1,11 @@
package eu.konggdev.strikemaps.map.layer;
import com.fasterxml.jackson.databind.JsonNode;
public class MapLayer {
public JsonNode layer;
public MapLayer(JsonNode layer) {
this.layer = layer;
}
}
@@ -0,0 +1,16 @@
package eu.konggdev.strikemaps.map.layer;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.map.source.MapSource;
public class SourcedMapLayer {
public String key;
public MapSource source;
public JsonNode layer;
public SourcedMapLayer(String key, MapSource source, JsonNode layer) {
this.key = key;
this.source = source;
this.layer = layer;
}
}
@@ -1,12 +0,0 @@
package eu.konggdev.strikemaps.map.offline;
import okhttp3.*;
import java.io.IOException;
public final class OfflineTileResolver {
public static byte[] resolve(String url) throws IOException {
//TODO
return null;
}
}
@@ -1,22 +0,0 @@
package eu.konggdev.strikemaps.map.offline.download;
import eu.konggdev.strikemaps.map.source.MapSource;
import java.net.URI;
public final class OfflineDownloader {
public static String[] fetchAvailableExports(MapSource source) {
//URI uri = URI.create(source.url);
//String host = uri.getHost();
return new String[0];
}
public static void download(MapSource source, String export) {
//TODO
}
public static void download(MapSource source, int[] bounds) {
//TODO
}
}
@@ -1,8 +1,8 @@
package eu.konggdev.strikemaps.map.overlay;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
/* More or less a data-driven layer factory */
public interface MapOverlay {
public JsonNode makePatch();
public SourcedMapLayer makeLayer();
}
@@ -1,15 +1,19 @@
package eu.konggdev.strikemaps.map.overlay.implementation;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationListener;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.data.provider.LocationDataProvider;
import org.maplibre.geojson.Feature;
import org.maplibre.geojson.FeatureCollection;
import org.maplibre.geojson.Point;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -17,48 +21,46 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
public class LocationOverlay implements MapOverlay, LocationListener {
LocationDataProvider locationDataProvider;
AppController app;
MapComponent map;
public Location currentLocation = null;
public LocationOverlay(MapComponent map, AppCompatActivity activity) {
this.map = map;
this.locationDataProvider = new LocationDataProvider(activity, this);
public LocationOverlay(AppController app) {
this.app = app;
this.map = app.getMap();
this.locationDataProvider = new LocationDataProvider(app.getActivity(), this);
}
@Override
public JsonNode makePatch() {
public SourcedMapLayer makeLayer() {
MapSource source = new MapSource();
source.type = "geojson";
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
ObjectNode sources = mapper.createObjectNode();
ObjectNode location = mapper.createObjectNode();
try {
ObjectNode data = mapper.createObjectNode();
data.put("type", "Feature");
if(currentLocation != null) {
ObjectNode geometry = mapper.createObjectNode();
ObjectNode properties = mapper.createObjectNode();
geometry.put("type", "Point");
ArrayNode coordinates = mapper.createArrayNode();
coordinates.add(currentLocation.getLongitude());
coordinates.add(currentLocation.getLatitude());
geometry.put("type", "Point");
geometry.set("coordinates", coordinates);
data.put("type", "Feature");
data.set("geometry", geometry);
data.set("properties", properties);
data.set("properties", mapper.createObjectNode());
}
source.data = data;
} catch (Exception e) {
e.printStackTrace();
}
location.put("type", "geojson");
location.set("data", data);
sources.set("location", location);
root.set("sources", sources);
// layers
ArrayNode layers = mapper.createArrayNode();
ObjectNode layer = mapper.createObjectNode();
layer.put("id", "location");
layer.put("type", "circle");
layer.put("source", "location");
@@ -66,21 +68,25 @@ public class LocationOverlay implements MapOverlay, LocationListener {
ObjectNode paint = mapper.createObjectNode();
paint.put("circle-radius", 5);
paint.put("circle-color", "#1E88E5");
paint.put("circle-stroke-color", "#FFFFFF");
paint.put("circle-stroke-width", 1.5);
layer.set("paint", paint);
layers.add(layer);
root.set("layers", layers);
ObjectNode layout = mapper.createObjectNode();
layout.put("circle-pitch-alignment", "map");
return root;
layer.set("layout", layout);
ArrayNode layers = mapper.createArrayNode();
layers.add(layer);
return new SourcedMapLayer("location", source, layers);
}
@Override
public void onLocationChanged(@NonNull Location location) {
this.currentLocation = location;
map.overlayUpdate(this);
map.onOverlayUpdate();
}
}
@@ -1,15 +1,12 @@
package eu.konggdev.strikemaps.map.overlay.implementation;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
public class PointSelectionOverlay implements MapOverlay {
ComponentHolderActivity app;
MapComponent map;
@Override
public JsonNode makePatch() {
public SourcedMapLayer makeLayer() {
return null;
}
}
@@ -1,18 +1,17 @@
package eu.konggdev.strikemaps.map.renderer;
import android.view.View;
import android.view.ViewGroup;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import java.util.List;
public interface MapRenderer {
void styleUpdate(StyleDocument style);
void overlayUpdate(MapOverlay overlay);
void reload();
View getView();
@@ -1,152 +0,0 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.annotation.SuppressLint;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;
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.ComponentHolderActivity;
import eu.konggdev.strikemaps.util.json.JsonPatcher;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.style.source.StyleSource;
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 {
private final static String TAG = "MapLibreGLJSRenderer";
private final AppCompatActivity activity;
private final MapComponent controller;
final WebView webView;
private JsonNode origin;
@SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled"})
public MapLibreGLJSRenderer(ComponentHolderActivity activity, MapComponent controller) {
this.activity = activity;
this.controller = controller;
webView = new WebView(activity);
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 (StyleSource source : style.sources)
sources.set(source.key,
source.current.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) {
Log.e(TAG, "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) {
Log.e(TAG, "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) {
Log.e(TAG, "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 { }
}
@@ -1,21 +1,18 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import eu.konggdev.strikemaps.util.json.JsonPatcher;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.layer.SourcedMapLayer;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.style.source.StyleSource;
import eu.konggdev.strikemaps.map.style.MapStyle;
import org.maplibre.android.MapLibre;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.android.maps.MapLibreMap;
@@ -26,96 +23,56 @@ import org.maplibre.geojson.Feature;
import java.util.List;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
private final static String TAG = "MapLibreNativeRenderer";
private final AppCompatActivity activity;
private final MapComponent controller;
AppController app;
MapComponent controller;
MapLibreMap map;
final MapView mapView;
private JsonNode origin;
public MapLibreNativeRenderer(ComponentHolderActivity activity, MapComponent controller) {
this.activity = activity;
public MapLibreNativeRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
MapLibre.getInstance(activity);
this.mapView = new MapView(activity);
//TODO: Investigate if we might want to restore any savedInstanceState for MapLibre
MapLibre.getInstance(app.getActivity());
this.mapView = new MapView(app.getActivity());
mapView.onCreate(null);
//We need this to get the onMapReady callback
mapView.getMapAsync(this);
}
@Override
public void styleUpdate(StyleDocument style) {
public void reload() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
//Thanks to this, styleUpdate(null) can be used as an origin reload
if (style != null) {
MapStyle style = controller.style;
try {
/* Take metadata from MapStyle
everything outside sources, layers */
ObjectNode root = style.metadata.deepCopy();
//Sources
ObjectNode sources = mapper.createObjectNode();
if (style.sources != null)
for (StyleSource source : style.sources)
sources.set(source.key,
source.current.makeJson());
style.sources.forEach((k, v) -> sources.set(k, mapper.valueToTree(v)));
//Layers
ArrayNode layers = mapper.createArrayNode();
if (style.layerDefinitions != null)
layers.addAll((ArrayNode) style.layerDefinitions);
//Overlays
for (MapOverlay overlay : controller.overlays.values()) {
SourcedMapLayer overlayLayer = overlay.makeLayer();
sources.set(overlayLayer.key, mapper.valueToTree(overlayLayer.source));
layers.addAll((ArrayNode) overlayLayer.layer);
}
//Set all to root
root.set("sources", sources);
root.set("layers", layers);
this.origin = root;
map.setStyle(new Style.Builder().fromJson(mapper.writeValueAsString(root)));
} catch (Exception e) {
Log.e(TAG, "Failed to parse style: " + style.name);
e.printStackTrace();
}
}
try {
map.setStyle(new Style.Builder().fromJson(mapper.writeValueAsString(origin)));
} catch (Exception e) {
Log.e(TAG, "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) {
Log.e(TAG, "Failed to patch overlay: " + overlay.toString());
app.logcat("Failed to reload Map");
e.printStackTrace();
}
}
@@ -134,7 +91,7 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
public void onMapReady(@NonNull MapLibreMap maplibreMap) {
this.map = maplibreMap;
controller.onMapInit();
controller.setStyle(MapStyle.fromMapLibreJsonFile(UserPrefsHelper.startupMapStyle(app.getPrefs()), app));
//I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :)
map.getUiSettings().setLogoEnabled(false);
@@ -1,11 +1,9 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.view.View;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import okhttp3.OkHttpClient;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
@@ -18,27 +16,21 @@ import java.util.Collections;
import java.util.List;
public class VtmRenderer implements MapRenderer {
ComponentHolderActivity activity;
AppController app;
MapComponent controller;
Map map;
final MapView mapView;
public VtmRenderer(ComponentHolderActivity activity, MapComponent controller) {
this.activity = activity;
public VtmRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
this.mapView = new MapView(activity);
this.mapView = new MapView(app.getActivity());
this.map = mapView.map();
}
@Override
public void overlayUpdate(MapOverlay overlay) {
//TODO
}
@Override
public void styleUpdate(StyleDocument style) {
public void reload() {
//TODO
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OSciMap4TileSource tileSource = OSciMap4TileSource.builder().httpFactory(new OkHttpEngine.OkHttpFactory(builder)).build();
@@ -1,90 +1,21 @@
package eu.konggdev.strikemaps.map.source;
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.tiles.SourceTiles;
public class MapSource {
public String name;
public SourceTiles tileSource;
public String url;
public JsonNode data;
public String type;
public String schema;
public JsonNode tiles;
public int minzoom;
public int maxzoom;
public String scheme;
public int tileSize;
public String attribution;
public String encoding;
public MapSource(String name, SourceTiles tileSource, String type, String schema) {
this.name = name;
this.tileSource = tileSource;
this.type = type;
this.schema = schema;
}
public MapSource(String key, JsonNode sourceNode) {
this.name = key;
this.type = sourceNode.path("type").asText(null);
this.schema = sourceNode.path("schema").asText(null);
this.scheme = sourceNode.path("scheme").asText(null);
this.encoding = sourceNode.path("encoding").asText(null);
this.minzoom = sourceNode.path("minZoom").asInt(0);
this.maxzoom = sourceNode.path("maxZoom").asInt(24);
this.tileSize = sourceNode.path("tileSize").asInt(256);
this.tileSource = handleJsonTileSource(sourceNode);
}
private static SourceTiles handleJsonTileSource(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 SourceTiles(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 SourceTiles(tiles);
}
//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 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;
}
public MapSource() { }
}
@@ -1,14 +0,0 @@
package eu.konggdev.strikemaps.map.source.properties;
import com.fasterxml.jackson.databind.JsonNode;
public class SourceProperties {
public String type;
public String schema;
// Json constructor
public SourceProperties(JsonNode sourceNode) {
this.type = sourceNode.path("type").asText(null);
this.schema = sourceNode.path("schema").asText(null);
}
}
@@ -1,52 +0,0 @@
package eu.konggdev.strikemaps.map.source.tiles;
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 SourceTiles {
public enum TilesType {
URL,
TILES,
DATA
}
private final TilesType type;
private String url;
private String[] tiles;
private JsonNode data;
public SourceTiles(String url) {
this.url = url;
this.type = TilesType.URL;
}
public SourceTiles(String[] tiles) {
this.tiles = tiles;
this.type = TilesType.TILES;
}
public SourceTiles(JsonNode data) {
this.data = data;
this.type = TilesType.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,62 +1,68 @@
package eu.konggdev.strikemaps.map.style;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.data.helper.FileHelper;
import eu.konggdev.strikemaps.map.source.MapSource;
import java.util.*;
public class MapStyle {
public static final class StoredRepresentation {
public final String json;
public final StyleOptions options;
//Only local data
public String name;
public Bitmap icon;
public final StyleManagementMetadata managementMetadata;
public JsonNode metadata; // everything except layers + sources
public Map<String, MapSource> sources;
public ArrayNode layerDefinitions; // the "layers" array
public StoredRepresentation(String json, StyleOptions options, StyleManagementMetadata managementMetadata) {
this.json = json;
this.options = options;
this.managementMetadata = managementMetadata;
}
//FIXME
public static MapStyle fromMapLibreJsonFile(String filename, AppController app) {
String styleContents;
if (filename.startsWith("/storage")) styleContents = FileHelper.loadStringFromUserFile(filename);
else styleContents = FileHelper.loadStringFromAssetFile(filename, app);
public MapStyle restore() {
return new MapStyle(
json,
options,
managementMetadata
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode root = mapper.readTree(styleContents);
MapStyle style = new MapStyle();
style.name = root.path("name").asText();
style.icon = getIcon(root.path("icon").asText(), app);
style.sources = mapper.convertValue(
root.path("sources"),
new TypeReference<Map<String, MapSource>>() {}
);
style.layerDefinitions = root.withArray("layers");
ObjectNode metadata = root.deepCopy();
metadata.remove("layers");
metadata.remove("sources");
style.metadata = metadata;
return style;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@NonNull public final StyleDocument document;
@NonNull public final StyleOptions options;
// Null when the style is not managed
@Nullable public StyleManagementMetadata managementMetadata;
// Original json representation of the style document, as we got it
@NonNull public final String json;
public MapStyle(@NonNull String json, @NonNull StyleOptions styleOptions, @Nullable StyleManagementMetadata managementMetadata) {
this.json = json;
this.document = new StyleDocument(json);
this.options = styleOptions;
this.managementMetadata = managementMetadata;
public 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 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(RegistryStorageComponent registry) {
return document.effectiveDocument(options, registry);
}
}
@@ -1,75 +0,0 @@
package eu.konggdev.strikemaps.map.style.document;
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.map.source.MapSource;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.map.style.source.StyleSource;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import java.util.ArrayList;
import java.util.List;
public class StyleDocument {
//Only local data
public String name;
public String icon;
public JsonNode metadata; // everything except layers + sources
public List<StyleSource> 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<StyleSource> sources = new ArrayList<>();
jsonSources.fields().forEachRemaining(entry -> {
sources.add(new StyleSource(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) {
e.printStackTrace();
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, RegistryStorageComponent registry) {
StyleDocument result = new StyleDocument(this); //Copy
for (int i = 0; i < sources.size(); i++) {
StyleSource source = sources.get(i);
MapSource effectiveSource = registry.getSource(
options.getInteger(source.key, 0)
);
if (effectiveSource != null) source.current = effectiveSource;
}
return result;
}
}
@@ -1,20 +0,0 @@
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;
}
}
@@ -1,30 +0,0 @@
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;
}
}
@@ -1,28 +0,0 @@
package eu.konggdev.strikemaps.map.style.source;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.databind.JsonNode;
import eu.konggdev.strikemaps.map.source.MapSource;
import eu.konggdev.strikemaps.map.source.properties.SourceProperties;
// Stores a source currently used by a style
// Along with requirements of what source the style needs here
public class StyleSource {
public String key;
// Requirement properties
public SourceProperties properties;
// In an effective style, this is the actual source matched to the
// style's requirements according to user's preferences (StyleOptions).
// Otherwise, this is the fallback source.
// It also preserves the default source defined by the JSON for non-effective style documents
@NonNull public MapSource current;
// Json constructor
public StyleSource(String key, JsonNode sourceNode) {
this.key = key;
this.properties = new SourceProperties(sourceNode);
this.current = new MapSource(key, sourceNode);
}
}
@@ -1,87 +0,0 @@
package eu.konggdev.strikemaps.storage;
import android.content.SharedPreferences;
import androidx.annotation.Nullable;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
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 final ComponentHolderActivity activity;
private final SharedPreferences userPrefs;
private Map<Integer, MapStyle> styles;
private Map<Integer, MapSource> sources;
public RegistryStorageComponent(ComponentHolderActivity activity, SharedPreferences userPrefs) {
this.activity = activity;
this.userPrefs = userPrefs;
initAll();
}
private Map<Integer, MapSource> sources() {
if (sources == null) sources = UserPrefsHelper.sources(userPrefs);
return sources;
}
public Map<Integer, MapSource> getSources() {
return sources();
}
@Nullable public MapSource getSource(Integer id) {
return sources().get(id);
}
private Map<Integer, MapStyle> styles() {
if (styles == null) styles = UserPrefsHelper.styles(userPrefs, activity);
return styles;
}
public Map<Integer, MapStyle> getStyles() {
return styles();
}
@Nullable 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(userPrefs, styles);
}
public void checkForUpdates() {
//
}
private void initAll() {
styles(); sources();
}
}
@@ -1,59 +1,62 @@
package eu.konggdev.strikemaps.ui;
import android.app.AlertDialog;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
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.FragmentLayoutSearch;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentOfflineMaps;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentSettings;
import eu.konggdev.strikemaps.ui.screen.Screen;
import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.function.Consumer;
public class UIComponent implements Component {
private final String TAG = "UIComponent";
private final ComponentHolderActivity activity;
private final MapComponent map;
private final RegistryStorageComponent registry;
private final SharedPreferences userPrefs;
@NonNull AppController app;
MapComponent map;
private final ArrayDeque<Screen> screenStack = new ArrayDeque<>();
private Map<String, Screen> screens;
public UIComponent(ComponentHolderActivity activity, MapComponent map, RegistryStorageComponent registry, SharedPreferences userPrefs) {
this.activity = activity;
public UIComponent(AppController app, MapComponent map) {
this.app = app;
this.map = map;
this.registry = registry;
this.userPrefs = userPrefs;
}
public void defineScreens(Map<String, Screen> screens) {
this.screens = screens;
public Map<DefinedScreen, Screen> getScreens(MapComponent map) {
return Map.of(
//Main screen
DefinedScreen.MAIN, new Screen(
//App reference
app,
//Main screen init regions definition
Map.of(
R.id.mainContentView, new MainContentRegion(map.toFragment(), R.id.mainContentView),
R.id.bottomUi, new UIRegion(new FragmentLayoutControls(app, R.id.bottomUi), R.id.bottomUi),
R.id.topUi, new UIRegion(new FragmentLayoutSearch(app, R.id.topUi), R.id.topUi)
) //TODO: Probably stop referencing layout 3(!) times everytime
),
//Settings screen
DefinedScreen.SETTINGS, new Screen(
app,
//Just the settings content fragment
Map.of(
R.id.mainContentView, new MainContentRegion(new FragmentLayoutContentSettings(), R.id.mainContentView)
)
)
);
}
public void swapScreen(String screenKey) {
if (screens == null) return;
Screen newScreen = screens.get(screenKey);
if (newScreen == null) {
Log.e(TAG, "Invalid screen " + screenKey + " invoked");
return;
}
public void swapScreen(DefinedScreen screenKey) {
if (!screenStack.isEmpty()) getCurrentScreen().detachAll();
screenStack.add(newScreen);
screenStack.add(getScreens(map).get(screenKey));
getCurrentScreen().attachAll();
}
@@ -74,7 +77,11 @@ public class UIComponent implements Component {
dialog.show();
}
public <T> void alert(AlertDialog dialog, Consumer<T> callback) {
dialog.show();
}
public View inflateUi(int layout) {
return activity.getLayoutInflater().inflate(layout, null);
return app.getActivity().getLayoutInflater().inflate(layout, null);
}
}
@@ -5,82 +5,51 @@ import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.map.style.document.StyleDocument;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.util.icon.IconResolver;
public class GenericItem implements UIItem {
public String name;
@NonNull public String name;
public Bitmap image;
public Runnable onClick;
public Runnable onLongClick;
boolean hasImage;
public GenericItem(String name) {
this.name = name;
public GenericItem(String refName) {
this.name = refName;
hasImage = false;
}
public GenericItem(String name, Runnable onClick) {
this.name = name;
public GenericItem(String refName, Runnable onClick) {
this.name = refName;
this.onClick = onClick;
hasImage = false;
}
public GenericItem(String name, Runnable onClick, Runnable onLongClick) {
this.name = name;
this.onClick = onClick;
this.onLongClick = onLongClick;
hasImage = false;
}
public GenericItem(String name, Bitmap Image) {
this.name = name;
this.image = Image;
public GenericItem(String refName, Bitmap refImage) {
this.name = refName;
this.image = refImage;
hasImage = true;
}
public GenericItem(String name, Bitmap Image, Runnable onClick) {
this.name = name;
this.image = Image;
public GenericItem(String refName, Bitmap refImage, Runnable onClick) {
this.name = refName;
this.image = refImage;
this.onClick = onClick;
hasImage = true;
}
public GenericItem(String name, Bitmap Image, Runnable onClick, Runnable onLongClick) {
this.name = name;
this.image = Image;
this.onClick = onClick;
this.onLongClick = onLongClick;
hasImage = true;
public final static GenericItem fromStyle(MapStyle style, MapComponent map) {
if(style == null) return new GenericItem("Unknown");
if(style.icon != null)
return new GenericItem(style.name, style.icon, () -> map.setStyle(style));
return new GenericItem(style.name, () -> map.setStyle(style));
}
public GenericItem(StyleDocument style, Runnable onClick, AppCompatActivity activity) {
if (style == null) return;
this.name = style.name;
this.image = style.icon != null ? IconResolver.getIcon(style.icon, activity) : null;
this.onClick = onClick;
}
public GenericItem(StyleDocument style, Runnable onClick, Runnable onLongClick, AppCompatActivity activity) {
if (style == null) return;
this.name = style.name;
this.image = style.icon != null ? IconResolver.getIcon(style.icon, activity) : null;
this.onClick = onClick;
this.onLongClick = onLongClick;
}
@Override
public View makeView(UIComponent spawner) {
View v = spawner.inflateUi(R.layout.item_generic);
//FIXME: These shouldn't be casted like that!
((TextView) v.findViewById(R.id.name)).setText(name);
if(image != null) ((ImageButton) v.findViewById(R.id.image)).setImageBitmap(image);
if(onClick != null) v.findViewById(R.id.image).setOnClickListener(click(onClick));
if(onLongClick != null) v.findViewById(R.id.image).setOnLongClickListener(longClick(onLongClick));
return v;
}
@@ -1,38 +0,0 @@
package eu.konggdev.strikemaps.ui.element.item;
import android.view.View;
import android.widget.TextView;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.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,41 +4,35 @@ import android.graphics.Bitmap;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.ui.UIComponent;
import org.maplibre.geojson.Feature;
import eu.konggdev.strikemaps.R;
public class PreviewItem implements UIItem {
@NonNull public String name;
public String details;
public String name;
public String type;
public Bitmap image;
boolean hasImage;
public PreviewItem(String refName, String refType) {
this.name = refName;
this.details = refType;
this.type = refType;
hasImage = false;
}
public PreviewItem(String refName, String refType, Bitmap refImage) {
this.name = refName;
this.details = refType;
this.type = refType;
this.image = refImage;
hasImage = true;
}
public static PreviewItem fromFeature(Feature feature) {
return new PreviewItem(feature.getStringProperty("name"), feature.getStringProperty("class"));
}
@Override
public View makeView(UIComponent spawner) {
View view = spawner.inflateUi(R.layout.item_preview);
((TextView) view.findViewById(R.id.choiceName)).setText(name);
if (details != null)
((TextView) view.findViewById(R.id.details)).setText(details);
((TextView) view.findViewById(R.id.type)).setText(type);
return view;
}
public View makeView(UIComponent spawner, View.OnClickListener onClick) {
View view = makeView(spawner);
view.setOnClickListener(onClick);
@@ -1,12 +0,0 @@
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,4 +12,5 @@ public interface UIItem {
}
default View.OnLongClickListener longClick(Runnable action) { return v -> { action.run(); return true; };}
}
@@ -1,178 +1,36 @@
package eu.konggdev.strikemaps.ui.factory;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.*;
import androidx.appcompat.app.AppCompatActivity;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.util.json.JsonPatcher;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.map.style.management.StyleManagementMetadata;
import eu.konggdev.strikemaps.map.style.options.StyleOptions;
import eu.konggdev.strikemaps.ui.UIComponent;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.element.item.PreviewItem;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
import org.maplibre.geojson.Feature;
import java.util.List;
import java.util.function.Consumer;
//FIXME: Cleaner architecture would be having a class for each AlertDialog type
//FIXME: Move Item functions into specific classes for specific types - e.g. StyleItem
public final class AlertDialogFactory {
public static AlertDialog styleManagementOptions(AppCompatActivity activity, StyleManagementMetadata metadata) {
String[] options = {
"Update style",
"Update automatically"
};
boolean[] checked = {
metadata.doUpdates,
metadata.autoUpdate
};
return new AlertDialog.Builder(activity)
.setTitle("Built-in Style")
.setMultiChoiceItems(options, checked, (dialog, which, isChecked) -> {
if (which == 0) {
metadata.doUpdates = isChecked;
} else if (which == 1) {
metadata.autoUpdate = isChecked;
}
})
.setNegativeButton("Cancel", null)
.setPositiveButton("OK", null)
.create();
}
public static AlertDialog copyBuiltInStyle(AppCompatActivity activity, MapComponent map, UIComponent ui, FragmentMapChangePopup mapChangePopup) {
//TODO: Use an UI element that's supposed to be vertical, instead of GenericItem
LinearLayout container = new LinearLayout(activity);
container.setOrientation(LinearLayout.VERTICAL);
ScrollView scrollView = new ScrollView(activity);
scrollView.addView(container);
AlertDialog dialog = new AlertDialog.Builder(activity)
.setTitle("Copy from")
.setView(scrollView)
.setNegativeButton("Cancel", null)
.create();
return dialog;
}
public static AlertDialog createStyle(AppCompatActivity activity, RegistryStorageComponent registry, String baseStyleContents, FragmentMapChangePopup mapChangePopup) {
final EditText nameInput = new EditText(activity);
nameInput.setHint("Name");
LinearLayout container = new LinearLayout(activity);
container.setOrientation(LinearLayout.VERTICAL);
int padding = (int) (20 * activity.getResources().getDisplayMetrics().density);
container.setPadding(padding, padding, padding, 0);
container.addView(nameInput);
AlertDialog dialog = new AlertDialog.Builder(activity)
.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 mactivityer = new ObjectMapper();
boolean nameEntryRequired = true;
String styleContentName = "";
if (baseStyleContents != null) {
try {
JsonNode root = mactivityer.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 = mactivityer.readTree(baseStyleContents);
} else {
root = mactivityer.createObjectNode();
}
} else {
root = mactivityer.createObjectNode();
}
if (!root.path("name").asText().isEmpty()) {
ObjectNode node = mactivityer.createObjectNode();
node.put("name", name);
root = JsonPatcher.patch(root, node);
}
registry.addStyle(new MapStyle(
mactivityer.writeValueAsString(root),
new StyleOptions(),
null
));
dialog.dismiss();
} catch (Exception e) {
Toast.makeText(activity, "Failed to create", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
mapChangePopup.reloadStyles();
});
});
return dialog;
}
public static AlertDialog pointSelector(ComponentHolderActivity activity, UIComponent ui, List<Feature> features, Consumer<Feature> callback) {
LinearLayout layout = new LinearLayout(activity);
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(activity);
ScrollView scrollView = new ScrollView(app.getActivity());
scrollView.addView(layout);
AlertDialog dialog = new AlertDialog.Builder(activity)
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(ui, v -> {
View itemView = PreviewItem.fromFeature(feature).makeView(app.getUi(), v -> {
dialog.dismiss();
new Handler(Looper.getMainLooper())
new android.os.Handler(android.os.Looper.getMainLooper())
.post(() -> callback.accept(feature));
});
layout.addView(itemView);
@@ -180,34 +38,4 @@ public final class AlertDialogFactory {
return dialog;
}
public static AlertDialog searchSettings(AppCompatActivity activity) {
return new AlertDialog.Builder(activity)
.setTitle("Configure Search")
.setPositiveButton("OK", null)
.create();
}
public static AlertDialog restartDialog(AppCompatActivity activity) {
return new AlertDialog.Builder(activity)
.setTitle("Restart required")
.setMessage("Restart the activity to activityly changes.")
.setCancelable(false)
.setNegativeButton("Cancel", (d, w) -> {
Toast.makeText(activity,
"Changes will be activitylied on next restart",
Toast.LENGTH_SHORT).show();
d.dismiss();
})
.setPositiveButton("Restart", (d, w) -> {
Intent i = activity.getPackageManager()
.getLaunchIntentForPackage(activity.getPackageName());
if (i != null) {
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(i);
}
Runtime.getRuntime().exit(0);
})
.create();
}
}
@@ -1,150 +0,0 @@
package eu.konggdev.strikemaps.ui.fragment.dialog;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.card.MaterialCardView;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
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.storage.RegistryStorageComponent;
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\"}";
private AppCompatActivity activity;
private MapComponent map;
private UIComponent ui;
private RegistryStorageComponent registry;
private 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")) {
ui.alert(
AlertDialogFactory.createStyle(
activity,
registry,
content,
mapChangePopup
)
);
} else {
registry.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(AppCompatActivity activity, MapComponent map, UIComponent ui, RegistryStorageComponent registry, FragmentMapChangePopup mapChangePopup) {
this.activity = activity;
this.map = map;
this.ui = ui;
this.registry = registry;
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 -> {
ui.alert(AlertDialogFactory.copyBuiltInStyle(activity, map, ui, mapChangePopup));
});
fileBtn.setOnClickListener(v -> {
showImportDialog();
});
emptyBtn.setOnClickListener(v -> {
ui.alert(AlertDialogFactory.createStyle(activity, registry, styleBase, mapChangePopup));
});
return view;
}
}
@@ -1,136 +0,0 @@
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.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.card.MaterialCardView;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
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 {
private final AppCompatActivity activity;
private final MapComponent map;
private final UIComponent ui;
private final RegistryStorageComponent registry;
private final FragmentMapChangePopup mapChangePopup;
private final Integer styleId;
private ActivityResultLauncher<Intent> exportLauncher;
void deleteStyle() {
registry.deleteStyle(styleId);
mapChangePopup.reloadStyles();
dismiss();
}
private void showExportDialog() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/json");
exportLauncher.launch(intent);
}
public StyleDetailsBottomSheet(AppCompatActivity activity, MapComponent map, UIComponent ui, RegistryStorageComponent registry, FragmentMapChangePopup mapChangePopup, Integer styleId) {
this.activity = activity;
this.map = map;
this.ui = ui;
this.registry = registry;
this.mapChangePopup = mapChangePopup;
this.styleId = styleId;
}
@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) {
String styleJson = registry.getStyle(styleId).json;
if (styleJson != null) out.write(styleJson.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);
MapStyle style = registry.getStyle(styleId);
TextView styleNameView = view.findViewById(R.id.styleName);
LinearLayout styleManagementInfo = view.findViewById(R.id.styleManagementInfo);
TextView styleTypeView = view.findViewById(R.id.styleType);
ImageView managedStyleIndicator = view.findViewById(R.id.managedStyleIndicator);
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);
styleNameView.setText(style.document.name);
if (style.managementMetadata != null) {
styleTypeView.setText("Built-In Style");
managedStyleIndicator.setVisibility(View.VISIBLE);
styleManagementInfo.setOnClickListener(v ->
ui.alert(
AlertDialogFactory.styleManagementOptions(activity, style.managementMetadata)
)
);
} 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(activity, registry, style.json, mapChangePopup)));
exportButtonLayout.setOnClickListener(v -> showExportDialog());
deleteButtonLayout.setOnClickListener(v -> deleteStyle());
return view;
}
}
@@ -2,42 +2,31 @@ package eu.konggdev.strikemaps.ui.fragment.layout;
import android.Manifest;
import android.app.AlertDialog;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
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.ComponentHolderActivity;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.implementation.LocationOverlay;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
public class FragmentLayoutControls extends Fragment implements Layout {
private final AppCompatActivity activity;
private final UIComponent ui;
private final MapComponent map;
private final RegistryStorageComponent registry;
private final SharedPreferences userPrefs;
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();
}
@@ -46,32 +35,29 @@ public class FragmentLayoutControls extends Fragment implements Layout {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
} else {
map.switchOverlay(new LocationOverlay(map, activity));
app.getMap().switchOverlay(new LocationOverlay(app));
setupView();
}
}
public void zoomToLocation() {
if(map.hasOverlay(LocationOverlay.class)) {
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(activity)
.setTitle(activity.getString(R.string.attribution_title))
.setMessage(activity.getString(R.string.shipped_attribution))
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(AppCompatActivity activity, UIComponent ui, MapComponent map, RegistryStorageComponent registry, SharedPreferences userPrefs, Integer region) {
public FragmentLayoutControls(AppController app, Integer region) {
super(R.layout.fragment_controls);
this.activity = activity;
this.ui = ui;
this.map = map;
this.registry = registry;
this.userPrefs = userPrefs;
this.app = app;
this.region = region;
}
@@ -92,7 +78,7 @@ public class FragmentLayoutControls extends Fragment implements Layout {
/* Restores location enabled status from user prefs,
TODO: Should be moved out of UI code in the future */
if(UserPrefsHelper.persistLocationEnabled(userPrefs) && UserPrefsHelper.lastLocationEnabled(userPrefs) && !map.hasOverlay(LocationOverlay.class))
if(UserPrefsHelper.persistLocationEnabled(app.getPrefs()) && UserPrefsHelper.lastLocationEnabled(app.getPrefs()) && !app.getMap().hasOverlay(LocationOverlay.class))
toggleLocationService();
this.setupView();
@@ -100,7 +86,7 @@ public class FragmentLayoutControls extends Fragment implements Layout {
public void setupView() {
if (rootView == null) return;
setupButton(rootView, R.id.layersButton, click(() -> ui.getCurrentScreen().open(new FragmentMapChangePopup(activity, ui, map, registry, R.id.bottomUi))));
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));
@@ -111,14 +97,14 @@ public class FragmentLayoutControls extends Fragment implements Layout {
setupButton(rootView, R.id.modeButton, click(this::notImplemented));
TextView locationServiceStatusIndicator = rootView.findViewById(R.id.locationServiceStatusIndicator);
if (map.hasOverlay(LocationOverlay.class)) {
if (app.getMap().hasOverlay(LocationOverlay.class)) {
locationServiceStatusIndicator.setBackgroundColor(Color.parseColor("#00FF00")); //green
} else {
locationServiceStatusIndicator.setBackgroundColor(Color.parseColor("#FB0303")); //red
}
if(UserPrefsHelper.persistLocationEnabled(userPrefs))
UserPrefsHelper.lastLocationEnabled(userPrefs, map.hasOverlay(LocationOverlay.class));
if(UserPrefsHelper.persistLocationEnabled(app.getPrefs()))
UserPrefsHelper.lastLocationEnabled(app.getPrefs(), app.getMap().hasOverlay(LocationOverlay.class));
}
@Override
@@ -1,28 +1,34 @@
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.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.appcompat.app.AppCompatActivity;
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.ui.UIComponent;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.implementation.LocationOverlay;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
import eu.konggdev.strikemaps.ui.screen.definition.DefinedScreen;
public class FragmentLayoutSearch extends Fragment implements Layout {
private final AppCompatActivity activity;
private final UIComponent ui;
AppController app;
View rootView;
private final Integer region;
public FragmentLayoutSearch(AppCompatActivity activity, UIComponent ui, Integer region) {
public FragmentLayoutSearch(AppController app, Integer region) {
super(R.layout.fragment_search);
this.activity = activity;
this.ui = ui;
this.app = app;
this.region = region;
}
@@ -39,49 +45,7 @@ public class FragmentLayoutSearch extends Fragment implements Layout {
@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(() -> {
ui.alert(AlertDialogFactory.searchSettings(activity));
}));
setupButton(menuView, R.id.menuSettings, click(() -> {
popupWindow.dismiss();
ui.swapScreen("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);
});
}));
//TODO: Make a floating menu instead of going right in settings
setupButton(view, R.id.hamburgerButton, click(() -> app.getUi().swapScreen(DefinedScreen.SETTINGS)));
}
}
@@ -1,32 +0,0 @@
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.ComponentHolderActivity;
public class FragmentLayoutContentOfflineMaps extends Fragment implements MainContentLayout {
private final ComponentHolderActivity activity;
public FragmentLayoutContentOfflineMaps(ComponentHolderActivity activity) {
super(R.layout.fragment_offline_maps);
this.activity = activity;
}
@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(activity.getActivity(), "Work in progress", Toast.LENGTH_SHORT).show()).makeView(app.getUi()));
}
}
@@ -1,68 +1,20 @@
package eu.konggdev.strikemaps.ui.fragment.layout.content.main;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
public class FragmentLayoutContentSettings extends Fragment implements MainContentLayout {
private final AppCompatActivity activity;
private final UIComponent ui;
private final SharedPreferences userPrefs;
public FragmentLayoutContentSettings(AppCompatActivity activity, UIComponent ui, SharedPreferences userPrefs) {
public FragmentLayoutContentSettings() {
super(R.layout.fragment_settings);
this.activity = activity;
this.ui = ui;
this.userPrefs = userPrefs;
}
@Override
public Fragment toFragment() {
return this;
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
Spinner mapRendererSelector = view.findViewById(R.id.mapRendererSelector);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
view.getContext(),
R.array.map_renderers,
android.R.layout.simple_spinner_item
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mapRendererSelector.setAdapter(adapter);
mapRendererSelector.setSelection(UserPrefsHelper.mapRenderer(userPrefs));
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(userPrefs, position);
ui.alert(AlertDialogFactory.restartDialog(activity));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
}
);
}
}
@@ -1,117 +1,38 @@
package eu.konggdev.strikemaps.ui.fragment.popup;
import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import android.widget.LinearLayout;
import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.data.helper.FileHelper;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.storage.RegistryStorageComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.fragment.dialog.NewStyleBottomSheet;
import eu.konggdev.strikemaps.ui.fragment.dialog.StyleDetailsBottomSheet;
import eu.konggdev.strikemaps.ui.element.item.GenericItem;
import java.util.Map;
import java.util.Objects;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FragmentMapChangePopup extends Fragment implements Popup {
private final AppCompatActivity activity;
private final MapComponent map;
private final UIComponent ui;
private final RegistryStorageComponent registry;
@NonNull AppController app;
@NonNull MapComponent map;
@NonNull UIComponent ui;
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();
registry.getStyles().forEach((id, style) -> {
View item = new GenericItem(
style.document,
() -> this.setStyle(id),
() -> this.styleDetails(id), activity).makeView(ui);
if (Objects.equals(map.styleId, id)) item.findViewById(R.id.indicator)
.setVisibility(View.VISIBLE);
stylesLayout.addView(item);
});
Bitmap addNewIcon = BitmapFactory.decodeResource(activity.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(activity, map, ui, registry, this).show(activity.getSupportFragmentManager(), "NewStyleBottomSheet");
}
void styleDetails(Integer id) {
new StyleDetailsBottomSheet(activity, map, ui, registry, this, id).show(activity.getSupportFragmentManager(), "StyleDetailsBottomSheet");
}
void setStyle(Integer id) {
map.setStyle(id);
reloadStyles();
}
public FragmentMapChangePopup(AppCompatActivity activity, UIComponent ui, MapComponent map, RegistryStorageComponent registry, Integer region) {
public FragmentMapChangePopup(AppController app, Integer region) {
super(R.layout.popup_map_change);
this.activity = activity;
this.ui = ui;
this.map = map;
this.registry = registry;
this.app = app;
this.map = app.getMap();
this.ui = app.getUi();
this.region = region;
}
@@ -125,30 +46,16 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
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
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
//FIXME
this.view = view;
tabs.keySet().forEach(button -> {
view.findViewById(button).setOnClickListener(v -> switchTab(button));
});
switchTab(R.id.stylesTab); // Show styles
setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup()));
setupDragHandle(view, view, () -> ui.getCurrentScreen().closePopup());
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));
}
}
@@ -2,23 +2,25 @@ package eu.konggdev.strikemaps.ui.screen;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.appcompat.widget.Toolbar;
import java.util.Map;
import eu.konggdev.strikemaps.app.ComponentHolderActivity;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.ui.fragment.ContainerFragment;
import eu.konggdev.strikemaps.ui.fragment.FragmentEmptyPlaceholder;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.MainContentLayout;
import eu.konggdev.strikemaps.ui.fragment.popup.Popup;
import eu.konggdev.strikemaps.ui.element.region.UIRegion;
public class Screen {
private final ComponentHolderActivity activity;
public Screen(ComponentHolderActivity activity, Map<Integer, UIRegion> regions) {
this.activity = activity;
@NonNull AppController app;
public Screen(AppController app, Map<Integer, UIRegion> regions) {
this.app = app;
this.uiRegions = regions;
}
private MainContentLayout mainContent;
Map<Integer, UIRegion> uiRegions;
public Integer popup;
@@ -54,7 +56,7 @@ public class Screen {
}
public void fragmentTransaction(int layoutId, Fragment fragment) {
activity.getSupportFragmentManager()
app.getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(layoutId, fragment)
.commit();
@@ -0,0 +1,5 @@
package eu.konggdev.strikemaps.ui.screen.definition;
public enum DefinedScreen {
MAIN,
SETTINGS
}
@@ -1,67 +0,0 @@
package eu.konggdev.strikemaps.util.file;
import android.content.res.AssetManager;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import androidx.appcompat.app.AppCompatActivity;
//FIXME: Ugly
public final class FileTools {
private static final String TAG = "FileTools";
public static String loadStringFromAssetFile(String filePath, AppCompatActivity activity) {
try (InputStream is = activity.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[] getAssetFiles(String path, String fileExt, AppCompatActivity activity) {
AssetManager assetManager = activity.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, AppCompatActivity activity) {
try {
return activity.getAssets().open(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,22 +0,0 @@
package eu.konggdev.strikemaps.util.icon;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import eu.konggdev.strikemaps.util.file.FileTools;
public final class IconResolver {
private static final String TAG = "IconResolver";
public static Bitmap getIcon(String iconLocator, AppCompatActivity activity) {
switch (iconLocator.split("//")[0]) {
//TODO: https
case "assets:":
return BitmapFactory.decodeStream(FileTools.openAssetStream("bundled/icon/" + iconLocator.split("//")[1], activity));
default:
Log.e(TAG, "Unimplemented icon locator space: " + iconLocator);
return null;
}
}
}
@@ -1,93 +0,0 @@
package eu.konggdev.strikemaps.util.json;
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();
}
}
-14
View File
@@ -1,14 +0,0 @@
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`
* `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
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#2A2100" />
<corners android:radius="10dp" />
<stroke
android:width="1dp"
android:color="#4D3D00" />
</shape>
@@ -1,17 +0,0 @@
<?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>
@@ -1,5 +0,0 @@
<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
@@ -1,5 +0,0 @@
<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
@@ -1,17 +0,0 @@
<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.

After

Width:  |  Height:  |  Size: 5.2 KiB

-5
View File
@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M360,720Q327,720 303.5,696.5Q280,673 280,640L280,160Q280,127 303.5,103.5Q327,80 360,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,640Q800,673 776.5,696.5Q753,720 720,720L360,720ZM360,640L720,640Q720,640 720,640Q720,640 720,640L720,160Q720,160 720,160Q720,160 720,160L360,160Q360,160 360,160Q360,160 360,160L360,640Q360,640 360,640Q360,640 360,640ZM200,880Q167,880 143.5,856.5Q120,833 120,800L120,240L200,240L200,800Q200,800 200,800Q200,800 200,800L640,800L640,880L200,880ZM360,640Q360,640 360,640Q360,640 360,640L360,160Q360,160 360,160Q360,160 360,160L360,160Q360,160 360,160Q360,160 360,160L360,640Q360,640 360,640Q360,640 360,640Z"/>
</vector>
@@ -1,5 +0,0 @@
<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
@@ -1,5 +0,0 @@
<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: 177 B

After

Width:  |  Height:  |  Size: 214 B

-5
View File
@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10s10,-4.49 10,-10S17.51,2 12,2zM11,10V6h2v4h3l-4,4l-4,-4H11zM17,17H7v-2h10V17z"/>
</vector>
-24
View File
@@ -1,24 +0,0 @@
<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
@@ -1,17 +0,0 @@
<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>
-7
View File
@@ -1,7 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="#FFFFFF" />
</shape>
@@ -1,173 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
android: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="#6d6c6c"
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="#6d6c6c"
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>
@@ -1,303 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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" />
<LinearLayout
android:id="@+id/styleManagementInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/styleName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/styleType"
android:layout_width="112dp"
android:layout_height="27dp"
android:gravity="center"
android:text="Style Type?"
android:textColor="#A4A3A3"
android:textSize="18sp" />
<ImageView
android:id="@+id/managedStyleIndicator"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_marginStart="-8dp"
android:layout_marginTop="2dp"
android:visibility="gone"
android:background="@android:color/transparent"
android:src="@drawable/ic_arrow_forward"
app:tint="#FFFFFF"
android:contentDescription="Manage updates" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/optionsButton"
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/styleManagementInfo"
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/optionsIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_sliders"
app:tint="#A4A3A3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/optionsBtnText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Options"
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/editButton"
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/optionsButton"
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="#6d6c6c"
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>
</androidx.constraintlayout.widget.ConstraintLayout>
-52
View File
@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="200dp"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="12dp"
app:cardBackgroundColor="#000000">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/menuSearchSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="Configure Search"
android:textColor="#FFFFFF"
android:background="?android:attr/selectableItemBackground"/>
<TextView
android:id="@+id/menuSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="Settings"
android:textColor="#FFFFFF"
android:background="?android:attr/selectableItemBackground"/>
<TextView
android:id="@+id/menuAbout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="About"
android:textColor="#FFFFFF"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
+11 -17
View File
@@ -8,36 +8,33 @@
<ImageButton
android:id="@+id/routeButton"
android:layout_width="64dp"
android:layout_height="38dp"
android:layout_width="65dp"
android:layout_height="42dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="34dp"
android:layout_marginBottom="36dp"
android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
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="64dp"
android:layout_height="38dp"
android:layout_width="65dp"
android:layout_height="43dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="34dp"
android:layout_marginBottom="36dp"
android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
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="64dp"
android:layout_height="38dp"
android:layout_width="65dp"
android:layout_height="42dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_styles_menu" />
@@ -45,11 +42,10 @@
<ImageButton
android:id="@+id/modeButton"
android:layout_width="60dp"
android:layout_height="34dp"
android:layout_height="36dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="44dp"
android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
android:src="@android:drawable/ic_menu_manage"
app:layout_constraintBottom_toBottomOf="@+id/locationButtonContainer"
app:layout_constraintEnd_toEndOf="parent"
@@ -58,12 +54,11 @@
<ImageButton
android:id="@+id/attributionButton"
android:layout_width="60dp"
android:layout_height="24dp"
android:layout_height="31dp"
android:layout_marginStart="272dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.375"
@@ -74,7 +69,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="21dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="@+id/attributionButton"
app:layout_constraintEnd_toEndOf="parent">
@@ -83,7 +78,6 @@
android:layout_width="60dp"
android:layout_height="47dp"
android:backgroundTint="#000000"
android:background="@drawable/bg_map_button"
android:contentDescription="Location"
app:srcCompat="@android:drawable/ic_menu_mylocation" />
+1
View File
@@ -2,4 +2,5 @@
<android.widget.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.widget.LinearLayout>
@@ -1,41 +0,0 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<TextView
android:id="@+id/tvDownloadLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Download for source:"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:includeFontPadding="true"
android:paddingBottom="4dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollDownloadContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/tvDownloadLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="8dp"
android:fillViewport="true">
<LinearLayout
android:id="@+id/llDownloadContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
+4 -3
View File
@@ -4,12 +4,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp">
android:padding="24dp">
<LinearLayout
android:id="@+id/searchContainer"
android:layout_width="320dp"
android:layout_height="35dp"
android:layout_width="290dp"
android:layout_height="33dp"
android:layout_marginTop="4dp"
android:orientation="horizontal"
android:background="#000000"
app:layout_constraintTop_toTopOf="parent"
+5 -14
View File
@@ -8,23 +8,14 @@
android:background="@android:color/background_dark">
<TextView
android:id="@+id/mapRendererLabel"
android:id="@+id/settingsBanner"
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"
android:text="Settings"
android:textColor="#FFFFFF"
android:textSize="21sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
+5 -13
View File
@@ -9,11 +9,10 @@
<ImageButton
android:id="@+id/image"
android:layout_width="54dp"
android:layout_height="46dp"
android:background="@android:color/transparent"
android:contentDescription="Image"
android:src="@android:drawable/ic_menu_gallery"
android:padding="0dp" />
android:layout_height="47dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Style Image"
android:src="@android:drawable/ic_menu_gallery" />
<TextView
android:id="@+id/name"
@@ -21,15 +20,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="Name"
android:text="Item Name"
android:textColor="#FFFFFF"
android:textSize="14sp" />
<View
android:id="@+id/indicator"
android:layout_width="24dp"
android:layout_height="3dp"
android:layout_marginTop="5dp"
android:visibility="gone"
android:background="@drawable/indicator"/>
</LinearLayout>
-28
View File
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
android:foregroundTint="#FFFFFF">
<TextView
android:id="@+id/inline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:paddingVertical="12dp"
android:text=""
android:textColor="#FFFFFF"
android:textSize="16sp"
android:drawableEnd="@drawable/ic_arrow_forward"
android:drawablePadding="8dp"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:clickable="true"
android:focusable="true"/>
</androidx.constraintlayout.widget.ConstraintLayout>
+8 -7
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
android:background="#0E0000"
android:foregroundTint="#FFFFFF">
<TextView
@@ -15,7 +16,7 @@
android:layout_marginEnd="12dp"
android:text="Choice Item"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textSize="24sp"
android:ellipsize="end"
android:maxLines="1"
android:scrollHorizontally="false"
@@ -23,12 +24,12 @@
android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="24sp"
android:autoSizeStepGranularity="1sp"
app:layout_constraintStart_toEndOf="@+id/icon"
app:layout_constraintStart_toEndOf="@+id/poiIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/icon"
android:id="@+id/poiIcon"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginStart="7dp"
@@ -38,20 +39,20 @@
app:srcCompat="@drawable/maplibre_info_bg_selector" />
<TextView
android:id="@+id/details"
android:id="@+id/type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:text="Type"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:ellipsize="end"
android:singleLine="true"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintStart_toEndOf="@id/poiIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/choiceName"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,25 +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"
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>
+13 -156
View File
@@ -2,7 +2,6 @@
<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"
@@ -20,183 +19,41 @@
<ImageButton
android:id="@+id/closeButton"
android:layout_width="30dp"
android:layout_height="27dp"
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" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="28dp"
<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"
android:layout_marginBottom="2dp"
app:layout_constraintTop_toBottomOf="@id/dragHandle"
app:layout_constraintTop_toTopOf="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" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/tabContent"
<HorizontalScrollView
android:id="@+id/styles"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/tabs"
app:layout_constraintTop_toBottomOf="@id/stylesLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="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
android:id="@+id/stylesLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</HorizontalScrollView>
</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>
<!-- Sources selection to be implemented here -->
</androidx.constraintlayout.widget.ConstraintLayout>
+2 -1
View File
@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
@@ -11,5 +11,6 @@
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
+1 -7
View File
@@ -1,11 +1,5 @@
<resources>
<string name="app_name">Strike Maps</string>
<string name="settings_title">Settings</string>
<string name="attribution_title"> Attribution </string>
<string name="shipped_attribution">Map Rendering libraries Included;\n - MapLibre developed by MapLibre Organization\n - Vtm developed by Mapsforge\nStrike Maps made with &lt;3 by konggdev</string>
<string-array name="map_renderers">
<item>MapLibre GL JS</item>
<item>MapLibre Native</item>
<item>VTM</item>
</string-array>
<string name="shipped_attribution">Earth map data Included;\n - © OpenStreetMap Contributors\nMap Rendering libraries Included;\n - MapLibre developed by MapLibre Organization\n - Vtm developed by Mapsforge\nStrike Maps made with &lt;3 by konggdev</string>
</resources>
+2 -1
View File
@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="Theme.StrikeMaps" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryVariant">@color/black</item>
@@ -11,5 +11,6 @@
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
+3
View File
@@ -10,6 +10,7 @@ constraintlayout = "2.1.4"
playServicesMaps = "19.0.0"
viewpager2 = "1.1.0"
kotlin = "2.3.10"
coreKtx = "1.18.0"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -19,7 +20,9 @@ appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "a
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" }
viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }