From d461dd4840660de93e6a2a198dc9b95d96bd71e4 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Wed, 28 Feb 2024 15:58:40 +0100 Subject: [PATCH] Added read status selector --- lib/constants/enums.dart | 2 + lib/i18n/strings.g.dart | 36 ++++- lib/i18n/strings_en.i18n.json | 10 +- lib/i18n/strings_es.i18n.json | 10 +- .../bookmarks/model/bookmarks.model.dart | 2 + .../provider/bookmarks.provider.dart | 14 +- .../provider/bookmarks.provider.g.dart | 25 ++- .../provider/favicon_loader.provider.g.dart | 2 +- lib/screens/bookmarks/ui/bookmarks.dart | 143 +++++++++++++++--- lib/services/api_client.dart | 12 +- pubspec.lock | 16 ++ pubspec.yaml | 1 + 12 files changed, 238 insertions(+), 35 deletions(-) diff --git a/lib/constants/enums.dart b/lib/constants/enums.dart index 4308001..102e340 100644 --- a/lib/constants/enums.dart +++ b/lib/constants/enums.dart @@ -7,3 +7,5 @@ enum SelectedTheme { system, light, dark } enum AuthResult { success, invalidToken, other } enum FilteredBookmarksMode { tag, shared, archived } + +enum ReadStatus { all, unread, read } diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index c7a0fdd..41ac2cc 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 332 (166 per locale) +/// Strings: 348 (174 per locale) /// -/// Built on 2024-02-28 at 00:11 UTC +/// Built on 2024-02-28 at 14:44 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -172,6 +172,7 @@ class _StringsGenericEn { String get error => 'Error'; String get optional => 'Optional'; String get authTokenNotValid => 'Your authorization token is not valid anymore. Please, reauthenticate.'; + String get options => 'Options'; } // Path: onboarding @@ -226,6 +227,13 @@ class _StringsBookmarksEn { late final _StringsBookmarksDatesEn dates = _StringsBookmarksDatesEn._(_root); String get shared => 'Shared'; String get archived => 'Archived'; + String get showOnlyRead => 'Show only read'; + String get showOnlyUnread => 'Show only unread'; + String get showAllBookmarks => 'Show all bookmarks'; + String get readStatus => 'Read status'; + String get all => 'All'; + String get unread => 'Unread'; + String get read => 'Read'; late final _StringsBookmarksAddBookmarkEn addBookmark = _StringsBookmarksAddBookmarkEn._(_root); late final _StringsBookmarksSearchEn search = _StringsBookmarksSearchEn._(_root); late final _StringsBookmarksBookmarkOptionsEn bookmarkOptions = _StringsBookmarksBookmarkOptionsEn._(_root); @@ -534,6 +542,7 @@ class _StringsGenericEs implements _StringsGenericEn { @override String get error => 'Error'; @override String get optional => 'Opcional'; @override String get authTokenNotValid => 'Su token de autorización ya no es válido. Por favor, vuelva a autenticarse.'; + @override String get options => 'Opciones'; } // Path: onboarding @@ -588,6 +597,13 @@ class _StringsBookmarksEs implements _StringsBookmarksEn { @override late final _StringsBookmarksDatesEs dates = _StringsBookmarksDatesEs._(_root); @override String get shared => 'Compartidos'; @override String get archived => 'Archivados'; + @override String get showOnlyRead => 'Mostrar solo leídos'; + @override String get showOnlyUnread => 'Mostrar solo no leídos'; + @override String get showAllBookmarks => 'Mostrar todos los marcadores'; + @override String get readStatus => 'Estado de lectura'; + @override String get all => 'Todos'; + @override String get unread => 'No leídos'; + @override String get read => 'Leídos'; @override late final _StringsBookmarksAddBookmarkEs addBookmark = _StringsBookmarksAddBookmarkEs._(_root); @override late final _StringsBookmarksSearchEs search = _StringsBookmarksSearchEs._(_root); @override late final _StringsBookmarksBookmarkOptionsEs bookmarkOptions = _StringsBookmarksBookmarkOptionsEs._(_root); @@ -862,6 +878,7 @@ extension on Translations { case 'generic.error': return 'Error'; case 'generic.optional': return 'Optional'; case 'generic.authTokenNotValid': return 'Your authorization token is not valid anymore. Please, reauthenticate.'; + case 'generic.options': return 'Options'; case 'onboarding.title': return 'Welcome to Linkdy'; case 'onboarding.subtitle': return 'An application to manage your bookmarks.'; case 'onboarding.start': return 'Start'; @@ -899,6 +916,13 @@ extension on Translations { case 'bookmarks.dates.yesterdayAt': return ({required Object time}) => 'Yesterday, ${time}'; case 'bookmarks.shared': return 'Shared'; case 'bookmarks.archived': return 'Archived'; + case 'bookmarks.showOnlyRead': return 'Show only read'; + case 'bookmarks.showOnlyUnread': return 'Show only unread'; + case 'bookmarks.showAllBookmarks': return 'Show all bookmarks'; + case 'bookmarks.readStatus': return 'Read status'; + case 'bookmarks.all': return 'All'; + case 'bookmarks.unread': return 'Unread'; + case 'bookmarks.read': return 'Read'; case 'bookmarks.addBookmark.addBookmark': return 'Add new bookmark'; case 'bookmarks.addBookmark.editBookmark': return 'Edit bookmark'; case 'bookmarks.addBookmark.bookmarkUrl': return 'Bookmark URL'; @@ -1036,6 +1060,7 @@ extension on _StringsEs { case 'generic.error': return 'Error'; case 'generic.optional': return 'Opcional'; case 'generic.authTokenNotValid': return 'Su token de autorización ya no es válido. Por favor, vuelva a autenticarse.'; + case 'generic.options': return 'Opciones'; case 'onboarding.title': return 'Bienvenido a Linkdy'; case 'onboarding.subtitle': return 'Una aplicación para gestionar tus enlaces favoritos.'; case 'onboarding.start': return 'Comenzar'; @@ -1073,6 +1098,13 @@ extension on _StringsEs { case 'bookmarks.dates.yesterdayAt': return ({required Object time}) => 'Ayer, ${time}'; case 'bookmarks.shared': return 'Compartidos'; case 'bookmarks.archived': return 'Archivados'; + case 'bookmarks.showOnlyRead': return 'Mostrar solo leídos'; + case 'bookmarks.showOnlyUnread': return 'Mostrar solo no leídos'; + case 'bookmarks.showAllBookmarks': return 'Mostrar todos los marcadores'; + case 'bookmarks.readStatus': return 'Estado de lectura'; + case 'bookmarks.all': return 'Todos'; + case 'bookmarks.unread': return 'No leídos'; + case 'bookmarks.read': return 'Leídos'; case 'bookmarks.addBookmark.addBookmark': return 'Añadir nuevo marcador'; case 'bookmarks.addBookmark.editBookmark': return 'Editar marcador'; case 'bookmarks.addBookmark.url': return 'URL'; diff --git a/lib/i18n/strings_en.i18n.json b/lib/i18n/strings_en.i18n.json index 8531d1b..2e8f7a3 100644 --- a/lib/i18n/strings_en.i18n.json +++ b/lib/i18n/strings_en.i18n.json @@ -7,7 +7,8 @@ "close": "Close", "error": "Error", "optional": "Optional", - "authTokenNotValid": "Your authorization token is not valid anymore. Please, reauthenticate." + "authTokenNotValid": "Your authorization token is not valid anymore. Please, reauthenticate.", + "options": "Options" }, "onboarding": { "title": "Welcome to Linkdy", @@ -51,6 +52,13 @@ }, "shared": "Shared", "archived": "Archived", + "showOnlyRead": "Show only read", + "showOnlyUnread": "Show only unread", + "showAllBookmarks": "Show all bookmarks", + "readStatus": "Read status", + "all": "All", + "unread": "Unread", + "read": "Read", "addBookmark": { "addBookmark": "Add new bookmark", "editBookmark": "Edit bookmark", diff --git a/lib/i18n/strings_es.i18n.json b/lib/i18n/strings_es.i18n.json index 37548a4..4bdadf9 100644 --- a/lib/i18n/strings_es.i18n.json +++ b/lib/i18n/strings_es.i18n.json @@ -7,7 +7,8 @@ "close": "Cerrar", "error": "Error", "optional": "Opcional", - "authTokenNotValid": "Su token de autorización ya no es válido. Por favor, vuelva a autenticarse." + "authTokenNotValid": "Su token de autorización ya no es válido. Por favor, vuelva a autenticarse.", + "options": "Opciones" }, "onboarding": { "title": "Bienvenido a Linkdy", @@ -51,6 +52,13 @@ }, "shared": "Compartidos", "archived": "Archivados", + "showOnlyRead": "Mostrar solo leídos", + "showOnlyUnread": "Mostrar solo no leídos", + "showAllBookmarks": "Mostrar todos los marcadores", + "readStatus": "Estado de lectura", + "all": "Todos", + "unread": "No leídos", + "read": "Leídos", "addBookmark": { "addBookmark": "Añadir nuevo marcador", "editBookmark": "Editar marcador", diff --git a/lib/screens/bookmarks/model/bookmarks.model.dart b/lib/screens/bookmarks/model/bookmarks.model.dart index 014841e..bb322d9 100644 --- a/lib/screens/bookmarks/model/bookmarks.model.dart +++ b/lib/screens/bookmarks/model/bookmarks.model.dart @@ -9,6 +9,7 @@ class BookmarksModel { LoadStatus inialLoadStatus; bool loadingMore; int maxNumber; + ReadStatus readStatus; BookmarksModel({ this.currentPage = 0, @@ -17,5 +18,6 @@ class BookmarksModel { this.inialLoadStatus = LoadStatus.loading, this.loadingMore = false, this.maxNumber = 0, + this.readStatus = ReadStatus.all, }); } diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.dart b/lib/screens/bookmarks/provider/bookmarks.provider.dart index fc77a32..fece45a 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.dart @@ -12,10 +12,11 @@ import 'package:linkdy/constants/enums.dart'; part 'bookmarks.provider.g.dart'; @riverpod -FutureOr bookmarksRequest(BookmarksRequestRef ref, int limit) async { +FutureOr bookmarksRequest(BookmarksRequestRef ref, ReadStatus readStatus, int limit) async { final result = await ref.watch(apiClientProvider)!.fetchBookmarks( limit: limit, offset: 0, + unread: readStatus, ); if (result.successful == true) { @@ -40,6 +41,7 @@ FutureOr bookmarksRequestLoadMore(BookmarksRequestLoadMoreRef ref) async { final result = await ref.watch(apiClientProvider)!.fetchBookmarks( limit: provider.limit, offset: newOffset, + unread: ref.read(bookmarksProvider).readStatus, ); if (result.successful == true) { @@ -59,7 +61,7 @@ class Bookmarks extends _$Bookmarks { final model = BookmarksModel( bookmarks: [], ); - ref.read(bookmarksRequestProvider(model.limit)); + ref.read(bookmarksRequestProvider(model.readStatus, model.limit)); return model; } @@ -85,8 +87,14 @@ class Bookmarks extends _$Bookmarks { ref.notifyListeners(); } + void setReadStatus(ReadStatus status) { + state.readStatus = status; + ref.read(bookmarksRequestProvider(status, state.limit)); + ref.notifyListeners(); + } + Future refresh() async { - await ref.read(bookmarksRequestProvider(state.limit).future); + await ref.read(bookmarksRequestProvider(state.readStatus, state.limit).future); } void deleteBookmark(Bookmark bookmark) async { diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.g.dart b/lib/screens/bookmarks/provider/bookmarks.provider.g.dart index 0f17e8e..e3e516d 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.g.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.g.dart @@ -6,7 +6,7 @@ part of 'bookmarks.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$bookmarksRequestHash() => r'2da88ec52b4f959e6355f5f5fa8660d6cebd157d'; +String _$bookmarksRequestHash() => r'eb1ddffb83edc0fa433fae76859bde4206e59940'; /// Copied from Dart SDK class _SystemHash { @@ -40,9 +40,11 @@ class BookmarksRequestFamily extends Family> { /// See also [bookmarksRequest]. BookmarksRequestProvider call( + ReadStatus readStatus, int limit, ) { return BookmarksRequestProvider( + readStatus, limit, ); } @@ -52,6 +54,7 @@ class BookmarksRequestFamily extends Family> { covariant BookmarksRequestProvider provider, ) { return call( + provider.readStatus, provider.limit, ); } @@ -75,10 +78,12 @@ class BookmarksRequestFamily extends Family> { class BookmarksRequestProvider extends AutoDisposeFutureProvider { /// See also [bookmarksRequest]. BookmarksRequestProvider( + ReadStatus readStatus, int limit, ) : this._internal( (ref) => bookmarksRequest( ref as BookmarksRequestRef, + readStatus, limit, ), from: bookmarksRequestProvider, @@ -90,6 +95,7 @@ class BookmarksRequestProvider extends AutoDisposeFutureProvider { dependencies: BookmarksRequestFamily._dependencies, allTransitiveDependencies: BookmarksRequestFamily._allTransitiveDependencies, + readStatus: readStatus, limit: limit, ); @@ -100,9 +106,11 @@ class BookmarksRequestProvider extends AutoDisposeFutureProvider { required super.allTransitiveDependencies, required super.debugGetCreateSourceHash, required super.from, + required this.readStatus, required this.limit, }) : super.internal(); + final ReadStatus readStatus; final int limit; @override @@ -118,6 +126,7 @@ class BookmarksRequestProvider extends AutoDisposeFutureProvider { dependencies: null, allTransitiveDependencies: null, debugGetCreateSourceHash: null, + readStatus: readStatus, limit: limit, ), ); @@ -130,12 +139,15 @@ class BookmarksRequestProvider extends AutoDisposeFutureProvider { @override bool operator ==(Object other) { - return other is BookmarksRequestProvider && other.limit == limit; + return other is BookmarksRequestProvider && + other.readStatus == readStatus && + other.limit == limit; } @override int get hashCode { var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, readStatus.hashCode); hash = _SystemHash.combine(hash, limit.hashCode); return _SystemHash.finish(hash); @@ -143,6 +155,9 @@ class BookmarksRequestProvider extends AutoDisposeFutureProvider { } mixin BookmarksRequestRef on AutoDisposeFutureProviderRef { + /// The parameter `readStatus` of this provider. + ReadStatus get readStatus; + /// The parameter `limit` of this provider. int get limit; } @@ -151,12 +166,14 @@ class _BookmarksRequestProviderElement extends AutoDisposeFutureProviderElement with BookmarksRequestRef { _BookmarksRequestProviderElement(super.provider); + @override + ReadStatus get readStatus => (origin as BookmarksRequestProvider).readStatus; @override int get limit => (origin as BookmarksRequestProvider).limit; } String _$bookmarksRequestLoadMoreHash() => - r'36b0703684218e1c6bf01643788d5f9d3da32327'; + r'1a206bd974677fd862eb4d5b5f453df9f566d995'; /// See also [bookmarksRequestLoadMore]. @ProviderFor(bookmarksRequestLoadMore) @@ -172,7 +189,7 @@ final bookmarksRequestLoadMoreProvider = ); typedef BookmarksRequestLoadMoreRef = AutoDisposeFutureProviderRef; -String _$bookmarksHash() => r'602a50d02cde9a67335372659f8e2f656ac21995'; +String _$bookmarksHash() => r'0a1d73459b3c148ba31655347364ee3a9e70b68a'; /// See also [Bookmarks]. @ProviderFor(Bookmarks) diff --git a/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart b/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart index f4e401e..73226b8 100644 --- a/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart +++ b/lib/screens/bookmarks/provider/favicon_loader.provider.g.dart @@ -6,7 +6,7 @@ part of 'favicon_loader.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$faviconStoreHash() => r'aeda388d2c7d220a43a29825a9850d8d7433d84f'; +String _$faviconStoreHash() => r'459949315bf6a236946c8b03ba90ce047b691b7e'; /// See also [FaviconStore]. @ProviderFor(FaviconStore) diff --git a/lib/screens/bookmarks/ui/bookmarks.dart b/lib/screens/bookmarks/ui/bookmarks.dart index 76ab93b..1310aa0 100644 --- a/lib/screens/bookmarks/ui/bookmarks.dart +++ b/lib/screens/bookmarks/ui/bookmarks.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:quds_popup_menu/quds_popup_menu.dart'; import 'package:linkdy/screens/bookmarks/provider/bookmarks.provider.dart'; import 'package:linkdy/screens/bookmarks/ui/bookmark_item.dart'; @@ -50,6 +51,22 @@ class BookmarksScreen extends ConsumerWidget { return false; } + String readStatus() { + switch (bookmarks.readStatus) { + case ReadStatus.all: + return t.bookmarks.all; + + case ReadStatus.unread: + return t.bookmarks.unread; + + case ReadStatus.read: + return t.bookmarks.read; + + default: + return ""; + } + } + return ScaffoldMessenger( key: ScaffoldMessengerKeys.bookmarks, child: Scaffold( @@ -62,7 +79,44 @@ class BookmarksScreen extends ConsumerWidget { floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, - title: Text(t.bookmarks.bookmarks), + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text(t.bookmarks.bookmarks), + if (bookmarks.readStatus == ReadStatus.read || bookmarks.readStatus == ReadStatus.unread) + Container( + margin: const EdgeInsets.only(left: 8), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.primaryContainer, + ), + child: Row( + children: [ + Icon( + bookmarks.readStatus == ReadStatus.read + ? Icons.mark_email_read_rounded + : Icons.mark_as_unread_rounded, + size: 14, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + const SizedBox(width: 4), + Text( + bookmarks.readStatus == ReadStatus.read ? t.bookmarks.read : t.bookmarks.unread, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ], + ), actions: [ IconButton( key: _searchButtonKey, @@ -70,31 +124,76 @@ class BookmarksScreen extends ConsumerWidget { icon: const Icon(Icons.search_rounded), tooltip: t.bookmarks.search.searchBookmarks, ), - PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - onTap: () => ref.read(routerProvider).push(RoutesPaths.archivedBookmarks), - child: Row( - children: [ - const Icon(Icons.archive_rounded), - const SizedBox(width: 16), - Text(t.bookmarks.archived), - ], - ), + const SizedBox(width: 8), + QudsPopupButton( + radius: 22, + backgroundColor: Theme.of(context).popupMenuTheme.surfaceTintColor, + items: [ + QudsPopupMenuItem( + leading: const Icon(Icons.archive_rounded), + title: Text(t.bookmarks.archived), + onPressed: () => ref.read(routerProvider).push(RoutesPaths.archivedBookmarks), ), - PopupMenuItem( - onTap: () => ref.read(routerProvider).push(RoutesPaths.sharedBookmarks), - child: Row( - children: [ - const Icon(Icons.share_rounded), - const SizedBox(width: 16), - Text(t.bookmarks.shared), - ], - ), + QudsPopupMenuItem( + leading: const Icon(Icons.share_rounded), + title: Text(t.bookmarks.shared), + onPressed: () => ref.read(routerProvider).push(RoutesPaths.sharedBookmarks), + ), + QudsPopupMenuDivider(), + QudsPopupMenuSection( + leading: const Icon(Icons.list_rounded), + titleText: t.bookmarks.readStatus, + subTitle: Text(readStatus()), + subItems: [ + QudsPopupMenuItem( + leading: const Icon(Icons.list_rounded), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(t.bookmarks.showAllBookmarks)), + if (bookmarks.readStatus == ReadStatus.all) ...[ + const SizedBox(width: 8), + const Icon(Icons.check_rounded), + ], + ], + ), + onPressed: () => ref.read(bookmarksProvider.notifier).setReadStatus(ReadStatus.all), + ), + QudsPopupMenuItem( + leading: const Icon(Icons.mark_as_unread_rounded), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(t.bookmarks.showOnlyUnread)), + if (bookmarks.readStatus == ReadStatus.unread) ...[ + const SizedBox(width: 8), + const Icon(Icons.check_rounded), + ], + ], + ), + onPressed: () => ref.read(bookmarksProvider.notifier).setReadStatus(ReadStatus.unread), + ), + QudsPopupMenuItem( + leading: const Icon(Icons.mark_email_read_rounded), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(t.bookmarks.showOnlyRead)), + if (bookmarks.readStatus == ReadStatus.read) ...[ + const SizedBox(width: 8), + const Icon(Icons.check_rounded), + ], + ], + ), + onPressed: () => ref.read(bookmarksProvider.notifier).setReadStatus(ReadStatus.read), + ), + ], ), ], + tooltip: t.generic.options, + child: const Icon(Icons.more_vert_rounded), ), - const SizedBox(width: 8), + const SizedBox(width: 16), ], ), ), diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index 188aa79..79bfa1d 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -47,7 +47,12 @@ class ApiClientService { } } - Future> fetchBookmarks({String? q, int? limit, int? offset}) async { + Future> fetchBookmarks({ + String? q, + int? limit, + int? offset, + ReadStatus? unread, + }) async { try { final response = await dioInstance.get( "/bookmarks/", @@ -55,6 +60,11 @@ class ApiClientService { "q": q, "limit": limit, "offset": offset, + "unread": unread == ReadStatus.unread + ? "yes" + : unread == ReadStatus.read + ? "no" + : null, }, ); return ApiResponse( diff --git a/pubspec.lock b/pubspec.lock index 4a2b712..fbff66d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -853,6 +853,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" + quds_popup_menu: + dependency: "direct main" + description: + name: quds_popup_menu + sha256: d0f3f2f84c0bbf2934b961abbdafaa112c7ff9057585a24526a1ef61d2cd79e7 + url: "https://pub.dev" + source: hosted + version: "0.0.1+1" + quds_ui_kit: + dependency: transitive + description: + name: quds_ui_kit + sha256: "12018041085bf3f6bdb30744ba86cdb4b3a8e7d51dc2b271f275885508dd21f4" + url: "https://pub.dev" + source: hosted + version: "0.0.7+10" riverpod: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8ad840f..df282e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: skeletonizer: ^1.0.1 http: ^1.2.0 flutter_slidable: ^3.0.1 + quds_popup_menu: ^0.0.1+1 dev_dependencies: build_runner: ^2.4.8