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`
///
/// 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
// ignore_for_file: type=lint
@@ -227,6 +227,8 @@ class _StringsTagsEn {
// Translations
String get tags => 'Tags';
String created({required Object created}) => 'Created: ${created}';
late final _StringsTagsCreateTagEn createTag = _StringsTagsCreateTagEn._(_root);
}
// Path: settings
@@ -341,6 +343,19 @@ class _StringsBookmarksSearchEn {
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
class _StringsSettingsCustomizationEn {
_StringsSettingsCustomizationEn._(this._root);
@@ -487,6 +502,8 @@ class _StringsTagsEs implements _StringsTagsEn {
// Translations
@override String get tags => 'Etiquetas';
@override String created({required Object created}) => 'Creado: ${created}';
@override late final _StringsTagsCreateTagEs createTag = _StringsTagsCreateTagEs._(_root);
}
// Path: settings
@@ -601,6 +618,19 @@ class _StringsBookmarksSearchEs implements _StringsBookmarksSearchEn {
@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
class _StringsSettingsCustomizationEs implements _StringsSettingsCustomizationEn {
_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.inputtedSearchTermNoResults': return 'The inputted search term doesn\'t have any results';
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.appSettings': return 'App settings';
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.inputtedSearchTermNoResults': return 'El término de búsqueda introducido no tiene resultados';
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.appSettings': return 'Ajustes de la aplicación';
case 'settings.aboutApp': return 'Sobre la aplicación';

View File

@@ -75,7 +75,14 @@
}
},
"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",

View File

@@ -75,7 +75,14 @@
}
},
"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": "Ajustes",

View File

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

View File

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

View File

@@ -175,7 +175,7 @@ final getTagsProvider =
);
typedef GetTagsRef = AutoDisposeFutureProviderRef<ApiResponse<TagsResponse>>;
String _$addBookmarkHash() => r'2953a4223fe70809fbcf37af23b4f61a62f0de9f';
String _$addBookmarkHash() => r'7a2a1c6c6597aa28fa478d2ef9dc3bc4d71c143d';
/// See also [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: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/widgets/error_screen.dart';
import 'package:linkdy/widgets/no_data_screen.dart';
@@ -17,22 +18,13 @@ class TagsScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final tags = ref.watch(tagsRequestProvider);
final width = MediaQuery.of(context).size.width;
void openAddModal() {
// showGeneralDialog(
// context: context,
// 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) => AddBookmarkModal(fullscreen: width <= 700),
// );
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => const AddTagModal(),
);
}
return Scaffold(
@@ -101,7 +93,7 @@ class TagsScreen extends ConsumerWidget {
),
),
subtitle: Text(
dateToString(tag.dateAdded!),
t.tags.created(created: dateToString(tag.dateAdded!)),
style: TextStyle(
fontSize: 14,
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 {
final response = await dioInstance.post(
"/bookmarks/",
@@ -105,4 +105,20 @@ class ApiClientService {
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);
}
}
}