diff --git a/assets/translations/de-DE.json b/assets/translations/de-DE.json index 5b96056..1631cce 100644 --- a/assets/translations/de-DE.json +++ b/assets/translations/de-DE.json @@ -31,7 +31,6 @@ "share": "Teilen", "suggest": "Vorschlagen" }, - "share-book": "Hi! Ich lese gerade {title} von {author} und finde es großartig!", "book_detail": { "label": "Labels", "notes": "Notizen", @@ -48,6 +47,11 @@ "restore_strategy": "Wiederherstellungsstrategie", "title": "Bücherverwaltung" }, + "book_notes": { + "my_notes": "", + "my_thoughts": "", + "subtitle": "Write down your thoughts, feelings or some general remarks about {title}" + }, "cancel": "Zurück", "change": "Ändern", "change_password": "Passwort ändern", @@ -85,59 +89,6 @@ "timeline": "Timeline", "wishlist": "Wunschliste" }, - "stats": { - "books-and-pages": { - "title": "Bücher & Seiten", - "empty": "Es liegen keine Daten vor.", - "books": "Bücher", - "books-waiting": "{} Bücher warten", - "books-reading": "{} Bücher werden gelesen", - "books-read": "{} Bücher gelesen", - "pages": "Seiten", - "pages-waiting": "{} Seiten warten", - "pages-read": "{} Seiten gelesen" - }, - "label": { - "title": "Labels", - "empty": "Es liegen keine Daten für Labels vor.", - "na": "NA" - }, - "language": { - "title": "Sprachen", - "empty": "Es liegen keine Daten für Sprachen vor." - }, - "reading-time": { - "title": "Lesezeit", - "empty": "Es liegen keine Daten vor.", - "fastest-book": "Schnellstes Buch", - "slowest-book": "Langsamstes Buch", - "days": "{} Tage" - }, - "misc": { - "title": "Verschiedenes", - "empty": "Keine Daten verfügbar.", - "average-books": { - "description": "Bücher pro Monat" - }, - "most-read-month": { - "description": "Bücher im {}" - } - }, - "favorites": { - "title": "Favoriten", - "empty": "Keine Daten für Favoriten vorhanden.", - "favorite-author": "Liebingsautor", - "first-five-star-book": "Erstes 5-Sterne Buch" - }, - "books-per-year": { - "title": "Bücher pro Jahr", - "empty": "Es liegen keine Daten vor." - }, - "books-per-month": { - "title": "Bücher pro Monat", - "empty": "Es liegen keine Daten vor." - } - }, "no_thanks": "Nein danke", "not_my_book": "Nicht mein Buch", "password": "Passwort", @@ -170,6 +121,7 @@ "reset": "Zurücksetzen", "reset_password": "Password zurücksetzen", "reset_password_text": "Wir versenden ein Email an deine registrierte Adresse, wo du weitere Instruktionen zum Zurücksetzen findest.", + "save": "", "search": { "empty": { "action": "Online suchen", @@ -229,8 +181,62 @@ }, "title": "Einstellungen" }, + "share-book": "Hi! Ich lese gerade {title} von {author} und finde es großartig!", "sign_in": "Anmelden", "sign_up_with_mail": "Anmelden mit Email", + "stats": { + "books-and-pages": { + "books": "Bücher", + "books-read": "{} Bücher gelesen", + "books-reading": "{} Bücher werden gelesen", + "books-waiting": "{} Bücher warten", + "empty": "Es liegen keine Daten vor.", + "pages": "Seiten", + "pages-read": "{} Seiten gelesen", + "pages-waiting": "{} Seiten warten", + "title": "Bücher & Seiten" + }, + "books-per-month": { + "empty": "Es liegen keine Daten vor.", + "title": "Bücher pro Monat" + }, + "books-per-year": { + "empty": "Es liegen keine Daten vor.", + "title": "Bücher pro Jahr" + }, + "favorites": { + "empty": "Keine Daten für Favoriten vorhanden.", + "favorite-author": "Liebingsautor", + "first-five-star-book": "Erstes 5-Sterne Buch", + "title": "Favoriten" + }, + "label": { + "empty": "Es liegen keine Daten für Labels vor.", + "na": "NA", + "title": "Labels" + }, + "language": { + "empty": "Es liegen keine Daten für Sprachen vor.", + "title": "Sprachen" + }, + "misc": { + "average-books": { + "description": "Bücher pro Monat" + }, + "empty": "Keine Daten verfügbar.", + "most-read-month": { + "description": "Bücher im {}" + }, + "title": "Verschiedenes" + }, + "reading-time": { + "days": "{} Tage", + "empty": "Es liegen keine Daten vor.", + "fastest-book": "Schnellstes Buch", + "slowest-book": "Langsamstes Buch", + "title": "Lesezeit" + } + }, "stay_anonymous": "Anonym bleiben", "tabs": { "for_later": "Für später", diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index b58fd4d..0564a5e 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -30,7 +30,6 @@ "share": "Share", "suggest": "Suggest" }, - "share-book": "Hey! I am currently reading {title} from {author} and I find it awesome!", "book_detail": { "label": "Label", "notes": "Notes", @@ -47,6 +46,11 @@ "restore_strategy": "Restore strategy", "title": "Book management" }, + "book_notes": { + "my_notes": "My notes", + "my_thoughts": "My thoughts belong here...", + "subtitle": "Write down your thoughts, feelings or some general remarks about {title}" + }, "cancel": "Cancel", "change": "Change", "change_password": "Change password", @@ -84,59 +88,6 @@ "timeline": "Timeline", "wishlist": "Wishlist" }, - "stats": { - "books-and-pages": { - "title": "Books & Pages", - "empty": "No data available.", - "books": "Books", - "books-waiting": "{} books waiting", - "books-reading": "{} books reading", - "books-read": "{} books read", - "pages": "Pages", - "pages-waiting": "{} pages waiting", - "pages-read": "{} pages read" - }, - "label": { - "title": "Labels", - "empty": "No data for label distribution." - }, - "language": { - "title": "Languages", - "empty": "No data for language distribution.", - "na": "NA" - }, - "reading-time": { - "title": "Reading time", - "empty": "No data available.", - "fastest-book": "Fastest book", - "slowest-book": "Slowest book", - "days": "{} days" - }, - "misc": { - "title": "Misc", - "empty": "No data available.", - "average-books": { - "description": "Books per Month" - }, - "most-read-month": { - "description": "Books in {}" - } - }, - "favorites": { - "title": "Favorites", - "empty": "No data for favorites available.", - "favorite-author": "Favorite Author", - "first-five-star-book": "First 5-star rating" - }, - "books-per-year": { - "title": "Books per year", - "empty": "No data available" - }, - "books-per-month": { - "title": "Books per month", - "empty": "No data available" - } - }, "no_thanks": "No thanks", "not_my_book": "Not my book", "password": "Password", @@ -169,6 +120,7 @@ "reset": "Reset", "reset_password": "Reset Password", "reset_password_text": "A link will be sent to your registered email address with instructions on how to reset your password.", + "save": "Save", "search": { "empty": { "action": "Search online", @@ -228,8 +180,62 @@ }, "title": "Settings" }, + "share-book": "Hey! I am currently reading {title} from {author} and I find it awesome!", "sign_in": "Sign in", "sign_up_with_mail": "Sign up with email", + "stats": { + "books-and-pages": { + "books": "Books", + "books-read": "{} books read", + "books-reading": "{} books reading", + "books-waiting": "{} books waiting", + "empty": "No data available.", + "pages": "Pages", + "pages-read": "{} pages read", + "pages-waiting": "{} pages waiting", + "title": "Books & Pages" + }, + "books-per-month": { + "empty": "No data available", + "title": "Books per month" + }, + "books-per-year": { + "empty": "No data available", + "title": "Books per year" + }, + "favorites": { + "empty": "No data for favorites available.", + "favorite-author": "Favorite Author", + "first-five-star-book": "First 5-star rating", + "title": "Favorites" + }, + "label": { + "empty": "No data for label distribution.", + "title": "Labels" + }, + "language": { + "empty": "No data for language distribution.", + "na": "NA", + "title": "Languages" + }, + "misc": { + "average-books": { + "description": "Books per Month" + }, + "empty": "No data available.", + "most-read-month": { + "description": "Books in {}" + }, + "title": "Misc" + }, + "reading-time": { + "days": "{} days", + "empty": "No data available.", + "fastest-book": "Fastest book", + "slowest-book": "Slowest book", + "title": "Reading time" + } + }, "stay_anonymous": "Stay Anonymous", "tabs": { "for_later": "For later", diff --git a/lib/src/data/book/book_repository.dart b/lib/src/data/book/book_repository.dart index 4be5cb6..c4f44e7 100644 --- a/lib/src/data/book/book_repository.dart +++ b/lib/src/data/book/book_repository.dart @@ -39,4 +39,8 @@ abstract class BookRepository { Future overwriteBooks(List books); Future mergeBooks(List books); + + Future deleteNotes(String bookId); + + Future saveNotes(String bookId, String notes); } diff --git a/lib/src/data/book/firebase_book_repository.dart b/lib/src/data/book/firebase_book_repository.dart index 53362fb..a40d921 100644 --- a/lib/src/data/book/firebase_book_repository.dart +++ b/lib/src/data/book/firebase_book_repository.dart @@ -222,6 +222,16 @@ class FirebaseBookRepository implements BookRepository { } } } + + @override + Future deleteNotes(String bookId) async { + return _booksRef().child(bookId).update({'notes': null}); + } + + @override + Future saveNotes(String bookId, String notes) { + return _booksRef().child(bookId).update({'notes': notes}); + } } extension DataSnapshotExtension on DataSnapshot { diff --git a/lib/src/providers/app_router.dart b/lib/src/providers/app_router.dart index 38ae65f..72fbd34 100644 --- a/lib/src/providers/app_router.dart +++ b/lib/src/providers/app_router.dart @@ -1,6 +1,7 @@ import 'package:dantex/src/providers/authentication.dart'; import 'package:dantex/src/ui/add/scan_book_page.dart'; import 'package:dantex/src/ui/book/book_detail_page.dart'; +import 'package:dantex/src/ui/book/book_notes_page.dart'; import 'package:dantex/src/ui/boot_page.dart'; import 'package:dantex/src/ui/core/dante_page_scaffold.dart'; import 'package:dantex/src/ui/login/email_login_page.dart'; @@ -163,6 +164,13 @@ List _mainRoutes = [ return BookDetailPage(id: bookId); }, ), + GoRoute( + path: DanteRoute.bookNotes.url, + builder: (context, state) { + final bookId = state.pathParameters['bookId'] ?? ''; + return BookNotesPage(id: bookId); + }, + ), ]; enum DanteRoute { @@ -240,6 +248,11 @@ enum DanteRoute { webUrl: '/book/:bookId', mobileUrl: 'book/:bookId', navigationUrl: '/book/:bookId', + ), + bookNotes( + webUrl: '/book/:bookId/notes', + mobileUrl: 'book/:bookId/notes', + navigationUrl: '/book/:bookId/notes', ); /// Url used for registering the route in the [_router] field for Web. diff --git a/lib/src/ui/book/book_detail_page.dart b/lib/src/ui/book/book_detail_page.dart index 640bf5c..44c4c03 100644 --- a/lib/src/ui/book/book_detail_page.dart +++ b/lib/src/ui/book/book_detail_page.dart @@ -1,6 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:dantex/src/data/book/entity/book.dart'; import 'package:dantex/src/data/book/entity/book_label.dart'; +import 'package:dantex/src/providers/app_router.dart'; import 'package:dantex/src/providers/book.dart'; import 'package:dantex/src/providers/repository.dart'; import 'package:dantex/src/ui/book/add_label_bottom_sheet.dart'; @@ -9,6 +10,7 @@ import 'package:dantex/src/util/extensions.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; class BookDetailPage extends ConsumerWidget { @@ -71,16 +73,24 @@ class BookDetailPage extends ConsumerWidget { class _IconSubtitle extends StatelessWidget { final IconData icon; final String subtitle; + final void Function()? onTap; - const _IconSubtitle({required this.icon, required this.subtitle}); + const _IconSubtitle({ + required this.icon, + required this.subtitle, + this.onTap, + }); @override Widget build(BuildContext context) { - return Column( - children: [ - Icon(icon), - Text(subtitle), - ], + return InkWell( + onTap: onTap, + child: Column( + children: [ + Icon(icon), + Text(subtitle), + ], + ), ); } } @@ -196,6 +206,9 @@ class _BookActions extends StatelessWidget { child: _IconSubtitle( icon: Icons.assignment, subtitle: 'book_detail.notes'.tr(), + onTap: () async => context.push( + DanteRoute.bookNotes.navigationUrl.replaceAll(':bookId', book.id), + ), ), ), Expanded( diff --git a/lib/src/ui/book/book_notes_page.dart b/lib/src/ui/book/book_notes_page.dart new file mode 100644 index 0000000..a44ca06 --- /dev/null +++ b/lib/src/ui/book/book_notes_page.dart @@ -0,0 +1,139 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dantex/src/providers/book.dart'; +import 'package:dantex/src/providers/repository.dart'; +import 'package:dantex/src/ui/core/generic_error_widget.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class BookNotesPage extends ConsumerStatefulWidget { + final String id; + + const BookNotesPage({required this.id, super.key}); + + @override + ConsumerState createState() => _BookNotesPageState(); +} + +class _BookNotesPageState extends ConsumerState { + late TextEditingController _controller; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final book = ref.watch(bookProvider(widget.id)); + + return book.when( + data: (book) { + _controller.text = book.notes ?? ''; + return Scaffold( + appBar: AppBar( + leading: const BackButton(), + title: Text( + 'book_notes.my_notes'.tr(), + key: const ValueKey('book-notes-app-bar-title'), + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Text( + 'book_notes.subtitle'.tr( + namedArgs: {'title': book.title}, + ), + softWrap: true, + ), + ), + const SizedBox(width: 20), + CachedNetworkImage( + key: const ValueKey('book-notes-image'), + imageUrl: book.thumbnailAddress!, + width: 40, + ), + ], + ), + const SizedBox(height: 20), + Expanded( + child: TextField( + controller: _controller, + textAlignVertical: TextAlignVertical.top, + maxLines: null, + expands: true, + decoration: InputDecoration( + hintText: 'book_notes.my_thoughts'.tr(), + border: const OutlineInputBorder(), + ), + ), + ), + ], + ), + ), + bottomNavigationBar: SafeArea( + child: Padding( + padding: + const EdgeInsets.only(top: 20.0, left: 20.0, right: 20.0), + child: Row( + children: [ + InkWell( + onTap: () async { + _controller.clear(); + await ref.read(bookRepositoryProvider).deleteNotes( + widget.id, + ); + }, + child: Row( + children: [ + const Icon(Icons.delete_outline), + Text('delete'.tr()), + ], + ), + ), + const Spacer(), + InkWell( + onTap: () async { + await ref.read(bookRepositoryProvider).saveNotes( + widget.id, + _controller.text, + ); + if (context.mounted) { + Navigator.of(context).pop(); + } + }, + child: Row( + children: [ + Text('save'.tr()), + const Icon(Icons.check_outlined), + ], + ), + ), + ], + ), + ), + ), + ); + }, + error: (error, stackTrace) => GenericErrorWidget( + error, + key: const ValueKey('book-notes-error'), + ), + loading: () => const CircularProgressIndicator.adaptive( + key: ValueKey('book-notes-loading'), + ), + ); + } +}