22 Commits

Author SHA1 Message Date
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
21 changed files with 548 additions and 91 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

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

@@ -0,0 +1,34 @@
{
"@@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",
"activateJsonExport": "Json-Export aktivieren",
"@@comment": "Errors",
"errorStoragePermisson": "Zugriff auf Speicher verwehrt",
"errorCouldNotLaunchUrl": "Konnte Url nicht öffnen",
"errorInvalidUrl": "Fehlerhafte Url"
}

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

@@ -0,0 +1,34 @@
{
"@@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",
"activateJsonExport": "Activate json export",
"@@comment": "Errors",
"errorStoragePermisson": "Storage permissions denied",
"errorCouldNotLaunchUrl": "Could not launch Url",
"errorInvalidUrl": "Invalid Url"
}

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(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: ListView.separated(
itemBuilder: (context, index) => itemBuilder: (context, index) =>
bookmarksListItemBuilder(context, bookmarks.elementAt(index)), bookmarksListItemBuilder(context, bookmarks.elementAt(index)),
itemCount: bookmarks.length, 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,24 +85,31 @@ 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),
), ),
], ],
), ),
@@ -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(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: ListView.separated(
itemBuilder: (context, index) => collectionsListItemBuilder( itemBuilder: (context, index) => collectionsListItemBuilder(
context, context,
collections.elementAt(index), collections.elementAt(index),
), ),
itemCount: collections.length, itemCount: collections.length,
separatorBuilder: (context, index) => SizedBox(height: 10),
),
),
) )
: 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,16 +13,23 @@ 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(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: Column(
children: [ children: [
Padding(padding: EdgeInsetsGeometry.only(top: 10)),
SearchBarWidget( SearchBarWidget(
onEditingComplete: context.read<SearchProvider>().setSearchText, onEditingComplete: context.read<SearchProvider>().setSearchText,
onResetSearch: context.read<SearchProvider>().removeSearchText, onResetSearch: context.read<SearchProvider>().removeSearchText,
), ),
Padding(padding: EdgeInsetsGeometry.only(top: 10)),
Expanded(child: SearchResultsWidget()), Expanded(child: SearchResultsWidget()),
], ],
), ),
),
),
); );
} }
} }

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import '../l10n/app_localizations.dart';
class SettingsPage extends StatelessWidget {
static const routeName = '/settings';
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
);
}
}

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,14 +9,32 @@ 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,
content: SizedBox(
height: 30,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
text, text,
style: isError style: isError
? TextStyle(color: Theme.of(context).colorScheme.error) ? TextStyle(color: Theme.of(context).colorScheme.onError)
: null, : 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 showStoragePermissionErrorSnackbar(BuildContext context) {
showSnackbar(
context,
text: AppLocalizations.of(context)!.errorStoragePermisson,
isError: true,
);
}
} }

View File

@@ -50,6 +50,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);

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,
); ThemeData get lightTheme => _baseTheme(_lightColorScheme);
ColorScheme get _lightColorScheme => ColorScheme.fromSeed( ThemeData get darkTheme => _baseTheme(_darkColorScheme);
seedColor: Colors.deepPurple,
brightness: Brightness.light, 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,5 +1,7 @@
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({ const SearchBarWidget({
super.key, super.key,
@@ -28,6 +30,7 @@ class SearchBarWidget extends StatelessWidget {
}, },
icon: Icon(Icons.delete_outline_outlined), 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';
@@ -46,11 +47,12 @@ class _SearchResultsWidgetState extends State<SearchResultsWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (filteredBookmarks.isNotEmpty) { if (filteredBookmarks.isNotEmpty) {
return ListView.builder( return ListView.separated(
itemBuilder: bookmarkListItemBuilder, itemBuilder: bookmarkListItemBuilder,
itemCount: filteredBookmarks.length, itemCount: filteredBookmarks.length,
separatorBuilder: (context, index) => SizedBox(height: 10),
); );
} }
return Center(child: Text('Start searching')); return Center(child: Text(AppLocalizations.of(context)!.tipNoResults));
} }
} }

View File

@@ -41,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@@ -49,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" version: "1.19.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@@ -102,6 +118,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
@@ -112,6 +133,22 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
hooks:
dependency: transitive
description:
name: hooks
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
html: html:
dependency: transitive dependency: transitive
description: description:
@@ -136,6 +173,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:
@@ -168,6 +213,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -200,6 +253,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.2" version: "0.4.2"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@@ -208,6 +269,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "9922a1ad59ac5afb154cc948aa6ded01987a75003651d0a2866afc23f4da624e"
url: "https://pub.dev"
source: hosted
version: "9.2.3"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -216,6 +285,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@@ -240,6 +333,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:
@@ -264,6 +405,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.5+1" version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -485,6 +634,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks: sdks:
dart: ">=3.9.2 <4.0.0" dart: ">=3.10.3 <4.0.0"
flutter: ">=3.35.0" flutter: ">=3.38.4"

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
path_provider: ^2.1.5
permission_handler: ^12.0.1
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