Changed add link design

This commit is contained in:
Juan Gilsanz Polo
2024-02-23 19:48:52 +01:00
parent 371081ab73
commit ca15b8a445
8 changed files with 359 additions and 265 deletions

View File

@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 2 /// Locales: 2
/// Strings: 162 (81 per locale) /// Strings: 182 (91 per locale)
/// ///
/// Built on 2024-02-23 at 17:01 UTC /// Built on 2024-02-23 at 18:23 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
@@ -170,6 +170,7 @@ class _StringsGenericEn {
String get save => 'Save'; String get save => 'Save';
String get close => 'Close'; String get close => 'Close';
String get error => 'Error'; String get error => 'Error';
String get optional => 'Optional';
} }
// Path: onboarding // Path: onboarding
@@ -297,6 +298,13 @@ class _StringsLinksAddLinkEn {
// Translations // Translations
String get addLink => 'Add new link'; String get addLink => 'Add new link';
String get bookmarkUrl => 'Bookmark URL';
String get bookmarkDetails => 'Bookmark details';
String get validateUrl => 'Validate URL';
String get urlValid => 'The URL is valid';
String get urlNotValid => 'The URL is not valid';
String get cannotCheckUrl => 'The URL couldn\'t be checked';
String get invalidUrl => 'Invalid URL';
String get url => 'URL'; String get url => 'URL';
String get checkingUrl => 'Checking URL...'; String get checkingUrl => 'Checking URL...';
String get title => 'Title'; String get title => 'Title';
@@ -304,6 +312,8 @@ class _StringsLinksAddLinkEn {
String get markAsUnread => 'Mark as unread'; String get markAsUnread => 'Mark as unread';
String get notes => 'Notes'; String get notes => 'Notes';
String get addNotes => 'Add notes'; String get addNotes => 'Add notes';
String get tags => 'Tags';
String get others => 'Others';
String get savingLink => 'Saving link...'; String get savingLink => 'Saving link...';
String get errorSavingLink => 'An error occured when saving the new link.'; String get errorSavingLink => 'An error occured when saving the new link.';
String get leaveEmptyUseWebsiteTitle => 'Leave empty to use the website\'s title.'; String get leaveEmptyUseWebsiteTitle => 'Leave empty to use the website\'s title.';
@@ -397,6 +407,7 @@ class _StringsGenericEs implements _StringsGenericEn {
@override String get save => 'Guardar'; @override String get save => 'Guardar';
@override String get close => 'Cerrar'; @override String get close => 'Cerrar';
@override String get error => 'Error'; @override String get error => 'Error';
@override String get optional => 'Opcional';
} }
// Path: onboarding // Path: onboarding
@@ -525,12 +536,21 @@ class _StringsLinksAddLinkEs implements _StringsLinksAddLinkEn {
// Translations // Translations
@override String get addLink => 'Añadir nuevo enlace'; @override String get addLink => 'Añadir nuevo enlace';
@override String get url => 'URL'; @override String get url => 'URL';
@override String get bookmarkUrl => 'URL del marcador';
@override String get bookmarkDetails => 'Detalles del marcador';
@override String get validateUrl => 'Validar URL';
@override String get urlValid => 'La URL es válida';
@override String get urlNotValid => 'La URL no es válida';
@override String get cannotCheckUrl => 'No se pudo comprobar la URL';
@override String get invalidUrl => 'URL no válida';
@override String get checkingUrl => 'Comprobando URL...'; @override String get checkingUrl => 'Comprobando URL...';
@override String get title => 'Title'; @override String get title => 'Title';
@override String get description => 'Description'; @override String get description => 'Description';
@override String get markAsUnread => 'Marcar como no leído'; @override String get markAsUnread => 'Marcar como no leído';
@override String get notes => 'Notas'; @override String get notes => 'Notas';
@override String get addNotes => 'Añadir notas'; @override String get addNotes => 'Añadir notas';
@override String get tags => 'Etiquetas';
@override String get others => 'Otros';
@override String get savingLink => 'Guardando enlace...'; @override String get savingLink => 'Guardando enlace...';
@override String get errorSavingLink => 'Ocurrió un error al guardar el nuevo enlace.'; @override String get errorSavingLink => 'Ocurrió un error al guardar el nuevo enlace.';
@override String get leaveEmptyUseWebsiteTitle => 'Dejar vacío para usar el título del sitio web.'; @override String get leaveEmptyUseWebsiteTitle => 'Dejar vacío para usar el título del sitio web.';
@@ -590,6 +610,7 @@ extension on Translations {
case 'generic.save': return 'Save'; case 'generic.save': return 'Save';
case 'generic.close': return 'Close'; case 'generic.close': return 'Close';
case 'generic.error': return 'Error'; case 'generic.error': return 'Error';
case 'generic.optional': return 'Optional';
case 'onboarding.title': return 'Welcome to Linkdy'; case 'onboarding.title': return 'Welcome to Linkdy';
case 'onboarding.subtitle': return 'An application to manage your bookmarks.'; case 'onboarding.subtitle': return 'An application to manage your bookmarks.';
case 'onboarding.start': return 'Start'; case 'onboarding.start': return 'Start';
@@ -615,6 +636,13 @@ extension on Translations {
case 'links.dates.todayAt': return ({required Object time}) => 'Today, ${time}'; case 'links.dates.todayAt': return ({required Object time}) => 'Today, ${time}';
case 'links.dates.yesterdayAt': return ({required Object time}) => 'Yesterday, ${time}'; case 'links.dates.yesterdayAt': return ({required Object time}) => 'Yesterday, ${time}';
case 'links.addLink.addLink': return 'Add new link'; case 'links.addLink.addLink': return 'Add new link';
case 'links.addLink.bookmarkUrl': return 'Bookmark URL';
case 'links.addLink.bookmarkDetails': return 'Bookmark details';
case 'links.addLink.validateUrl': return 'Validate URL';
case 'links.addLink.urlValid': return 'The URL is valid';
case 'links.addLink.urlNotValid': return 'The URL is not valid';
case 'links.addLink.cannotCheckUrl': return 'The URL couldn\'t be checked';
case 'links.addLink.invalidUrl': return 'Invalid URL';
case 'links.addLink.url': return 'URL'; case 'links.addLink.url': return 'URL';
case 'links.addLink.checkingUrl': return 'Checking URL...'; case 'links.addLink.checkingUrl': return 'Checking URL...';
case 'links.addLink.title': return 'Title'; case 'links.addLink.title': return 'Title';
@@ -622,6 +650,8 @@ extension on Translations {
case 'links.addLink.markAsUnread': return 'Mark as unread'; case 'links.addLink.markAsUnread': return 'Mark as unread';
case 'links.addLink.notes': return 'Notes'; case 'links.addLink.notes': return 'Notes';
case 'links.addLink.addNotes': return 'Add notes'; case 'links.addLink.addNotes': return 'Add notes';
case 'links.addLink.tags': return 'Tags';
case 'links.addLink.others': return 'Others';
case 'links.addLink.savingLink': return 'Saving link...'; case 'links.addLink.savingLink': return 'Saving link...';
case 'links.addLink.errorSavingLink': return 'An error occured when saving the new link.'; case 'links.addLink.errorSavingLink': return 'An error occured when saving the new link.';
case 'links.addLink.leaveEmptyUseWebsiteTitle': return 'Leave empty to use the website\'s title.'; case 'links.addLink.leaveEmptyUseWebsiteTitle': return 'Leave empty to use the website\'s title.';
@@ -679,6 +709,7 @@ extension on _StringsEs {
case 'generic.save': return 'Guardar'; case 'generic.save': return 'Guardar';
case 'generic.close': return 'Cerrar'; case 'generic.close': return 'Cerrar';
case 'generic.error': return 'Error'; case 'generic.error': return 'Error';
case 'generic.optional': return 'Opcional';
case 'onboarding.title': return 'Bienvenido a Linkdy'; case 'onboarding.title': return 'Bienvenido a Linkdy';
case 'onboarding.subtitle': return 'Una aplicación para gestionar tus enlaces favoritos.'; case 'onboarding.subtitle': return 'Una aplicación para gestionar tus enlaces favoritos.';
case 'onboarding.start': return 'Comenzar'; case 'onboarding.start': return 'Comenzar';
@@ -705,12 +736,21 @@ extension on _StringsEs {
case 'links.dates.yesterdayAt': return ({required Object time}) => 'Ayer, ${time}'; case 'links.dates.yesterdayAt': return ({required Object time}) => 'Ayer, ${time}';
case 'links.addLink.addLink': return 'Añadir nuevo enlace'; case 'links.addLink.addLink': return 'Añadir nuevo enlace';
case 'links.addLink.url': return 'URL'; case 'links.addLink.url': return 'URL';
case 'links.addLink.bookmarkUrl': return 'URL del marcador';
case 'links.addLink.bookmarkDetails': return 'Detalles del marcador';
case 'links.addLink.validateUrl': return 'Validar URL';
case 'links.addLink.urlValid': return 'La URL es válida';
case 'links.addLink.urlNotValid': return 'La URL no es válida';
case 'links.addLink.cannotCheckUrl': return 'No se pudo comprobar la URL';
case 'links.addLink.invalidUrl': return 'URL no válida';
case 'links.addLink.checkingUrl': return 'Comprobando URL...'; case 'links.addLink.checkingUrl': return 'Comprobando URL...';
case 'links.addLink.title': return 'Title'; case 'links.addLink.title': return 'Title';
case 'links.addLink.description': return 'Description'; case 'links.addLink.description': return 'Description';
case 'links.addLink.markAsUnread': return 'Marcar como no leído'; case 'links.addLink.markAsUnread': return 'Marcar como no leído';
case 'links.addLink.notes': return 'Notas'; case 'links.addLink.notes': return 'Notas';
case 'links.addLink.addNotes': return 'Añadir notas'; case 'links.addLink.addNotes': return 'Añadir notas';
case 'links.addLink.tags': return 'Etiquetas';
case 'links.addLink.others': return 'Otros';
case 'links.addLink.savingLink': return 'Guardando enlace...'; case 'links.addLink.savingLink': return 'Guardando enlace...';
case 'links.addLink.errorSavingLink': return 'Ocurrió un error al guardar el nuevo enlace.'; case 'links.addLink.errorSavingLink': return 'Ocurrió un error al guardar el nuevo enlace.';
case 'links.addLink.leaveEmptyUseWebsiteTitle': return 'Dejar vacío para usar el título del sitio web.'; case 'links.addLink.leaveEmptyUseWebsiteTitle': return 'Dejar vacío para usar el título del sitio web.';

View File

@@ -5,7 +5,8 @@
"next": "Next", "next": "Next",
"save": "Save", "save": "Save",
"close": "Close", "close": "Close",
"error": "Error" "error": "Error",
"optional": "Optional"
}, },
"onboarding": { "onboarding": {
"title": "Welcome to Linkdy", "title": "Welcome to Linkdy",
@@ -38,6 +39,13 @@
}, },
"addLink": { "addLink": {
"addLink": "Add new link", "addLink": "Add new link",
"bookmarkUrl": "Bookmark URL",
"bookmarkDetails": "Bookmark details",
"validateUrl": "Validate URL",
"urlValid": "The URL is valid",
"urlNotValid": "The URL is not valid",
"cannotCheckUrl": "The URL couldn't be checked",
"invalidUrl": "Invalid URL",
"url": "URL", "url": "URL",
"checkingUrl": "Checking URL...", "checkingUrl": "Checking URL...",
"title": "Title", "title": "Title",
@@ -45,6 +53,8 @@
"markAsUnread": "Mark as unread", "markAsUnread": "Mark as unread",
"notes": "Notes", "notes": "Notes",
"addNotes": "Add notes", "addNotes": "Add notes",
"tags": "Tags",
"others": "Others",
"savingLink": "Saving link...", "savingLink": "Saving link...",
"errorSavingLink": "An error occured when saving the new link.", "errorSavingLink": "An error occured when saving the new link.",
"leaveEmptyUseWebsiteTitle": "Leave empty to use the website's title.", "leaveEmptyUseWebsiteTitle": "Leave empty to use the website's title.",

View File

@@ -5,7 +5,8 @@
"next": "Siguiente", "next": "Siguiente",
"save": "Guardar", "save": "Guardar",
"close": "Cerrar", "close": "Cerrar",
"error": "Error" "error": "Error",
"optional": "Opcional"
}, },
"onboarding": { "onboarding": {
"title": "Bienvenido a Linkdy", "title": "Bienvenido a Linkdy",
@@ -39,12 +40,21 @@
"addLink": { "addLink": {
"addLink": "Añadir nuevo enlace", "addLink": "Añadir nuevo enlace",
"url": "URL", "url": "URL",
"bookmarkUrl": "URL del marcador",
"bookmarkDetails": "Detalles del marcador",
"validateUrl": "Validar URL",
"urlValid": "La URL es válida",
"urlNotValid": "La URL no es válida",
"cannotCheckUrl": "No se pudo comprobar la URL",
"invalidUrl": "URL no válida",
"checkingUrl": "Comprobando URL...", "checkingUrl": "Comprobando URL...",
"title": "Title", "title": "Title",
"description": "Description", "description": "Description",
"markAsUnread": "Marcar como no leído", "markAsUnread": "Marcar como no leído",
"notes": "Notas", "notes": "Notas",
"addNotes": "Añadir notas", "addNotes": "Añadir notas",
"tags": "Etiquetas",
"others": "Otros",
"savingLink": "Guardando enlace...", "savingLink": "Guardando enlace...",
"errorSavingLink": "Ocurrió un error al guardar el nuevo enlace.", "errorSavingLink": "Ocurrió un error al guardar el nuevo enlace.",
"leaveEmptyUseWebsiteTitle": "Dejar vacío para usar el título del sitio web.", "leaveEmptyUseWebsiteTitle": "Dejar vacío para usar el título del sitio web.",

View File

@@ -1,4 +1,3 @@
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:linkdy/constants/enums.dart'; import 'package:linkdy/constants/enums.dart';
@@ -12,8 +11,8 @@ class AddLinkModel {
final TextEditingController titleController; final TextEditingController titleController;
final TextEditingController descriptionController; final TextEditingController descriptionController;
bool markAsUnread; bool markAsUnread;
final List<String> tags;
final TextEditingController notesController; final TextEditingController notesController;
final ExpandableController expandableController;
AddLinkModel({ AddLinkModel({
required this.urlController, required this.urlController,
@@ -23,7 +22,7 @@ class AddLinkModel {
required this.titleController, required this.titleController,
required this.descriptionController, required this.descriptionController,
this.markAsUnread = false, this.markAsUnread = false,
required this.tags,
required this.notesController, required this.notesController,
required this.expandableController,
}); });
} }

View File

@@ -1,13 +1,11 @@
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:linkdy/utils/snackbar.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:linkdy/screens/links/ui/error_save_link.dart';
import 'package:linkdy/screens/links/provider/links.provider.dart'; import 'package:linkdy/screens/links/provider/links.provider.dart';
import 'package:linkdy/screens/links/model/add_link.model.dart'; import 'package:linkdy/screens/links/model/add_link.model.dart';
import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/i18n/strings.g.dart';
import 'package:linkdy/router/routes.dart';
import 'package:linkdy/utils/process_modal.dart'; import 'package:linkdy/utils/process_modal.dart';
import 'package:linkdy/models/data/post_bookmark.dart'; import 'package:linkdy/models/data/post_bookmark.dart';
import 'package:linkdy/providers/router_provider.dart'; import 'package:linkdy/providers/router_provider.dart';
@@ -40,8 +38,8 @@ class AddLink extends _$AddLink {
urlController: TextEditingController(), urlController: TextEditingController(),
titleController: TextEditingController(), titleController: TextEditingController(),
descriptionController: TextEditingController(), descriptionController: TextEditingController(),
tags: [],
notesController: TextEditingController(), notesController: TextEditingController(),
expandableController: ExpandableController(),
); );
} }
@@ -52,7 +50,7 @@ class AddLink extends _$AddLink {
state.urlError = null; state.urlError = null;
ref.notifyListeners(); ref.notifyListeners();
} else { } else {
state.urlError = "Invalid URL"; state.urlError = t.links.addLink.invalidUrl;
ref.notifyListeners(); ref.notifyListeners();
} }
} }
@@ -102,9 +100,9 @@ class AddLink extends _$AddLink {
ref.invalidate(linksRequestProvider); ref.invalidate(linksRequestProvider);
ref.watch(routerProvider).pop(); ref.watch(routerProvider).pop();
} else { } else {
showDialog( showSnacbkar(
context: rootNavigatorKey.currentContext!, label: t.links.addLink.errorSavingLink,
builder: (context) => const ErrorSaveLink(), color: Colors.red,
); );
} }
} }

