diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 35e4a0a..e031953 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: 224 (112 per locale) +/// Strings: 226 (113 per locale) /// -/// Built on 2024-02-24 at 15:14 UTC +/// Built on 2024-02-24 at 17:36 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -229,6 +229,7 @@ class _StringsTagsEn { String get tags => 'Tags'; String created({required Object created}) => 'Created: ${created}'; late final _StringsTagsCreateTagEn createTag = _StringsTagsCreateTagEn._(_root); + late final _StringsTagsTagBookmarksEn tagBookmarks = _StringsTagsTagBookmarksEn._(_root); } // Path: settings @@ -356,6 +357,16 @@ class _StringsTagsCreateTagEn { String get errorCreatingTag => 'An error occured when creating the tag.'; } +// Path: tags.tagBookmarks +class _StringsTagsTagBookmarksEn { + _StringsTagsTagBookmarksEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get noBookmarksWithThisTag => 'There are no bookmarks with this tag'; +} + // Path: settings.customization class _StringsSettingsCustomizationEn { _StringsSettingsCustomizationEn._(this._root); @@ -507,6 +518,7 @@ class _StringsTagsEs implements _StringsTagsEn { @override String get tags => 'Etiquetas'; @override String created({required Object created}) => 'Creado: ${created}'; @override late final _StringsTagsCreateTagEs createTag = _StringsTagsCreateTagEs._(_root); + @override late final _StringsTagsTagBookmarksEs tagBookmarks = _StringsTagsTagBookmarksEs._(_root); } // Path: settings @@ -634,6 +646,16 @@ class _StringsTagsCreateTagEs implements _StringsTagsCreateTagEn { @override String get errorCreatingTag => 'Ocurrió un error al crear la etiqueta.'; } +// Path: tags.tagBookmarks +class _StringsTagsTagBookmarksEs implements _StringsTagsTagBookmarksEn { + _StringsTagsTagBookmarksEs._(this._root); + + @override final _StringsEs _root; // ignore: unused_field + + // Translations + @override String get noBookmarksWithThisTag => 'No hay marcadores con esta etiqueta'; +} + // Path: settings.customization class _StringsSettingsCustomizationEs implements _StringsSettingsCustomizationEn { _StringsSettingsCustomizationEs._(this._root); @@ -755,6 +777,7 @@ extension on Translations { case 'tags.createTag.name': return 'Name'; case 'tags.createTag.creatingTag': return 'Creating tag...'; case 'tags.createTag.errorCreatingTag': return 'An error occured when creating the tag.'; + case 'tags.tagBookmarks.noBookmarksWithThisTag': return 'There are no bookmarks with this tag'; case 'settings.settings': return 'Settings'; case 'settings.appSettings': return 'App settings'; case 'settings.aboutApp': return 'About the app'; @@ -875,6 +898,7 @@ extension on _StringsEs { case 'tags.createTag.name': return 'Nombre'; case 'tags.createTag.creatingTag': return 'Creando etiqueta...'; case 'tags.createTag.errorCreatingTag': return 'Ocurrió un error al crear la etiqueta.'; + case 'tags.tagBookmarks.noBookmarksWithThisTag': return 'No hay marcadores con esta etiqueta'; case 'settings.settings': return 'Ajustes'; case 'settings.appSettings': return 'Ajustes de la aplicación'; case 'settings.aboutApp': return 'Sobre la aplicación'; diff --git a/lib/i18n/strings_en.i18n.json b/lib/i18n/strings_en.i18n.json index aa1c8ee..fecef1c 100644 --- a/lib/i18n/strings_en.i18n.json +++ b/lib/i18n/strings_en.i18n.json @@ -82,6 +82,9 @@ "name": "Name", "creatingTag": "Creating tag...", "errorCreatingTag": "An error occured when creating the tag." + }, + "tagBookmarks": { + "noBookmarksWithThisTag": "There are no bookmarks with this tag" } }, "settings": { diff --git a/lib/i18n/strings_es.i18n.json b/lib/i18n/strings_es.i18n.json index bcacba7..86c9259 100644 --- a/lib/i18n/strings_es.i18n.json +++ b/lib/i18n/strings_es.i18n.json @@ -82,6 +82,9 @@ "name": "Nombre", "creatingTag": "Creando etiqueta...", "errorCreatingTag": "Ocurrió un error al crear la etiqueta." + }, + "tagBookmarks": { + "noBookmarksWithThisTag": "No hay marcadores con esta etiqueta" } }, "settings": { diff --git a/lib/providers/app_status_provider.g.dart b/lib/providers/app_status_provider.g.dart index 22feff7..21d66bd 100644 --- a/lib/providers/app_status_provider.g.dart +++ b/lib/providers/app_status_provider.g.dart @@ -21,7 +21,7 @@ final selectedThemeProvider = AutoDisposeProvider.internal( ); typedef SelectedThemeRef = AutoDisposeProviderRef; -String _$appStatusHash() => r'7f263e1933715c0ec6305e453af99b9a264156b6'; +String _$appStatusHash() => r'3b87acee218adcc5707acfb30fd6e5c650b410ce'; /// See also [AppStatus]. @ProviderFor(AppStatus) diff --git a/lib/router/names.dart b/lib/router/names.dart new file mode 100644 index 0000000..3775fcf --- /dev/null +++ b/lib/router/names.dart @@ -0,0 +1,3 @@ +class RoutesNames { + static const tagBookmarks = "tagBookmarks"; +} diff --git a/lib/router/paths.dart b/lib/router/paths.dart index 554685a..81bd540 100644 --- a/lib/router/paths.dart +++ b/lib/router/paths.dart @@ -4,6 +4,7 @@ class RoutesPaths { static const bookmarksSearch = "/bookmarks/search"; static const webview = "/webview"; static const tags = "/tags"; + static const tagBookmarks = "/tags/:id"; static const settings = "/settings"; static const customization = "/settings/customization"; static const generalSettings = "/settings/general"; diff --git a/lib/router/routes.dart b/lib/router/routes.dart index 934869f..6523ae6 100644 --- a/lib/router/routes.dart +++ b/lib/router/routes.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:linkdy/models/data/tags.dart'; +import 'package:linkdy/router/names.dart'; import 'package:linkdy/screens/bookmarks/ui/bookmarks.dart'; import 'package:linkdy/screens/bookmarks/ui/search_bookmarks.dart'; @@ -7,6 +9,7 @@ import 'package:linkdy/screens/onboarding/ui/onboarding.dart'; import 'package:linkdy/screens/settings/ui/customization/customization.dart'; import 'package:linkdy/screens/settings/ui/general_settings/general_settings.dart'; import 'package:linkdy/screens/settings/ui/settings.dart'; +import 'package:linkdy/screens/tag_bookmarks/ui/tag_bookmarks.dart'; import 'package:linkdy/screens/tags/ui/tags.dart'; import 'package:linkdy/screens/webview/ui/webview.dart'; import 'package:linkdy/widgets/layout.dart'; @@ -49,6 +52,11 @@ final List appRoutes = [ path: RoutesPaths.bookmarksSearch, builder: (context, state) => const SearchBookmarksScreen(), ), + GoRoute( + path: RoutesPaths.tagBookmarks, + name: RoutesNames.tagBookmarks, + builder: (context, state) => TagBookmarksScreen(tag: state.extra as Tag), + ), GoRoute( path: RoutesPaths.customization, builder: (context, state) => const Customization(), diff --git a/lib/screens/bookmarks/model/links.model.dart b/lib/screens/bookmarks/model/bookmarks.model.dart similarity index 100% rename from lib/screens/bookmarks/model/links.model.dart rename to lib/screens/bookmarks/model/bookmarks.model.dart diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.dart b/lib/screens/bookmarks/provider/bookmarks.provider.dart index 063a65f..ce777e3 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.dart @@ -1,7 +1,7 @@ import 'package:linkdy/models/data/bookmarks.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:linkdy/screens/bookmarks/model/links.model.dart'; +import 'package:linkdy/screens/bookmarks/model/bookmarks.model.dart'; import 'package:linkdy/models/api_response.dart'; import 'package:linkdy/providers/api_client_provider.dart'; diff --git a/lib/screens/tag_bookmarks/model/tag_bookmarks.model.dart b/lib/screens/tag_bookmarks/model/tag_bookmarks.model.dart new file mode 100644 index 0000000..3a725bb --- /dev/null +++ b/lib/screens/tag_bookmarks/model/tag_bookmarks.model.dart @@ -0,0 +1,9 @@ +class TagBookmarksModel { + int currentPage; + int limit; + + TagBookmarksModel({ + this.currentPage = 0, + this.limit = 100, + }); +} diff --git a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart new file mode 100644 index 0000000..c61f2d5 --- /dev/null +++ b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart @@ -0,0 +1,31 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'package:linkdy/screens/tag_bookmarks/model/tag_bookmarks.model.dart'; + +import 'package:linkdy/models/api_response.dart'; +import 'package:linkdy/models/data/bookmarks.dart'; +import 'package:linkdy/providers/api_client_provider.dart'; + +part 'tag_bookmarks.provider.g.dart'; + +@riverpod +FutureOr> tagBookmarksRequest(TagBookmarksRequestRef ref, String tagName) async { + final result = await ref.watch(apiClientProvider)!.fetchBookmarks(q: tagName); + return result; +} + +@riverpod +class TagBookmarks extends _$TagBookmarks { + @override + TagBookmarksModel build() { + return TagBookmarksModel(); + } + + void setCurrentPage(int page) { + state.currentPage = page; + } + + void setLimit(int limit) { + state.limit = limit; + } +} diff --git a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.g.dart b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.g.dart new file mode 100644 index 0000000..52a7e80 --- /dev/null +++ b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.g.dart @@ -0,0 +1,182 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tag_bookmarks.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$tagBookmarksRequestHash() => + r'0625c18022480eb1356396d9e46f677f21106797'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [tagBookmarksRequest]. +@ProviderFor(tagBookmarksRequest) +const tagBookmarksRequestProvider = TagBookmarksRequestFamily(); + +/// See also [tagBookmarksRequest]. +class TagBookmarksRequestFamily + extends Family>> { + /// See also [tagBookmarksRequest]. + const TagBookmarksRequestFamily(); + + /// See also [tagBookmarksRequest]. + TagBookmarksRequestProvider call( + String tagName, + ) { + return TagBookmarksRequestProvider( + tagName, + ); + } + + @override + TagBookmarksRequestProvider getProviderOverride( + covariant TagBookmarksRequestProvider provider, + ) { + return call( + provider.tagName, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'tagBookmarksRequestProvider'; +} + +/// See also [tagBookmarksRequest]. +class TagBookmarksRequestProvider + extends AutoDisposeFutureProvider> { + /// See also [tagBookmarksRequest]. + TagBookmarksRequestProvider( + String tagName, + ) : this._internal( + (ref) => tagBookmarksRequest( + ref as TagBookmarksRequestRef, + tagName, + ), + from: tagBookmarksRequestProvider, + name: r'tagBookmarksRequestProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$tagBookmarksRequestHash, + dependencies: TagBookmarksRequestFamily._dependencies, + allTransitiveDependencies: + TagBookmarksRequestFamily._allTransitiveDependencies, + tagName: tagName, + ); + + TagBookmarksRequestProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.tagName, + }) : super.internal(); + + final String tagName; + + @override + Override overrideWith( + FutureOr> Function( + TagBookmarksRequestRef provider) + create, + ) { + return ProviderOverride( + origin: this, + override: TagBookmarksRequestProvider._internal( + (ref) => create(ref as TagBookmarksRequestRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + tagName: tagName, + ), + ); + } + + @override + AutoDisposeFutureProviderElement> + createElement() { + return _TagBookmarksRequestProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is TagBookmarksRequestProvider && other.tagName == tagName; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, tagName.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin TagBookmarksRequestRef + on AutoDisposeFutureProviderRef> { + /// The parameter `tagName` of this provider. + String get tagName; +} + +class _TagBookmarksRequestProviderElement + extends AutoDisposeFutureProviderElement> + with TagBookmarksRequestRef { + _TagBookmarksRequestProviderElement(super.provider); + + @override + String get tagName => (origin as TagBookmarksRequestProvider).tagName; +} + +String _$tagBookmarksHash() => r'bcfcc986b8d58ccff54ed6dec3ab2a1a8c33a4f7'; + +/// See also [TagBookmarks]. +@ProviderFor(TagBookmarks) +final tagBookmarksProvider = + AutoDisposeNotifierProvider.internal( + TagBookmarks.new, + name: r'tagBookmarksProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$tagBookmarksHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TagBookmarks = AutoDisposeNotifier; +// 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/tag_bookmarks/ui/tag_bookmarks.dart b/lib/screens/tag_bookmarks/ui/tag_bookmarks.dart new file mode 100644 index 0000000..838e724 --- /dev/null +++ b/lib/screens/tag_bookmarks/ui/tag_bookmarks.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:linkdy/screens/bookmarks/ui/bookmark_item.dart'; +import 'package:linkdy/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart'; +import 'package:linkdy/widgets/error_screen.dart'; +import 'package:linkdy/widgets/no_data_screen.dart'; + +import 'package:linkdy/i18n/strings.g.dart'; +import 'package:linkdy/models/data/tags.dart'; + +class TagBookmarksScreen extends ConsumerWidget { + final Tag tag; + + const TagBookmarksScreen({ + Key? key, + required this.tag, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bookmarks = ref.watch(tagBookmarksRequestProvider(tag.name!)); + + 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(tag.name!), + ), + ), + ], + body: SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => RefreshIndicator( + displacement: 120, + onRefresh: () => ref.refresh(tagBookmarksRequestProvider(tag.name!).future), + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + if (bookmarks.isLoading && !bookmarks.isRefreshing) + const SliverFillRemaining( + child: Center(child: CircularProgressIndicator()), + ), + if (bookmarks.value != null && bookmarks.value!.successful == false) + SliverFillRemaining( + child: ErrorScreen( + error: t.bookmarks.cannotLoadBookmarks, + ), + ), + if (bookmarks.value != null && + bookmarks.value!.successful == true && + bookmarks.value!.content!.results!.isEmpty) + SliverFillRemaining( + child: NoDataScreen( + message: t.tags.tagBookmarks.noBookmarksWithThisTag, + ), + ), + if (bookmarks.value != null && + bookmarks.value!.successful == true && + bookmarks.value!.content!.results!.isNotEmpty) + SliverList.builder( + itemCount: bookmarks.value!.content!.results!.length, + itemBuilder: (context, index) => BookmarkItem( + bookmark: bookmarks.value!.content!.results![index], + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/tags/ui/tags.dart b/lib/screens/tags/ui/tags.dart index 18c71da..486a2be 100644 --- a/lib/screens/tags/ui/tags.dart +++ b/lib/screens/tags/ui/tags.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:linkdy/providers/router_provider.dart'; +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'; @@ -78,7 +80,11 @@ class TagsScreen extends ConsumerWidget { } final tag = tags.value?.content?.results?[index]; return InkWell( - onTap: () => {}, + 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), diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index ef3d10b..6c3c640 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -35,7 +35,7 @@ class ApiClientService { Future> fetchBookmarks({String? q, int? limit, int? offset}) async { try { final response = await dioInstance.get( - "/bookmarks", + "/bookmarks/", queryParameters: { "q": q, "limit": limit,