35 Commits

Author SHA1 Message Date
b4016e6e5b localized dialog
All checks were successful
Flutter APK Build / Build Flutter APK (pull_request) Successful in 7m28s
2026-01-22 18:23:05 +01:00
eae4a853e9 removed unnecessary code 2026-01-22 18:20:08 +01:00
8eb4cadc85 Merge pull request 'Json export and import feature' (#5) from json-export-feature into development
Reviewed-on: #5
2026-01-22 18:12:01 +01:00
06c5ca9910 added minimal error handling and user feedback 2026-01-22 18:09:34 +01:00
27c3804b1e removed path provider 2026-01-22 17:52:18 +01:00
debf960d70 simple working json import and export 2026-01-22 17:51:50 +01:00
1029bad20f added localization for settings 2026-01-22 17:16:37 +01:00
cef23a1c83 added constant global values 2026-01-22 17:00:23 +01:00
c4fe32e4b1 replaced file_picker with file_selector 2026-01-22 16:58:33 +01:00
893a1b558f added file picker 2026-01-22 16:44:43 +01:00
b0eebb5ee8 added permission service 2026-01-22 16:28:05 +01:00
56daf1b940 added localization and permission error snackbar 2026-01-22 16:27:41 +01:00
8687b7788b updated gitignore to ignore generated localization files 2026-01-22 16:27:02 +01:00
eeae1d919e updated gitignore 2026-01-22 16:21:39 +01:00
d02684bb84 requested storage permission 2026-01-22 16:19:46 +01:00
ea961da678 added permisson_handler package 2026-01-22 16:19:33 +01:00
632da54311 added error texts 2026-01-22 16:19:20 +01:00
bc20593661 added button to navigate to settings 2026-01-22 14:55:46 +01:00
045f8b5b6b added localization for settings 2026-01-22 14:55:31 +01:00
81f7b45619 added settings page 2026-01-22 14:42:07 +01:00
3032e13dc9 Added dismiss button
Some checks failed
Flutter APK Build / Build Flutter APK (pull_request) Has been cancelled
2026-01-21 16:37:03 +01:00
d0492b2f79 changed snackbar styling 2026-01-21 16:31:43 +01:00
c2506fab7a refactored code so change in bookmark count is visible immediately 2026-01-21 16:30:30 +01:00
c80606b7d0 changed appearance of lists
All checks were successful
Flutter APK Build / Build Flutter APK (pull_request) Successful in 6m44s
2026-01-21 15:04:24 +01:00
be6020d6c5 added label text 2026-01-21 14:44:36 +01:00
5eb58d7cf2 Changed theme to globally enable rounded borders for textfields 2026-01-21 14:43:02 +01:00
306a38a36a revert workflow to a working state 2026-01-21 14:30:25 +01:00
99a8aaa409 added localizations
Some checks failed
Flutter APK Build / Calculate Version (pull_request) Successful in 15s
Flutter APK Build / Create Release (pull_request) Has been cancelled
Flutter APK Build / Build Flutter APK (pull_request) Has been cancelled
2026-01-21 14:11:50 +01:00
85d57c6e1c changed workflow to support pull requests 2026-01-21 14:06:06 +01:00
77a647d17d [fix] Added basic locatlization
All checks were successful
Flutter APK Build / Calculate Version (pull_request) Successful in 14s
Flutter APK Build / Build Flutter APK (pull_request) Successful in 6m35s
Flutter APK Build / Create Release (pull_request) Has been skipped
2026-01-21 13:50:24 +01:00
cf88a9a371 added bookmark count number in collections view
All checks were successful
Flutter APK Build / Calculate Version (pull_request) Successful in 12s
Flutter APK Build / Build Flutter APK (pull_request) Successful in 6m38s
Flutter APK Build / Create Release (pull_request) Has been skipped
2026-01-21 13:19:45 +01:00
5feb535cf3 changed app version 2026-01-21 13:19:13 +01:00
2d23207497 added automatic versioning in workflow 2026-01-21 12:40:52 +01:00
c7c5b3682d added functionality to clear search text
All checks were successful
Flutter APK Build / Build Flutter APK (push) Successful in 6m38s
2026-01-21 12:27:52 +01:00
321a310add added function to remove search text 2026-01-21 12:16:37 +01:00
27 changed files with 771 additions and 138 deletions

View File

@@ -23,13 +23,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
# - name: Cache pub deps
# uses: actions/cache@v3
# with:
# path: ~/.pub-cache
# key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }}
# restore-keys: ${{ runner.os }}-pub-
- name: Setup Java (Temurin 17) - name: Setup Java (Temurin 17)
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
@@ -66,7 +59,6 @@ jobs:
env: env:
GITEA_TOKEN: ${{ secrets.RUNNER_CREATE_RELEASE }} GITEA_TOKEN: ${{ secrets.RUNNER_CREATE_RELEASE }}
with: with:
# tag & name: adjust to your versioning
tag_name: "v0.1.${{ github.run_number }}" tag_name: "v0.1.${{ github.run_number }}"
name: "Flutter Android v0.1.${{ github.run_number }}" name: "Flutter Android v0.1.${{ github.run_number }}"
body: "Automated build from CI" body: "Automated build from CI"

155
.gitignore vendored
View File

@@ -1,16 +1,23 @@
# Do not remove or rename entries in this file, only add new ones
# See https://github.com/flutter/flutter/issues/128635 for more context.
# Miscellaneous # Miscellaneous
*.class *.class
*.lock
*.log *.log
*.pyc *.pyc
*.swp *.swp
.DS_Store .DS_Store
.atom/ .atom/
.build/
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
.swiftpm/ lib/l10n/app_localizations*
migrate_working_dir/
# As packages are no longer pinned, we use a lockfile for testing locally.
# When unpinning packages, Using lockfiles ensures that failures in PRs are
# actually due to those PRs, not due to a package being updated.
!/pubspec.lock
# IntelliJ related # IntelliJ related
*.iml *.iml
@@ -18,28 +25,142 @@ migrate_working_dir/
*.iws *.iws
.idea/ .idea/
# The .vscode folder contains launch configuration and tasks you configure in # Visual Studio Code related
# VS Code which you may wish to be included in version control, so this line .classpath
# is commented out by default. .project
#.vscode/ .settings/
.vscode/*
.ccls-cache
# This file, on the master branch, should never exist or be checked-in.
#
# On a *final* release branch, that is, what will ship to stable or beta, the
# file can be force added (git add --force) and checked-in in order to effectively
# "pin" the engine artifact version so the flutter tool does not need to use git
# to determine the engine artifacts.
#
# See https://github.com/flutter/flutter/blob/main/docs/tool/Engine-artifacts.md.
/bin/internal/engine.version
# Flutter repo-specific
/bin/cache/
/bin/internal/bootstrap.bat
/bin/internal/bootstrap.sh
/bin/internal/engine.realm
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/devicelab/ABresults*.json
/dev/docs/doc/
/dev/docs/api_docs.zip
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/ .dart_tool/
.flutter-plugins-dependencies .flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-preload-cache/
.pub-cache/ .pub-cache/
.pub/ .pub/
/build/ build/
/coverage/ flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
# Symbolication related # Android related
**/android/**/gradle-wrapper.jar
.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks
local.properties
**/.cxx/
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/
# Windows
**/windows/flutter/ephemeral/
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Linux
**/linux/flutter/ephemeral/
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake
# Coverage
coverage/
# Symbols
app.*.symbols app.*.symbols
# Obfuscation related # Exceptions to above rules.
app.*.map.json !**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!.vscode/settings.json
# Android Studio will place build artifacts here # Monorepo
/android/app/debug .cipd
/android/app/profile .gclient
/android/app/release .gclient_entries
.python-version
.gclient_previous_custom_vars
.gclient_previous_sync_commits

View File

@@ -1,5 +1,6 @@
<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"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application <application
android:label="maps_bookmarks" android:label="maps_bookmarks"
android:name="${applicationName}" android:name="${applicationName}"

3
l10n.yaml Normal file
View File

@@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

View File

@@ -0,0 +1,3 @@
const String appName = 'Maps Bookmarks';
const String jsonFileName = 'MapsBookmarksData.json';
const String defaultAndroidExportDirectory = '/storage/emulated/0/Documents';

43
lib/l10n/app_de.arb Normal file
View File

@@ -0,0 +1,43 @@
{
"@@locale": "de",
"addToCollection": "Speichern in {collection_name}",
"@addToCollection": {
"placeholders": {
"collection_name" : {
"type": "String"
}
}
},
"cancel": "Abbrechen",
"chooseCollection": "Sammlung auswählen",
"collections": "Sammlungen",
"tipCreateCollections": "Erstelle deine erste Sammlung!",
"search": "Suche",
"createBookmark": "Lesezeichen erstellen",
"createCollection": "Sammlung erstellen",
"create": "Erstellen",
"delete": "Löschen",
"add": "Hinzufügen",
"startSearching": "Suche etwas",
"tipNoResults": "Keine Suchergebnisse gefunden",
"collectionName": "Name der Sammlung",
"bookmarkTitle": "Titel des Lesezeichens",
"url": "Url",
"description": "Beschreibung",
"settings": "Einstellungen",
"appData": "App-Daten",
"export": "Exportieren",
"import": "Importieren",
"activateJsonExport": "Immer als JSON speichern",
"@@comment": "Info",
"exportSuccess": "Daten exportiert",
"importSuccess": "Daten importiert",
"@@comment": "Errors",
"errorStoragePermisson": "Zugriff auf Speicher verwehrt",
"errorCouldNotLaunchUrl": "Konnte Url nicht öffnen",
"errorInvalidUrl": "Fehlerhafte Url",
"exportFailed": "Export fehlgeschlagen",
"importFailed": "Import fehlgeschlagen"
}

44
lib/l10n/app_en.arb Normal file
View File

@@ -0,0 +1,44 @@
{
"@@locale": "en",
"addToCollection": "Add to {collection_name}",
"@addToCollection": {
"placeholders": {
"collection_name" : {
"type": "String"
}
}
},
"cancel": "Cancel",
"chooseCollection": "Choose Collection",
"collections": "Collections",
"tipCreateCollections": "Create your first Collection to get started!",
"search": "Search",
"createBookmark": "Create Bookmark",
"createCollection": "Create Collection",
"create": "Create",
"delete": "Delete",
"add": "Add",
"startSearching": "Start searching",
"tipNoResults": "There are no results that match your search",
"collectionName": "Collection Name",
"bookmarkTitle": "Bookmark Title",
"url": "Url",
"description": "Description",
"settings": "Settings",
"appData": "App data",
"export": "Export",
"import": "Import",
"activateJsonExport": "Always save to JSON",
"@@comment": "Info",
"exportSuccess": "Exported data",
"importSuccess": "Imported data",
"@@comment": "Errors",
"errorStoragePermisson": "Storage permissions denied",
"errorCouldNotLaunchUrl": "Could not launch Url",
"errorInvalidUrl": "Invalid Url",
"exportFailed": "Export failed",
"importFailed": "Import failed"
}

View File

@@ -1,8 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'l10n/app_localizations.dart';
import 'pages/collection_page.dart'; import 'pages/collection_page.dart';
import 'pages/collections_list_page.dart'; import 'pages/collections_list_page.dart';
import 'pages/search_page.dart'; import 'pages/search_page.dart';
import 'pages/settings_page.dart';
import 'service/search_provider.dart'; import 'service/search_provider.dart';
import 'service/shared_link_provider.dart'; import 'service/shared_link_provider.dart';
import 'service/storage.dart'; import 'service/storage.dart';
@@ -66,6 +69,13 @@ class _MapsBookmarksState extends State<MapsBookmarks>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [Locale('en'), Locale('de')],
navigatorKey: _navigatorKey, navigatorKey: _navigatorKey,
theme: lightTheme, theme: lightTheme,
darkTheme: darkTheme, darkTheme: darkTheme,
@@ -74,6 +84,7 @@ class _MapsBookmarksState extends State<MapsBookmarks>
CollectionsListPage.routeName: (context) => const CollectionsListPage(), CollectionsListPage.routeName: (context) => const CollectionsListPage(),
CollectionPage.routeName: (context) => const CollectionPage(), CollectionPage.routeName: (context) => const CollectionPage(),
SearchPage.routeName: (context) => const SearchPage(), SearchPage.routeName: (context) => const SearchPage(),
SettingsPage.routeName: (context) => const SettingsPage(),
}, },
); );
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/app_localizations.dart';
import '../model/bookmark.dart'; import '../model/bookmark.dart';
import '../model/maps_link_metadata.dart'; import '../model/maps_link_metadata.dart';
import '../service/bookmarks_provider.dart'; import '../service/bookmarks_provider.dart';
@@ -89,20 +90,28 @@ class _CollectionPageState extends State<CollectionPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: selectedMapsLink != null title: selectedMapsLink != null
? Text('Add to ${collection.name}') ? Text(
AppLocalizations.of(context)!.addToCollection(collection.name),
)
: Text(collection.name), : Text(collection.name),
actions: [ actions: [
if (selectedMapsLink != null) if (selectedMapsLink != null)
TextButton( TextButton(
onPressed: () => provider.removeCurrentMapsLink(), onPressed: () => provider.removeCurrentMapsLink(),
child: Text('Cancel'), child: Text(AppLocalizations.of(context)!.cancel),
), ),
], ],
), ),
body: ListView.builder( body: Center(
itemBuilder: (context, index) => child: SizedBox(
bookmarksListItemBuilder(context, bookmarks.elementAt(index)), width: MediaQuery.of(context).size.width * 0.9,
itemCount: bookmarks.length, child: ListView.separated(
itemBuilder: (context, index) =>
bookmarksListItemBuilder(context, bookmarks.elementAt(index)),
itemCount: bookmarks.length,
separatorBuilder: (context, index) => SizedBox(height: 10),
),
),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: onAddButtonPressed, onPressed: onAddButtonPressed,

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/app_localizations.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/shared_link_provider.dart';
@@ -8,6 +9,7 @@ import '../service/storage.dart';
import '../widgets/create_bookmark_collection_dialog.dart'; import '../widgets/create_bookmark_collection_dialog.dart';
import 'collection_page.dart'; import 'collection_page.dart';
import 'search_page.dart' show SearchPage; import 'search_page.dart' show SearchPage;
import 'settings_page.dart';
class CollectionsListPage extends StatefulWidget { class CollectionsListPage extends StatefulWidget {
const CollectionsListPage({super.key}); const CollectionsListPage({super.key});
@@ -20,6 +22,7 @@ class CollectionsListPage extends StatefulWidget {
class _CollectionsListPageState extends State<CollectionsListPage> { class _CollectionsListPageState extends State<CollectionsListPage> {
bool addingNewBookmark = false; bool addingNewBookmark = false;
var bookmarkCountMap = <int, int>{};
Widget bottomSheetBuilder(BuildContext context) { Widget bottomSheetBuilder(BuildContext context) {
final titleTextFieldController = TextEditingController( final titleTextFieldController = TextEditingController(
@@ -53,6 +56,11 @@ class _CollectionsListPageState extends State<CollectionsListPage> {
title: Text(collection.name), title: Text(collection.name),
onTap: () => navigateToCollection(collection.id), onTap: () => navigateToCollection(collection.id),
onLongPress: () => onEditCollection(collection), onLongPress: () => onEditCollection(collection),
leading: const Icon(Icons.list_rounded),
trailing: Text(
bookmarkCountMap[collection.id]?.toString() ?? '0',
style: Theme.of(context).textTheme.bodyMedium,
),
); );
} }
@@ -77,25 +85,32 @@ class _CollectionsListPageState extends State<CollectionsListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final collections = Storage.loadCollections(); final collections = Storage.loadCollections();
final provider = context.watch<SharedLinkProvider>(); bookmarkCountMap = Storage.loadPerCollectionBookmarkCount();
addingNewBookmark = provider.currentMapsLinkMetadata != null; addingNewBookmark =
context.watch<SharedLinkProvider>().currentMapsLinkMetadata != null;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: addingNewBookmark title: addingNewBookmark
? Text('Choose Collection') ? Text(AppLocalizations.of(context)!.chooseCollection)
: Text('Collections'), : Text(AppLocalizations.of(context)!.collections),
actions: [ actions: [
if (addingNewBookmark) if (addingNewBookmark)
TextButton( TextButton(
onPressed: () => provider.removeCurrentMapsLink(), onPressed: () =>
child: Text('Cancel'), context.read<SharedLinkProvider>().removeCurrentMapsLink(),
child: Text(AppLocalizations.of(context)!.cancel),
) )
else else
IconButton( IconButton(
onPressed: () => onPressed: () =>
Navigator.of(context).pushNamed(SearchPage.routeName), Navigator.of(context).pushNamed(SearchPage.routeName),
icon: Icon(Icons.search), icon: Icon(Icons.search_rounded),
), ),
IconButton(
onPressed: () =>
Navigator.of(context).pushNamed(SettingsPage.routeName),
icon: Icon(Icons.settings_rounded),
),
], ],
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
@@ -103,14 +118,22 @@ class _CollectionsListPageState extends State<CollectionsListPage> {
child: Icon(Icons.add), child: Icon(Icons.add),
), ),
body: collections.isNotEmpty body: collections.isNotEmpty
? ListView.builder( ? Center(
itemBuilder: (context, index) => collectionsListItemBuilder( child: SizedBox(
context, width: MediaQuery.of(context).size.width * 0.9,
collections.elementAt(index), child: ListView.separated(
itemBuilder: (context, index) => collectionsListItemBuilder(
context,
collections.elementAt(index),
),
itemCount: collections.length,
separatorBuilder: (context, index) => SizedBox(height: 10),
),
), ),
itemCount: collections.length,
) )
: Center(child: Text('Create your first Collection to get started!')), : Center(
child: Text(AppLocalizations.of(context)!.tipCreateCollections),
),
); );
} }
} }

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/app_localizations.dart';
import '../service/search_provider.dart'; import '../service/search_provider.dart';
import '../widgets/search_widgets/search_bar_widget.dart'; import '../widgets/search_widgets/search_bar_widget.dart';
import '../widgets/search_widgets/search_results_widget.dart'; import '../widgets/search_widgets/search_results_widget.dart';
@@ -12,14 +13,22 @@ class SearchPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text('Search')), appBar: AppBar(title: Text(AppLocalizations.of(context)!.search)),
body: Column( body: Center(
children: [ child: SizedBox(
SearchBarWidget( width: MediaQuery.of(context).size.width * 0.9,
onEditingComplete: context.read<SearchProvider>().setSearchText, child: Column(
children: [
Padding(padding: EdgeInsetsGeometry.only(top: 10)),
SearchBarWidget(
onEditingComplete: context.read<SearchProvider>().setSearchText,
onResetSearch: context.read<SearchProvider>().removeSearchText,
),
Padding(padding: EdgeInsetsGeometry.only(top: 10)),
Expanded(child: SearchResultsWidget()),
],
), ),
Expanded(child: SearchResultsWidget()), ),
],
), ),
); );
} }

