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`
///
/// 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
// ignore_for_file: type=lint
@@ -170,6 +170,7 @@ class _StringsGenericEn {
String get save => 'Save';
String get close => 'Close';
String get error => 'Error';
String get optional => 'Optional';
}
// Path: onboarding
@@ -297,6 +298,13 @@ class _StringsLinksAddLinkEn {
// Translations
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 checkingUrl => 'Checking URL...';
String get title => 'Title';
@@ -304,6 +312,8 @@ class _StringsLinksAddLinkEn {
String get markAsUnread => 'Mark as unread';
String get notes => 'Notes';
String get addNotes => 'Add notes';
String get tags => 'Tags';
String get others => 'Others';
String get savingLink => 'Saving link...';
String get errorSavingLink => 'An error occured when saving the new link.';
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 close => 'Cerrar';
@override String get error => 'Error';
@override String get optional => 'Opcional';
}
// Path: onboarding
@@ -525,12 +536,21 @@ class _StringsLinksAddLinkEs implements _StringsLinksAddLinkEn {
// Translations
@override String get addLink => 'Añadir nuevo enlace';
@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 title => 'Title';
@override String get description => 'Description';
@override String get markAsUnread => 'Marcar como no leído';
@override String get notes => '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 errorSavingLink => 'Ocurrió un error al guardar el nuevo enlace.';
@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.close': return 'Close';
case 'generic.error': return 'Error';
case 'generic.optional': return 'Optional';
case 'onboarding.title': return 'Welcome to Linkdy';
case 'onboarding.subtitle': return 'An application to manage your bookmarks.';
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.yesterdayAt': return ({required Object time}) => 'Yesterday, ${time}';
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.checkingUrl': return 'Checking URL...';
case 'links.addLink.title': return 'Title';
@@ -622,6 +650,8 @@ extension on Translations {
case 'links.addLink.markAsUnread': return 'Mark as unread';
case 'links.addLink.notes': return '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.errorSavingLink': return 'An error occured when saving the new link.';
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.close': return 'Cerrar';
case 'generic.error': return 'Error';
case 'generic.optional': return 'Opcional';
case 'onboarding.title': return 'Bienvenido a Linkdy';
case 'onboarding.subtitle': return 'Una aplicación para gestionar tus enlaces favoritos.';
case 'onboarding.start': return 'Comenzar';
@@ -705,12 +736,21 @@ extension on _StringsEs {
case 'links.dates.yesterdayAt': return ({required Object time}) => 'Ayer, ${time}';
case 'links.addLink.addLink': return 'Añadir nuevo enlace';
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.title': return 'Title';
case 'links.addLink.description': return 'Description';
case 'links.addLink.markAsUnread': return 'Marcar como no leído';
case 'links.addLink.notes': return '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.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.';

View File

@@ -5,7 +5,8 @@
"next": "Next",
"save": "Save",
"close": "Close",
"error": "Error"
"error": "Error",
"optional": "Optional"
},
"onboarding": {
"title": "Welcome to Linkdy",
@@ -38,6 +39,13 @@
},
"addLink": {
"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",
"checkingUrl": "Checking URL...",
"title": "Title",
@@ -45,6 +53,8 @@
"markAsUnread": "Mark as unread",
"notes": "Notes",
"addNotes": "Add notes",
"tags": "Tags",
"others": "Others",
"savingLink": "Saving link...",
"errorSavingLink": "An error occured when saving the new link.",
"leaveEmptyUseWebsiteTitle": "Leave empty to use the website's title.",

View File

@@ -5,7 +5,8 @@
"next": "Siguiente",
"save": "Guardar",
"close": "Cerrar",
"error": "Error"
"error": "Error",
"optional": "Opcional"
},
"onboarding": {
"title": "Bienvenido a Linkdy",
@@ -39,12 +40,21 @@
"addLink": {
"addLink": "Añadir nuevo enlace",
"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...",
"title": "Title",
"description": "Description",
"markAsUnread": "Marcar como no leído",
"notes": "Notas",
"addNotes": "Añadir notas",
"tags": "Etiquetas",
"others": "Otros",
"savingLink": "Guardando enlace...",
"errorSavingLink": "Ocurrió un error al guardar el nuevo enlace.",
"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:linkdy/constants/enums.dart';
@@ -12,8 +11,8 @@ class AddLinkModel {
final TextEditingController titleController;
final TextEditingController descriptionController;
bool markAsUnread;
final List<String> tags;
final TextEditingController notesController;
final ExpandableController expandableController;
AddLinkModel({
required this.urlController,
@@ -23,7 +22,7 @@ class AddLinkModel {
required this.titleController,
required this.descriptionController,
this.markAsUnread = false,
required this.tags,
required this.notesController,
required this.expandableController,
});
}

View File

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

View File

@@ -1,266 +1,231 @@
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/constants/enums.dart';
class AddLinkModal extends ConsumerStatefulWidget {
const AddLinkModal({Key? key}) : super(key: key);
class AddLinkModal extends ConsumerWidget {
final bool fullscreen;
const AddLinkModal({
Key? key,
required this.fullscreen,
}) : super(key: key);
@override
AddLinkModalState createState() => AddLinkModalState();
}
class AddLinkModalState extends ConsumerState<AddLinkModal> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
_animationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_animation = Tween(
begin: 0.0,
end: 0.5,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
Widget build(BuildContext context, WidgetRef ref) {
return Dialog.fullscreen(
child: Scaffold(
appBar: AppBar(
leading: CloseButton(
onPressed: () => Navigator.pop(context),
),
title: Text(t.links.addLink.addLink),
actions: [
IconButton(
onPressed: ref.watch(addLinkProvider).checkBookmark != null
? () => ref.read(addLinkProvider.notifier).addBookmark()
: null,
icon: const Icon(Icons.save_rounded),
tooltip: t.generic.save,
),
const SizedBox(width: 8),
],
),
body: const _ModalContent(),
),
);
super.initState();
}
}
class _ModalContent extends ConsumerWidget {
const _ModalContent({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(addLinkProvider);
return Padding(
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: [
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),
Text(
t.links.addLink.addLink,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24),
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: provider.urlController,
onChanged: ref.read(addLinkProvider.notifier).validateUrl,
enabled: provider.checkBookmarkLoadStatus != LoadStatus.loading,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.urlError,
labelText: t.links.addLink.url,
),
),
),
if (provider.checkBookmarkLoadStatus == LoadStatus.loading)
Padding(
padding: const EdgeInsets.only(top: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 12,
width: 12,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
const SizedBox(width: 8),
Text(t.links.addLink.checkingUrl),
],
),
),
if (provider.checkBookmark != null) ...[
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Divider(),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: provider.titleController,
// onChanged: ref.read(addLinkProvider.notifier).validateUrl,
// enabled: provider.checkBookmarkLoadStatus != LoadStatus.loading,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.title_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.urlError,
labelText: t.links.addLink.title,
hintText: provider.checkBookmark?.metadata?.title,
floatingLabelBehavior: FloatingLabelBehavior.always,
helperText: t.links.addLink.leaveEmptyUseWebsiteTitle,
),
),
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: provider.descriptionController,
// onChanged: ref.read(addLinkProvider.notifier).validateUrl,
// enabled: provider.checkBookmarkLoadStatus != LoadStatus.loading,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.description_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.urlError,
labelText: t.links.addLink.description,
hintText: provider.checkBookmark?.metadata?.description,
floatingLabelBehavior: FloatingLabelBehavior.always,
helperText: t.links.addLink.leaveEmptyUseWebsiteDescription,
),
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: ExpandablePanel(
controller: provider.expandableController,
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(
controller: provider.notesController,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.note_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
labelText: t.links.addLink.notes,
),
autocorrect: false,
expands: true,
minLines: null,
maxLines: null,
textAlignVertical: TextAlignVertical.top,
),
),
const SizedBox(height: 16),
],
),
),
),
SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 0),
title: Text(t.links.addLink.markAsUnread),
value: provider.markAsUnread,
onChanged: ref.read(addLinkProvider.notifier).updateMarkAsUnread,
),
],
],
),
),
),
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),
),
],
),
),
],
return ListView(
children: [
const SizedBox(height: 16),
SectionLabel(
label: t.links.addLink.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(addLinkProvider.notifier).validateUrl,
enabled: provider.checkBookmarkLoadStatus != LoadStatus.loading,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.urlError,
labelText: t.links.addLink.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(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(
mainAxisSize: MainAxisSize.min,
children: [
if (provider.checkBookmarkLoadStatus == null) Text(t.links.addLink.validateUrl),
if (provider.checkBookmarkLoadStatus == LoadStatus.loading) ...[
const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
const SizedBox(width: 8),
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),
],
],
),
),
),
),
SectionLabel(
label: t.links.addLink.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.links.addLink.title,
hintText: provider.checkBookmark?.metadata?.title,
floatingLabelBehavior:
provider.checkBookmark != null ? FloatingLabelBehavior.always : FloatingLabelBehavior.auto,
helperText: t.links.addLink.leaveEmptyUseWebsiteTitle,
enabled: provider.checkBookmark != null,
),
),
),
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.links.addLink.description,
hintText: provider.checkBookmark?.metadata?.description,
floatingLabelBehavior:
provider.checkBookmark != null ? FloatingLabelBehavior.always : FloatingLabelBehavior.auto,
helperText: t.links.addLink.leaveEmptyUseWebsiteDescription,
enabled: provider.checkBookmark != null,
),
),
),
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: 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.links.addLink.notes,
helperText: t.generic.optional,
),
autocorrect: false,
expands: true,
minLines: null,
maxLines: null,
textAlignVertical: TextAlignVertical.top,
enabled: provider.checkBookmark != null,
),
),
),
const SizedBox(height: 12),
SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
title: Text(t.links.addLink.markAsUnread),
value: provider.markAsUnread,
onChanged:
provider.checkBookmark != null ? (v) => ref.read(addLinkProvider.notifier).updateMarkAsUnread(v) : null,
),
],
);
}
}

View File

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