Skip to content

Commit

Permalink
map: create shortcut to custom region and filters
Browse files Browse the repository at this point in the history
  • Loading branch information
deckerst committed Oct 22, 2024
1 parent 5ce8bef commit b9327db
Show file tree
Hide file tree
Showing 25 changed files with 350 additions and 137 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## <a id="unreleased"></a>[Unreleased]

### Added

- Map: create shortcut to custom region and filters

### Fixed

- crash when loading large collection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ open class MainActivity : FlutterFragmentActivity() {
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW_GEO,
INTENT_DATA_KEY_URI to uri.toString(),
INTENT_DATA_KEY_FILTERS to extractFiltersFromIntent(intent),
)
}

Expand Down Expand Up @@ -584,6 +585,8 @@ open class MainActivity : FlutterFragmentActivity() {

// dart page routes
const val COLLECTION_PAGE_ROUTE_NAME = "/collection"
const val ENTRY_VIEWER_PAGE_ROUTE_NAME = "/viewer"
const val EXPLORER_PAGE_ROUTE_NAME = "/explorer"
const val MAP_PAGE_ROUTE_NAME = "/map"
const val SEARCH_PAGE_ROUTE_NAME = "/search"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.request.RequestOptions
import deckers.thibault.aves.MainActivity
import deckers.thibault.aves.MainActivity.Companion.COLLECTION_PAGE_ROUTE_NAME
import deckers.thibault.aves.MainActivity.Companion.ENTRY_VIEWER_PAGE_ROUTE_NAME
import deckers.thibault.aves.MainActivity.Companion.EXPLORER_PAGE_ROUTE_NAME
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_EXPLORER_PATH
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_ARRAY
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_STRING
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_PAGE
import deckers.thibault.aves.MainActivity.Companion.EXTRA_STRING_ARRAY_SEPARATOR
import deckers.thibault.aves.MainActivity.Companion.MAP_PAGE_ROUTE_NAME
import deckers.thibault.aves.R
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
Expand Down Expand Up @@ -354,12 +358,17 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
// shortcuts

private fun pinShortcut(call: MethodCall, result: MethodChannel.Result) {
// common arguments
val label = call.argument<String>("label")
val iconBytes = call.argument<ByteArray>("iconBytes")
val route = call.argument<String>("route")
// route dependent arguments
val filters = call.argument<List<String>>("filters")
val explorerPath = call.argument<String>("explorerPath")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (label == null) {
val explorerPath = call.argument<String>("path")
val viewUri = call.argument<String>("viewUri")?.let { Uri.parse(it) }
val geoUri = call.argument<String>("geoUri")?.let { Uri.parse(it) }

if (label == null || route == null) {
result.error("pin-args", "missing arguments", null)
return
}
Expand All @@ -383,24 +392,60 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
// so that foreground is rendered at the intended scale
val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

icon = IconCompat.createWithResource(context, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection)
val resId = when (route) {
MAP_PAGE_ROUTE_NAME -> if (supportAdaptiveIcon) R.mipmap.ic_shortcut_map else R.drawable.ic_shortcut_map
else -> if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection
}
icon = IconCompat.createWithResource(context, resId)
}

val intent = when {
filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, "/collection")
.putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray())
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
// so we use a joined `String` as fallback
.putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
val intent: Intent = when (route) {
COLLECTION_PAGE_ROUTE_NAME -> {
if (filters == null) {
result.error("pin-filters", "collection shortcut requires filters", null)
return
}
Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, route)
.putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray())
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
// so we use a joined `String` as fallback
.putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
}

explorerPath != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, "/explorer")
.putExtra(EXTRA_KEY_EXPLORER_PATH, explorerPath)
ENTRY_VIEWER_PAGE_ROUTE_NAME -> {
if (viewUri == null) {
result.error("pin-viewUri", "viewer shortcut requires URI", null)
return
}
Intent(Intent.ACTION_VIEW, viewUri, context, MainActivity::class.java)
}

EXPLORER_PAGE_ROUTE_NAME -> {
Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, route)
.putExtra(EXTRA_KEY_EXPLORER_PATH, explorerPath)
}

