refactor: polish map module ui/ux

This commit is contained in:
Rainer Killinger
2022-09-08 14:44:20 +00:00
parent 37dd29a60f
commit 855531fefb
20 changed files with 119 additions and 77 deletions

View File

@@ -56,6 +56,8 @@
<array> <array>
<string>armv7</string> <string>armv7</string>
</array> </array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
@@ -70,6 +72,6 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<true/> <false/>
</dict> </dict>
</plist> </plist>

6
package-lock.json generated
View File

@@ -3916,9 +3916,9 @@
} }
}, },
"@openstapps/core": { "@openstapps/core": {
"version": "0.69.0", "version": "0.70.0",
"resolved": "https://registry.npmjs.org/@openstapps/core/-/core-0.69.0.tgz", "resolved": "https://registry.npmjs.org/@openstapps/core/-/core-0.70.0.tgz",
"integrity": "sha512-IaUb10IfldokrqopdqsyHl5j3rVvg8CA10s2E+WpVOjgkXnW8SP9jVg5AP0jx3vgN3e0xHsrPtvrZWTPgyoSRQ==", "integrity": "sha512-obLFSiI5P0cEAeadxEQ1Wbbe/FDiSi0p4azpmszUPyshpSXKqt4y6F4Z+dnswBSe85wtPg/PuHC1MYZ79SJB4g==",
"requires": { "requires": {
"@openstapps/core-tools": "0.32.0", "@openstapps/core-tools": "0.32.0",
"@types/geojson": "1.0.6", "@types/geojson": "1.0.6",

View File

@@ -87,7 +87,7 @@
"@ngx-translate/http-loader": "7.0.0", "@ngx-translate/http-loader": "7.0.0",
"@openstapps/api": "0.43.0", "@openstapps/api": "0.43.0",
"@openstapps/configuration": "0.33.0", "@openstapps/configuration": "0.33.0",
"@openstapps/core": "0.69.0", "@openstapps/core": "0.70.0",
"@transistorsoft/capacitor-background-fetch": "1.0.0", "@transistorsoft/capacitor-background-fetch": "1.0.0",
"capacitor-secure-storage-plugin": "0.8.0", "capacitor-secure-storage-plugin": "0.8.0",
"cordova-plugin-calendar": "5.1.6", "cordova-plugin-calendar": "5.1.6",

View File

@@ -51,7 +51,7 @@ export class AboutLicensesComponent implements OnInit {
modal.dismiss(); modal.dismiss();
}, },
}, },
swipeToClose: true, canDismiss: true,
}); });
return await modal.present(); return await modal.present();
} }

View File

