From d8d55d7e4771d69285c2d174b1bc573825b727c1 Mon Sep 17 00:00:00 2001 From: Filip Czaplicki Date: Mon, 19 Aug 2024 21:53:05 +0200 Subject: [PATCH] Open location from Android intent with geo: url --- android/app/src/main/AndroidManifest.xml | 5 ++ .../zverev/ilya/every_door/MainActivity.kt | 24 +++++++ lib/screens/loading.dart | 67 ++++++++++++++++--- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 236b0cd3..21d77941 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,6 +30,11 @@ + + + + + diff --git a/android/app/src/main/kotlin/info/zverev/ilya/every_door/MainActivity.kt b/android/app/src/main/kotlin/info/zverev/ilya/every_door/MainActivity.kt index 4036f9e3..f9854579 100644 --- a/android/app/src/main/kotlin/info/zverev/ilya/every_door/MainActivity.kt +++ b/android/app/src/main/kotlin/info/zverev/ilya/every_door/MainActivity.kt @@ -1,6 +1,30 @@ package info.zverev.ilya.every_door +import androidx.annotation.NonNull +import android.os.Bundle import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { + private var lastIntentUrlPassed = "" + private val CHANNEL = "info.zverev.ilya.every_door/location" + + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { + call, result -> + if (call.method == "getLocationFromIntent") { + val intentLocationUrl = intent.data?.toString() ?: "" + if (intentLocationUrl.startsWith("geo:") && intentLocationUrl != lastIntentUrlPassed) { + result.success(intentLocationUrl) + lastIntentUrlPassed = intentLocationUrl + } else { + result.error("UNAVAILABLE", "Location url from Intent not available", null) + } + } else { + result.notImplemented() + } + } + } } diff --git a/lib/screens/loading.dart b/lib/screens/loading.dart index cd7c8bc7..959ce27a 100644 --- a/lib/screens/loading.dart +++ b/lib/screens/loading.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:every_door/providers/changes.dart'; import 'package:every_door/providers/changeset_tags.dart'; @@ -12,12 +13,14 @@ import 'package:every_door/screens/browser.dart'; import 'package:flutter/foundation.dart' show compute; import 'package:flutter/material.dart'; import 'package:every_door/constants.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_dropdown_alert/alert_controller.dart'; import 'package:flutter_dropdown_alert/model/data_alert.dart'; import 'package:latlong2/latlong.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:country_coder/country_coder.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:logging/logging.dart'; import 'package:shared_preferences/shared_preferences.dart'; class LoadingPage extends ConsumerStatefulWidget { @@ -26,9 +29,57 @@ class LoadingPage extends ConsumerStatefulWidget { } class _LoadingPageState extends ConsumerState { + static final _logger = Logger('LoadingPageState'); String? message; static const kPrefLastSizeWarning = 'last_size_warning'; + // https://en.wikipedia.org/wiki/Geo_URI_scheme + parseGeoLocation(String geo) { + if (!geo.startsWith("geo:")) return null; + final geoSplit = geo.split("geo:"); + if (geoSplit.length != 2) return null; + final semicolonSplit = geoSplit[1].split(";"); + final questionMarkSplit = semicolonSplit[0].split("?"); + final latLonSplit = questionMarkSplit[0].split(","); + try { + final lat = double.parse(latLonSplit[0]); + final lon = double.parse(latLonSplit[1]); + return LatLng(lat, lon); + } catch (e) { + _logger.severe("Couldn't parseGeoLocation: " + questionMarkSplit[0] + "\n" + e.toString()); + return null; + } + } + + Future setLocationFromAndroidIntent() async { + const intentLocationChannel = MethodChannel('info.zverev.ilya.every_door/location'); + try { + final result = await intentLocationChannel.invokeMethod('getLocationFromIntent'); + if (result != null) { + final parsedLocation = parseGeoLocation(result); + if (parsedLocation != null) { + ref.read(effectiveLocationProvider.notifier).set(parsedLocation); + return true; + } + } + } on PlatformException catch (e) { + _logger.severe("Failed calling getLocationFromIntent: " + e.toString()); + } + return false; + } + + Future acquireUserLocation(AppLocalizations loc) async { + setState(() { + message = loc.loadingLocation; + }); + if (!mounted) return; + await ref.read(geolocationProvider.notifier).enableTracking(context); + LatLng? location = ref.read(geolocationProvider); + if (location != null) { + ref.read(effectiveLocationProvider.notifier).set(location); + } + } + Future doInit() async { final loc = AppLocalizations.of(context)!; @@ -69,15 +120,13 @@ class _LoadingPageState extends ConsumerState { // Update floors in the background. ref.read(osmDataProvider).updateAddressesWithFloors(); - // Acquire user location. - setState(() { - message = loc.loadingLocation; - }); - if (!mounted) return; - await ref.read(geolocationProvider.notifier).enableTracking(context); - LatLng? location = ref.read(geolocationProvider); - if (location != null) { - ref.read(effectiveLocationProvider.notifier).set(location); + if (Platform.isAndroid) { + final success = await setLocationFromAndroidIntent(); + if (!success) { + await acquireUserLocation(loc); + } + } else { + await acquireUserLocation(loc); } // Alert if there are too many changes loaded.