import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:easy_autocomplete/easy_autocomplete.dart'; import 'package:linkdy/screens/bookmarks/provider/bookmark_form.provider.dart'; import 'package:linkdy/widgets/section_label.dart'; import 'package:linkdy/models/data/bookmarks.dart'; import 'package:linkdy/config/sizes.dart'; import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/constants/global_keys.dart'; import 'package:linkdy/constants/enums.dart'; class BookmarkFormModal extends ConsumerStatefulWidget { final Bookmark? bookmark; const BookmarkFormModal({ super.key, this.bookmark, }); @override BookmarkFormModalState createState() => BookmarkFormModalState(); } class BookmarkFormModalState extends ConsumerState { @override void initState() { if (widget.bookmark != null) { ref.read(bookmarkFormProvider.notifier).initializeProvider(widget.bookmark!); } super.initState(); } @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; if (width > Sizes.tabletBreakpoint) { return ScaffoldMessenger( key: ScaffoldMessengerKeys.addBookmark, child: Dialog( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500, maxHeight: 700), child: Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ CloseButton( onPressed: () => Navigator.pop(context), ), const SizedBox(width: 16), Text( widget.bookmark != null ? t.bookmarks.addBookmark.editBookmark : t.bookmarks.addBookmark.addBookmark, style: const TextStyle(fontSize: 24), ), ], ), IconButton( onPressed: ref.watch(bookmarkFormProvider).checkBookmark != null ? () => ref.read(bookmarkFormProvider.notifier).saveBookmark() : null, icon: const Icon(Icons.save_rounded), tooltip: t.generic.save, ), ], ), ), Expanded( child: ListView( children: const [_ModalContent()], ), ), ], ), ), ), ); } else { return ScaffoldMessenger( key: ScaffoldMessengerKeys.addBookmark, child: Dialog.fullscreen( child: Scaffold( body: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) => [ SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: SliverAppBar.large( pinned: true, floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, leading: CloseButton( onPressed: () => Navigator.pop(context), ), title: Text( widget.bookmark != null ? t.bookmarks.addBookmark.editBookmark : t.bookmarks.addBookmark.addBookmark, ), actions: [ IconButton( onPressed: ref.watch(bookmarkFormProvider).checkBookmark != null ? () => ref.read(bookmarkFormProvider.notifier).saveBookmark() : null, icon: const Icon(Icons.save_rounded), tooltip: t.generic.save, ), const SizedBox(width: 8), ], ), ), ], body: SafeArea( top: false, bottom: true, child: Builder( builder: (context) => CustomScrollView( slivers: [ SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), ), SliverList.list( children: const [_ModalContent()], ), ], ), ), ), ), ), ), ); } } } class _ModalContent extends ConsumerWidget { const _ModalContent(); @override Widget build(BuildContext context, WidgetRef ref) { final provider = ref.watch(bookmarkFormProvider); final tags = ref.watch(getTagsProvider); final enabledFields = provider.checkBookmark != null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), SectionLabel( label: t.bookmarks.addBookmark.bookmarkUrl, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 0, ), ), const SizedBox(height: 24), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextFormField( controller: provider.urlController, onChanged: ref.read(bookmarkFormProvider.notifier).validateUrl, enabled: provider.checkBookmarkLoadStatus != LoadStatus.loading, autocorrect: false, decoration: InputDecoration( prefixIcon: const Icon(Icons.link_rounded), border: const OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10), ), ), errorText: provider.urlError, labelText: t.bookmarks.addBookmark.url, ), ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Center( child: ElevatedButton( onPressed: provider.checkBookmarkLoadStatus == null && provider.urlError == null && provider.urlController.text != "" ? () => ref.read(bookmarkFormProvider.notifier).checkUrlDetails() : null, style: ButtonStyle( foregroundColor: provider.checkBookmarkLoadStatus == LoadStatus.loaded ? const MaterialStatePropertyAll(Colors.green) : provider.checkBookmarkLoadStatus == LoadStatus.error ? const MaterialStatePropertyAll(Colors.red) : null, backgroundColor: provider.checkBookmarkLoadStatus == LoadStatus.loaded ? MaterialStatePropertyAll(Colors.green.withOpacity(0.15)) : provider.checkBookmarkLoadStatus == LoadStatus.error ? MaterialStatePropertyAll(Colors.red.withOpacity(0.15)) : null, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (provider.checkBookmarkLoadStatus == null) Text(t.bookmarks.addBookmark.validateUrl), if (provider.checkBookmarkLoadStatus == LoadStatus.loading) ...[ const SizedBox( width: 12, height: 12, child: CircularProgressIndicator( strokeWidth: 2, ), ), const SizedBox(width: 8), Text(t.bookmarks.addBookmark.checkingUrl), ], if (provider.checkBookmarkLoadStatus == LoadStatus.loaded) ...[ const Icon(Icons.check_circle_rounded), const SizedBox(width: 8), Text(t.bookmarks.addBookmark.urlValid), ], if (provider.checkBookmarkLoadStatus == LoadStatus.error) ...[ const Icon(Icons.error_rounded), const SizedBox(width: 8), Text(t.bookmarks.addBookmark.cannotCheckUrl), ], ], ), ), ), ), SectionLabel( label: t.bookmarks.addBookmark.bookmarkDetails, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 24, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextFormField( controller: provider.titleController, decoration: InputDecoration( prefixIcon: const Icon(Icons.title_rounded), border: const OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10), ), ), labelText: t.bookmarks.addBookmark.title, hintText: provider.checkBookmark?.metadata?.title, floatingLabelBehavior: provider.checkBookmark != null ? FloatingLabelBehavior.always : FloatingLabelBehavior.auto, helperText: t.bookmarks.addBookmark.leaveEmptyUseWebsiteTitle, enabled: enabledFields, ), ), ), const SizedBox(height: 24), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextFormField( controller: provider.descriptionController, decoration: InputDecoration( prefixIcon: const Icon(Icons.description_rounded), border: const OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10), ), ), labelText: t.bookmarks.addBookmark.description, hintText: provider.checkBookmark?.metadata?.description, floatingLabelBehavior: provider.checkBookmark != null ? FloatingLabelBehavior.always : FloatingLabelBehavior.auto, helperText: t.bookmarks.addBookmark.leaveEmptyUseWebsiteDescription, enabled: enabledFields, ), ), ), SectionLabel( label: t.bookmarks.addBookmark.tags, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 24, ), ), if (tags.isLoading == false) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ if (enabledFields == true) Row( children: [ Expanded( child: EasyAutocomplete( controller: provider.tagsController, onChanged: ref.read(bookmarkFormProvider.notifier).validateTagInput, suggestions: tags.value?.content?.results?.map((t) => t.name!).toList() ?? [], decoration: InputDecoration( border: const OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10), ), ), labelText: t.bookmarks.addBookmark.tags, errorText: provider.tagsError, ), suggestionBuilder: (data) => Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( data, style: TextStyle(fontSize: 16, color: Theme.of(context).colorScheme.onSurfaceVariant), ), ), onSubmitted: provider.tagsController.text != "" && provider.tagsError == null ? (v) { ref.read(bookmarkFormProvider.notifier).setTags([...provider.tags, v]); ref.read(bookmarkFormProvider.notifier).clearTagsController(); } : null, ), ), const SizedBox(width: 16), IconButton( onPressed: provider.tagsController.text != "" && provider.tagsError == null ? () { ref .read(bookmarkFormProvider.notifier) .setTags([...provider.tags, provider.tagsController.text]); ref.read(bookmarkFormProvider.notifier).clearTagsController(); } : null, icon: const Icon(Icons.check_rounded), tooltip: t.bookmarks.addBookmark.addTag, ), ], ), if (enabledFields == true) const SizedBox(height: 16), SizedBox( height: 40, child: provider.tags.isNotEmpty ? ListView.separated( scrollDirection: Axis.horizontal, itemCount: provider.tags.length, separatorBuilder: (context, index) => const SizedBox(width: 8), itemBuilder: (context, index) => InputChip( label: Text(provider.tags[index]), onDeleted: () => ref .read(bookmarkFormProvider.notifier) .setTags(provider.tags.where((tag) => tag != provider.tags[index]).toList()), ), ) : Center( child: Text( t.bookmarks.addBookmark.noTagsAdded, style: TextStyle( fontSize: 16, color: Theme.of(context).colorScheme.secondary, ), ), ), ), ], ), ), SectionLabel( label: t.bookmarks.addBookmark.others, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 24, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: SizedBox( height: 150, child: TextFormField( controller: provider.notesController, decoration: InputDecoration( prefixIcon: const Icon(Icons.note_rounded), border: const OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10), ), ), labelText: t.bookmarks.addBookmark.notes, helperText: t.generic.optional, ), autocorrect: false, expands: true, minLines: null, maxLines: null, textAlignVertical: TextAlignVertical.top, enabled: enabledFields, ), ), ), const SizedBox(height: 12), SwitchListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), title: Text(t.bookmarks.addBookmark.markAsUnread), value: provider.markAsUnread, onChanged: provider.checkBookmark != null ? (v) => ref.read(bookmarkFormProvider.notifier).updateMarkAsUnread(v) : null, ), const SizedBox(height: 16), ], ); } } void openBookmarkFormModal({ required BuildContext context, required double width, Bookmark? bookmark, }) { showGeneralDialog( context: context, barrierColor: Colors.black54, transitionBuilder: (context, anim1, anim2, child) { return SlideTransition( position: Tween(begin: const Offset(0, 1), end: const Offset(0, 0)).animate( CurvedAnimation(parent: anim1, curve: Curves.easeInOutCubicEmphasized), ), child: child, ); }, pageBuilder: (context, animation, secondaryAnimation) => const BookmarkFormModal(), ); }