@@ -249,7 +249,7 @@ export class AddEventPopoverComponent implements OnInit, OnDestroy {
async export() { async export() {
const modal = await this.modalController.create({ const modal = await this.modalController.create({
component: AddEventReviewModalComponent, component: AddEventReviewModalComponent,
swipeToClose: true, canDismiss: true,
cssClass: 'add-modal', cssClass: 'add-modal',
componentProps: { componentProps: {
dismissAction: () => { dismissAction: () => {

View File

@@ -22,7 +22,7 @@
<ion-modal <ion-modal
trigger="show-more" trigger="show-more"
[presentingElement]="routerOutlet.nativeEl" [presentingElement]="routerOutlet.nativeEl"
swipeToClose="true" canDismiss="true"
class="modal-large" class="modal-large"
> >
<ng-template> <ng-template>
@@ -31,14 +31,6 @@
</ng-template> </ng-template>
</ion-modal> </ion-modal>
<stapps-skeleton-list-item *ngIf="!item"></stapps-skeleton-list-item> <stapps-skeleton-list-item *ngIf="!item"></stapps-skeleton-list-item>
<ion-button
size="small"
fill="clear"
class="close"
(click)="onCloseClick()"
>
<ion-icon name="cancel" fill></ion-icon>
</ion-button>
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content>
<ion-note> <ion-note>
@@ -52,7 +44,7 @@
}}</ion-button> }}</ion-button>
<ion-modal <ion-modal
trigger="show-more-button" trigger="show-more-button"
swipeToClose="true" canDismiss="true"
[presentingElement]="routerOutlet.nativeEl" [presentingElement]="routerOutlet.nativeEl"
class="modal-large" class="modal-large"
> >

View File

@@ -22,7 +22,7 @@ import {
SCUuid, SCUuid,
} from '@openstapps/core'; } from '@openstapps/core';
import {Point, Polygon} from 'geojson'; import {Point, Polygon} from 'geojson';
import {divIcon, geoJSON, icon, LatLng, Map, marker, Marker} from 'leaflet'; import {divIcon, geoJSON, LatLng, Map, marker, Marker} from 'leaflet';
import {DataProvider} from '../data/data.provider'; import {DataProvider} from '../data/data.provider';
import {MapPosition, PositionService} from './position.service'; import {MapPosition, PositionService} from './position.service';
import {hasValidLocation} from '../data/types/place/place-types'; import {hasValidLocation} from '../data/types/place/place-types';
@@ -45,14 +45,20 @@ export class MapProvider {
* Provide a point marker for a leaflet map * Provide a point marker for a leaflet map
* *
* @param point Point to get marker for * @param point Point to get marker for
* @param className CSS class name
* @param iconSize Size of the position icon
*/ */
static getPointMarker(point: Point) { static getPointMarker(point: Point, className: string, iconSize: number) {
return marker(geoJSON(point).getBounds().getCenter(), { return marker(geoJSON(point).getBounds().getCenter(), {
icon: icon({ icon: divIcon({
iconAnchor: [13, 41], className: className,
iconSize: [25, 41], html: `<span
iconUrl: '../assets/marker-icon.png', name="${SCIcon`location_on`}"
shadowUrl: '../assets/marker-shadow.png', class="material-symbols-rounded map-location-pin"
style="font-size: ${iconSize}px;"
>${SCIcon`location_on`}</span>`,
iconSize: [iconSize, iconSize],
iconAnchor: [iconSize / 2, iconSize],
}), }),
}); });
} }
@@ -80,12 +86,13 @@ export class MapProvider {
transform-origin: center; transform-origin: center;
transform: rotate(${position.heading}deg); transform: rotate(${position.heading}deg);
font-size: ${iconSize}px; font-size: ${iconSize}px;
color: var(--ion-color-primary);
" "
>${SCIcon`navigation`}</span>` >${SCIcon`navigation`}</span>`
: `<span : `<span
name="${SCIcon`person_pin_circle`}" name="${SCIcon`person_pin_circle`}"
class="material-symbols-rounded map-location-pin" class="material-symbols-rounded map-location-pin"
style="font-size: ${iconSize}px;" style="font-size: ${iconSize}px; color: var(--ion-color-primary);"
>${SCIcon`person_pin_circle`}</span>`, >${SCIcon`person_pin_circle`}</span>`,
iconSize: [iconSize, iconSize], iconSize: [iconSize, iconSize],
}), }),

View File

@@ -13,7 +13,6 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Location} from '@angular/common'; import {Location} from '@angular/common';
import {trigger, style, animate, transition} from '@angular/animations';
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
@@ -60,24 +59,6 @@ import {Capacitor} from '@capacitor/core';
styleUrls: ['./map-page.scss'], styleUrls: ['./map-page.scss'],
templateUrl: './map-page.html', templateUrl: './map-page.html',
providers: [ContextMenuService], providers: [ContextMenuService],
animations: [
trigger('fadeInOut', [
transition(':enter', [
style({transform: 'translateY(200%)', opacity: 0}),
animate(
'500ms ease-in-out',
style({transform: 'translateY(0%)', opacity: 1}),
),
]),
transition(':leave', [
style({transform: 'translateY(0%)', opacity: 1}),
animate(
'500ms ease-in-out',
style({transform: 'translateY(200%)', opacity: 0}),
),
]),
]),
],
}) })
export class MapPageComponent { export class MapPageComponent {
/** /**
@@ -230,7 +211,11 @@ export class MapPageComponent {
return polygonLayer.on('click', this.showItem.bind(this, place.uid)); return polygonLayer.on('click', this.showItem.bind(this, place.uid));
} }
const markerLayer = MapProvider.getPointMarker(place.geo.point); const markerLayer = MapProvider.getPointMarker(
place.geo.point,
'stapps-location',
32,
);
return markerLayer.on('click', this.showItem.bind(this, place.uid)); return markerLayer.on('click', this.showItem.bind(this, place.uid));
}; };
@@ -327,7 +312,7 @@ export class MapPageComponent {
this.positionMarker = MapProvider.getPositionMarker( this.positionMarker = MapProvider.getPositionMarker(
position, position,
'stapps-device-location', 'stapps-device-location',
30, 32,
); );
}, },
error: async _error => { error: async _error => {
@@ -407,14 +392,13 @@ export class MapPageComponent {
await ( await (
await this.alertController.create({ await this.alertController.create({
header: location.TITLE, header: location.TITLE,
subHeader: location.SUBTITLE,
message: `${ message: `${
this.locationStatus?.location === 'denied' this.locationStatus?.location === 'denied'
? location.NOT_ALLOWED ? location.NOT_ALLOWED
: this.locationStatus?.location !== 'granted' : this.locationStatus?.location !== 'granted'
? location.NOT_ENABLED ? location.NOT_ENABLED
: unknownError : unknownError
}.`, }`,
buttons: ['OK'], buttons: ['OK'],
}) })
).present(); ).present();

View File

@@ -59,7 +59,6 @@
<div class="map-buttons above"> <div class="map-buttons above">
<ion-button <ion-button
*ngIf="items.length > 1" *ngIf="items.length > 1"
[@fadeInOut]
color="light" color="light"
shape="round" shape="round"
size="small" size="small"
@@ -70,24 +69,31 @@
}} }}
</ion-button> </ion-button>
<ion-button <ion-button
[disabled]="position === undefined"
color="light" color="light"
shape="round" shape="round"
size="small" size="small"
(click)="onPositionClick()" (click)="onPositionClick()"
> >
<ion-icon <ion-icon
*ngIf="position !== null; else questionIcon" *ngIf="position !== null; else noLocationIcon"
name="my_location" name="my_location"
></ion-icon> ></ion-icon>
<ng-template #questionIcon> <ng-template #noLocationIcon>
<ion-icon name="location_searching"></ion-icon> <ion-icon
*ngIf="
locationStatus.location !== 'denied';
else deniedLocationIcon
"
name="location_searching"
></ion-icon>
</ng-template>
<ng-template #deniedLocationIcon>
<ion-icon name="location_disabled"></ion-icon>
</ng-template> </ng-template>
</ion-button> </ion-button>
</div> </div>
<stapps-map-item <stapps-map-item
*ngIf="items.length === 1" *ngIf="items.length === 1"
[@fadeInOut]
[item]="items[0]" [item]="items[0]"
(onClose)="resetView()" (onClose)="resetView()"
></stapps-map-item> ></stapps-map-item>
@@ -95,7 +101,6 @@
<div class="map-buttons floating-buttons"> <div class="map-buttons floating-buttons">
<ion-button <ion-button
*ngIf="items.length > 1" *ngIf="items.length > 1"
[@fadeInOut]
color="light" color="light"
shape="round" shape="round"
size="small" size="small"
@@ -106,24 +111,29 @@
}} }}
</ion-button> </ion-button>
<ion-button <ion-button
[disabled]="position === undefined"
color="light" color="light"
shape="round" shape="round"
size="small" size="small"
(click)="onPositionClick()" (click)="onPositionClick()"
> >
<ion-icon <ion-icon
*ngIf="position !== null; else questionIcon" *ngIf="position !== null; else noLocationIcon"
name="my_location" name="my_location"
></ion-icon> ></ion-icon>
<ng-template #questionIcon> <ng-template #noLocationIcon>
<ion-icon name="location_searching"></ion-icon> <ion-icon
*ngIf="locationStatus.location !== 'denied'; else deniedLocationIcon"
name="location_searching"
></ion-icon>
</ng-template>
<ng-template #deniedLocationIcon>
<ion-icon name="location_disabled"></ion-icon>
</ng-template> </ng-template>
</ion-button> </ion-button>
</div> </div>
<ion-modal <ion-modal
[swipeToClose]="true" [canDismiss]="true"
[presentingElement]="routerOutlet.nativeEl" [presentingElement]="routerOutlet.nativeEl"
#mapListModal #mapListModal
> >
@@ -131,6 +141,7 @@
<map-list-modal <map-list-modal
style="height: 100%" style="height: 100%"
[filterQuery]="filterQuery" [filterQuery]="filterQuery"
[mapBounds]="this.map.getBounds()"
[queryText]="queryText" [queryText]="queryText"
></map-list-modal> ></map-list-modal>
</ng-template> </ng-template>

View File

@@ -4,6 +4,7 @@ ion-content {
div.map-container { div.map-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: fixed;
} }
& > div { & > div {
overflow: hidden; overflow: hidden;
@@ -33,7 +34,7 @@ ion-toolbar:first-of-type {
div.floating-content { div.floating-content {
display: block; display: block;
position: absolute; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;

View File

@@ -13,9 +13,10 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core';
import {SCPlace, SCSearchFilter} from '@openstapps/core'; import {SCSearchBooleanFilter, SCPlace, SCSearchFilter} from '@openstapps/core';
import {MapProvider} from '../../map.provider'; import {MapProvider} from '../../map.provider';
import {ModalController} from '@ionic/angular'; import {ModalController} from '@ionic/angular';
import {LatLngBounds} from 'leaflet';
/** /**
* Modal showing a provided list of places * Modal showing a provided list of places
@@ -31,6 +32,11 @@ export class MapListModalComponent implements OnInit {
*/ */
@Input() filterQuery?: SCSearchFilter; @Input() filterQuery?: SCSearchFilter;
/**
* Map visible boundaries limiting items in lust
*/
@Input() mapBounds?: LatLngBounds;
/** /**
* Places to show in the list * Places to show in the list
*/ */
@@ -50,8 +56,44 @@ export class MapListModalComponent implements OnInit {
* Populate the list with the results from the search * Populate the list with the results from the search
*/ */
ngOnInit() { ngOnInit() {
let geofencedFilter: SCSearchBooleanFilter | undefined;
if (typeof this.mapBounds !== 'undefined') {
geofencedFilter = {
arguments: {
operation: 'and',
filters: [
{
type: 'geo',
arguments: {
field: 'geo',
shape: {
coordinates: [
[
this.mapBounds.getNorthWest().lng,
this.mapBounds.getNorthWest().lat,
],
[
this.mapBounds.getSouthEast().lng,
this.mapBounds.getSouthEast().lat,
],
],
type: 'envelope',
},
spatialRelation: 'intersects',
},
},
],
},
type: 'boolean',
};
if (typeof this.filterQuery !== 'undefined') {
geofencedFilter.arguments.filters.push(this.filterQuery);
}
}
const geofencedFilterQuery = geofencedFilter ?? this.filterQuery;
this.mapProvider this.mapProvider
.searchPlaces(this.filterQuery, this.queryText) .searchPlaces(geofencedFilterQuery, this.queryText)
.then(result => { .then(result => {
this.items = result.data as SCPlace[]; this.items = result.data as SCPlace[];
}); });

View File

@@ -58,7 +58,11 @@ export class MapWidgetComponent implements OnInit {
* Prepare the map * Prepare the map
*/ */
ngOnInit() { ngOnInit() {
const markerLayer = MapProvider.getPointMarker(this.place.geo.point); const markerLayer = MapProvider.getPointMarker(
this.place.geo.point,
'stapps-location',
32,
);
this.options = { this.options = {
center: geoJSON(this.place.geo.polygon || this.place.geo.point) center: geoJSON(this.place.geo.polygon || this.place.geo.point)
.getBounds() .getBounds()

View File

@@ -17,6 +17,7 @@
type="overlay" type="overlay"
menuId="context" menuId="context"
contentId="{{ contentId }}" contentId="{{ contentId }}"
maxEdgeStart="0"
side="end" side="end"
> >
<ion-toolbar color="primary" mode="ios"> <ion-toolbar color="primary" mode="ios">

View File

@@ -44,7 +44,7 @@ export class ModalEventCreatorComponent implements OnInit, OnDestroy {
isModal: true, isModal: true,
inputItem: item, inputItem: item,
}, },
swipeToClose: true, canDismiss: true,
presentingElement: await this.modalController.getTop(), presentingElement: await this.modalController.getTop(),
}); });
return modal.present(); return modal.present();

View File

@@ -81,7 +81,7 @@
</ion-fab> </ion-fab>
<ion-modal <ion-modal
swipeToClose="true" canDismiss="true"
[presentingElement]="routerOutlet.nativeEl" [presentingElement]="routerOutlet.nativeEl"
[isOpen]="isModalOpen" [isOpen]="isModalOpen"
(ionModalWillDismiss)="onModalDismiss()" (ionModalWillDismiss)="onModalDismiss()"

View File

@@ -112,7 +112,7 @@ export class CalendarSyncSettingsComponent implements OnInit {
const modal = await this.modalController.create({ const modal = await this.modalController.create({
component: AddEventReviewModalComponent, component: AddEventReviewModalComponent,
swipeToClose: true, canDismiss: true,
cssClass: 'add-modal', cssClass: 'add-modal',
componentProps: { componentProps: {
dismissAction: () => { dismissAction: () => {

View File

@@ -213,10 +213,9 @@
"MORE": "Mehr" "MORE": "Mehr"
}, },
"geolocation": { "geolocation": {
"TITLE": "Standort", "TITLE": "Standort nicht verfügbar",
"SUBTITLE": "Standort nicht erreichbar", "NOT_ENABLED": "Die Standortermittlung auf dem Gerät ist nicht aktiviert",
"NOT_ENABLED": "Standortermittlung auf Deinem Gerät ist nicht aktiviert", "NOT_ALLOWED": "Du hast den Zugriff auf deinen Standort für diese App abgelehnt. Nutze die Datenschutz-Einstellungen deines Gerätes um den Zugriff zu erlauben."
"NOT_ALLOWED": "Zugriff auf den Standort für die App nicht zugelassen"
} }
}, },
"modals": { "modals": {

View File

@@ -213,10 +213,9 @@
"MORE": "More" "MORE": "More"
}, },
"geolocation": { "geolocation": {
"TITLE": "Location", "TITLE": "Location not available",
"SUBTITLE": "Location not available", "NOT_ENABLED": "Location services are not enabled on your device",
"NOT_ENABLED": "Location service is not enabled on your device", "NOT_ALLOWED": "The app is not allowed to access your location. Use your device privacy settings to allow it again."
"NOT_ALLOWED": "The app is not allowed to access your location"
} }
}, },
"modals": { "modals": {

Binary file not shown.

View File

@@ -45,7 +45,7 @@ stapps-icon {
.map-location-pin { .map-location-pin {
font-variation-settings: 'FILL' 1; font-variation-settings: 'FILL' 1;
color: var(--ion-color-primary); color: var(--ion-color-tertiary);
&::before { &::before {
content: attr(name); content: attr(name);