MAP_PAGE_ROUTE_NAME -> {
if (geoUri == null) {
result.error("pin-geoUri", "map shortcut requires URI", null)
return
}
Intent(Intent.ACTION_VIEW, geoUri, context, MainActivity::class.java).apply {
putExtra(EXTRA_KEY_PAGE, route)
// filters are optional
filters?.let {
putExtra(EXTRA_KEY_FILTERS_ARRAY, it.toTypedArray())
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
// so we use a joined `String` as fallback
putExtra(EXTRA_KEY_FILTERS_STRING, it.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
}
}
}

uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java)
else -> {
result.error("pin-intent", "failed to build intent", null)
result.error("pin-route", "unsupported shortcut route=$route", null)
return
}
}
Expand Down
11 changes: 11 additions & 0 deletions lib/geo/uri.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:aves/utils/math_utils.dart';
import 'package:latlong2/latlong.dart';

// e.g. `geo:44.4361283,26.1027248?z=4.0(Bucharest)`
Expand All @@ -24,3 +25,13 @@ import 'package:latlong2/latlong.dart';
}
return null;
}

String toGeoUri(LatLng latLng, {double? zoom}) {
final latitude = roundToPrecision(latLng.latitude, decimals: 6);
final longitude = roundToPrecision(latLng.longitude, decimals: 6);
var uri = 'geo:$latitude,$longitude?q=$latitude,$longitude';
if (zoom != null) {
uri += '&z=$zoom';
}
return uri;
}
1 change: 1 addition & 0 deletions lib/model/app/contributors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class Contributors {
Contributor('splice11', 'trenchedgrandpa@protonmail.com'),
Contributor('Ihor Hordiichuk', 'igor_ck@outlook.com'),
Contributor('João Palmeiro', 'joaommpalmeiro@gmail.com'),
Contributor('Whoever4976', 'wolffjonas47@gmail.com'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
Expand Down
34 changes: 24 additions & 10 deletions lib/services/app_service.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'dart:async';

import 'package:aves/geo/uri.dart';
import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/utils/math_utils.dart';
import 'package:collection/collection.dart';
import 'package:flutter/services.dart';
import 'package:latlong2/latlong.dart';
Expand All @@ -30,7 +30,15 @@ abstract class AppService {

Future<bool> shareSingle(String uri, String mimeType);

Future<void> pinToHomeScreen(String label, AvesEntry? coverEntry, {Set<CollectionFilter>? filters, String? explorerPath, String? uri});
Future<void> pinToHomeScreen(
String label,
AvesEntry? coverEntry, {
required String route,
Set<CollectionFilter>? filters,
String? path,
String? viewUri,
String? geoUri,
});
}

class PlatformAppService implements AppService {
Expand Down Expand Up @@ -138,13 +146,9 @@ class PlatformAppService implements AppService {

@override
Future<bool> openMap(LatLng latLng) async {
final latitude = roundToPrecision(latLng.latitude, decimals: 6);
final longitude = roundToPrecision(latLng.longitude, decimals: 6);
final geoUri = 'geo:$latitude,$longitude?q=$latitude,$longitude';

try {
final result = await _platform.invokeMethod('openMap', <String, dynamic>{
'geoUri': geoUri,
'geoUri': toGeoUri(latLng),
});
if (result != null) return result as bool;
} on PlatformException catch (e, stack) {
Expand Down Expand Up @@ -203,7 +207,15 @@ class PlatformAppService implements AppService {
// app shortcuts

@override
Future<void> pinToHomeScreen(String label, AvesEntry? coverEntry, {Set<CollectionFilter>? filters, String? explorerPath, String? uri}) async {
Future<void> pinToHomeScreen(
String label,
AvesEntry? coverEntry, {
required String route,
Set<CollectionFilter>? filters,
String? path,
String? viewUri,
String? geoUri,
}) async {
Uint8List? iconBytes;
if (coverEntry != null) {
final size = coverEntry.isVideo ? 0.0 : 256.0;
Expand All @@ -221,9 +233,11 @@ class PlatformAppService implements AppService {
await _platform.invokeMethod('pinShortcut', <String, dynamic>{
'label': label,
'iconBytes': iconBytes,
'route': route,
'filters': filters?.map((filter) => filter.toJson()).toList(),
'explorerPath': explorerPath,
'uri': uri,
'path': path,
'viewUri': viewUri,
'geoUri': geoUri,
});
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
Expand Down
2 changes: 2 additions & 0 deletions lib/view/src/actions/map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extension ExtraMapActionView on MapAction {
MapAction.openMapApp => l10n.entryActionOpenMap,
MapAction.zoomIn => l10n.mapZoomInTooltip,
MapAction.zoomOut => l10n.mapZoomOutTooltip,
MapAction.addShortcut => l10n.collectionActionAddShortcut,
};
}

Expand All @@ -22,6 +23,7 @@ extension ExtraMapActionView on MapAction {
MapAction.openMapApp => AIcons.openOutside,
MapAction.zoomIn => AIcons.zoomIn,
MapAction.zoomOut => AIcons.zoomOut,
MapAction.addShortcut => AIcons.addShortcut,
};
}
}
3 changes: 2 additions & 1 deletion lib/widgets/collection/entry_set_action_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/utils/collection_utils.dart';
import 'package:aves/utils/mime_utils.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/action_mixins/entry_editor.dart';
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
Expand Down Expand Up @@ -746,7 +747,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
final (coverEntry, name) = result;
if (name.isEmpty) return;

await appService.pinToHomeScreen(name, coverEntry, filters: filters);
await appService.pinToHomeScreen(name, coverEntry, route: CollectionPage.routeName, filters: filters);
if (!device.showPinShortcutFeedback) {
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
}
Expand Down
23 changes: 13 additions & 10 deletions lib/widgets/common/identity/aves_filter_chip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -423,17 +423,20 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
);

final animate = context.select<Settings, bool>((v) => v.animate);
if (animate && (widget.heroType == HeroType.always || widget.heroType == HeroType.onTap && _tapped)) {
chip = Hero(
tag: filter,
transitionOnUserGestures: true,
child: MediaQueryDataProvider(
child: DefaultTextStyle(
style: const TextStyle(),
child: chip,
if (animate) {
final heroType = widget.heroType;
if (heroType == HeroType.always || (heroType == HeroType.onTap && _tapped)) {
chip = Hero(
tag: filter,
transitionOnUserGestures: true,
child: MediaQueryDataProvider(
child: DefaultTextStyle(
style: const TextStyle(),
child: chip,
),
),
),
);
);
}
}
return chip;
}
Expand Down
39 changes: 23 additions & 16 deletions lib/widgets/common/map/buttons/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,31 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class MapOverlayButton extends StatelessWidget {
final Key? buttonKey;
final Widget icon;
final String tooltip;
final VoidCallback? onPressed;
final ValueWidgetBuilder<VisualDensity> builder;

const MapOverlayButton({
super.key,
this.buttonKey,
required this.icon,
required this.tooltip,
required this.onPressed,
required this.builder,
});

factory MapOverlayButton.icon({
Key? buttonKey,
required Widget icon,
required String tooltip,
VoidCallback? onPressed,
}) {
return MapOverlayButton(
builder: (context, visualDensity, child) => IconButton(
key: buttonKey,
iconSize: iconSize(visualDensity),
visualDensity: visualDensity,
icon: icon,
onPressed: onPressed,
tooltip: tooltip,
),
);
}

@override
Widget build(BuildContext context) {
return Selector<MapThemeData, Animation<double>>(
Expand All @@ -27,15 +39,10 @@ class MapOverlayButton extends StatelessWidget {
),
child: Selector<MapThemeData, VisualDensity>(
selector: (context, v) => v.visualDensity,
builder: (context, visualDensity, child) => IconButton(
key: buttonKey,
iconSize: 20 + 1.5 * visualDensity.horizontal,
visualDensity: visualDensity,
icon: icon,
onPressed: onPressed,
tooltip: tooltip,
),
builder: builder,
),
);
}

static double iconSize(VisualDensity visualDensity) => 20 + 1.5 * visualDensity.horizontal;
}
Loading

0 comments on commit b9327db

Please sign in to comment.