From a842b8bfd271237a6ed7668ce65640cd6a7b7a6e Mon Sep 17 00:00:00 2001 From: Lockie Richter Date: Wed, 6 Mar 2024 07:07:21 +1030 Subject: [PATCH] Show other book suggestions if main suggestion is wrong --- ios/Podfile.lock | 2 +- ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/src/ui/add/add_book_widget.dart | 196 +++++++++++++++--- lib/src/ui/book/book_image.dart | 2 +- 5 files changed, 169 insertions(+), 35 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index dfb1bb2..39ba3bc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -294,7 +294,7 @@ SPEC CHECKSUMS: FirebaseSessions: 2f348975f6d1c139231c180e12194161da2e0cd6 FirebaseSharedSwift: 2fbf73618288b7a36b2014b957745dcdd781389e FirebaseStorage: 8505bae8ac6662474b5b50e07759fb2765c15746 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_barcode_scanner: 7a1144744c28dc0c57a8de7218ffe5ec59a9e4bf google_sign_in_ios: 1bfaf6607b44cd1b24c4d4bc39719870440f9ce1 GoogleAppMeasurement: bb3c564c3efb933136af0e94899e0a46167466a8 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 36afeec..ceecec0 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -163,7 +163,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826d..5e31d3d 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ SizedBox( - height: _bottomSheetHeight, - child: child, - ), - AddBookWidgetAppearance.fullScreen => Expanded(child: child), - }, - ], - ); + switch (appearance) { + case AddBookWidgetAppearance.bottomSheet: + return child; + case AddBookWidgetAppearance.fullScreen: + return Expanded(child: child); + } } } @@ -83,6 +75,8 @@ openAddBookSheet( }) async { await showModalBottomSheet( context: context, + showDragHandle: true, + isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(20), @@ -90,23 +84,104 @@ openAddBookSheet( ), ), barrierColor: Colors.black54, - builder: (context) => AddBookWidget( - query, - appearance: AddBookWidgetAppearance.bottomSheet, + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AddBookWidget( + query, + appearance: AddBookWidgetAppearance.bottomSheet, + ), + ], + ), ), ); } -class BookWidget extends ConsumerWidget { +enum BookSuggestionView { + first, + other, +} + +class _BookSuggestionWidget extends ConsumerStatefulWidget { final BookSuggestion bookSuggestion; final AddBookWidgetAppearance appearance; - const BookWidget({ + const _BookSuggestionWidget({ required this.bookSuggestion, required this.appearance, super.key, }); + @override + createState() => _BookSuggestionWidgetState(); +} + +class _BookSuggestionWidgetState extends ConsumerState<_BookSuggestionWidget> { + BookSuggestionView currentView = BookSuggestionView.first; + late Book currentBook; + late List otherBooks; + + @override + void initState() { + super.initState(); + currentBook = widget.bookSuggestion.target; + otherBooks = widget.bookSuggestion.suggestions; + } + + @override + Widget build(BuildContext context) { + return AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: SingleChildScrollView( + child: switch (currentView) { + BookSuggestionView.first => _FirstBookSuggestion( + bookSuggestion: currentBook, + appearance: widget.appearance, + notMyBookCallback: () { + setState(() { + currentView = BookSuggestionView.other; + }); + }, + ), + BookSuggestionView.other => _OtherBookSuggestions( + otherSuggestions: widget.bookSuggestion.suggestions, + selectOtherBookCallback: (book) { + setState(() { + otherBooks.insert(0, currentBook); + otherBooks.remove(book); + currentBook = book; + currentView = BookSuggestionView.first; + }); + }, + appearance: widget.appearance, + backButtonCallback: () { + setState(() { + currentView = BookSuggestionView.first; + }); + }, + ), + }, + ), + ), + ); + } +} + +class _FirstBookSuggestion extends ConsumerWidget { + final Book bookSuggestion; + final AddBookWidgetAppearance appearance; + final void Function() notMyBookCallback; + + const _FirstBookSuggestion({ + required this.bookSuggestion, + required this.appearance, + required this.notMyBookCallback, + super.key, + }); @override Widget build(BuildContext context, WidgetRef ref) { return Column( @@ -115,7 +190,7 @@ class BookWidget extends ConsumerWidget { if (appearance == AddBookWidgetAppearance.fullScreen) const SizedBox(height: 16), BookImage( - bookSuggestion.target.thumbnailAddress, + bookSuggestion.thumbnailAddress, size: appearance.imageSize, ), Column( @@ -123,13 +198,13 @@ class BookWidget extends ConsumerWidget { Padding( padding: const EdgeInsets.all(16.0), child: Text( - bookSuggestion.target.title, + bookSuggestion.title, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), ), Text( - bookSuggestion.target.author, + bookSuggestion.author, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyLarge, ), @@ -146,7 +221,7 @@ class BookWidget extends ConsumerWidget { onPressed: () async { await ref .read(bookRepositoryProvider) - .addToForLater(bookSuggestion.target); + .addToForLater(bookSuggestion); // Just pop the screen here, no need to handle something else if (context.mounted) { Navigator.of(context).pop(); @@ -161,7 +236,7 @@ class BookWidget extends ConsumerWidget { onPressed: () async { await ref .read(bookRepositoryProvider) - .addToReading(bookSuggestion.target); + .addToReading(bookSuggestion); // Just pop the screen here, no need to handle something else if (context.mounted) { Navigator.of(context).pop(); @@ -176,7 +251,7 @@ class BookWidget extends ConsumerWidget { onPressed: () async { await ref .read(bookRepositoryProvider) - .addToRead(bookSuggestion.target); + .addToRead(bookSuggestion); // Just pop the screen here, no need to handle something else if (context.mounted) { Navigator.of(context).pop(); @@ -193,7 +268,7 @@ class BookWidget extends ConsumerWidget { onPressed: () async { await ref .read(bookRepositoryProvider) - .addToWishlist(bookSuggestion.target); + .addToWishlist(bookSuggestion); // Just pop the screen here, no need to handle something else if (context.mounted) { Navigator.of(context).pop(); @@ -204,12 +279,71 @@ class BookWidget extends ConsumerWidget { ], ), TextButton( + onPressed: notMyBookCallback, child: Text('not_my_book'.tr()), - onPressed: () { - // TODO Show bookSuggestion.suggestions in another ticket - }, ), ], ); } } + +class _OtherBookSuggestions extends ConsumerWidget { + final List otherSuggestions; + final AddBookWidgetAppearance appearance; + + final void Function() backButtonCallback; + final void Function(Book) selectOtherBookCallback; + + const _OtherBookSuggestions({ + required this.otherSuggestions, + required this.appearance, + required this.backButtonCallback, + required this.selectOtherBookCallback, + super.key, + }); + @override + Widget build(BuildContext context, WidgetRef ref) { + final mq = MediaQuery.of(context); + + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: mq.size.height - mq.viewInsets.bottom - 225, + ), + child: Column( + children: [ + Text( + 'Maybe one of them?', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Expanded( + child: ListView.separated( + separatorBuilder: (context, index) => const SizedBox(height: 16), + itemCount: otherSuggestions.length, + itemBuilder: (context, index) { + final book = otherSuggestions[index]; + return ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + title: Text(book.title), + subtitle: Text(book.author), + leading: BookImage( + book.thumbnailAddress, + size: appearance.imageSize, + ), + onTap: () => selectOtherBookCallback(book), + ); + }, + ), + ), + const Divider(), + OutlinedButton( + onPressed: backButtonCallback, + child: const Text('Nope'), + ), + ], + ), + ); + } +} diff --git a/lib/src/ui/book/book_image.dart b/lib/src/ui/book/book_image.dart index e62d4ce..aa485fc 100644 --- a/lib/src/ui/book/book_image.dart +++ b/lib/src/ui/book/book_image.dart @@ -15,7 +15,7 @@ class BookImage extends StatelessWidget { Widget build(BuildContext context) { if (_imageUrl != null) { return CachedNetworkImage( - imageUrl: _imageUrl!, + imageUrl: _imageUrl, width: size, ); } else {