View File

@@ -0,0 +1,86 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import '../l10n/app_localizations.dart';
import '../service/notifying.dart';
import '../service/permission_service.dart';
import '../service/storage.dart';
class SettingsPage extends StatefulWidget {
static const routeName = '/settings';
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
body: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
AppLocalizations.of(context)!.appData,
style: Theme.of(context).textTheme.titleLarge,
),
ElevatedButton(
onPressed: () => onActivateJsonImportPressed(),
child: Text(AppLocalizations.of(context)!.import),
),
ElevatedButton(
onPressed: () => onActivateJsonExportPressed(),
child: Text(AppLocalizations.of(context)!.export),
),
],
),
),
);
}
void onActivateJsonExportPressed() async {
if (!await checkStoragePermission) return;
Storage.exportToJsonFile().then(showExportInfo);
}
void onActivateJsonImportPressed() async {
if (!await checkStoragePermission) return;
Storage.importFromJsonFile().then(showImportInfo);
}
Future<bool> get checkStoragePermission async {
if (!(await PermissionService.requestStoragePermission).isGranted) {
if (mounted) {
Notifying.showErrorSnackbar(
context,
AppLocalizations.of(context)!.errorStoragePermisson,
);
return false;
}
}
return true;
}
void showExportInfo(bool success) => Notifying.showSnackbar(
context,
text: success
? AppLocalizations.of(context)!.exportSuccess
: AppLocalizations.of(context)!.exportFailed,
isError: success,
);
void showImportInfo(bool success) => Notifying.showSnackbar(
context,
text: success
? AppLocalizations.of(context)!.importSuccess
: AppLocalizations.of(context)!.importFailed,
isError: success,
);
}

