diff --git a/lib/main.dart b/lib/main.dart index 2275d2e..335ac9a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,9 @@ +// main.dart import 'package:flutter/material.dart'; import 'pages/bookmarks_page.dart'; import 'pages/collections_page.dart'; import 'service/storage.dart'; +import 'service/share_intent_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -9,12 +11,70 @@ void main() async { runApp(const MapsBookmarks()); } -class MapsBookmarks extends StatelessWidget { +class MapsBookmarks extends StatefulWidget { const MapsBookmarks({super.key}); + @override + State createState() => _MapsBookmarksState(); +} + +class _MapsBookmarksState extends State + with WidgetsBindingObserver { + final ShareIntentService _shareIntentService = ShareIntentService(); + final GlobalKey _navigatorKey = GlobalKey(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + // Check for shared content on app start + _checkForSharedContent(); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + // Check for shared content when app resumes + if (state == AppLifecycleState.resumed) { + _checkForSharedContent(); + } + } + + Future _checkForSharedContent() async { + final sharedText = await _shareIntentService.getSharedText(); + + if (sharedText != null && mounted) { + // Validate it's a Google Maps link + if (_shareIntentService.isGoogleMapsLink(sharedText)) { + final metadata = _shareIntentService.extractMetadata(sharedText); + _handleSharedMapsLink(metadata); + } + } + } + + void _handleSharedMapsLink(MapsLinkMetadata metadata) { + // Navigate to the appropriate page or show a dialog + // You can customize this based on your app's flow + + // Option 1: Navigate to collections page with data + _navigatorKey.currentState?.pushNamed( + CollectionsPage.routeName, + arguments: metadata, + ); + + // Option 2: Show a dialog to select collection and save + // This would be implemented based on your UI requirements + } + @override Widget build(BuildContext context) { return MaterialApp( + navigatorKey: _navigatorKey, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), diff --git a/lib/pages/collections_page.dart b/lib/pages/collections_page.dart index dcb633d..403b7b5 100644 --- a/lib/pages/collections_page.dart +++ b/lib/pages/collections_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import '../model/collection.dart'; import '../service/bookmarks_provider.dart'; +import '../service/share_intent_service.dart'; import '../service/storage.dart'; import '../widgets/create_bookmark_collection_dialog.dart'; import 'bookmarks_page.dart'; @@ -54,4 +55,18 @@ class _CollectionsPageState extends State { }, ); } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // Check if we received shared data + final metadata = + ModalRoute.of(context)?.settings.arguments as MapsLinkMetadata?; + if (metadata != null) { + // Handle the shared link + WidgetsBinding.instance.addPostFrameCallback((_) { + // _showSaveBookmarkDialog(metadata); + }); + } + } } diff --git a/lib/service/maps_launcher_service.dart b/lib/service/maps_launcher_service.dart new file mode 100644 index 0000000..4968fdc --- /dev/null +++ b/lib/service/maps_launcher_service.dart @@ -0,0 +1,79 @@ +// service/maps_launcher_service.dart +import 'dart:io' show Platform; +import 'package:android_intent_plus/android_intent.dart'; + +class MapsLauncherService { + /// Opens a URL in Google Maps app + /// Falls back to browser if Maps app is not installed + static Future openInGoogleMaps(String url) async { + if (!Platform.isAndroid) { + // Handle iOS or other platforms if needed + return false; + } + + try { + // Try to open in Google Maps app + final intent = AndroidIntent( + action: 'action_view', + data: url, + package: 'com.google.android.apps.maps', + ); + + await intent.launch(); + return true; + } catch (e) { + // Google Maps not installed, try browser as fallback + try { + final browserIntent = AndroidIntent(action: 'action_view', data: url); + await browserIntent.launch(); + return true; + } catch (e) { + print('Failed to open maps link: $e'); + return false; + } + } + } + + /// Opens navigation to specific coordinates + static Future navigateToCoordinates( + String latitude, + String longitude, + ) async { + final url = 'google.navigation:q=$latitude,$longitude'; + return openInGoogleMaps(url); + } + + /// Opens a search query in Google Maps + static Future searchInMaps(String query) async { + final encodedQuery = Uri.encodeComponent(query); + final url = 'geo:0,0?q=$encodedQuery'; + return openInGoogleMaps(url); + } + + /// Shares a Google Maps link or location via Android share sheet + static Future shareLocation({ + required String text, + String? subject, + }) async { + if (!Platform.isAndroid) { + return false; + } + + try { + final intent = AndroidIntent( + action: 'action_send', + type: 'text/plain', + arguments: { + 'android.intent.extra.TEXT': text, + if (subject != null) 'android.intent.extra.SUBJECT': subject, + }, + ); + + await intent.launch(); + return true; + } catch (e) { + print('Failed to share location: $e'); + return false; + } + } +} diff --git a/lib/service/share_intent_service.dart b/lib/service/share_intent_service.dart new file mode 100644 index 0000000..9e26a17 --- /dev/null +++ b/lib/service/share_intent_service.dart @@ -0,0 +1,116 @@ +import 'package:flutter/services.dart'; + +class ShareIntentService { + static const _platform = MethodChannel('app.channel.shared.data'); + static ShareIntentService? _instance; + + // Singleton pattern + factory ShareIntentService() { + _instance ??= ShareIntentService._internal(); + return _instance!; + } + + ShareIntentService._internal(); + + /// Retrieves shared text from the platform channel + /// Returns null if no shared text is available + Future getSharedText() async { + try { + final String? sharedText = await _platform.invokeMethod('getSharedText'); + return sharedText; + } on PlatformException catch (e) { + // Log error in production app + print('Failed to get shared text: ${e.message}'); + return null; + } + } + + /// Checks if the text is a Google Maps link + bool isGoogleMapsLink(String text) { + return text.contains('maps.google.com') || + text.contains('maps.app.goo.gl') || + text.contains('goo.gl/maps'); + } + + /// Extracts metadata from a Google Maps link + MapsLinkMetadata extractMetadata(String mapsLink) { + String? placeName; + String? latitude; + String? longitude; + String? address; + + // Extract place name from URL + final placeMatch = RegExp(r'/place/([^/]+)').firstMatch(mapsLink); + if (placeMatch != null) { + placeName = Uri.decodeComponent( + placeMatch.group(1)!, + ).replaceAll('+', ' '); + } + + // Extract coordinates + final coordMatch = RegExp( + r'@(-?\d+\.\d+),(-?\d+\.\d+)', + ).firstMatch(mapsLink); + if (coordMatch != null) { + latitude = coordMatch.group(1); + longitude = coordMatch.group(2); + } + + // Extract search query/address + final searchMatch = RegExp(r'/search/([^/?]+)').firstMatch(mapsLink); + if (searchMatch != null) { + address = Uri.decodeComponent(searchMatch.group(1)!).replaceAll('+', ' '); + // Use address as place name if name not found + placeName ??= address; + } + + // Extract from data parameter (alternative format) + final dataMatch = RegExp(r'[?&]q=([^&]+)').firstMatch(mapsLink); + if (dataMatch != null && placeName == null) { + final query = Uri.decodeComponent(dataMatch.group(1)!); + // Check if it's coordinates + final coordPattern = RegExp(r'^(-?\d+\.\d+),(-?\d+\.\d+)$'); + final coords = coordPattern.firstMatch(query); + if (coords != null) { + latitude ??= coords.group(1); + longitude ??= coords.group(2); + } else { + placeName = query.replaceAll('+', ' '); + } + } + + return MapsLinkMetadata( + url: mapsLink, + placeName: placeName ?? 'Unknown Location', + latitude: latitude, + longitude: longitude, + address: address, + ); + } +} + +/// Data class for Google Maps link metadata +class MapsLinkMetadata { + final String url; + final String placeName; + final String? latitude; + final String? longitude; + final String? address; + + const MapsLinkMetadata({ + required this.url, + required this.placeName, + this.latitude, + this.longitude, + this.address, + }); + + String get coordinates { + if (latitude != null && longitude != null) { + return '$latitude, $longitude'; + } + return ''; + } + + bool get hasCoordinates => latitude != null && longitude != null; +}