added collections page
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
|
// main.dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'pages/bookmarks_page.dart';
|
import 'pages/bookmarks_page.dart';
|
||||||
import 'pages/collections_page.dart';
|
import 'pages/collections_page.dart';
|
||||||
import 'service/storage.dart';
|
import 'service/storage.dart';
|
||||||
|
import 'service/share_intent_service.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -9,12 +11,70 @@ void main() async {
|
|||||||
runApp(const MapsBookmarks());
|
runApp(const MapsBookmarks());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapsBookmarks extends StatelessWidget {
|
class MapsBookmarks extends StatefulWidget {
|
||||||
const MapsBookmarks({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
navigatorKey: _navigatorKey,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import '../model/collection.dart';
|
import '../model/collection.dart';
|
||||||
import '../service/bookmarks_provider.dart';
|
import '../service/bookmarks_provider.dart';
|
||||||
|
import '../service/share_intent_service.dart';
|
||||||
import '../service/storage.dart';
|
import '../service/storage.dart';
|
||||||
import '../widgets/create_bookmark_collection_dialog.dart';
|
import '../widgets/create_bookmark_collection_dialog.dart';
|
||||||
import 'bookmarks_page.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