Added create tag

This commit is contained in:
Juan Gilsanz Polo
2024-02-24 13:50:07 +01:00
parent 5378d020e4
commit fe2f5629e3
13 changed files with 285 additions and 28 deletions

View File

@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 2 /// Locales: 2
/// Strings: 208 (104 per locale) /// Strings: 218 (109 per locale)
/// ///
/// Built on 2024-02-24 at 12:07 UTC /// Built on 2024-02-24 at 12:45 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
@@ -227,6 +227,8 @@ class _StringsTagsEn {
// Translations // Translations
String get tags => 'Tags'; String get tags => 'Tags';
String created({required Object created}) => 'Created: ${created}';
late final _StringsTagsCreateTagEn createTag = _StringsTagsCreateTagEn._(_root);
} }
// Path: settings // Path: settings
@@ -341,6 +343,19 @@ class _StringsBookmarksSearchEn {
String get inputtedSearchTermNoResults => 'The inputted search term doesn\'t have any results'; String get inputtedSearchTermNoResults => 'The inputted search term doesn\'t have any results';
} }
// Path: tags.createTag
class _StringsTagsCreateTagEn {
_StringsTagsCreateTagEn._(this._root);
final Translations _root; // ignore: unused_field
// Translations
String get createTag => 'Create tag';
String get name => 'Name';
String get creatingTag => 'Creating tag...';
String get errorCreatingTag => 'An error occured when creating the tag.';
}
// Path: settings.customization // Path: settings.customization
class _StringsSettingsCustomizationEn { class _StringsSettingsCustomizationEn {
_StringsSettingsCustomizationEn._(this._root); _StringsSettingsCustomizationEn._(this._root);
@@ -487,6 +502,8 @@ class _StringsTagsEs implements _StringsTagsEn {
// Translations // Translations
@override String get tags => 'Etiquetas'; @override String get tags => 'Etiquetas';
@override String created({required Object created}) => 'Creado: ${created}';
@override late final _StringsTagsCreateTagEs createTag = _StringsTagsCreateTagEs._(_root);
} }
// Path: settings // Path: settings
@@ -601,6 +618,19 @@ class _StringsBookmarksSearchEs implements _StringsBookmarksSearchEn {
@override String get inputtedSearchTermNoResults => 'El término de búsqueda introducido no tiene resultados'; @override String get inputtedSearchTermNoResults => 'El término de búsqueda introducido no tiene resultados';
} }
// Path: tags.createTag
class _StringsTagsCreateTagEs implements _StringsTagsCreateTagEn {
_StringsTagsCreateTagEs._(this._root);
@override final _StringsEs _root; // ignore: unused_field
// Translations
@override String get createTag => 'Crear etiqueta';
@override String get name => 'Nombre';
@override String get creatingTag => 'Creando etiqueta...';
@override String get errorCreatingTag => 'Ocurrió un error al crear la etiqueta.';
}
// Path: settings.customization // Path: settings.customization
class _StringsSettingsCustomizationEs implements _StringsSettingsCustomizationEn { class _StringsSettingsCustomizationEs implements _StringsSettingsCustomizationEn {
_StringsSettingsCustomizationEs._(this._root); _StringsSettingsCustomizationEs._(this._root);
@@ -714,6 +744,11 @@ extension on Translations {
case 'bookmarks.search.cannotSearchError': return 'Cannot search content due to an error'; case 'bookmarks.search.cannotSearchError': return 'Cannot search content due to an error';
case 'bookmarks.search.inputtedSearchTermNoResults': return 'The inputted search term doesn\'t have any results'; case 'bookmarks.search.inputtedSearchTermNoResults': return 'The inputted search term doesn\'t have any results';
case 'tags.tags': return 'Tags'; case 'tags.tags': return 'Tags';
case 'tags.created': return ({required Object created}) => 'Created: ${created}';
case 'tags.createTag.createTag': return 'Create tag';
case 'tags.createTag.name': return 'Name';
case 'tags.createTag.creatingTag': return 'Creating tag...';
case 'tags.createTag.errorCreatingTag': return 'An error occured when creating the tag.';
case 'settings.settings': return 'Settings'; case 'settings.settings': return 'Settings';
case 'settings.appSettings': return 'App settings'; case 'settings.appSettings': return 'App settings';
case 'settings.aboutApp': return 'About the app'; case 'settings.aboutApp': return 'About the app';
@@ -826,6 +861,11 @@ extension on _StringsEs {
case 'bookmarks.search.cannotSearchError': return 'No se puede buscar el contenido por un error'; case 'bookmarks.search.cannotSearchError': return 'No se puede buscar el contenido por un error';
case 'bookmarks.search.inputtedSearchTermNoResults': return 'El término de búsqueda introducido no tiene resultados'; case 'bookmarks.search.inputtedSearchTermNoResults': return 'El término de búsqueda introducido no tiene resultados';
case 'tags.tags': return 'Etiquetas'; case 'tags.tags': return 'Etiquetas';
case 'tags.created': return ({required Object created}) => 'Creado: ${created}';
case 'tags.createTag.createTag': return 'Crear etiqueta';
case 'tags.createTag.name': return 'Nombre';
case 'tags.createTag.creatingTag': return 'Creando etiqueta...';
case 'tags.createTag.errorCreatingTag': return 'Ocurrió un error al crear la etiqueta.';
case 'settings.settings': return 'Ajustes'; case 'settings.settings': return 'Ajustes';
case 'settings.appSettings': return 'Ajustes de la aplicación'; case 'settings.appSettings': return 'Ajustes de la aplicación';
case 'settings.aboutApp': return 'Sobre la aplicación'; case 'settings.aboutApp': return 'Sobre la aplicación';

View File

@@ -75,7 +75,14 @@
} }
}, },
"tags": { "tags": {
"tags": "Tags" "tags": "Tags",
"created": "Created: $created",
"createTag": {
"createTag": "Create tag",
"name": "Name",
"creatingTag": "Creating tag...",
"errorCreatingTag": "An error occured when creating the tag."
}
}, },
"settings": { "settings": {
"settings": "Settings", "settings": "Settings",

View File

@@ -75,7 +75,14 @@
} }
}, },
"tags": { "tags": {
"tags": "Etiquetas" "tags": "Etiquetas",
"created": "Creado: $created",
"createTag": {
"createTag": "Crear etiqueta",
"name": "Nombre",
"creatingTag": "Creando etiqueta...",
"errorCreatingTag": "Ocurrió un error al crear la etiqueta."
}
}, },
"settings": { "settings": {
"settings": "Ajustes", "settings": "Ajustes",

View File

@@ -2,7 +2,7 @@ class TagsResponse {
final int? count; final int? count;
final int? next; final int? next;
final int? previous; final int? previous;
final List<Result>? results; final List<Tag>? results;
TagsResponse({ TagsResponse({
this.count, this.count,
@@ -15,7 +15,7 @@ class TagsResponse {
count: json["count"], count: json["count"],
next: json["next"], next: json["next"],
previous: json["previous"], previous: json["previous"],
results: json["results"] == null ? [] : List<Result>.from(json["results"]!.map((x) => Result.fromJson(x))), results: json["results"] == null ? [] : List<Tag>.from(json["results"]!.map((x) => Tag.fromJson(x))),
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@@ -26,18 +26,18 @@ class TagsResponse {
}; };
} }
class Result { class Tag {
final int? id; final int? id;
final String? name; final String? name;
final DateTime? dateAdded; final DateTime? dateAdded;
Result({ Tag({
this.id, this.id,
this.name, this.name,
this.dateAdded, this.dateAdded,
}); });
factory Result.fromJson(Map<String, dynamic> json) => Result( factory Tag.fromJson(Map<String, dynamic> json) => Tag(
id: json["id"], id: json["id"],
name: json["name"], name: json["name"],
dateAdded: json["date_added"] == null ? null : DateTime.parse(json["date_added"]), dateAdded: json["date_added"] == null ? null : DateTime.parse(json["date_added"]),

View File

@@ -93,7 +93,7 @@ class AddBookmark extends _$AddBookmark {
final processModal = ProcessModal(); final processModal = ProcessModal();
processModal.open(t.bookmarks.addBookmark.savingBookmark); processModal.open(t.bookmarks.addBookmark.savingBookmark);
final result = await ref.watch(apiClientProvider)!.fetchPostBookmark(newBookmark); final result = await ref.watch(apiClientProvider)!.postBookmark(newBookmark);
processModal.close(); processModal.close();

View File

@@ -175,7 +175,7 @@ final getTagsProvider =
); );
typedef GetTagsRef = AutoDisposeFutureProviderRef<ApiResponse<TagsResponse>>; typedef GetTagsRef = AutoDisposeFutureProviderRef<ApiResponse<TagsResponse>>;
String _$addBookmarkHash() => r'2953a4223fe70809fbcf37af23b4f61a62f0de9f'; String _$addBookmarkHash() => r'7a2a1c6c6597aa28fa478d2ef9dc3bc4d71c143d';
/// See also [AddBookmark]. /// See also [AddBookmark].
@ProviderFor(AddBookmark) @ProviderFor(AddBookmark)

View File

@@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
class AddTagModel {
final TextEditingController nameController;
const AddTagModel({
required this.nameController,
});
}

View File

@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:linkdy/screens/tags/ui/add_tag_error_modal.dart';
import 'package:linkdy/screens/tags/provider/tags.provider.dart';
import 'package:linkdy/screens/tags/models/add_tag.modal.dart';
import 'package:linkdy/router/routes.dart';
import 'package:linkdy/providers/api_client_provider.dart';
import 'package:linkdy/providers/router_provider.dart';
import 'package:linkdy/utils/process_modal.dart';
import 'package:linkdy/i18n/strings.g.dart';
part 'add_tag.provider.g.dart';
@riverpod
class AddTag extends _$AddTag {
@override
AddTagModel build() {
return AddTagModel(
nameController: TextEditingController(),
);
}
void notifyOnWrite() {
ref.notifyListeners();
}
void onAddTag() async {
final processModal = ProcessModal();
processModal.open(t.tags.createTag.creatingTag);
final result = await ref.watch(apiClientProvider)!.postTag(state.nameController.text);
processModal.close();
if (result.successful == true) {
ref.invalidate(tagsRequestProvider);
ref.watch(routerProvider).pop();
} else {
if (rootNavigatorKey.currentContext == null) return;
showDialog(
context: rootNavigatorKey.currentContext!,
builder: (context) => const AddTagErrorModal(),
);
}
}
}

View File

@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'add_tag.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$addTagHash() => r'8157bc7911ae64627adabbe1528f519a4b1cb913';
/// See also [AddTag].
@ProviderFor(AddTag)
final addTagProvider =
AutoDisposeNotifierProvider<AddTag, AddTagModel>.internal(
AddTag.new,
name: r'addTagProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$addTagHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AddTag = AutoDisposeNotifier<AddTagModel>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:linkdy/i18n/strings.g.dart';
class AddTagErrorModal extends StatelessWidget {
const AddTagErrorModal({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(t.generic.error),
content: Text(t.tags.createTag.errorCreatingTag),
actions: [
Row(
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(t.generic.close),
),
],
),
],
);
}
}

View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:linkdy/screens/tags/provider/add_tag.provider.dart';
import 'package:linkdy/i18n/strings.g.dart';
class AddTagModal extends ConsumerWidget {
const AddTagModal({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(addTagProvider);
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: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.add_rounded,
color: Theme.of(context).colorScheme.secondary,
size: 24,
),
const SizedBox(height: 16),
Text(
t.tags.createTag.createTag,
style: const TextStyle(
fontSize: 24,
),
),
const SizedBox(height: 24),
TextFormField(
controller: provider.nameController,
onChanged: (_) => ref.read(addTagProvider.notifier).notifyOnWrite(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.label_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
labelText: t.tags.createTag.name,
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(t.generic.cancel),
),
const SizedBox(width: 8),
TextButton(
onPressed: provider.nameController.text != ""
? () => ref.read(addTagProvider.notifier).onAddTag()
: null,
child: Text(t.generic.confirm),
),
],
),
),
],
),
),
),
),
);
}
}

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:linkdy/screens/tags/provider/tags.provider.dart'; import 'package:linkdy/screens/tags/provider/tags.provider.dart';
import 'package:linkdy/screens/tags/ui/add_tag_modal.dart';
import 'package:linkdy/utils/date_to_string.dart'; import 'package:linkdy/utils/date_to_string.dart';
import 'package:linkdy/widgets/error_screen.dart'; import 'package:linkdy/widgets/error_screen.dart';
import 'package:linkdy/widgets/no_data_screen.dart'; import 'package:linkdy/widgets/no_data_screen.dart';
@@ -17,22 +18,13 @@ class TagsScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tags = ref.watch(tagsRequestProvider); final tags = ref.watch(tagsRequestProvider);
final width = MediaQuery.of(context).size.width;
void openAddModal() { void openAddModal() {
// showGeneralDialog( showModalBottomSheet(
// context: context, context: context,
// barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)) ? Colors.transparent : Colors.black54, useRootNavigator: true,
// transitionBuilder: (context, anim1, anim2, child) { isScrollControlled: true,
// return SlideTransition( builder: (context) => const AddTagModal(),
// 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) => AddBookmarkModal(fullscreen: width <= 700),
// );
} }
return Scaffold( return Scaffold(
@@ -101,7 +93,7 @@ class TagsScreen extends ConsumerWidget {
), ),
), ),
subtitle: Text( subtitle: Text(
dateToString(tag.dateAdded!), t.tags.created(created: dateToString(tag.dateAdded!)),
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,

View File

@@ -70,7 +70,7 @@ class ApiClientService {
} }
} }
Future<ApiResponse<Bookmark>> fetchPostBookmark(PostBookmark bookmark) async { Future<ApiResponse<Bookmark>> postBookmark(PostBookmark bookmark) async {
try { try {
final response = await dioInstance.post( final response = await dioInstance.post(
"/bookmarks/", "/bookmarks/",
@@ -105,4 +105,20 @@ class ApiClientService {
return const ApiResponse(successful: false); return const ApiResponse(successful: false);
} }
} }
Future<ApiResponse<Tag>> postTag(String name) async {
try {
final response = await dioInstance.post(
"/tags/",
data: FormData.fromMap({"name": name}),
);
return ApiResponse(
successful: true,
content: Tag.fromJson(response.data),
);
} catch (e, stackTrace) {
Sentry.captureException(e, stackTrace: stackTrace);
return const ApiResponse(successful: false);
}
}
} }