View File

@@ -0,0 +1,77 @@
import 'dart:convert';
import 'dart:io';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/services.dart';
import '../model/bookmark.dart';
import '../model/collection.dart';
import '../assets/constants.dart' as constants;
class JsonFileService {
static Future<bool> exportToJson({
required List<Collection> collections,
required List<Bookmark> bookmarks,
}) async {
try {
final dir = await _directoryPath;
if (dir.isEmpty) return false;
final data = {
'collections': collections.map((c) => c.toJson()).toList(),
'bookmarks': bookmarks.map((b) => b.toJson()).toList(),
};
final json = jsonEncode(data).codeUnits;
final file = XFile.fromData(
Uint8List.fromList(json),
mimeType: 'application/json',
name: constants.jsonFileName,
);
file.saveTo('$dir/${constants.jsonFileName}');
} catch (e) {
return false;
}
return false;
}
static Future<({List<Collection> collections, List<Bookmark> bookmarks})>
importFromJson() async {
try {
const typeGroup = XTypeGroup(label: 'json', extensions: <String>['json']);
final XFile? file = await openFile(
acceptedTypeGroups: <XTypeGroup>[typeGroup],
);
if (file == null) {
return (collections: <Collection>[], bookmarks: <Bookmark>[]);
}
final jsonString = await file.readAsString();
final data = jsonDecode(jsonString) as Map<String, dynamic>;
final collections = (data['collections'] as List<dynamic>? ?? [])
.map((json) => Collection.fromJson(json as Map<String, dynamic>))
.toList();
final bookmarks = (data['bookmarks'] as List<dynamic>? ?? [])
.map((json) => Bookmark.fromJson(json as Map<String, dynamic>))
.toList();
return (collections: collections, bookmarks: bookmarks);
} catch (e) {
return (collections: <Collection>[], bookmarks: <Bookmark>[]);
}
}
static Future<String> get _directoryPath async {
if (Platform.isAndroid) {
return await getDirectoryPath(
initialDirectory: constants.defaultAndroidExportDirectory,
) ??
'';
}
return await getDirectoryPath() ?? '';
}
}

