From 3f298cc22d4ac0619d22a2e6baafe983589f588a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 27 Feb 2024 18:20:00 +0100 Subject: [PATCH] Added share and unshare bookmarks --- lib/i18n/strings.g.dart | 80 ++++++++++++++++++- lib/i18n/strings_en.i18n.json | 16 ++++ lib/i18n/strings_es.i18n.json | 16 ++++ lib/models/data/patch_bookmark_data.dart | 50 ++++++++++++ .../provider/bookmarks.provider.dart | 13 +++ .../provider/bookmarks.provider.g.dart | 2 +- .../bookmarks/provider/common_functions.dart | 41 ++++++++++ .../provider/search_bookmarks.provider.dart | 16 ++++ .../provider/search_bookmarks.provider.g.dart | 2 +- lib/screens/bookmarks/ui/bookmark_item.dart | 50 +++++++++++- lib/screens/bookmarks/ui/bookmarks.dart | 1 + .../bookmarks/ui/search_bookmarks.dart | 1 + .../bookmarks/ui/share_options_modal.dart | 76 ++++++++++++++++++ .../provider/tag_bookmarks.provider.dart | 13 +++ .../provider/tag_bookmarks.provider.g.dart | 2 +- .../tag_bookmarks/ui/tag_bookmarks.dart | 1 + lib/services/api_client.dart | 19 +++++ 17 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 lib/models/data/patch_bookmark_data.dart create mode 100644 lib/screens/bookmarks/ui/share_options_modal.dart diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index d8c110e..e27c0d6 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: 294 (147 per locale) +/// Strings: 322 (161 per locale) /// -/// Built on 2024-02-27 at 14:35 UTC +/// Built on 2024-02-27 at 17:13 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -227,6 +227,7 @@ class _StringsBookmarksEn { late final _StringsBookmarksAddBookmarkEn addBookmark = _StringsBookmarksAddBookmarkEn._(_root); late final _StringsBookmarksSearchEn search = _StringsBookmarksSearchEn._(_root); late final _StringsBookmarksBookmarkOptionsEn bookmarkOptions = _StringsBookmarksBookmarkOptionsEn._(_root); + late final _StringsBookmarksShareOptionsEn shareOptions = _StringsBookmarksShareOptionsEn._(_root); } // Path: tags @@ -368,6 +369,7 @@ class _StringsBookmarksBookmarkOptionsEn { String get unread => 'Unread'; String get read => 'Read'; String get share => 'Share'; + String get shared => 'Shared'; String get archive => 'Archive'; String get deletingBookmark => 'Deleting bookmark...'; String get bookmarkDeleted => 'Bookmark deleted successfully'; @@ -388,6 +390,28 @@ class _StringsBookmarksBookmarkOptionsEn { String get bookmarkNotUnrchived => 'The bookmark couldn\'t be unarchived.'; } +// Path: bookmarks.shareOptions +class _StringsBookmarksShareOptionsEn { + _StringsBookmarksShareOptionsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get shareOptions => 'Share options'; + String get shareInternally => 'Share internally'; + String get unshareInternally => 'Unhare internally'; + String get shareInternallyDescription => 'Share internally with the rest of the users of this Linkding instance'; + String get unshareInternallyDescription => 'Unshare this internally shared bookmark'; + String get shareExternally => 'Share bookmark URL externally'; + String get shareExternallyDescription => 'Share the bookmark URL externally using a third party application'; + String get sharingBookmark => 'Sharing bookmark...'; + String get unsharingBookmark => 'Unsharing bookmark...'; + String get bookmarkSharedSuccessfully => 'Bookmark shared successfully.'; + String get bookmarkNotShared => 'Bookmark couldn\'t be shared.'; + String get bookmarkUnsharedSuccessfully => 'Bookmark unshared successfully.'; + String get bookmarkNotUnshared => 'Bookmark couldn\'t be unshared.'; +} + // Path: tags.createTag class _StringsTagsCreateTagEn { _StringsTagsCreateTagEn._(this._root); @@ -560,6 +584,7 @@ class _StringsBookmarksEs implements _StringsBookmarksEn { @override late final _StringsBookmarksAddBookmarkEs addBookmark = _StringsBookmarksAddBookmarkEs._(_root); @override late final _StringsBookmarksSearchEs search = _StringsBookmarksSearchEs._(_root); @override late final _StringsBookmarksBookmarkOptionsEs bookmarkOptions = _StringsBookmarksBookmarkOptionsEs._(_root); + @override late final _StringsBookmarksShareOptionsEs shareOptions = _StringsBookmarksShareOptionsEs._(_root); } // Path: tags @@ -701,6 +726,7 @@ class _StringsBookmarksBookmarkOptionsEs implements _StringsBookmarksBookmarkOpt @override String get unread => 'No leído'; @override String get read => 'Leído'; @override String get share => 'Compartir'; + @override String get shared => 'Compartido'; @override String get archive => 'Archivar'; @override String get deletingBookmark => 'Eliminando marcador...'; @override String get bookmarkDeleted => 'Marcador eliminado correctamente'; @@ -721,6 +747,28 @@ class _StringsBookmarksBookmarkOptionsEs implements _StringsBookmarksBookmarkOpt @override String get bookmarkNotUnrchived => 'El marcador no pudo ser desarchivado.'; } +// Path: bookmarks.shareOptions +class _StringsBookmarksShareOptionsEs implements _StringsBookmarksShareOptionsEn { + _StringsBookmarksShareOptionsEs._(this._root); + + @override final _StringsEs _root; // ignore: unused_field + + // Translations + @override String get shareOptions => 'Opciones de compartir'; + @override String get shareInternally => 'Compartir internamente'; + @override String get unshareInternally => 'Descompartir internamente'; + @override String get shareInternallyDescription => 'Compartir internamente con el resto de usuarios de la instancia de Linkding'; + @override String get unshareInternallyDescription => 'Descompartir el marcador compartido internamente'; + @override String get shareExternally => 'Compartir URL del marcador externamente'; + @override String get shareExternallyDescription => 'Compartir la URL del marcador externamente usando una aplicación de terceros'; + @override String get sharingBookmark => 'Compartiendo marcador...'; + @override String get unsharingBookmark => 'Descompartiendo marcador...'; + @override String get bookmarkSharedSuccessfully => 'Marcador compartido correctamente.'; + @override String get bookmarkNotShared => 'El marcador no pudo ser compartido.'; + @override String get bookmarkUnsharedSuccessfully => 'Marcador descompartido correctamente.'; + @override String get bookmarkNotUnshared => 'El marcador no pudo ser descompartido.'; +} + // Path: tags.createTag class _StringsTagsCreateTagEs implements _StringsTagsCreateTagEn { _StringsTagsCreateTagEs._(this._root); @@ -875,6 +923,7 @@ extension on Translations { case 'bookmarks.bookmarkOptions.unread': return 'Unread'; case 'bookmarks.bookmarkOptions.read': return 'Read'; case 'bookmarks.bookmarkOptions.share': return 'Share'; + case 'bookmarks.bookmarkOptions.shared': return 'Shared'; case 'bookmarks.bookmarkOptions.archive': return 'Archive'; case 'bookmarks.bookmarkOptions.deletingBookmark': return 'Deleting bookmark...'; case 'bookmarks.bookmarkOptions.bookmarkDeleted': return 'Bookmark deleted successfully'; @@ -893,6 +942,19 @@ extension on Translations { case 'bookmarks.bookmarkOptions.bookmarkUnrchivedSuccessfully': return 'Bookmark unarchived successfully.'; case 'bookmarks.bookmarkOptions.bookmarkNotArchived': return 'The bookmark couldn\'t be archived.'; case 'bookmarks.bookmarkOptions.bookmarkNotUnrchived': return 'The bookmark couldn\'t be unarchived.'; + case 'bookmarks.shareOptions.shareOptions': return 'Share options'; + case 'bookmarks.shareOptions.shareInternally': return 'Share internally'; + case 'bookmarks.shareOptions.unshareInternally': return 'Unhare internally'; + case 'bookmarks.shareOptions.shareInternallyDescription': return 'Share internally with the rest of the users of this Linkding instance'; + case 'bookmarks.shareOptions.unshareInternallyDescription': return 'Unshare this internally shared bookmark'; + case 'bookmarks.shareOptions.shareExternally': return 'Share bookmark URL externally'; + case 'bookmarks.shareOptions.shareExternallyDescription': return 'Share the bookmark URL externally using a third party application'; + case 'bookmarks.shareOptions.sharingBookmark': return 'Sharing bookmark...'; + case 'bookmarks.shareOptions.unsharingBookmark': return 'Unsharing bookmark...'; + case 'bookmarks.shareOptions.bookmarkSharedSuccessfully': return 'Bookmark shared successfully.'; + case 'bookmarks.shareOptions.bookmarkNotShared': return 'Bookmark couldn\'t be shared.'; + case 'bookmarks.shareOptions.bookmarkUnsharedSuccessfully': return 'Bookmark unshared successfully.'; + case 'bookmarks.shareOptions.bookmarkNotUnshared': return 'Bookmark couldn\'t be unshared.'; case 'tags.tags': return 'Tags'; case 'tags.created': return ({required Object created}) => 'Created: ${created}'; case 'tags.createTag.createTag': return 'Create tag'; @@ -1030,6 +1092,7 @@ extension on _StringsEs { case 'bookmarks.bookmarkOptions.unread': return 'No leído'; case 'bookmarks.bookmarkOptions.read': return 'Leído'; case 'bookmarks.bookmarkOptions.share': return 'Compartir'; + case 'bookmarks.bookmarkOptions.shared': return 'Compartido'; case 'bookmarks.bookmarkOptions.archive': return 'Archivar'; case 'bookmarks.bookmarkOptions.deletingBookmark': return 'Eliminando marcador...'; case 'bookmarks.bookmarkOptions.bookmarkDeleted': return 'Marcador eliminado correctamente'; @@ -1048,6 +1111,19 @@ extension on _StringsEs { case 'bookmarks.bookmarkOptions.bookmarkUnrchivedSuccessfully': return 'Marcador desarchivado correctamente.'; case 'bookmarks.bookmarkOptions.bookmarkNotArchived': return 'El marcador no pudo ser archivado.'; case 'bookmarks.bookmarkOptions.bookmarkNotUnrchived': return 'El marcador no pudo ser desarchivado.'; + case 'bookmarks.shareOptions.shareOptions': return 'Opciones de compartir'; + case 'bookmarks.shareOptions.shareInternally': return 'Compartir internamente'; + case 'bookmarks.shareOptions.unshareInternally': return 'Descompartir internamente'; + case 'bookmarks.shareOptions.shareInternallyDescription': return 'Compartir internamente con el resto de usuarios de la instancia de Linkding'; + case 'bookmarks.shareOptions.unshareInternallyDescription': return 'Descompartir el marcador compartido internamente'; + case 'bookmarks.shareOptions.shareExternally': return 'Compartir URL del marcador externamente'; + case 'bookmarks.shareOptions.shareExternallyDescription': return 'Compartir la URL del marcador externamente usando una aplicación de terceros'; + case 'bookmarks.shareOptions.sharingBookmark': return 'Compartiendo marcador...'; + case 'bookmarks.shareOptions.unsharingBookmark': return 'Descompartiendo marcador...'; + case 'bookmarks.shareOptions.bookmarkSharedSuccessfully': return 'Marcador compartido correctamente.'; + case 'bookmarks.shareOptions.bookmarkNotShared': return 'El marcador no pudo ser compartido.'; + case 'bookmarks.shareOptions.bookmarkUnsharedSuccessfully': return 'Marcador descompartido correctamente.'; + case 'bookmarks.shareOptions.bookmarkNotUnshared': return 'El marcador no pudo ser descompartido.'; case 'tags.tags': return 'Etiquetas'; case 'tags.created': return ({required Object created}) => 'Creado: ${created}'; case 'tags.createTag.createTag': return 'Crear etiqueta'; diff --git a/lib/i18n/strings_en.i18n.json b/lib/i18n/strings_en.i18n.json index d9d39b4..85ff93a 100644 --- a/lib/i18n/strings_en.i18n.json +++ b/lib/i18n/strings_en.i18n.json @@ -90,6 +90,7 @@ "unread": "Unread", "read": "Read", "share": "Share", + "shared": "Shared", "archive": "Archive", "deletingBookmark": "Deleting bookmark...", "bookmarkDeleted": "Bookmark deleted successfully", @@ -108,6 +109,21 @@ "bookmarkUnrchivedSuccessfully": "Bookmark unarchived successfully.", "bookmarkNotArchived": "The bookmark couldn't be archived.", "bookmarkNotUnrchived": "The bookmark couldn't be unarchived." + }, + "shareOptions": { + "shareOptions": "Share options", + "shareInternally": "Share internally", + "unshareInternally": "Unhare internally", + "shareInternallyDescription": "Share internally with the rest of the users of this Linkding instance", + "unshareInternallyDescription": "Unshare this internally shared bookmark", + "shareExternally": "Share bookmark URL externally", + "shareExternallyDescription": "Share the bookmark URL externally using a third party application", + "sharingBookmark": "Sharing bookmark...", + "unsharingBookmark": "Unsharing bookmark...", + "bookmarkSharedSuccessfully": "Bookmark shared successfully.", + "bookmarkNotShared": "Bookmark couldn't be shared.", + "bookmarkUnsharedSuccessfully": "Bookmark unshared successfully.", + "bookmarkNotUnshared": "Bookmark couldn't be unshared." } }, "tags": { diff --git a/lib/i18n/strings_es.i18n.json b/lib/i18n/strings_es.i18n.json index 2166ac1..f7eba12 100644 --- a/lib/i18n/strings_es.i18n.json +++ b/lib/i18n/strings_es.i18n.json @@ -90,6 +90,7 @@ "unread": "No leído", "read": "Leído", "share": "Compartir", + "shared": "Compartido", "archive": "Archivar", "deletingBookmark": "Eliminando marcador...", "bookmarkDeleted": "Marcador eliminado correctamente", @@ -108,6 +109,21 @@ "bookmarkUnrchivedSuccessfully": "Marcador desarchivado correctamente.", "bookmarkNotArchived": "El marcador no pudo ser archivado.", "bookmarkNotUnrchived": "El marcador no pudo ser desarchivado." + }, + "shareOptions": { + "shareOptions": "Opciones de compartir", + "shareInternally": "Compartir internamente", + "unshareInternally": "Descompartir internamente", + "shareInternallyDescription": "Compartir internamente con el resto de usuarios de la instancia de Linkding", + "unshareInternallyDescription": "Descompartir el marcador compartido internamente", + "shareExternally": "Compartir URL del marcador externamente", + "shareExternallyDescription": "Compartir la URL del marcador externamente usando una aplicación de terceros", + "sharingBookmark": "Compartiendo marcador...", + "unsharingBookmark": "Descompartiendo marcador...", + "bookmarkSharedSuccessfully": "Marcador compartido correctamente.", + "bookmarkNotShared": "El marcador no pudo ser compartido.", + "bookmarkUnsharedSuccessfully": "Marcador descompartido correctamente.", + "bookmarkNotUnshared": "El marcador no pudo ser descompartido." } }, "tags": { diff --git a/lib/models/data/patch_bookmark_data.dart b/lib/models/data/patch_bookmark_data.dart new file mode 100644 index 0000000..43675ae --- /dev/null +++ b/lib/models/data/patch_bookmark_data.dart @@ -0,0 +1,50 @@ +class PatchBookmarkData { + final String? url; + final String? title; + final String? description; + final bool? isArchived; + final bool? unread; + final bool? shared; + final String? tagNames; + final String? notes; + + const PatchBookmarkData({ + this.url, + this.title, + this.description, + this.isArchived, + this.unread, + this.shared, + this.tagNames, + this.notes, + }); + + Map toJson() { + Map ret = {}; + if (url != null) { + ret = {...ret, "url": url}; + } + if (title != null) { + ret = {...ret, "title": title}; + } + if (description != null) { + ret = {...ret, "description": description}; + } + if (isArchived != null) { + ret = {...ret, "is_archived": isArchived}; + } + if (unread != null) { + ret = {...ret, "unread": unread}; + } + if (shared != null) { + ret = {...ret, "shared": shared}; + } + if (notes != null) { + ret = {...ret, "notes": notes}; + } + if (tagNames != null) { + ret = {...ret, "notag_namestes": tagNames}; + } + return ret; + } +} diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.dart b/lib/screens/bookmarks/provider/bookmarks.provider.dart index b9bb1f1..fc77a32 100644 --- a/lib/screens/bookmarks/provider/bookmarks.provider.dart +++ b/lib/screens/bookmarks/provider/bookmarks.provider.dart @@ -128,4 +128,17 @@ class Bookmarks extends _$Bookmarks { ref.notifyListeners(); } } + + void shareUnshare(Bookmark bookmark) async { + final result = await BookmarkCommonFunctions.shareUnshare( + scaffoldMessengerKey: ScaffoldMessengerKeys.bookmarks, + ref: ref, + bookmark: bookmark, + apiClient: ref.read(apiClientProvider)!, + ); + if (result != null) { + state.bookmarks = state.bookmarks.map((b) => b.id == result.id ? result : b).toList(); + ref.notifyListeners(); + } + } } diff --git a/lib/screens/bookmarks/provider/bookmarks.provider.g.dart b/lib/screens/bookmarks/provider/bookmarks.provider.g.dart index 084d95c..0f17e8e 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'84a236ccce48c0a0069ca2b754da59ff88b17ef2'; +String _$bookmarksHash() => r'602a50d02cde9a67335372659f8e2f656ac21995'; /// See also [Bookmarks]. @ProviderFor(Bookmarks) diff --git a/lib/screens/bookmarks/provider/common_functions.dart b/lib/screens/bookmarks/provider/common_functions.dart index a8ed8bc..64c84d5 100644 --- a/lib/screens/bookmarks/provider/common_functions.dart +++ b/lib/screens/bookmarks/provider/common_functions.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:linkdy/i18n/strings.g.dart'; +import 'package:linkdy/models/data/patch_bookmark_data.dart'; import 'package:linkdy/models/data/bookmarks.dart'; import 'package:linkdy/utils/snackbar.dart'; import 'package:linkdy/models/data/set_bookmark_data.dart'; @@ -121,4 +122,44 @@ class BookmarkCommonFunctions { } return result.successful; } + + static Future shareUnshare({ + required AutoDisposeNotifierProviderRef ref, + required Bookmark bookmark, + required ApiClientService apiClient, + required GlobalKey scaffoldMessengerKey, + }) async { + final processModal = ProcessModal(); + processModal.open( + bookmark.shared == true ? t.bookmarks.shareOptions.unsharingBookmark : t.bookmarks.shareOptions.sharingBookmark, + ); + + final result = await apiClient.patchUpdateBookmark( + bookmark.id!, + PatchBookmarkData( + shared: !bookmark.shared!, + ), + ); + + processModal.close(); + if (result.successful == true) { + showSnackbar( + key: scaffoldMessengerKey, + label: bookmark.isArchived == true + ? t.bookmarks.shareOptions.bookmarkUnsharedSuccessfully + : t.bookmarks.shareOptions.bookmarkSharedSuccessfully, + color: Colors.green, + ); + return result.content; + } else { + showSnackbar( + key: scaffoldMessengerKey, + label: bookmark.isArchived == true + ? t.bookmarks.shareOptions.bookmarkNotUnshared + : t.bookmarks.shareOptions.bookmarkNotShared, + 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 d60d331..8c47f19 100644 --- a/lib/screens/bookmarks/provider/search_bookmarks.provider.dart +++ b/lib/screens/bookmarks/provider/search_bookmarks.provider.dart @@ -147,4 +147,20 @@ class SearchBookmarks extends _$SearchBookmarks { .setBookmarks(ref.read(bookmarksProvider).bookmarks.where((b) => b.id != bookmark.id).toList()); } } + + void shareUnshare(Bookmark bookmark) async { + final result = await BookmarkCommonFunctions.shareUnshare( + scaffoldMessengerKey: ScaffoldMessengerKeys.search, + ref: ref, + bookmark: bookmark, + apiClient: ref.read(apiClientProvider)!, + ); + if (result != null) { + state.bookmarks = state.bookmarks.map((b) => b.id == result.id ? result : b).toList(); + ref.notifyListeners(); + ref + .read(bookmarksProvider.notifier) + .setBookmarks(ref.read(bookmarksProvider).bookmarks.map((b) => b.id == result.id ? result : b).toList()); + } + } } diff --git a/lib/screens/bookmarks/provider/search_bookmarks.provider.g.dart b/lib/screens/bookmarks/provider/search_bookmarks.provider.g.dart index fe50972..8734592 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'c7c8394ae9ad71f8106a3b73100b88e09c154e13'; +String _$searchBookmarksHash() => r'bdb0eedf3ebdde4b2cb4795efe61ce42e4c85d00'; /// See also [SearchBookmarks]. @ProviderFor(SearchBookmarks) diff --git a/lib/screens/bookmarks/ui/bookmark_item.dart b/lib/screens/bookmarks/ui/bookmark_item.dart index 70b9452..82330a1 100644 --- a/lib/screens/bookmarks/ui/bookmark_item.dart +++ b/lib/screens/bookmarks/ui/bookmark_item.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:share_plus/share_plus.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:linkdy/screens/bookmarks/ui/share_options_modal.dart'; import 'package:linkdy/screens/bookmarks/provider/favicon_loader.provider.dart'; import 'package:linkdy/config/options.dart'; @@ -22,6 +22,7 @@ class BookmarkItem extends ConsumerWidget { final void Function(Bookmark bookmark) onReadUnread; final void Function(Bookmark bookmark) onArchiveUnarchive; final void Function(Bookmark bookmark) onEdit; + final void Function(Bookmark bookmark) onShareInternally; const BookmarkItem({ super.key, @@ -30,6 +31,7 @@ class BookmarkItem extends ConsumerWidget { required this.onReadUnread, required this.onArchiveUnarchive, required this.onEdit, + required this.onShareInternally, }); @override @@ -66,7 +68,10 @@ class BookmarkItem extends ConsumerWidget { padding: const EdgeInsets.all(4), ), SlidableAction( - onPressed: (ctx) => Share.shareUri(Uri.parse(bookmark.url!)), + onPressed: (ctx) => showDialog( + context: context, + builder: (ctx) => ShareOptionsModal(bookmark: bookmark, onShareInternally: onShareInternally), + ), backgroundColor: Colors.orange, label: t.bookmarks.bookmarkOptions.share, icon: Icons.share_rounded, @@ -214,7 +219,7 @@ class BookmarkItem extends ConsumerWidget { ), ), ), - if (bookmark.unread == true) ...[ + if (bookmark.shared == true) ...[ if (bookmark.tagNames?.isNotEmpty == true) Container( width: 1, @@ -222,6 +227,43 @@ class BookmarkItem extends ConsumerWidget { margin: const EdgeInsets.symmetric(horizontal: 8), color: Theme.of(context).colorScheme.tertiary.withOpacity(0.3), ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).colorScheme.primaryContainer, + ), + child: Row( + children: [ + Icon( + Icons.share_rounded, + size: 12, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + const SizedBox(width: 4), + Text( + t.bookmarks.bookmarkOptions.shared, + style: TextStyle( + fontSize: 10, + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ], + if (bookmark.unread == true) ...[ + if (bookmark.tagNames?.isNotEmpty == true || bookmark.shared == true) + Container( + width: 1, + height: 12, + margin: const EdgeInsets.symmetric(horizontal: 8), + color: Theme.of(context).colorScheme.tertiary.withOpacity(0.3), + ), Container( padding: const EdgeInsets.symmetric( horizontal: 6, @@ -252,7 +294,7 @@ class BookmarkItem extends ConsumerWidget { ), ], if (bookmark.dateModified != null) ...[ - if (bookmark.unread == true) + if (bookmark.unread == true || bookmark.shared == true || bookmark.tagNames?.isNotEmpty == true) Container( width: 1, height: 12, diff --git a/lib/screens/bookmarks/ui/bookmarks.dart b/lib/screens/bookmarks/ui/bookmarks.dart index 8205aaa..199c317 100644 --- a/lib/screens/bookmarks/ui/bookmarks.dart +++ b/lib/screens/bookmarks/ui/bookmarks.dart @@ -132,6 +132,7 @@ class BookmarksScreen extends ConsumerWidget { ), ), onArchiveUnarchive: ref.read(bookmarksProvider.notifier).archiveUnarchive, + onShareInternally: ref.read(bookmarksProvider.notifier).shareUnshare, onEdit: (b) => openBookmarkFormModal(context: context, width: width, bookmark: b), ); }, diff --git a/lib/screens/bookmarks/ui/search_bookmarks.dart b/lib/screens/bookmarks/ui/search_bookmarks.dart index 475ec80..45af90b 100644 --- a/lib/screens/bookmarks/ui/search_bookmarks.dart +++ b/lib/screens/bookmarks/ui/search_bookmarks.dart @@ -140,6 +140,7 @@ class SearchBookmarksModal extends ConsumerWidget { ), ), onArchiveUnarchive: ref.read(searchBookmarksProvider.notifier).archiveUnarchive, + onShareInternally: ref.read(searchBookmarksProvider.notifier).shareUnshare, onEdit: (b) => openBookmarkFormModal(context: context, width: width, bookmark: b), ); }, diff --git a/lib/screens/bookmarks/ui/share_options_modal.dart b/lib/screens/bookmarks/ui/share_options_modal.dart new file mode 100644 index 0000000..05a75b7 --- /dev/null +++ b/lib/screens/bookmarks/ui/share_options_modal.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:share_plus/share_plus.dart'; + +import 'package:linkdy/widgets/custom_list_tile.dart'; + +import 'package:linkdy/i18n/strings.g.dart'; +import 'package:linkdy/models/data/bookmarks.dart'; + +class ShareOptionsModal extends StatelessWidget { + final Bookmark bookmark; + final void Function(Bookmark bookmark) onShareInternally; + + const ShareOptionsModal({ + super.key, + required this.bookmark, + required this.onShareInternally, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + scrollable: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 24), + title: Column( + children: [ + Icon( + Icons.share_rounded, + size: 24, + color: Theme.of(context).colorScheme.secondary, + ), + const SizedBox(height: 16), + Text(t.bookmarks.shareOptions.shareOptions), + ], + ), + content: Column( + children: [ + CustomListTile( + icon: Icons.input_rounded, + title: bookmark.shared == true + ? t.bookmarks.shareOptions.unshareInternally + : t.bookmarks.shareOptions.shareInternally, + subtitle: bookmark.shared == true + ? t.bookmarks.shareOptions.unshareInternallyDescription + : t.bookmarks.shareOptions.shareInternallyDescription, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + onTap: () { + Navigator.pop(context); + onShareInternally(bookmark); + }, + ), + CustomListTile( + icon: Icons.output_rounded, + title: t.bookmarks.shareOptions.shareExternally, + subtitle: t.bookmarks.shareOptions.shareExternallyDescription, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + onTap: () { + Navigator.pop(context); + Share.shareUri(Uri.parse(bookmark.url!)); + }, + ), + ], + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(t.generic.close), + ), + ], + ), + ], + ); + } +} diff --git a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart index e3872f8..75248eb 100644 --- a/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart +++ b/lib/screens/tag_bookmarks/provider/tag_bookmarks.provider.dart @@ -137,4 +137,17 @@ class TagBookmarks extends _$TagBookmarks { ref.notifyListeners(); } } + + void shareUnshare(Bookmark bookmark) async { + final result = await BookmarkCommonFunctions.shareUnshare( + scaffoldMessengerKey: ScaffoldMessengerKeys.tagBookmarks, + ref: ref, + bookmark: bookmark, + apiClient: ref.read(apiClientProvider)!, + ); + if (result != null) { + state.bookmarks = state.bookmarks.map((b) => b.id == result.id ? result : b).toList(); + ref.notifyListeners(); + } + } } 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 e87baf7..710f8dd 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'd5004cac68d86490b49fc6f722ec819267aedf17'; +String _$tagBookmarksHash() => r'35a9ed7751c2d7e9644907f969fb03950b7b9a04'; /// See also [TagBookmarks]. @ProviderFor(TagBookmarks) diff --git a/lib/screens/tag_bookmarks/ui/tag_bookmarks.dart b/lib/screens/tag_bookmarks/ui/tag_bookmarks.dart index ae17b53..93d4ba4 100644 --- a/lib/screens/tag_bookmarks/ui/tag_bookmarks.dart +++ b/lib/screens/tag_bookmarks/ui/tag_bookmarks.dart @@ -147,6 +147,7 @@ class TagBookmarksScreenState extends ConsumerState { ), ), onArchiveUnarchive: ref.read(tagBookmarksProvider.notifier).archiveUnarchive, + onShareInternally: ref.read(tagBookmarksProvider.notifier).shareUnshare, onEdit: (b) => openBookmarkFormModal(context: context, width: width, bookmark: b), ); }, diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index 2d6421f..d39b570 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -5,6 +5,7 @@ import 'package:linkdy/constants/enums.dart'; import 'package:linkdy/models/api_response.dart'; import 'package:linkdy/models/data/bookmarks.dart'; import 'package:linkdy/models/data/check_bookmark.dart'; +import 'package:linkdy/models/data/patch_bookmark_data.dart'; import 'package:linkdy/models/data/set_bookmark_data.dart'; import 'package:linkdy/models/data/tags.dart'; import 'package:linkdy/models/server_instance.dart'; @@ -214,4 +215,22 @@ class ApiClientService { return const ApiResponse(successful: false); } } + + Future> patchUpdateBookmark(int bookmarkId, PatchBookmarkData bookmark) async { + try { + final result = await dioInstance.patch( + "/bookmarks/$bookmarkId/", + data: FormData.fromMap(bookmark.toJson()), + ); + return ApiResponse( + successful: true, + content: Bookmark.fromJson(result.data), + ); + } on DioException { + return const ApiResponse(successful: false); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } + } }