24 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
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
22 changed files with 592 additions and 105 deletions

View File

@@ -23,13 +23,6 @@ jobs:
- name: Checkout code
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)
uses: actions/setup-java@v3
with:
@@ -66,7 +59,6 @@ jobs:
env:
GITEA_TOKEN: ${{ secrets.RUNNER_CREATE_RELEASE }}
with:
# tag & name: adjust to your versioning
tag_name: "v0.1.${{ github.run_number }}"
name: "Flutter Android v0.1.${{ github.run_number }}"
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
*.class
*.lock
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
lib/l10n/app_localizations*
# 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
*.iml
@@ -18,28 +25,142 @@ migrate_working_dir/
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Visual Studio Code related
.classpath
.project
.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
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-preload-cache/
.pub-cache/
.pub/
/build/
/coverage/
build/
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
# Obfuscation related
app.*.map.json
# Exceptions to above rules.
!**/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
/android/app/debug
/android/app/profile
/android/app/release
# Monorepo
.cipd
.gclient
.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">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:label="maps_bookmarks"
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_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'l10n/app_localizations.dart';
import 'pages/collection_page.dart';
import 'pages/collections_list_page.dart';
import 'pages/search_page.dart';
import 'pages/settings_page.dart';
import 'service/search_provider.dart';
import 'service/shared_link_provider.dart';
import 'service/storage.dart';
@@ -66,6 +69,13 @@ class _MapsBookmarksState extends State<MapsBookmarks>
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [Locale('en'), Locale('de')],
navigatorKey: _navigatorKey,
theme: lightTheme,
darkTheme: darkTheme,
@@ -74,6 +84,7 @@ class _MapsBookmarksState extends State<MapsBookmarks>
CollectionsListPage.routeName: (context) => const CollectionsListPage(),
CollectionPage.routeName: (context) => const CollectionPage(),
SearchPage.routeName: (context) => const SearchPage(),
SettingsPage.routeName: (context) => const SettingsPage(),
},
);
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../l10n/app_localizations.dart';
import '../model/bookmark.dart';
import '../model/maps_link_metadata.dart';
import '../service/bookmarks_provider.dart';
@@ -89,20 +90,28 @@ class _CollectionPageState extends State<CollectionPage> {
return Scaffold(
appBar: AppBar(
title: selectedMapsLink != null
? Text('Add to ${collection.name}')
? Text(
AppLocalizations.of(context)!.addToCollection(collection.name),
)
: Text(collection.name),
actions: [
if (selectedMapsLink != null)
TextButton(
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) =>
bookmarksListItemBuilder(context, bookmarks.elementAt(index)),
itemCount: bookmarks.length,
separatorBuilder: (context, index) => SizedBox(height: 10),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: onAddButtonPressed,

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../l10n/app_localizations.dart';
import '../model/collection.dart';
import '../service/bookmarks_provider.dart';
import '../service/shared_link_provider.dart';
@@ -8,6 +9,7 @@ import '../service/storage.dart';
import '../widgets/create_bookmark_collection_dialog.dart';
import 'collection_page.dart';
import 'search_page.dart' show SearchPage;
import 'settings_page.dart';
class CollectionsListPage extends StatefulWidget {
const CollectionsListPage({super.key});
@@ -20,6 +22,7 @@ class CollectionsListPage extends StatefulWidget {
class _CollectionsListPageState extends State<CollectionsListPage> {
bool addingNewBookmark = false;
var bookmarkCountMap = <int, int>{};
Widget bottomSheetBuilder(BuildContext context) {
final titleTextFieldController = TextEditingController(
@@ -53,6 +56,11 @@ class _CollectionsListPageState extends State<CollectionsListPage> {
title: Text(collection.name),
onTap: () => navigateToCollection(collection.id),
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
Widget build(BuildContext context) {
final collections = Storage.loadCollections();
final provider = context.watch<SharedLinkProvider>();
addingNewBookmark = provider.currentMapsLinkMetadata != null;
bookmarkCountMap = Storage.loadPerCollectionBookmarkCount();
addingNewBookmark =
context.watch<SharedLinkProvider>().currentMapsLinkMetadata != null;
return Scaffold(
appBar: AppBar(
title: addingNewBookmark
? Text('Choose Collection')
: Text('Collections'),
? Text(AppLocalizations.of(context)!.chooseCollection)
: Text(AppLocalizations.of(context)!.collections),
actions: [
if (addingNewBookmark)
TextButton(
onPressed: () => provider.removeCurrentMapsLink(),
child: Text('Cancel'),
onPressed: () =>
context.read<SharedLinkProvider>().removeCurrentMapsLink(),
child: Text(AppLocalizations.of(context)!.cancel),
)
else
IconButton(
onPressed: () =>
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),
),
body: collections.isNotEmpty
? ListView.builder(
? Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: ListView.separated(
itemBuilder: (context, index) => collectionsListItemBuilder(
context,
collections.elementAt(index),
),
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:provider/provider.dart';
import '../l10n/app_localizations.dart';
import '../service/search_provider.dart';
import '../widgets/search_widgets/search_bar_widget.dart';
import '../widgets/search_widgets/search_results_widget.dart';
@@ -12,15 +13,23 @@ class SearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Search')),
body: Column(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.search)),
body: Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
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()),
],
),
),
),
);
}
}

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 '../l10n/app_localizations.dart';
import 'url_launcher.dart' show UrlLaunchErrorCode;
class Notifying {
@@ -8,14 +9,32 @@ class Notifying {
required String text,
bool isError = false,
}) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
backgroundColor: Theme.of(context).colorScheme.error,
content: SizedBox(
height: 30,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
text,
style: isError
? TextStyle(color: Theme.of(context).colorScheme.error)
? 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) {
return;
} else if (errorCode == UrlLaunchErrorCode.couldNotLaunch) {
errorText = 'Could not launch Url';
errorText = AppLocalizations.of(context)!.errorCouldNotLaunchUrl;
} else {
errorText = 'Invalid Url';
errorText = AppLocalizations.of(context)!.errorInvalidUrl;
}
showSnackbar(context, text: errorText, isError: true);
}
static void showStoragePermissionErrorSnackbar(BuildContext context) {
showSnackbar(
context,
text: AppLocalizations.of(context)!.errorStoragePermisson,
isError: true,
);
}
}

View File

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

View File

@@ -50,6 +50,14 @@ class Storage {
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 {
final bookmarks = loadBookmarks();
bookmarks.add(bookmark);

View File

@@ -1,15 +1,26 @@
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(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
);
ColorScheme get _lightColorScheme => ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
ColorScheme get _darkColorScheme =>
ColorScheme.fromSeed(seedColor: _seed, brightness: Brightness.dark);
ThemeData get lightTheme => _baseTheme(_lightColorScheme);
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/services.dart';
import '../l10n/app_localizations.dart';
import '../model/collection.dart';
import 'edit_dialog_widgets/edit_dialog_actions.dart' show EditDialogActions;
import 'edit_dialog_widgets/edit_dialog_title.dart';
@@ -40,9 +41,7 @@ class CreateBookmarkCollectionDialog extends StatelessWidget {
FilteringTextInputFormatter.deny(RegExp(r'\s\s+')),
],
decoration: InputDecoration(
// TODO: Localize
labelText: 'Collection Name',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
labelText: AppLocalizations.of(context)!.collectionName,
),
),
actions: [

View File

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

View File

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

View File

@@ -1,16 +1,37 @@
import 'package:flutter/material.dart';
import '../../l10n/app_localizations.dart';
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;
@override
Widget build(BuildContext context) {
return TextField(onChanged: (text) => onChanged(text, context));
}
final Function() onResetSearch;
void onChanged(String text, BuildContext context) {
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:provider/provider.dart';
import '../../l10n/app_localizations.dart';
import '../../model/bookmark.dart';
import '../../service/notifying.dart';
import '../../service/search_provider.dart';
@@ -17,6 +18,12 @@ class SearchResultsWidget extends StatefulWidget {
class _SearchResultsWidgetState extends State<SearchResultsWidget> {
final List<Bookmark> allBookmarks = Storage.loadBookmarks();
@override
void deactivate() {
context.read<SearchProvider>().removeSearchText(silent: true);
super.deactivate();
}
Widget bookmarkListItemBuilder(BuildContext context, int index) {
final bookmark = filteredBookmarks.elementAt(index);
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(
(bookmark) => bookmark.name.toLowerCase().contains(
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

@@ -41,6 +41,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -49,6 +57,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csslib:
dependency: transitive
description:
@@ -102,6 +118,11 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -112,6 +133,22 @@ packages:
description: flutter
source: sdk
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:
dependency: transitive
description:
@@ -136,6 +173,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -168,6 +213,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
@@ -200,6 +253,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -208,6 +269,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -216,6 +285,30 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -240,6 +333,54 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -264,6 +405,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@@ -485,6 +634,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.9.2 <4.0.0"
flutter: ">=3.35.0"
dart: ">=3.10.3 <4.0.0"
flutter: ">=3.38.4"

View File

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