View File

@@ -1,15 +1,9 @@
// service/maps_launcher_service.dart
import 'dart:io' show Platform; import 'dart:io' show Platform;
import 'package:android_intent_plus/android_intent.dart'; import 'package:android_intent_plus/android_intent.dart';
class MapsLauncherService { 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 { static Future<bool> openInGoogleMaps(String url) async {
if (!Platform.isAndroid) { if (!Platform.isAndroid) return false;
// Handle iOS or other platforms if needed
return false;
}
try { try {
// Try to open in Google Maps app // Try to open in Google Maps app
@@ -28,36 +22,16 @@ class MapsLauncherService {
await browserIntent.launch(); await browserIntent.launch();
return true; return true;
} catch (e) { } catch (e) {
print('Failed to open maps link: $e');
return false; 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({ static Future<bool> shareLocation({
required String text, required String text,
String? subject, String? subject,
}) async { }) async {
if (!Platform.isAndroid) { if (!Platform.isAndroid) return false;
return false;
}
try { try {
final intent = AndroidIntent( final intent = AndroidIntent(
@@ -72,7 +46,6 @@ class MapsLauncherService {
await intent.launch(); await intent.launch();
return true; return true;
} catch (e) { } catch (e) {
print('Failed to share location: $e');
return false; return false;
} }
} }

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../l10n/app_localizations.dart';
import 'url_launcher.dart' show UrlLaunchErrorCode; import 'url_launcher.dart' show UrlLaunchErrorCode;
class Notifying { class Notifying {
@@ -8,13 +9,31 @@ class Notifying {
required String text, required String text,
bool isError = false, bool isError = false,
}) { }) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( backgroundColor: Theme.of(context).colorScheme.error,
text, content: SizedBox(
style: isError height: 30,
? TextStyle(color: Theme.of(context).colorScheme.error) child: Row(
: null, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
text,
style: isError
? TextStyle(color: Theme.of(context).colorScheme.onError)
: null,
),
IconButton(
onPressed: () =>
ScaffoldMessenger.of(context).hideCurrentSnackBar(),
icon: Icon(
Icons.close_rounded,
color: Theme.of(context).colorScheme.onError,
),
),
],
),
), ),
), ),
); );
@@ -28,10 +47,18 @@ class Notifying {
if (errorCode == UrlLaunchErrorCode.none) { if (errorCode == UrlLaunchErrorCode.none) {
return; return;
} else if (errorCode == UrlLaunchErrorCode.couldNotLaunch) { } else if (errorCode == UrlLaunchErrorCode.couldNotLaunch) {
errorText = 'Could not launch Url'; errorText = AppLocalizations.of(context)!.errorCouldNotLaunchUrl;
} else { } else {
errorText = 'Invalid Url'; errorText = AppLocalizations.of(context)!.errorInvalidUrl;
} }
showSnackbar(context, text: errorText, isError: true); showSnackbar(context, text: errorText, isError: true);
} }
static void showErrorSnackbar(BuildContext context, String message) {
showSnackbar(context, text: message, isError: true);
}
static void showMessageSnackbar(BuildContext context, String message) {
showSnackbar(context, text: message, isError: false);
}
} }