View File

@@ -1,77 +1,67 @@
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:linkdy/screens/links/provider/add_link.provider.dart'; import 'package:linkdy/screens/links/provider/add_link.provider.dart';
import 'package:linkdy/widgets/autocomplete_text_field.dart';
import 'package:linkdy/widgets/section_label.dart';
import 'package:linkdy/constants/enums.dart';
import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/i18n/strings.g.dart';
import 'package:linkdy/constants/enums.dart';
class AddLinkModal extends ConsumerStatefulWidget { class AddLinkModal extends ConsumerWidget {
const AddLinkModal({Key? key}) : super(key: key); final bool fullscreen;
const AddLinkModal({
Key? key,
required this.fullscreen,
}) : super(key: key);
@override @override
AddLinkModalState createState() => AddLinkModalState(); Widget build(BuildContext context, WidgetRef ref) {
} return Dialog.fullscreen(
child: Scaffold(
class AddLinkModalState extends ConsumerState<AddLinkModal> with SingleTickerProviderStateMixin { appBar: AppBar(
late AnimationController _animationController; leading: CloseButton(
late Animation<double> _animation; onPressed: () => Navigator.pop(context),
),
@override title: Text(t.links.addLink.addLink),
void initState() { actions: [
_animationController = AnimationController( IconButton(
duration: const Duration(milliseconds: 200), onPressed: ref.watch(addLinkProvider).checkBookmark != null
vsync: this, ? () => ref.read(addLinkProvider.notifier).addBookmark()
); : null,
_animation = Tween( icon: const Icon(Icons.save_rounded),
begin: 0.0, tooltip: t.generic.save,
end: 0.5, ),
).animate( const SizedBox(width: 8),
CurvedAnimation( ],
parent: _animationController, ),
curve: Curves.easeInOut, body: const _ModalContent(),
), ),
); );
}
super.initState();
} }
class _ModalContent extends ConsumerWidget {
const _ModalContent({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(addLinkProvider); final provider = ref.watch(addLinkProvider);
return Padding( return ListView(
padding: MediaQuery.of(context).viewInsets,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(28), topRight: Radius.circular(28)),
color: Theme.of(context).dialogBackgroundColor,
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Flexible(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 24),
Icon(
Icons.add_rounded,
color: Theme.of(context).colorScheme.secondary,
size: 24,
),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( SectionLabel(
t.links.addLink.addLink, label: t.links.addLink.bookmarkUrl,
textAlign: TextAlign.center, padding: const EdgeInsets.symmetric(
style: const TextStyle(fontSize: 24), horizontal: 16,
vertical: 0,
),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField( child: TextFormField(
controller: provider.urlController, controller: provider.urlController,
onChanged: ref.read(addLinkProvider.notifier).validateUrl, onChanged: ref.read(addLinkProvider.notifier).validateUrl,
@@ -88,15 +78,36 @@ class AddLinkModalState extends ConsumerState<AddLinkModal> with SingleTickerPro
), ),
), ),
), ),
if (provider.checkBookmarkLoadStatus == LoadStatus.loading) const SizedBox(height: 16),
Padding( Padding(
padding: const EdgeInsets.only(top: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center(
child: ElevatedButton(
onPressed: provider.checkBookmarkLoadStatus == null &&
provider.urlError == null &&
provider.urlController.text != ""
? () => ref.read(addLinkProvider.notifier).checkUrlDetails()
: null,
style: ButtonStyle(
foregroundColor: provider.checkBookmarkLoadStatus == LoadStatus.loaded
? const MaterialStatePropertyAll(Colors.green)
: provider.checkBookmarkLoadStatus == LoadStatus.loaded
? const MaterialStatePropertyAll(Colors.red)
: null,
backgroundColor: provider.checkBookmarkLoadStatus == LoadStatus.loaded
? MaterialStatePropertyAll(Colors.green.withOpacity(0.15))
: provider.checkBookmarkLoadStatus == LoadStatus.loaded
? MaterialStatePropertyAll(Colors.red.withOpacity(0.15))
: null,
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (provider.checkBookmarkLoadStatus == null) Text(t.links.addLink.validateUrl),
if (provider.checkBookmarkLoadStatus == LoadStatus.loading) ...[
const SizedBox( const SizedBox(
height: 12,
width: 12, width: 12,
height: 12,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
), ),
@@ -104,19 +115,32 @@ class AddLinkModalState extends ConsumerState<AddLinkModal> with SingleTickerPro
const SizedBox(width: 8), const SizedBox(width: 8),
Text(t.links.addLink.checkingUrl), Text(t.links.addLink.checkingUrl),
], ],
if (provider.checkBookmarkLoadStatus == LoadStatus.loaded) ...[
const Icon(Icons.check_circle_rounded),
const SizedBox(width: 8),
Text(t.links.addLink.urlValid),
],
if (provider.checkBookmarkLoadStatus == LoadStatus.error) ...[
const Icon(Icons.error_rounded),
const SizedBox(width: 8),
Text(t.links.addLink.cannotCheckUrl),
],
],
), ),
), ),
if (provider.checkBookmark != null) ...[ ),
const Padding( ),
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16), SectionLabel(
child: Divider(), label: t.links.addLink.bookmarkDetails,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 24,
),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField( child: TextFormField(
controller: provider.titleController, controller: provider.titleController,
// onChanged: ref.read(addLinkProvider.notifier).validateUrl,
// enabled: provider.checkBookmarkLoadStatus != LoadStatus.loading,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.title_rounded), prefixIcon: const Icon(Icons.title_rounded),
border: const OutlineInputBorder( border: const OutlineInputBorder(
@@ -124,21 +148,20 @@ class AddLinkModalState extends ConsumerState<AddLinkModal> with SingleTickerPro
Radius.circular(10), Radius.circular(10),
), ),
), ),
errorText: provider.urlError,
labelText: t.links.addLink.title, labelText: t.links.addLink.title,
hintText: provider.checkBookmark?.metadata?.title, hintText: provider.checkBookmark?.metadata?.title,
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior:
provider.checkBookmark != null ? FloatingLabelBehavior.always : FloatingLabelBehavior.auto,
helperText: t.links.addLink.leaveEmptyUseWebsiteTitle, helperText: t.links.addLink.leaveEmptyUseWebsiteTitle,
enabled: provider.checkBookmark != null,
), ),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField( child: TextFormField(
controller: provider.descriptionController, controller: provider.descriptionController,
// onChanged: ref.read(addLinkProvider.notifier).validateUrl,
// enabled: provider.checkBookmarkLoadStatus != LoadStatus.loading,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.description_rounded), prefixIcon: const Icon(Icons.description_rounded),
border: const OutlineInputBorder( border: const OutlineInputBorder(
@@ -146,58 +169,33 @@ class AddLinkModalState extends ConsumerState<AddLinkModal> with SingleTickerPro
Radius.circular(10), Radius.circular(10),
), ),
), ),
errorText: provider.urlError,
labelText: t.links.addLink.description, labelText: t.links.addLink.description,
hintText: provider.checkBookmark?.metadata?.description, hintText: provider.checkBookmark?.metadata?.description,
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior:
provider.checkBookmark != null ? FloatingLabelBehavior.always : FloatingLabelBehavior.auto,
helperText: t.links.addLink.leaveEmptyUseWebsiteDescription, helperText: t.links.addLink.leaveEmptyUseWebsiteDescription,
enabled: provider.checkBookmark != null,
), ),
), ),
), ),
const SizedBox(height: 16), SectionLabel(
label: t.links.addLink.tags,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 24,
),
),
SectionLabel(
label: t.links.addLink.others,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 24,
),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: ExpandablePanel( child: SizedBox(
controller: provider.expandableController, height: 150,
collapsed: ListTile(
onTap: () {
_animationController.animateTo(provider.expandableController.expanded == true ? 0 : 1);
provider.expandableController.toggle();
},
title: Text(t.links.addLink.addNotes),
trailing: RotationTransition(
turns: _animation,
child: Icon(
Icons.expand_more_rounded,
size: 26,
color: Theme.of(context).colorScheme.secondary,
),
),
contentPadding: const EdgeInsets.all(0),
),
expanded: Column(
children: [
ListTile(
onTap: () {
_animationController.animateTo(
provider.expandableController.expanded == true ? 0 : 1,
);
provider.expandableController.toggle();
},
title: Text(t.links.addLink.addNotes),
trailing: RotationTransition(
turns: _animation,
child: Icon(
Icons.expand_more_rounded,
size: 26,
color: Theme.of(context).colorScheme.secondary,
),
),
contentPadding: const EdgeInsets.all(0),
),
const SizedBox(height: 8),
SizedBox(
height: 130,
child: TextFormField( child: TextFormField(
controller: provider.notesController, controller: provider.notesController,
decoration: InputDecoration( decoration: InputDecoration(
@@ -208,59 +206,26 @@ class AddLinkModalState extends ConsumerState<AddLinkModal> with SingleTickerPro
), ),
), ),
labelText: t.links.addLink.notes, labelText: t.links.addLink.notes,
helperText: t.generic.optional,
), ),
autocorrect: false, autocorrect: false,
expands: true, expands: true,
minLines: null, minLines: null,
maxLines: null, maxLines: null,
textAlignVertical: TextAlignVertical.top, textAlignVertical: TextAlignVertical.top,
), enabled: provider.checkBookmark != null,
),
const SizedBox(height: 16),
],
), ),
), ),
), ),
const SizedBox(height: 12),
SwitchListTile( SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 0), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
title: Text(t.links.addLink.markAsUnread), title: Text(t.links.addLink.markAsUnread),
value: provider.markAsUnread, value: provider.markAsUnread,
onChanged: ref.read(addLinkProvider.notifier).updateMarkAsUnread, onChanged:
provider.checkBookmark != null ? (v) => ref.read(addLinkProvider.notifier).updateMarkAsUnread(v) : null,
), ),
], ],
],
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(t.generic.cancel),
),
const SizedBox(width: 8),
if (provider.checkBookmark == null)
TextButton(
onPressed: provider.checkBookmarkLoadStatus != LoadStatus.loading
? ref.read(addLinkProvider.notifier).checkUrlDetails
: null,
child: Text(t.generic.next),
),
if (provider.checkBookmark != null)
TextButton(
onPressed: ref.read(addLinkProvider.notifier).addBookmark,
child: Text(t.generic.confirm),
),
],
),
),
],
),
),
),
); );
} }
} }

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -16,6 +18,8 @@ class Links extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final bookmarks = ref.watch(linksRequestProvider); final bookmarks = ref.watch(linksRequestProvider);
final width = MediaQuery.of(context).size.width;
String validateStrings(String? string1, String? string2) { String validateStrings(String? string1, String? string2) {
if (string1 != null && string1.isNotEmpty) { if (string1 != null && string1.isNotEmpty) {
return string1; return string1;
@@ -44,12 +48,18 @@ class Links extends ConsumerWidget {
} }
void openAddModal() { void openAddModal() {
showModalBottomSheet( showGeneralDialog(
useRootNavigator: true,
context: context, context: context,
isScrollControlled: true, barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)) ? Colors.transparent : Colors.black54,
useSafeArea: true, transitionBuilder: (context, anim1, anim2, child) {
builder: (context) => const AddLinkModal(), 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) => AddLinkModal(fullscreen: width <= 700),
); );
} }

View File

@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
class AutocompleteTextField extends StatefulWidget {
final IconData leadingIcon;
final String label;
const AutocompleteTextField({
Key? key,
required this.leadingIcon,
required this.label,
}) : super(key: key);
@override
State<AutocompleteTextField> createState() => _AutocompleteTextFieldState();
}
class _AutocompleteTextFieldState extends State<AutocompleteTextField> {
final _controller = TextEditingController();
final _overlayController = OverlayPortalController();
@override
Widget build(BuildContext context) {
return Stack(
children: [
TextFormField(
controller: _controller,
onChanged: (value) {
if (value.isNotEmpty) {
_overlayController.show();
} else {
_overlayController.hide();
}
},
decoration: InputDecoration(
prefixIcon: Icon(widget.leadingIcon),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
labelText: widget.label,
),
),
OverlayPortal(
controller: _overlayController,
overlayChildBuilder: (context) => Positioned(
top: 0,
left: 0,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.grey,
),
),
),
),
],
);
}
}