Compare commits
10 Commits
2d93d1a9d7
...
68a2a31d07
| Author | SHA1 | Date | |
|---|---|---|---|
| 68a2a31d07 | |||
| baf664a3ad | |||
| 6d51ef9ad0 | |||
| eddf2acbde | |||
| 7211560b8d | |||
| db61809939 | |||
| be171aa4bc | |||
| ca27cfeb96 | |||
| 8bdf036c1a | |||
| 6fb873163b |
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<application
|
<application
|
||||||
android:label="maps_bookmarks"
|
android:label="maps_bookmarks"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
@@ -6,7 +7,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTask"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
@@ -46,5 +47,10 @@
|
|||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain"/>
|
||||||
</intent>
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent>
|
||||||
|
<package android:name="com.google.android.apps.maps" />
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,27 +1,73 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'pages/bookmarks_page.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'pages/collections_page.dart';
|
import 'pages/collection_page.dart';
|
||||||
|
import 'pages/collections_list_page.dart';
|
||||||
|
import 'service/shared_link_provider.dart';
|
||||||
import 'service/storage.dart';
|
import 'service/storage.dart';
|
||||||
|
import 'service/share_intent_service.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Storage.initialize();
|
await Storage.initialize();
|
||||||
runApp(const MapsBookmarks());
|
runApp(
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => SharedLinkProvider(),
|
||||||
|
child: 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 GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
final ShareIntentService _shareIntentService = ShareIntentService();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
_checkForSharedContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
_checkForSharedContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkForSharedContent() async {
|
||||||
|
final sharedText = await _shareIntentService.getSharedMapsLink();
|
||||||
|
|
||||||
|
if (sharedText.isNotEmpty && mounted) {
|
||||||
|
context.read<SharedLinkProvider>().setCurrentMapsLink(sharedText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: ThemeData(
|
navigatorKey: _navigatorKey,
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
theme: lightTheme,
|
||||||
),
|
darkTheme: darkTheme,
|
||||||
initialRoute: CollectionsPage.routeName,
|
initialRoute: CollectionsListPage.routeName,
|
||||||
routes: {
|
routes: {
|
||||||
CollectionsPage.routeName: (context) => const CollectionsPage(),
|
CollectionsListPage.routeName: (context) => const CollectionsListPage(),
|
||||||
BookmarksPage.routeName: (context) => const BookmarksPage(),
|
CollectionPage.routeName: (context) => const CollectionPage(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
26
lib/model/maps_link_metadata.dart
Normal file
26
lib/model/maps_link_metadata.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
class MapsLinkMetadata {
|
||||||
|
final String url;
|
||||||
|
final String placeName;
|
||||||
|
final String latitude;
|
||||||
|
final String longitude;
|
||||||
|
final String address;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
const MapsLinkMetadata({
|
||||||
|
required this.url,
|
||||||
|
this.placeName = '',
|
||||||
|
this.latitude = '',
|
||||||
|
this.longitude = '',
|
||||||
|
this.address = '',
|
||||||
|
this.description = '',
|
||||||
|
});
|
||||||
|
|
||||||
|
String get coordinates {
|
||||||
|
if (hasCoordinates) {
|
||||||
|
return '$latitude, $longitude';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get hasCoordinates => latitude.isNotEmpty && longitude.isNotEmpty;
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../service/bookmarks_provider.dart';
|
|
||||||
import '../service/storage.dart';
|
|
||||||
|
|
||||||
class BookmarksPage extends StatelessWidget {
|
|
||||||
const BookmarksPage({super.key});
|
|
||||||
|
|
||||||
static const String routeName = '/bookmarks';
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (BookmarksProvider.selectedCollectionId == null) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
final bookmarks = Storage.loadBookmarksForCollection(
|
|
||||||
BookmarksProvider.selectedCollectionId!,
|
|
||||||
);
|
|
||||||
|
|
||||||
BookmarksProvider.selectedCollectionId == null;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(
|
|
||||||
Storage.loadCollections()
|
|
||||||
.firstWhere((c) => c.id == BookmarksProvider.selectedCollectionId)
|
|
||||||
.name,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
children: bookmarks.map((e) => ListTile(title: Text(e.name))).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
64
lib/pages/collection_page.dart
Normal file
64
lib/pages/collection_page.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../model/bookmark.dart';
|
||||||
|
import '../service/bookmarks_provider.dart';
|
||||||
|
import '../service/storage.dart';
|
||||||
|
import '../service/url_launcher.dart';
|
||||||
|
import '../widgets/create_bookmark_dialog.dart';
|
||||||
|
|
||||||
|
class CollectionPage extends StatefulWidget {
|
||||||
|
const CollectionPage({super.key});
|
||||||
|
|
||||||
|
static const String routeName = '/bookmarks';
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CollectionPage> createState() => _CollectionPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CollectionPageState extends State<CollectionPage> {
|
||||||
|
void onAddButtonPressed() => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CreateBookmarkDialog(
|
||||||
|
collectionId: BookmarksProvider.selectedCollectionId!,
|
||||||
|
onSavePressed: onBookmarkSaved,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void onBookmarkSaved(Bookmark bookmark) {
|
||||||
|
Storage.addBookmark(bookmark);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget bookmarkListBuilder(BuildContext context, Bookmark bookmark) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(bookmark.name),
|
||||||
|
onTap: () => launchUrlFromString(bookmark.link),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (BookmarksProvider.selectedCollectionId == null) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
final bookmarks = Storage.loadBookmarksForCollection(
|
||||||
|
BookmarksProvider.selectedCollectionId!,
|
||||||
|
);
|
||||||
|
final collection = Storage.loadCollections().firstWhere(
|
||||||
|
(c) => c.id == BookmarksProvider.selectedCollectionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(collection.name)),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
bookmarkListBuilder(context, bookmarks.elementAt(index)),
|
||||||
|
itemCount: bookmarks.length,
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: onAddButtonPressed,
|
||||||
|
child: Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,27 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../model/collection.dart';
|
import '../model/collection.dart';
|
||||||
import '../service/bookmarks_provider.dart';
|
import '../service/bookmarks_provider.dart';
|
||||||
|
import '../service/shared_link_provider.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 'collection_page.dart';
|
||||||
|
|
||||||
class CollectionsPage extends StatefulWidget {
|
class CollectionsListPage extends StatefulWidget {
|
||||||
const CollectionsPage({super.key});
|
const CollectionsListPage({super.key});
|
||||||
static const String routeName = '/collections';
|
static const String routeName = '/collections';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CollectionsPage> createState() => _CollectionsPageState();
|
State<CollectionsListPage> createState() => _CollectionsListPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CollectionsPageState extends State<CollectionsPage> {
|
class _CollectionsListPageState extends State<CollectionsListPage> {
|
||||||
final collections = Storage.loadCollections();
|
final collections = Storage.loadCollections();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final provider = context.watch<SharedLinkProvider>();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(),
|
appBar: AppBar(),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
@@ -29,6 +32,22 @@ class _CollectionsPageState extends State<CollectionsPage> {
|
|||||||
itemBuilder: itemBuilder,
|
itemBuilder: itemBuilder,
|
||||||
itemCount: collections.length,
|
itemCount: collections.length,
|
||||||
),
|
),
|
||||||
|
bottomSheet: provider.currentMapsLinkMetadata == null
|
||||||
|
? null
|
||||||
|
: BottomSheet(onClosing: () {}, builder: bottomSheetBuilder),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget bottomSheetBuilder(BuildContext context) {
|
||||||
|
final titleTextFieldController = TextEditingController(
|
||||||
|
text: context
|
||||||
|
.read<SharedLinkProvider>()
|
||||||
|
.currentMapsLinkMetadata!
|
||||||
|
.placeName,
|
||||||
|
);
|
||||||
|
return SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: TextField(controller: titleTextFieldController),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +69,13 @@ class _CollectionsPageState extends State<CollectionsPage> {
|
|||||||
title: Text(collection.name),
|
title: Text(collection.name),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
BookmarksProvider.selectedCollectionId = collection.id;
|
BookmarksProvider.selectedCollectionId = collection.id;
|
||||||
Navigator.pushNamed(context, BookmarksPage.routeName);
|
Navigator.pushNamed(context, CollectionPage.routeName);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/service/share_intent_service.dart
Normal file
25
lib/service/share_intent_service.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class ShareIntentService {
|
||||||
|
factory ShareIntentService() {
|
||||||
|
_instance ??= ShareIntentService._internal();
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShareIntentService._internal();
|
||||||
|
|
||||||
|
static ShareIntentService? _instance;
|
||||||
|
static const _platform = MethodChannel('app.channel.shared.data');
|
||||||
|
|
||||||
|
Future<String> getSharedMapsLink() async {
|
||||||
|
final String? sharedText = await _platform.invokeMethod('getSharedText');
|
||||||
|
if (sharedText != null && _isGoogleMapsLink(sharedText)) return sharedText;
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isGoogleMapsLink(String text) {
|
||||||
|
return text.contains('maps.google.com') ||
|
||||||
|
text.contains('maps.app.goo.gl') ||
|
||||||
|
text.contains('goo.gl/maps');
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/service/shared_link_provider.dart
Normal file
19
lib/service/shared_link_provider.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:metadata_fetch/metadata_fetch.dart';
|
||||||
|
|
||||||
|
import '../model/maps_link_metadata.dart';
|
||||||
|
|
||||||
|
class SharedLinkProvider extends ChangeNotifier {
|
||||||
|
MapsLinkMetadata? _currentMapsLinkMetadata;
|
||||||
|
|
||||||
|
void setCurrentMapsLink(String mapsLink) async {
|
||||||
|
final metadata = await MetadataFetch.extract(mapsLink);
|
||||||
|
_currentMapsLinkMetadata = MapsLinkMetadata(
|
||||||
|
url: mapsLink,
|
||||||
|
placeName: metadata?.title ?? '',
|
||||||
|
);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
MapsLinkMetadata? get currentMapsLinkMetadata => _currentMapsLinkMetadata;
|
||||||
|
}
|
||||||
8
lib/service/url_launcher.dart
Normal file
8
lib/service/url_launcher.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
Future<void> launchUrlFromString(String url) async {
|
||||||
|
final Uri uri = Uri.parse(url);
|
||||||
|
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
|
||||||
|
throw Exception('Could not launch $url');
|
||||||
|
}
|
||||||
|
}
|
||||||
15
lib/theme.dart
Normal file
15
lib/theme.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
ThemeData get lightTheme => ThemeData.from(colorScheme: _lightColorScheme);
|
||||||
|
|
||||||
|
ThemeData get darkTheme => ThemeData.from(colorScheme: _darkColorScheme);
|
||||||
|
|
||||||
|
ColorScheme get _darkColorScheme => ColorScheme.fromSeed(
|
||||||
|
seedColor: Colors.deepPurple,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
);
|
||||||
|
|
||||||
|
ColorScheme get _lightColorScheme => ColorScheme.fromSeed(
|
||||||
|
seedColor: Colors.deepPurple,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
);
|
||||||
102
lib/widgets/create_bookmark_dialog.dart
Normal file
102
lib/widgets/create_bookmark_dialog.dart
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../model/bookmark.dart';
|
||||||
|
|
||||||
|
class CreateBookmarkDialog extends StatelessWidget {
|
||||||
|
const CreateBookmarkDialog({
|
||||||
|
super.key,
|
||||||
|
required this.collectionId,
|
||||||
|
required this.onSavePressed,
|
||||||
|
this.selectedBookmark,
|
||||||
|
});
|
||||||
|
final void Function(Bookmark bookmark) onSavePressed;
|
||||||
|
final int collectionId;
|
||||||
|
final Bookmark? selectedBookmark;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final nameController = TextEditingController();
|
||||||
|
final linkController = TextEditingController();
|
||||||
|
final descriptionController = TextEditingController();
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('Create Bookmark'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(padding: EdgeInsetsGeometry.only(top: 10)),
|
||||||
|
TextField(
|
||||||
|
controller: nameController,
|
||||||
|
autofocus: true,
|
||||||
|
maxLines: 1,
|
||||||
|
maxLength: 50,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9äöüÄÖÜß\s]'),
|
||||||
|
),
|
||||||
|
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Bookmark Title',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: linkController,
|
||||||
|
maxLines: 1,
|
||||||
|
maxLength: 50,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9äöüÄÖÜß\s/:\.]'),
|
||||||
|
),
|
||||||
|
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Url',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: descriptionController,
|
||||||
|
maxLines: 3,
|
||||||
|
maxLength: 300,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9äöüÄÖÜß\s]'),
|
||||||
|
),
|
||||||
|
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Description',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
onSavePressed(
|
||||||
|
Bookmark(
|
||||||
|
collectionId: collectionId,
|
||||||
|
name: nameController.text,
|
||||||
|
link: linkController.text,
|
||||||
|
description: descriptionController.text,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Icon(Icons.save),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
154
pubspec.lock
154
pubspec.lock
@@ -49,6 +49,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -104,6 +112,30 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.6"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -156,10 +188,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.17.0"
|
||||||
|
metadata_fetch:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: metadata_fetch
|
||||||
|
sha256: "24a713eaddbebea3dc3036a6c1d6f7c57e187fff5f0ef07be3e3ebbb7820c3e7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
|
nested:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nested
|
||||||
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -208,6 +256,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: provider
|
||||||
|
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.5+1"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -220,18 +276,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
|
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.12"
|
version: "2.4.18"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "2.5.6"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -301,6 +357,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
string_validator:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_validator
|
||||||
|
sha256: "240f4c98027dfbe8639c8271ef18cc9de735b47067aa15a720cfed9576a512b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -313,10 +377,82 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.7"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.2"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.28"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.6"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.5"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.5"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -351,4 +487,4 @@ packages:
|
|||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.2 <4.0.0"
|
dart: ">=3.9.2 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.35.0"
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
android_intent_plus: ^6.0.0
|
android_intent_plus: ^6.0.0
|
||||||
shared_preferences: ^2.3.2
|
shared_preferences: ^2.3.2
|
||||||
|
provider: ^6.1.5+1
|
||||||
|
metadata_fetch: ^0.4.2
|
||||||
|
url_launcher: ^6.3.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user