View File

@@ -0,0 +1,9 @@
import 'package:permission_handler/permission_handler.dart';
class PermissionService {
static Future<PermissionStatus> get storagePermissionStatus =>
Permission.manageExternalStorage.status;
static Future<PermissionStatus> get requestStoragePermission =>
Permission.manageExternalStorage.request();
}

View File

@@ -8,5 +8,10 @@ class SearchProvider extends ChangeNotifier {
if (!silent) notifyListeners(); if (!silent) notifyListeners();
} }
void removeSearchText({bool silent = false}) {
_searchText = '';
if (!silent) notifyListeners();
}
String get searchText => _searchText; String get searchText => _searchText;
} }

View File

@@ -4,6 +4,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../model/bookmark.dart'; import '../model/bookmark.dart';
import '../model/collection.dart'; import '../model/collection.dart';
import 'json_file_service.dart';
class Storage { class Storage {
static const String _bookmarksKey = 'bookmarks'; static const String _bookmarksKey = 'bookmarks';
@@ -50,6 +51,14 @@ class Storage {
return allBookmarks.where((b) => b.collectionId == collectionId).toList(); return allBookmarks.where((b) => b.collectionId == collectionId).toList();
} }
static Map<int, int> loadPerCollectionBookmarkCount() {
return loadBookmarks().fold(<int, int>{}, (map, bookmark) {
map[bookmark.collectionId] ??= 0;
map[bookmark.collectionId] = map[bookmark.collectionId]! + 1;
return map;
});
}
static Future<void> addBookmark(Bookmark bookmark) async { static Future<void> addBookmark(Bookmark bookmark) async {
final bookmarks = loadBookmarks(); final bookmarks = loadBookmarks();
bookmarks.add(bookmark); bookmarks.add(bookmark);
@@ -155,4 +164,20 @@ class Storage {
} }
return _prefsWithCache!; return _prefsWithCache!;
} }
static Future<bool> exportToJsonFile() => JsonFileService.exportToJson(
collections: loadCollections(),
bookmarks: loadBookmarks(),
);
static Future<bool> importFromJsonFile() async {
final import = await JsonFileService.importFromJson();
if (import.bookmarks.isNotEmpty || import.collections.isNotEmpty) {
saveBookmarks(import.bookmarks);
saveCollections(import.collections);
return true;
}
return false;
}
} }

