From 9098f8b506c31dfd5a4ab085b63ca3a5fe716ae4 Mon Sep 17 00:00:00 2001 From: Martin Macheiner Date: Tue, 28 Nov 2023 19:51:51 +0100 Subject: [PATCH 1/6] Add wishlist page --- lib/src/ui/wishlist/wishlist_page.dart | 13 ++++++++--- pubspec.lock | 32 +++++++++++++------------- pubspec.yaml | 2 +- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/src/ui/wishlist/wishlist_page.dart b/lib/src/ui/wishlist/wishlist_page.dart index d3c9ce8..78f45e4 100644 --- a/lib/src/ui/wishlist/wishlist_page.dart +++ b/lib/src/ui/wishlist/wishlist_page.dart @@ -1,3 +1,6 @@ +import 'package:dantex/src/data/book/entity/book_state.dart'; +import 'package:dantex/src/ui/core/themed_app_bar.dart'; +import 'package:dantex/src/ui/main/book_state_page.dart'; import 'package:flutter/material.dart'; class WishlistPage extends StatelessWidget { @@ -5,9 +8,13 @@ class WishlistPage extends StatelessWidget { @override Widget build(BuildContext context) { - return const Material( - child: Center( - child: Text('TODO Implement Wishlist'), + return const Scaffold( + // TODO Show app bar only on mobile devices + appBar: ThemedAppBar( + title: Text('Wishlist'), + ), + body: Center( + child: BookStatePage(BookState.wishlist), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 1e23286..5cf6b58 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -213,10 +213,10 @@ packages: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -535,10 +535,10 @@ packages: dependency: "direct main" description: name: flutter_platform_widgets - sha256: "107d5bc9a167b4e268cba44075ee399b6b2c63d44ede28f7e8c983d7fa4b59be" + sha256: "4970c211af1dad0a161e6379d04de2cace80283da0439f2f87d31a541f9b2b84" url: "https://pub.dev" source: hosted - version: "3.3.5" + version: "6.0.2" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -761,10 +761,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -1118,10 +1118,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1134,10 +1134,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -1174,10 +1174,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" timeline_tile: dependency: "direct main" description: @@ -1326,10 +1326,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -1371,5 +1371,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0-194.0.dev <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index b1a37af..1c4cc35 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: sdk: flutter shared_preferences: ^2.2.1 - flutter_platform_widgets: ^3.3.5 + flutter_platform_widgets: ^6.0.2 flutter_svg: ^2.0.7 lottie: ^2.6.0 google_fonts: ^6.1.0 From 93fbb827bdfb3e6d9f2f93ad09d2a25ee4ffdcd3 Mon Sep 17 00:00:00 2001 From: Martin Macheiner Date: Tue, 28 Nov 2023 19:52:18 +0100 Subject: [PATCH 2/6] adjust firebase to load cache loaded books --- .../data/book/firebase_book_repository.dart | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/lib/src/data/book/firebase_book_repository.dart b/lib/src/data/book/firebase_book_repository.dart index 85458ad..159ac02 100644 --- a/lib/src/data/book/firebase_book_repository.dart +++ b/lib/src/data/book/firebase_book_repository.dart @@ -5,12 +5,57 @@ import 'package:dantex/src/data/book/entity/book_state.dart'; import 'package:dantex/src/data/book/search_criteria.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_database/firebase_database.dart'; +import 'package:rxdart/rxdart.dart'; class FirebaseBookRepository implements BookRepository { final FirebaseAuth _fbAuth; final FirebaseDatabase _fbDb; - FirebaseBookRepository(this._fbAuth, this._fbDb); + final BehaviorSubject> _booksSubject = BehaviorSubject(); + + FirebaseBookRepository(this._fbAuth, this._fbDb) { + _setupBooksSubject(); + } + + void _setupBooksSubject() { + _booksRef().onValue.listen( + (DatabaseEvent event) { + switch (event.type) { + case DatabaseEventType.childAdded: + // TODO: Handle this case. + break; + + case DatabaseEventType.childRemoved: + // TODO: Handle this case. + break; + + case DatabaseEventType.childChanged: + // TODO: Handle this case. + break; + case DatabaseEventType.childMoved: + // No need to handle this case. + break; + case DatabaseEventType.value: + final Map? data = event.snapshot.toMap(); + + if (data == null) { + return; + } + + final List books = data.values.map( + (value) { + final Map bookMap = + (value as Map).cast(); + return Book.fromJson(bookMap); + }, + ).toList(); + + _booksSubject.add(books); + break; + } + }, + ); + } @override Future create(Book book) { @@ -28,23 +73,7 @@ class FirebaseBookRepository implements BookRepository { @override Stream> getAllBooks() { - return _booksRef().onValue.map( - (DatabaseEvent event) { - final Map? data = event.snapshot.toMap(); - - if (data == null) { - return []; - } - - return data.values.map( - (value) { - final Map bookMap = - (value as Map).cast(); - return Book.fromJson(bookMap); - }, - ).toList(); - }, - ); + return _booksSubject.stream; } @override From 5b21662d202a6e60cbedcfd897f9bcdf1e5b5533 Mon Sep 17 00:00:00 2001 From: Martin Macheiner Date: Tue, 28 Nov 2023 20:10:11 +0100 Subject: [PATCH 3/6] Adjust book layout for bigger screens --- lib/src/ui/main/book_state_page.dart | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/src/ui/main/book_state_page.dart b/lib/src/ui/main/book_state_page.dart index 5b28dee..e971715 100644 --- a/lib/src/ui/main/book_state_page.dart +++ b/lib/src/ui/main/book_state_page.dart @@ -3,6 +3,7 @@ import 'package:dantex/src/data/book/entity/book_state.dart'; import 'package:dantex/src/providers/book.dart'; import 'package:dantex/src/ui/book/book_item_widget.dart'; import 'package:dantex/src/ui/core/generic_error_widget.dart'; +import 'package:dantex/src/util/layout_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -37,6 +38,20 @@ class _BooksScreen extends StatelessWidget { ); } + return LayoutBuilder( + builder: (context, constraints) { + final DeviceFormFactor formFactor = getDeviceFormFactor(constraints); + + return switch (formFactor) { + DeviceFormFactor.desktop => _buildLargeLayout(columns: 3), + DeviceFormFactor.tablet => _buildLargeLayout(columns: 2), + DeviceFormFactor.phone => _buildPhoneLayout(), + }; + }, + ); + } + + Widget _buildPhoneLayout() { return ListView.separated( padding: const EdgeInsets.all(16.0), physics: const BouncingScrollPhysics(), @@ -46,4 +61,20 @@ class _BooksScreen extends StatelessWidget { const SizedBox(height: 16), ); } + + Widget _buildLargeLayout({ + required int columns, + }) { + return GridView.builder( + padding: const EdgeInsets.all(16), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columns, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: 4, + ), + itemBuilder: (context, index) => BookItemWidget(books[index]), + itemCount: books.length, + ); + } } From 63b3b73aceea1dbbbc514cf7b88f7c65001e1b8e Mon Sep 17 00:00:00 2001 From: Martin Macheiner Date: Tue, 28 Nov 2023 20:38:02 +0100 Subject: [PATCH 4/6] Adjust appbar on wishlist page for desktop devices --- lib/src/ui/wishlist/wishlist_page.dart | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/src/ui/wishlist/wishlist_page.dart b/lib/src/ui/wishlist/wishlist_page.dart index 78f45e4..7c99c53 100644 --- a/lib/src/ui/wishlist/wishlist_page.dart +++ b/lib/src/ui/wishlist/wishlist_page.dart @@ -1,6 +1,8 @@ import 'package:dantex/src/data/book/entity/book_state.dart'; import 'package:dantex/src/ui/core/themed_app_bar.dart'; import 'package:dantex/src/ui/main/book_state_page.dart'; +import 'package:dantex/src/util/layout_utils.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; class WishlistPage extends StatelessWidget { @@ -8,14 +10,19 @@ class WishlistPage extends StatelessWidget { @override Widget build(BuildContext context) { - return const Scaffold( - // TODO Show app bar only on mobile devices - appBar: ThemedAppBar( - title: Text('Wishlist'), - ), - body: Center( - child: BookStatePage(BookState.wishlist), - ), + return LayoutBuilder( + builder: (context, constraints) { + return Scaffold( + appBar: ThemedAppBar( + title: isDesktop(constraints) + ? null + : Text('navigation.wishlist'.tr()) + ), + body: const Center( + child: BookStatePage(BookState.wishlist), + ), + ); + }, ); } } From 0a1a4bfbd27f6cea3c2785510e785a2c2cdeaa06 Mon Sep 17 00:00:00 2001 From: Martin Macheiner Date: Tue, 28 Nov 2023 21:18:24 +0100 Subject: [PATCH 5/6] Adjust app bar for wishlist page --- lib/src/ui/wishlist/wishlist_page.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/ui/wishlist/wishlist_page.dart b/lib/src/ui/wishlist/wishlist_page.dart index 7c99c53..d3c4acd 100644 --- a/lib/src/ui/wishlist/wishlist_page.dart +++ b/lib/src/ui/wishlist/wishlist_page.dart @@ -13,11 +13,11 @@ class WishlistPage extends StatelessWidget { return LayoutBuilder( builder: (context, constraints) { return Scaffold( - appBar: ThemedAppBar( - title: isDesktop(constraints) - ? null - : Text('navigation.wishlist'.tr()) - ), + appBar: isDesktop(constraints) + ? null + : ThemedAppBar( + title: Text('navigation.wishlist'.tr()), + ), body: const Center( child: BookStatePage(BookState.wishlist), ), From a3f5397c067dba31712da3ba5b17052463cae2c4 Mon Sep 17 00:00:00 2001 From: Martin Macheiner Date: Tue, 28 Nov 2023 21:25:14 +0100 Subject: [PATCH 6/6] Fix tests --- .../book/firebase_book_repository_test.dart | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/data/book/firebase_book_repository_test.dart b/test/data/book/firebase_book_repository_test.dart index 1a7a21c..64d345e 100644 --- a/test/data/book/firebase_book_repository_test.dart +++ b/test/data/book/firebase_book_repository_test.dart @@ -23,6 +23,13 @@ void main() { final oldDbRef = MockDatabaseReference(); final newDbRef = MockDatabaseReference(); + when(user.uid).thenReturn('userId'); + when(fbAuth.currentUser).thenReturn(user); + when(fbDb.ref('users/userId/books')).thenReturn(oldDbRef); + when(oldDbRef.onValue).thenAnswer((_) => const Stream.empty()); + when(oldDbRef.push()).thenReturn(newDbRef); + when(newDbRef.key).thenReturn('bookKey'); + final fbAuthRepo = FirebaseBookRepository(fbAuth, fbDb); final book = Book( id: 'id', @@ -46,15 +53,9 @@ void main() { labels: [], ); - when(user.uid).thenReturn('userId'); - when(fbAuth.currentUser).thenReturn(user); - when(fbDb.ref('users/userId/books')).thenReturn(oldDbRef); - when(oldDbRef.push()).thenReturn(newDbRef); - when(newDbRef.key).thenReturn('bookKey'); - await fbAuthRepo.create(book); - verify(fbAuth.currentUser).called(1); - verify(fbDb.ref('users/userId/books')).called(1); + verify(fbAuth.currentUser).called(2); + verify(fbDb.ref('users/userId/books')).called(2); verify(oldDbRef.push()).called(1); verify(newDbRef.key).called(1); verify(newDbRef.set(book.copyWith(id: 'bookKey').toJson())).called(1); @@ -67,17 +68,18 @@ void main() { final oldDbRef = MockDatabaseReference(); final newDbRef = MockDatabaseReference(); - final fbAuthRepo = FirebaseBookRepository(fbAuth, fbDb); - when(user.uid).thenReturn('userId'); when(fbAuth.currentUser).thenReturn(user); when(fbDb.ref('users/userId/books')).thenReturn(oldDbRef); + when(oldDbRef.onValue).thenAnswer((_) => const Stream.empty()); when(oldDbRef.child('bookId')).thenReturn(newDbRef); when(newDbRef.remove()).thenAnswer((_) async {}); + final fbAuthRepo = FirebaseBookRepository(fbAuth, fbDb); + await fbAuthRepo.delete('bookId'); - verify(fbAuth.currentUser).called(1); - verify(fbDb.ref('users/userId/books')).called(1); + verify(fbAuth.currentUser).called(2); + verify(fbDb.ref('users/userId/books')).called(2); verify(oldDbRef.child('bookId')).called(1); verify(newDbRef.remove()).called(1); });