added collections page
This commit is contained in:
@@ -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<MapsBookmarks> createState() => _MapsBookmarksState();
|
||||
}
|
||||
|
||||
class _MapsBookmarksState extends State<MapsBookmarks>
|
||||
with WidgetsBindingObserver {
|
||||
final ShareIntentService _shareIntentService = ShareIntentService();
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
@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<void> _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),
|
||||
),
|
||||
|
||||
@@ -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<CollectionsPage> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
lib/service/maps_launcher_service.dart
Normal file
79
lib/service/maps_launcher_service.dart
Normal file
@@ -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<bool> 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<bool> 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<bool> 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<bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
116
lib/service/share_intent_service.dart
Normal file
116
lib/service/share_intent_service.dart
Normal file
@@ -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<String?> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user