View File

@@ -1,15 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
ThemeData get lightTheme => ThemeData.from(colorScheme: _lightColorScheme); const _seed = Colors.deepPurple;
ThemeData get darkTheme => ThemeData.from(colorScheme: _darkColorScheme); ColorScheme get _lightColorScheme =>
ColorScheme.fromSeed(seedColor: _seed, brightness: Brightness.light);
ColorScheme get _darkColorScheme => ColorScheme.fromSeed( ColorScheme get _darkColorScheme =>
seedColor: Colors.deepPurple, ColorScheme.fromSeed(seedColor: _seed, brightness: Brightness.dark);
brightness: Brightness.dark,
);
ColorScheme get _lightColorScheme => ColorScheme.fromSeed( ThemeData get lightTheme => _baseTheme(_lightColorScheme);
seedColor: Colors.deepPurple,
brightness: Brightness.light, ThemeData get darkTheme => _baseTheme(_darkColorScheme);
);
ThemeData _baseTheme(ColorScheme scheme) =>
ThemeData.from(colorScheme: scheme, useMaterial3: true).copyWith(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
),
listTileTheme: ListTileThemeData(
tileColor: scheme.surfaceContainer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadiusGeometry.circular(12),
),
),
);

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../l10n/app_localizations.dart';
import '../model/collection.dart'; import '../model/collection.dart';
import 'edit_dialog_widgets/edit_dialog_actions.dart' show EditDialogActions; import 'edit_dialog_widgets/edit_dialog_actions.dart' show EditDialogActions;
import 'edit_dialog_widgets/edit_dialog_title.dart'; import 'edit_dialog_widgets/edit_dialog_title.dart';
@@ -40,9 +41,7 @@ class CreateBookmarkCollectionDialog extends StatelessWidget {
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')), FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
], ],
decoration: InputDecoration( decoration: InputDecoration(
// TODO: Localize labelText: AppLocalizations.of(context)!.collectionName,
labelText: 'Collection Name',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
), ),
), ),
actions: [ actions: [

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../l10n/app_localizations.dart';
import '../model/bookmark.dart'; import '../model/bookmark.dart';
import '../model/maps_link_metadata.dart'; import '../model/maps_link_metadata.dart';
import 'edit_dialog_widgets/edit_dialog_actions.dart'; import 'edit_dialog_widgets/edit_dialog_actions.dart';
@@ -59,10 +60,7 @@ class CreateBookmarkDialog extends StatelessWidget {
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')), FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
], ],
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Bookmark Title', labelText: AppLocalizations.of(context)!.bookmarkTitle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
), ),
), ),
TextField( TextField(
@@ -76,10 +74,7 @@ class CreateBookmarkDialog extends StatelessWidget {
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')), FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
], ],
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Url', labelText: AppLocalizations.of(context)!.url,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
), ),
), ),
TextField( TextField(
@@ -93,10 +88,7 @@ class CreateBookmarkDialog extends StatelessWidget {
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')), FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
], ],
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Description', labelText: AppLocalizations.of(context)!.description,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
), ),
), ),
], ],

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart'
show TextButton, FloatingActionButton, Icons; show TextButton, FloatingActionButton, Icons;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../../l10n/app_localizations.dart';
class EditDialogActions extends StatelessWidget { class EditDialogActions extends StatelessWidget {
const EditDialogActions({super.key, required this.onSavePressed}); const EditDialogActions({super.key, required this.onSavePressed});
final VoidCallback onSavePressed; final VoidCallback onSavePressed;
@@ -13,7 +15,7 @@ class EditDialogActions extends StatelessWidget {
children: [ children: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel'), child: Text(AppLocalizations.of(context)!.cancel),
), ),
FloatingActionButton(onPressed: onSavePressed, child: Icon(Icons.save)), FloatingActionButton(onPressed: onSavePressed, child: Icon(Icons.save)),
], ],

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart' show TextButton, Theme; import 'package:flutter/material.dart' show TextButton, Theme;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../../l10n/app_localizations.dart';
class EditDialogTitle extends StatelessWidget { class EditDialogTitle extends StatelessWidget {
const EditDialogTitle({ const EditDialogTitle({
super.key, super.key,
@@ -15,11 +17,10 @@ class EditDialogTitle extends StatelessWidget {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
// TODO: Localize
if (dialogType == DialogType.bookmark) if (dialogType == DialogType.bookmark)
Text('Create Bookmark') Text(AppLocalizations.of(context)!.createBookmark)
else else
Text('Create Collection'), Text(AppLocalizations.of(context)!.createCollection),
if (onDeletePressed != null) if (onDeletePressed != null)
TextButton( TextButton(
@@ -28,7 +29,7 @@ class EditDialogTitle extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
'Delete', AppLocalizations.of(context)!.delete,
style: TextStyle(color: Theme.of(context).colorScheme.error), style: TextStyle(color: Theme.of(context).colorScheme.error),
), ),
), ),

View File

@@ -1,16 +1,37 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../l10n/app_localizations.dart';
class SearchBarWidget extends StatelessWidget { class SearchBarWidget extends StatelessWidget {
const SearchBarWidget({super.key, required this.onEditingComplete}); const SearchBarWidget({
super.key,
required this.onEditingComplete,
required this.onResetSearch,
});
final Function(String searchString) onEditingComplete; final Function(String searchString) onEditingComplete;
final Function() onResetSearch;
@override
Widget build(BuildContext context) {
return TextField(onChanged: (text) => onChanged(text, context));
}
void onChanged(String text, BuildContext context) { void onChanged(String text, BuildContext context) {
if (context.mounted) onEditingComplete(text); if (context.mounted) onEditingComplete(text);
} }
@override
Widget build(BuildContext context) {
final searchTextController = TextEditingController();
return TextField(
controller: searchTextController,
onChanged: (text) => onChanged(text, context),
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: () {
searchTextController.clear();
onResetSearch();
},
icon: Icon(Icons.delete_outline_outlined),
),
labelText: AppLocalizations.of(context)!.search,
),
);
}
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../l10n/app_localizations.dart';
import '../../model/bookmark.dart'; import '../../model/bookmark.dart';
import '../../service/notifying.dart'; import '../../service/notifying.dart';
import '../../service/search_provider.dart'; import '../../service/search_provider.dart';
@@ -17,6 +18,12 @@ class SearchResultsWidget extends StatefulWidget {
class _SearchResultsWidgetState extends State<SearchResultsWidget> { class _SearchResultsWidgetState extends State<SearchResultsWidget> {
final List<Bookmark> allBookmarks = Storage.loadBookmarks(); final List<Bookmark> allBookmarks = Storage.loadBookmarks();
@override
void deactivate() {
context.read<SearchProvider>().removeSearchText(silent: true);
super.deactivate();
}
Widget bookmarkListItemBuilder(BuildContext context, int index) { Widget bookmarkListItemBuilder(BuildContext context, int index) {
final bookmark = filteredBookmarks.elementAt(index); final bookmark = filteredBookmarks.elementAt(index);
return ListTile( return ListTile(
@@ -31,20 +38,21 @@ class _SearchResultsWidgetState extends State<SearchResultsWidget> {
); );
} }
@override
Widget build(BuildContext context) {
if (filteredBookmarks.isNotEmpty) {
return ListView.builder(
itemBuilder: bookmarkListItemBuilder,
itemCount: filteredBookmarks.length,
);
}
return Center(child: Text('Start searching'));
}
Iterable<Bookmark> get filteredBookmarks => allBookmarks.where( Iterable<Bookmark> get filteredBookmarks => allBookmarks.where(
(bookmark) => bookmark.name.toLowerCase().contains( (bookmark) => bookmark.name.toLowerCase().contains(
context.watch<SearchProvider>().searchText.toLowerCase(), context.watch<SearchProvider>().searchText.toLowerCase(),
), ),
); );
@override
Widget build(BuildContext context) {
if (filteredBookmarks.isNotEmpty) {
return ListView.separated(
itemBuilder: bookmarkListItemBuilder,
itemCount: filteredBookmarks.length,
separatorBuilder: (context, index) => SizedBox(height: 10),
);
}
return Center(child: Text(AppLocalizations.of(context)!.tipNoResults));
}
} }

View File

@@ -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"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608"
url: "https://pub.dev"
source: hosted
version: "0.3.5+1"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@@ -89,6 +97,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
file_selector:
dependency: "direct main"
description:
name: file_selector
sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a
url: "https://pub.dev"
source: hosted
version: "1.1.0"
file_selector_android:
dependency: transitive
description:
name: file_selector_android
sha256: "51e8fd0446de75e4b62c065b76db2210c704562d072339d333bd89c57a7f8a7c"
url: "https://pub.dev"
source: hosted
version: "0.5.2+4"
file_selector_ios:
dependency: transitive
description:
name: file_selector_ios
sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca
url: "https://pub.dev"
source: hosted
version: "0.5.3+5"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
url: "https://pub.dev"
source: hosted
version: "0.9.5"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
file_selector_web:
dependency: transitive
description:
name: file_selector_web
sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7
url: "https://pub.dev"
source: hosted
version: "0.9.4+2"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -102,6 +174,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -136,6 +213,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -240,6 +325,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.dev"
source: hosted
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.dev"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.dev"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -486,5 +619,5 @@ packages:
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
sdks: sdks:
dart: ">=3.9.2 <4.0.0" dart: ">=3.10.0 <4.0.0"
flutter: ">=3.35.0" flutter: ">=3.38.0"

View File

@@ -3,7 +3,7 @@ description: "A new way to save google maps bookmarks"
publish_to: 'none' publish_to: 'none'
version: 1.0.0+1 version: 0.0.18
environment: environment:
sdk: ^3.9.2 sdk: ^3.9.2
@@ -18,6 +18,11 @@ dependencies:
provider: ^6.1.5+1 provider: ^6.1.5+1
metadata_fetch: ^0.4.2 metadata_fetch: ^0.4.2
url_launcher: ^6.3.2 url_launcher: ^6.3.2
flutter_localizations:
sdk: flutter
intl: any
permission_handler: ^12.0.1
file_selector: ^1.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -26,5 +31,5 @@ dev_dependencies:
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0
flutter: flutter:
generate: true
uses-material-design: true uses-material-design: true