From 8785334c258f9220d9bbfd2baa5882e0b3c52e5f Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 26 Feb 2024 11:15:38 +0100 Subject: [PATCH] Rise fabs when snackbar is shown --- lib/i18n/strings.g.dart | 2 +- .../app_status.model.dart} | 0 .../snackbar_status.model.dart | 7 + lib/providers/app_status.provider.dart | 2 +- lib/providers/dio_interceptor.provider.dart | 4 +- lib/providers/dio_interceptor.provider.g.dart | 2 +- lib/providers/snackbar.provider.dart | 42 ++++ lib/providers/snackbar.provider.g.dart | 25 +++ .../provider/add_bookmark.provider.dart | 10 +- .../provider/add_bookmark.provider.g.dart | 2 +- .../provider/bookmarks.provider.dart | 9 +- .../provider/bookmarks.provider.g.dart | 2 +- .../bookmarks/provider/common_functions.dart | 68 +++--- .../provider/search_bookmarks.provider.dart | 9 +- .../provider/search_bookmarks.provider.g.dart | 2 +- lib/screens/bookmarks/ui/bookmarks.dart | 178 ++++++++-------- .../onboarding/provider/connect.provider.dart | 6 +- .../provider/connect.provider.g.dart | 2 +- .../provider/tag_bookmarks.provider.dart | 9 +- .../provider/tag_bookmarks.provider.g.dart | 2 +- lib/screens/tags/ui/tags.dart | 201 +++++++++--------- lib/utils/snackbar.dart | 22 -- lib/widgets/snackbar_fab.dart | 29 +++ 23 files changed, 371 insertions(+), 264 deletions(-) rename lib/models/{app_status.dart => providers_models/app_status.model.dart} (100%) create mode 100644 lib/models/providers_models/snackbar_status.model.dart create mode 100644 lib/providers/snackbar.provider.dart create mode 100644 lib/providers/snackbar.provider.g.dart delete mode 100644 lib/utils/snackbar.dart create mode 100644 lib/widgets/snackbar_fab.dart diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 724f520..ca88f03 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -6,7 +6,7 @@ /// Locales: 2 /// Strings: 280 (140 per locale) /// -/// Built on 2024-02-25 at 20:58 UTC +/// Built on 2024-02-26 at 09:50 UTC // coverage:ignore-file // ignore_for_file: type=lint diff --git a/lib/models/app_status.dart b/lib/models/providers_models/app_status.model.dart similarity index 100% rename from lib/models/app_status.dart rename to lib/models/providers_models/app_status.model.dart diff --git a/lib/models/providers_models/snackbar_status.model.dart b/lib/models/providers_models/snackbar_status.model.dart new file mode 100644 index 0000000..8caaaa7 --- /dev/null +++ b/lib/models/providers_models/snackbar_status.model.dart @@ -0,0 +1,7 @@ +class SnackbarStatusModel { + bool visible; + + SnackbarStatusModel({ + this.visible = false, + }); +} diff --git a/lib/providers/app_status.provider.dart b/lib/providers/app_status.provider.dart index 06f7688..035de39 100644 --- a/lib/providers/app_status.provider.dart +++ b/lib/providers/app_status.provider.dart @@ -6,7 +6,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:linkdy/providers/shared_preferences.provider.dart'; import 'package:linkdy/constants/enums.dart'; -import 'package:linkdy/models/app_status.dart'; +import 'package:linkdy/models/providers_models/app_status.model.dart'; part 'app_status.provider.g.dart'; diff --git a/lib/providers/dio_interceptor.provider.dart b/lib/providers/dio_interceptor.provider.dart index b72b5e9..1101e84 100644 --- a/lib/providers/dio_interceptor.provider.dart +++ b/lib/providers/dio_interceptor.provider.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:linkdy/providers/snackbar.provider.dart'; import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/providers/server_instances.provider.dart'; import 'package:linkdy/router/routes.dart'; -import 'package:linkdy/utils/snackbar.dart'; import 'package:linkdy/router/paths.dart'; import 'package:linkdy/providers/api_client.provider.dart'; @@ -23,7 +23,7 @@ InterceptorsWrapper dioInterceptor(DioInterceptorRef ref) { GoRouter.of(rootNavigatorKey.currentContext!).pushReplacement(RoutesPaths.onboarding); ref.read(apiClientProvider.notifier).disconnectApiClient(); ref.read(serverInstancesProvider.notifier).removeServerInstances(); - showSnacbkar(label: t.generic.authTokenNotValid, color: Colors.red); + ref.read(snackbarProvider.notifier).showSnacbkar(label: t.generic.authTokenNotValid, color: Colors.red); } handler.next(error); }, diff --git a/lib/providers/dio_interceptor.provider.g.dart b/lib/providers/dio_interceptor.provider.g.dart index 63ebdcf..0490fcc 100644 --- a/lib/providers/dio_interceptor.provider.g.dart +++ b/lib/providers/dio_interceptor.provider.g.dart @@ -6,7 +6,7 @@ part of 'dio_interceptor.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$dioInterceptorHash() => r'449d19df5bf143a7e0f92249d99b4d223b2468f3'; +String _$dioInterceptorHash() => r'15186eb74bde12048a96b1d38f1c9308c9f435a8'; /// See also [dioInterceptor]. @ProviderFor(dioInterceptor) diff --git a/lib/providers/snackbar.provider.dart b/lib/providers/snackbar.provider.dart new file mode 100644 index 0000000..fcc6a71 --- /dev/null +++ b/lib/providers/snackbar.provider.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'package:linkdy/constants/global_keys.dart'; +import 'package:linkdy/models/providers_models/snackbar_status.model.dart'; + +part 'snackbar.provider.g.dart'; + +@Riverpod(keepAlive: true) +class Snackbar extends _$Snackbar { + @override + SnackbarStatusModel build() { + return SnackbarStatusModel(); + } + + void showSnacbkar({ + required String label, + required Color color, + Color? labelColor, + GlobalKey? key, + }) async { + final GlobalKey scaffoldKey = key ?? scaffoldMessengerGlobalKey; + if (state.visible == true) { + scaffoldKey.currentState?.clearSnackBars(); + await Future.delayed(const Duration(milliseconds: 500)); + } + state.visible = true; + ref.notifyListeners(); + + final snackBar = SnackBar( + content: Text( + label, + style: TextStyle(color: labelColor ?? Colors.white), + ), + backgroundColor: color, + ); + scaffoldKey.currentState?.showSnackBar(snackBar).closed.then((value) { + state.visible = false; + ref.notifyListeners(); + }); + } +} diff --git a/lib/providers/snackbar.provider.g.dart b/lib/providers/snackbar.provider.g.dart new file mode 100644 index 0000000..88c9ef8 --- /dev/null +++ b/lib/providers/snackbar.provider.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'snackbar.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$snackbarHash() => r'2522a7ebc0bdd891cea5cb342bd1ff2419473a00'; + +/// See also [Snackbar]. +@ProviderFor(Snackbar) +final snackbarProvider = + NotifierProvider.internal( + Snackbar.new, + name: r'snackbarProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$snackbarHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Snackbar = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/screens/bookmarks/provider/add_bookmark.provider.dart b/lib/screens/bookmarks/provider/add_bookmark.provider.dart index 46b1305..29289b2 100644 --- a/lib/screens/bookmarks/provider/add_bookmark.provider.dart +++ b/lib/screens/bookmarks/provider/add_bookmark.provider.dart @@ -4,8 +4,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:linkdy/screens/bookmarks/provider/bookmarks.provider.dart'; import 'package:linkdy/screens/bookmarks/model/add_link.model.dart'; +import 'package:linkdy/providers/snackbar.provider.dart'; import 'package:linkdy/models/data/tags.dart'; -import 'package:linkdy/utils/snackbar.dart'; import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/utils/process_modal.dart'; import 'package:linkdy/models/data/set_bookmark_data.dart'; @@ -101,10 +101,10 @@ class AddBookmark extends _$AddBookmark { ref.read(bookmarksProvider.notifier).refresh(); ref.watch(routerProvider).pop(); } else { - showSnacbkar( - label: t.bookmarks.addBookmark.errorSavingBookmark, - color: Colors.red, - ); + ref.read(snackbarProvider.notifier).showSnacbkar( + label: t.bookmarks.addBookmark.errorSavingBookmark, + color: Colors.red, + ); } } diff --git a/lib/screens/bookmarks/provider/add_bookmark.provider.g.dart b/lib/screens/bookmarks/provider/add_bookmark.provider.g.dart index 14b70e5..7ed1fed 100644 --- a/lib/screens/bookmarks/provider/add_bookmark.provider.g.dart +++ b/lib/screens/bookmarks/provider/add_bookmark.provider.g.dart @@ -175,7 +175,7 @@ final getTagsProvider = ); typedef GetTagsRef = AutoDisposeFutureProviderRef>; -String _$addBookmarkHash() => r'510fe4dbdc6628a13be9d8c5c2581fd5ac94ca68'; +String _$addBookmarkHash() => r'd6425004c3e755ec539ad28da3ef44349680b04b'; /// See also [AddBookmark]. @ProviderFor(AddBookmark) diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.dart b/lib/screens/bookmarks/provider/bookmarks.provider.dart index 9fcc28d..63827e1 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.dart @@ -89,7 +89,8 @@ class Bookmarks extends _$Bookmarks { } void deleteBookmark(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.deleteBookmark( + final result = await BookmarkCommonFunctions.deleteBookmark( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); @@ -100,7 +101,8 @@ class Bookmarks extends _$Bookmarks { } void markAsReadUnread(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.markAsReadUnread( + final result = await BookmarkCommonFunctions.markAsReadUnread( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); @@ -111,7 +113,8 @@ class Bookmarks extends _$Bookmarks { } void archiveUnarchive(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.markAsReadUnread( + final result = await BookmarkCommonFunctions.archiveUnarchive( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.g.dart b/lib/screens/bookmarks/provider/bookmarks.provider.g.dart index 9ad9e9b..a78952b 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.g.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.g.dart @@ -172,7 +172,7 @@ final bookmarksRequestLoadMoreProvider = ); typedef BookmarksRequestLoadMoreRef = AutoDisposeFutureProviderRef; -String _$bookmarksHash() => r'82c31fa1cbab1ba1e5c77486e7dc7ce5a86e20a5'; +String _$bookmarksHash() => r'71282b9487a2e9d4a17973af7fb3da7c0171f8d0'; /// See also [Bookmarks]. @ProviderFor(Bookmarks) diff --git a/lib/screens/bookmarks/provider/common_functions.dart b/lib/screens/bookmarks/provider/common_functions.dart index 1cc9547..f7aa886 100644 --- a/lib/screens/bookmarks/provider/common_functions.dart +++ b/lib/screens/bookmarks/provider/common_functions.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:linkdy/providers/snackbar.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/models/data/bookmarks.dart'; import 'package:linkdy/models/data/set_bookmark_data.dart'; import 'package:linkdy/services/api_client.dart'; import 'package:linkdy/utils/process_modal.dart'; -import 'package:linkdy/utils/snackbar.dart'; class BookmarkCommonFunctions { - static Future deleteBookmark({ + static Future deleteBookmark({ + required AutoDisposeNotifierProviderRef ref, required Bookmark bookmark, required ApiClientService apiClient, }) async { @@ -19,14 +21,19 @@ class BookmarkCommonFunctions { processModal.close(); if (result.successful == true) { - showSnacbkar(label: t.bookmarks.bookmarkOptions.bookmarkDeleted, color: Colors.green); + ref + .read(snackbarProvider.notifier) + .showSnacbkar(label: t.bookmarks.bookmarkOptions.bookmarkDeleted, color: Colors.green); } else { - showSnacbkar(label: t.bookmarks.bookmarkOptions.bookmarkNotDeleted, color: Colors.red); + ref + .read(snackbarProvider.notifier) + .showSnacbkar(label: t.bookmarks.bookmarkOptions.bookmarkNotDeleted, color: Colors.red); } return result.successful; } - static Future markAsReadUnread({ + static Future markAsReadUnread({ + required AutoDisposeNotifierProviderRef ref, required Bookmark bookmark, required ApiClientService apiClient, }) async { @@ -50,25 +57,26 @@ class BookmarkCommonFunctions { processModal.close(); if (result.successful == true) { - showSnacbkar( - label: bookmark.unread == true - ? t.bookmarks.bookmarkOptions.markedAsReadSuccessfully - : t.bookmarks.bookmarkOptions.markedAsUnreadSuccessfully, - color: Colors.green, - ); + ref.read(snackbarProvider.notifier).showSnacbkar( + label: bookmark.unread == true + ? t.bookmarks.bookmarkOptions.markedAsReadSuccessfully + : t.bookmarks.bookmarkOptions.markedAsUnreadSuccessfully, + color: Colors.green, + ); return result.content; } else { - showSnacbkar( - label: bookmark.unread == true - ? t.bookmarks.bookmarkOptions.bookmarkNotMarkedAsRead - : t.bookmarks.bookmarkOptions.bookmarkNotMarkedAsUnread, - color: Colors.red, - ); + ref.read(snackbarProvider.notifier).showSnacbkar( + label: bookmark.unread == true + ? t.bookmarks.bookmarkOptions.bookmarkNotMarkedAsRead + : t.bookmarks.bookmarkOptions.bookmarkNotMarkedAsUnread, + color: Colors.red, + ); return null; } } - static Future archiveUnarchive({ + static Future archiveUnarchive({ + required AutoDisposeNotifierProviderRef ref, required Bookmark bookmark, required ApiClientService apiClient, }) async { @@ -94,20 +102,20 @@ class BookmarkCommonFunctions { processModal.close(); if (result.successful == true) { - showSnacbkar( - label: bookmark.isArchived == true - ? t.bookmarks.bookmarkOptions.bookmarkUnrchivedSuccessfully - : t.bookmarks.bookmarkOptions.bookmarkArchivedSuccessfully, - color: Colors.green, - ); + ref.read(snackbarProvider.notifier).showSnacbkar( + label: bookmark.isArchived == true + ? t.bookmarks.bookmarkOptions.bookmarkUnrchivedSuccessfully + : t.bookmarks.bookmarkOptions.bookmarkArchivedSuccessfully, + color: Colors.green, + ); return result.content; } else { - showSnacbkar( - label: bookmark.isArchived == true - ? t.bookmarks.bookmarkOptions.bookmarkNotUnrchived - : t.bookmarks.bookmarkOptions.bookmarkNotArchived, - color: Colors.red, - ); + ref.read(snackbarProvider.notifier).showSnacbkar( + label: bookmark.isArchived == true + ? t.bookmarks.bookmarkOptions.bookmarkNotUnrchived + : t.bookmarks.bookmarkOptions.bookmarkNotArchived, + color: Colors.red, + ); return null; } } diff --git a/lib/screens/bookmarks/provider/search_bookmarks.provider.dart b/lib/screens/bookmarks/provider/search_bookmarks.provider.dart index d77e452..e47c7a4 100644 --- a/lib/screens/bookmarks/provider/search_bookmarks.provider.dart +++ b/lib/screens/bookmarks/provider/search_bookmarks.provider.dart @@ -99,7 +99,8 @@ class SearchBookmarks extends _$SearchBookmarks { } void deleteBookmark(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.deleteBookmark( + final result = await BookmarkCommonFunctions.deleteBookmark( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); @@ -113,7 +114,8 @@ class SearchBookmarks extends _$SearchBookmarks { } void markAsReadUnread(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.markAsReadUnread( + final result = await BookmarkCommonFunctions.markAsReadUnread( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); @@ -127,7 +129,8 @@ class SearchBookmarks extends _$SearchBookmarks { } void archiveUnarchive(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.markAsReadUnread( + final result = await BookmarkCommonFunctions.markAsReadUnread( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); diff --git a/lib/screens/bookmarks/provider/search_bookmarks.provider.g.dart b/lib/screens/bookmarks/provider/search_bookmarks.provider.g.dart index 7715501..43ddb3e 100644 --- a/lib/screens/bookmarks/provider/search_bookmarks.provider.g.dart +++ b/lib/screens/bookmarks/provider/search_bookmarks.provider.g.dart @@ -174,7 +174,7 @@ final fetchSearchBookmarksLoadMoreProvider = ); typedef FetchSearchBookmarksLoadMoreRef = AutoDisposeFutureProviderRef; -String _$searchBookmarksHash() => r'843fe597bceab3e3f512b5b4a40ddd1dbeb09f9d'; +String _$searchBookmarksHash() => r'6e8eea2bf9c19b33f626e64ae74210d9b374065f'; /// See also [SearchBookmarks]. @ProviderFor(SearchBookmarks) diff --git a/lib/screens/bookmarks/ui/bookmarks.dart b/lib/screens/bookmarks/ui/bookmarks.dart index 899277e..945afc7 100644 --- a/lib/screens/bookmarks/ui/bookmarks.dart +++ b/lib/screens/bookmarks/ui/bookmarks.dart @@ -11,10 +11,10 @@ import 'package:linkdy/screens/bookmarks/ui/add_bookmark_modal.dart'; import 'package:linkdy/screens/bookmarks/ui/delete_bookmark_modal.dart'; import 'package:linkdy/widgets/error_screen.dart'; import 'package:linkdy/widgets/no_data_screen.dart'; +import 'package:linkdy/widgets/snackbar_fab.dart'; import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/providers/router.provider.dart'; -import 'package:linkdy/models/data/bookmarks.dart'; import 'package:linkdy/router/paths.dart'; class BookmarksScreen extends ConsumerWidget { @@ -42,8 +42,6 @@ class BookmarksScreen extends ConsumerWidget { ); } - void openDeleteBookmarkModal(Bookmark bookmark) {} - bool scrollListener(ScrollUpdateNotification scrollNotification) { if (scrollNotification.metrics.extentAfter < 100 && bookmarks.loadingMore == false && @@ -55,99 +53,103 @@ class BookmarksScreen extends ConsumerWidget { } return Scaffold( - body: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - title: Text(t.bookmarks.bookmarks), - actions: [ - IconButton( - onPressed: () => ref.watch(routerProvider).push(RoutesPaths.bookmarksSearch), - icon: const Icon(Icons.search_rounded), - tooltip: t.bookmarks.search.searchBookmarks, - ), - const SizedBox(width: 8), - ], - ), - ), - ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => RefreshIndicator( - displacement: 120, - onRefresh: () => ref.read(bookmarksProvider.notifier).refresh(), - child: NotificationListener( - onNotification: scrollListener, - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + body: Stack( + children: [ + NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar.large( + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + title: Text(t.bookmarks.bookmarks), + actions: [ + IconButton( + onPressed: () => ref.watch(routerProvider).push(RoutesPaths.bookmarksSearch), + icon: const Icon(Icons.search_rounded), + tooltip: t.bookmarks.search.searchBookmarks, ), - if (bookmarks.inialLoadStatus == LoadStatus.loading) - const SliverFillRemaining( - child: Center(child: CircularProgressIndicator()), - ), - if (bookmarks.inialLoadStatus == LoadStatus.error) - SliverFillRemaining( - child: ErrorScreen( - error: t.bookmarks.cannotLoadBookmarks, + const SizedBox(width: 8), + ], + ), + ), + ], + body: SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => RefreshIndicator( + displacement: 120, + onRefresh: () => ref.read(bookmarksProvider.notifier).refresh(), + child: NotificationListener( + onNotification: scrollListener, + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), ), - ), - if (bookmarks.bookmarks.isEmpty) - SliverFillRemaining( - child: NoDataScreen( - message: t.bookmarks.noBookmarksAdded, - ), - ), - if (bookmarks.bookmarks.isNotEmpty) - SlidableAutoCloseBehavior( - child: SliverList.builder( - itemCount: bookmarks.bookmarks.length + 1, - itemBuilder: (context, index) { - // index == bookmarks.value!.content!.results!.length -> itemCount + 1 - if (index == bookmarks.bookmarks.length) { - if (bookmarks.loadingMore) { - return const SizedBox( - height: 80, - child: Center( - child: CircularProgressIndicator(), + if (bookmarks.inialLoadStatus == LoadStatus.loading) + const SliverFillRemaining( + child: Center(child: CircularProgressIndicator()), + ), + if (bookmarks.inialLoadStatus == LoadStatus.error) + SliverFillRemaining( + child: ErrorScreen( + error: t.bookmarks.cannotLoadBookmarks, + ), + ), + if (bookmarks.bookmarks.isEmpty) + SliverFillRemaining( + child: NoDataScreen( + message: t.bookmarks.noBookmarksAdded, + ), + ), + if (bookmarks.bookmarks.isNotEmpty) + SlidableAutoCloseBehavior( + child: SliverList.builder( + itemCount: bookmarks.bookmarks.length + 1, + itemBuilder: (context, index) { + // index == bookmarks.value!.content!.results!.length -> itemCount + 1 + if (index == bookmarks.bookmarks.length) { + if (bookmarks.loadingMore) { + return const SizedBox( + height: 80, + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + // Bottom gap for FAB + return const SizedBox(height: 80); + } + return BookmarkItem( + bookmark: bookmarks.bookmarks[index], + onReadUnread: ref.read(bookmarksProvider.notifier).markAsReadUnread, + onDelete: (bookmark) => showDialog( + context: context, + builder: (context) => DeleteBookmarkModal( + bookmark: bookmark, + onDelete: ref.read(bookmarksProvider.notifier).deleteBookmark, + ), ), ); - } - // Bottom gap for FAB - return const SizedBox(height: 80); - } - return BookmarkItem( - bookmark: bookmarks.bookmarks[index], - onReadUnread: ref.read(bookmarksProvider.notifier).markAsReadUnread, - onDelete: (bookmark) => showDialog( - context: context, - builder: (context) => DeleteBookmarkModal( - bookmark: bookmark, - onDelete: ref.read(bookmarksProvider.notifier).deleteBookmark, - ), - ), - ); - }, - ), - ), - ], + }, + ), + ), + ], + ), + ), ), ), ), ), - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: openAddModal, - child: const Icon(Icons.add_rounded), + SnackbarFloatingActionButton( + onPressed: openAddModal, + child: const Icon(Icons.add_rounded), + ), + ], ), ); } diff --git a/lib/screens/onboarding/provider/connect.provider.dart b/lib/screens/onboarding/provider/connect.provider.dart index 92d9010..6afd43e 100644 --- a/lib/screens/onboarding/provider/connect.provider.dart +++ b/lib/screens/onboarding/provider/connect.provider.dart @@ -6,12 +6,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:linkdy/screens/onboarding/model/connect.model.dart'; import 'package:linkdy/providers/dio_interceptor.provider.dart'; +import 'package:linkdy/providers/snackbar.provider.dart'; import 'package:linkdy/providers/server_instances.provider.dart'; import 'package:linkdy/utils/api_base_url.dart'; import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/constants/enums.dart'; import 'package:linkdy/utils/process_modal.dart'; -import 'package:linkdy/utils/snackbar.dart'; import 'package:linkdy/models/server_instance.dart'; import 'package:linkdy/providers/api_client.provider.dart'; import 'package:linkdy/services/api_client.dart'; @@ -116,9 +116,9 @@ FutureOr connectToServer(ConnectToServerRef ref) async { ref.read(apiClientProvider.notifier).setApiClient(apiClient); ref.read(serverInstancesProvider.notifier).saveNewInstance(serverInstance); } else if (result == AuthResult.invalidToken) { - showSnacbkar(label: t.onboarding.invalidToken, color: Colors.red); + ref.read(snackbarProvider.notifier).showSnacbkar(label: t.onboarding.invalidToken, color: Colors.red); } else { - showSnacbkar(label: t.onboarding.cannotConnectToServer, color: Colors.red); + ref.read(snackbarProvider.notifier).showSnacbkar(label: t.onboarding.cannotConnectToServer, color: Colors.red); } return false; diff --git a/lib/screens/onboarding/provider/connect.provider.g.dart b/lib/screens/onboarding/provider/connect.provider.g.dart index c7b2196..94de27c 100644 --- a/lib/screens/onboarding/provider/connect.provider.g.dart +++ b/lib/screens/onboarding/provider/connect.provider.g.dart @@ -6,7 +6,7 @@ part of 'connect.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$connectToServerHash() => r'19a35bf2985d18662ce0b7ece8284cb4e83f1529'; +String _$connectToServerHash() => r'370f2b67b9d20818d94e4d9b6ffb57c44aa18494'; /// See also [connectToServer]. @ProviderFor(connectToServer) diff --git a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart index beaf019..637f0f3 100644 --- a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart +++ b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart @@ -98,7 +98,8 @@ class TagBookmarks extends _$TagBookmarks { } void deleteBookmark(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.deleteBookmark( + final result = await BookmarkCommonFunctions.deleteBookmark( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); @@ -109,7 +110,8 @@ class TagBookmarks extends _$TagBookmarks { } void markAsReadUnread(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.markAsReadUnread( + final result = await BookmarkCommonFunctions.markAsReadUnread( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); @@ -120,7 +122,8 @@ class TagBookmarks extends _$TagBookmarks { } void archiveUnarchive(Bookmark bookmark) async { - final result = await BookmarkCommonFunctions.markAsReadUnread( + final result = await BookmarkCommonFunctions.markAsReadUnread( + ref: ref, bookmark: bookmark, apiClient: ref.read(apiClientProvider)!, ); diff --git a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.g.dart b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.g.dart index 4e43f8b..8c99d68 100644 --- a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.g.dart +++ b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.g.dart @@ -206,7 +206,7 @@ final tagBookmarksRequestLoadMoreProvider = ); typedef TagBookmarksRequestLoadMoreRef = AutoDisposeFutureProviderRef; -String _$tagBookmarksHash() => r'452c0ac002a2c1a5b841d4233fc177a8281f9ea1'; +String _$tagBookmarksHash() => r'ba10c98bdd9e359e885ee8edf56b99cdfcf2eb10'; /// See also [TagBookmarks]. @ProviderFor(TagBookmarks) diff --git a/lib/screens/tags/ui/tags.dart b/lib/screens/tags/ui/tags.dart index 49ff27b..4534295 100644 --- a/lib/screens/tags/ui/tags.dart +++ b/lib/screens/tags/ui/tags.dart @@ -5,10 +5,11 @@ import 'package:linkdy/router/names.dart'; import 'package:linkdy/screens/tags/provider/tags.provider.dart'; import 'package:linkdy/screens/tags/ui/add_tag_modal.dart'; -import 'package:linkdy/utils/date_to_string.dart'; import 'package:linkdy/widgets/error_screen.dart'; import 'package:linkdy/widgets/no_data_screen.dart'; +import 'package:linkdy/widgets/snackbar_fab.dart'; +import 'package:linkdy/utils/date_to_string.dart'; import 'package:linkdy/i18n/strings.g.dart'; class TagsScreen extends ConsumerWidget { @@ -28,113 +29,119 @@ class TagsScreen extends ConsumerWidget { } return Scaffold( - body: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - title: Text(t.tags.tags), - ), - ), - ], - body: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => RefreshIndicator( - displacement: 120, - onRefresh: () => ref.refresh(tagsRequestProvider.future), - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (tags.isLoading && !tags.isRefreshing) - const SliverFillRemaining( - child: Center(child: CircularProgressIndicator()), - ), - if (tags.value != null && tags.value!.successful == false) - SliverFillRemaining( - child: ErrorScreen( - error: t.bookmarks.cannotLoadBookmarks, + body: Stack( + children: [ + NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar.large( + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + title: Text(t.tags.tags), + ), + ), + ], + body: SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => RefreshIndicator( + displacement: 120, + onRefresh: () => ref.refresh(tagsRequestProvider.future), + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), ), - ), - if (tags.value != null && tags.value!.successful == true && tags.value!.content!.results!.isEmpty) - SliverFillRemaining( - child: NoDataScreen( - message: t.bookmarks.noBookmarksAdded, - ), - ), - if (tags.value != null && tags.value!.successful == true && tags.value!.content!.results!.isNotEmpty) - SliverList.builder( - itemCount: tags.value!.content!.results!.length + 1, - itemBuilder: (context, index) { - // index == bookmarks.value!.content!.results!.length -> itemCount + 1 - if (index == tags.value!.content!.results!.length) { - // Bottom gap for FAB - return const SizedBox(height: 80); - } - final tag = tags.value?.content?.results?[index]; - return InkWell( - onTap: () => ref.watch(routerProvider).pushNamed( - RoutesNames.tagBookmarks, - extra: tag, - pathParameters: {'id': tag.id!.toString()}, + if (tags.isLoading && !tags.isRefreshing) + const SliverFillRemaining( + child: Center(child: CircularProgressIndicator()), + ), + if (tags.value != null && tags.value!.successful == false) + SliverFillRemaining( + child: ErrorScreen( + error: t.bookmarks.cannotLoadBookmarks, ), - child: Container( - width: double.maxFinite, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + ), + if (tags.value != null && tags.value!.successful == true && tags.value!.content!.results!.isEmpty) + SliverFillRemaining( + child: NoDataScreen( + message: t.bookmarks.noBookmarksAdded, + ), + ), + if (tags.value != null && + tags.value!.successful == true && + tags.value!.content!.results!.isNotEmpty) + SliverList.builder( + itemCount: tags.value!.content!.results!.length + 1, + itemBuilder: (context, index) { + // index == bookmarks.value!.content!.results!.length -> itemCount + 1 + if (index == tags.value!.content!.results!.length) { + // Bottom gap for FAB + return const SizedBox(height: 80); + } + final tag = tags.value?.content?.results?[index]; + return InkWell( + onTap: () => ref.watch(routerProvider).pushNamed( + RoutesNames.tagBookmarks, + extra: tag, + pathParameters: {'id': tag.id!.toString()}, + ), + child: Container( + width: double.maxFinite, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( - Icons.label_rounded, - size: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.7), - ), - const SizedBox(width: 8), - Flexible( - child: Text( - tag!.name!, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, + Row( + children: [ + Icon( + Icons.label_rounded, + size: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.7), ), + const SizedBox(width: 8), + Flexible( + child: Text( + tag!.name!, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + t.tags.created(created: dateToString(tag.dateAdded!)), + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), - const SizedBox(height: 4), - Text( - t.tags.created(created: dateToString(tag.dateAdded!)), - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - ); - }, - ), - ], + ), + ); + }, + ), + ], + ), + ), ), ), ), - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: openAddModal, - child: const Icon(Icons.add_rounded), + SnackbarFloatingActionButton( + onPressed: openAddModal, + child: const Icon(Icons.add_rounded), + ), + ], ), ); } diff --git a/lib/utils/snackbar.dart b/lib/utils/snackbar.dart deleted file mode 100644 index 5271515..0000000 --- a/lib/utils/snackbar.dart +++ /dev/null @@ -1,22 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'package:flutter/material.dart'; -import 'package:linkdy/constants/global_keys.dart'; - -void showSnacbkar({ - required String label, - required Color color, - Color? labelColor, - GlobalKey? key, -}) async { - final GlobalKey scaffoldKey = key ?? scaffoldMessengerGlobalKey; - - final snackBar = SnackBar( - content: Text( - label, - style: TextStyle(color: labelColor ?? Colors.white), - ), - backgroundColor: color, - ); - scaffoldKey.currentState?.showSnackBar(snackBar); -} diff --git a/lib/widgets/snackbar_fab.dart b/lib/widgets/snackbar_fab.dart new file mode 100644 index 0000000..e63e2f0 --- /dev/null +++ b/lib/widgets/snackbar_fab.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:linkdy/providers/snackbar.provider.dart'; + +class SnackbarFloatingActionButton extends ConsumerWidget { + final Widget child; + final void Function() onPressed; + + const SnackbarFloatingActionButton({ + Key? key, + required this.onPressed, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AnimatedPositioned( + bottom: ref.watch(snackbarProvider).visible == true ? 70 : 20, + right: 20, + duration: const Duration(milliseconds: 200), + curve: Curves.ease, + child: FloatingActionButton( + onPressed: onPressed, + child: child, + ), + ); + } +}