diff --git a/assets/translations/de-DE.json b/assets/translations/de-DE.json index a57d4cc..03bce9b 100644 --- a/assets/translations/de-DE.json +++ b/assets/translations/de-DE.json @@ -108,6 +108,11 @@ "hint": "Title oder ISBN", "title": "Titelsuche" }, + "random_book": { + "description": "TODO", + "move_to_reading": "TODO", + "title": "TODO" + }, "recommendations": { "add-to-wishlist": "Zur Wunschliste hinzufügen", "empty": "Keine Vorschläge verfügbar.", diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index bd51d96..a6abf0d 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -107,6 +107,11 @@ "hint": "Title or ISBN", "title": "Title search" }, + "random_book": { + "description": "Can't decide which book to read next? We've got you covered. Let us decide for you what you should read next.", + "move_to_reading": "Move To Reading", + "title": "Pick Random Book" + }, "recommendations": { "add-to-wishlist": "Add to wishlist", "empty": "No recommendations available.", diff --git a/lib/src/ui/main/book_state_page.dart b/lib/src/ui/main/book_state_page.dart index b20b585..c91b6c0 100644 --- a/lib/src/ui/main/book_state_page.dart +++ b/lib/src/ui/main/book_state_page.dart @@ -1,14 +1,18 @@ import 'dart:async'; +import 'dart:math'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:dantex/src/data/book/book_repository.dart'; import 'package:dantex/src/data/book/entity/book.dart'; import 'package:dantex/src/data/book/entity/book_state.dart'; import 'package:dantex/src/providers/book.dart'; import 'package:dantex/src/providers/repository.dart'; import 'package:dantex/src/ui/book/book_item_widget.dart'; +import 'package:dantex/src/ui/core/dante_components.dart'; import 'package:dantex/src/ui/core/generic_error_widget.dart'; import 'package:dantex/src/ui/main/empty_state_view.dart'; import 'package:dantex/src/util/layout_utils.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -45,31 +49,58 @@ class _BooksScreen extends ConsumerWidget { return LayoutBuilder( builder: (context, constraints) { + final isRandomBooksEnabled = ref.watch(isRandomBooksEnabledProvider); final DeviceFormFactor formFactor = getDeviceFormFactor(constraints); return switch (formFactor) { - DeviceFormFactor.desktop => - _buildLargeLayout(bookRepository, columns: 3), - DeviceFormFactor.tablet => - _buildLargeLayout(bookRepository, columns: 2), + DeviceFormFactor.desktop => _buildLargeLayout( + bookRepository, + columns: 3, + showPickRandomBookTile: + isRandomBooksEnabled && state == BookState.readLater, + ), + DeviceFormFactor.tablet => _buildLargeLayout( + bookRepository, + columns: 2, + showPickRandomBookTile: + isRandomBooksEnabled && state == BookState.readLater, + ), DeviceFormFactor.phone => _buildPhoneLayout( bookRepository, + showPickRandomBookTile: + isRandomBooksEnabled && state == BookState.readLater, ), }; }, ); } - Widget _buildPhoneLayout(BookRepository bookRepository) { + Widget _buildPhoneLayout( + BookRepository bookRepository, { + bool showPickRandomBookTile = false, + }) { return ListView.separated( padding: const EdgeInsets.all(16.0), physics: const BouncingScrollPhysics(), - itemCount: books.length, - itemBuilder: (context, index) => _buildItem( - books[index], - bookRepository, - useMobileLayout: true, - ), + itemCount: showPickRandomBookTile ? books.length + 1 : books.length, + itemBuilder: (context, index) { + if (showPickRandomBookTile) { + if (index == 0) { + return _buildPickRandomBookWidget(context, bookRepository); + } + return _buildItem( + books[index - 1], + bookRepository, + useMobileLayout: true, + ); + } else { + return _buildItem( + books[index], + bookRepository, + useMobileLayout: true, + ); + } + }, separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 16), ); @@ -78,6 +109,7 @@ class _BooksScreen extends ConsumerWidget { Widget _buildLargeLayout( BookRepository bookRepository, { required int columns, + bool showPickRandomBookTile = false, }) { return GridView.builder( padding: const EdgeInsets.all(16), @@ -87,12 +119,70 @@ class _BooksScreen extends ConsumerWidget { crossAxisSpacing: 16, childAspectRatio: 4, ), - itemBuilder: (context, index) => _buildItem( - books[index], - bookRepository, - useMobileLayout: false, + itemBuilder: (context, index) { + if (showPickRandomBookTile) { + if (index == 0) { + return _buildPickRandomBookWidget(context, bookRepository); + } + return _buildItem( + books[index - 1], + bookRepository, + useMobileLayout: false, + ); + } else { + return _buildItem( + books[index], + bookRepository, + useMobileLayout: false, + ); + } + }, + itemCount: showPickRandomBookTile ? books.length + 1 : books.length, + ); + } + + Widget _buildPickRandomBookWidget( + BuildContext context, + BookRepository bookRepository, + ) { + return Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.outline, + width: .2, + ), + borderRadius: BorderRadius.circular(12.0), + ), + child: Column( + children: [ + Text( + 'random_book.description'.tr(), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 8.0), + DanteOutlinedButton( + child: Text( + 'random_book.title'.tr(), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + onPressed: () async { + // Get random book from list + final book = books[Random().nextInt(books.length)]; + await _showRandomBookDialog( + context, + book, + bookRepository, + ); + }, + ), + ], ), - itemCount: books.length, ); } @@ -131,4 +221,55 @@ class _BooksScreen extends ConsumerWidget { repository.delete(book.id), ); } + + Future _showRandomBookDialog( + BuildContext context, + Book book, + BookRepository repository, + ) async { + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + alignment: Alignment.center, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + book.title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 8.0), + CachedNetworkImage( + key: const ValueKey('book-detail-image'), + imageUrl: book.thumbnailAddress!, + width: 80, + ), + ], + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + DanteOutlinedButton( + onPressed: () { + _handleBookUpdate( + repository, + book, + BookState.reading, + ); + Navigator.of(context).pop(); + }, + child: Text( + 'random_book.move_to_reading'.tr(), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ], + ); + }, + ); + } }