From 023f1096b650b6b4713bce5dcf82fb8ba3e8d5d8 Mon Sep 17 00:00:00 2001 From: Camilo Cubillos Date: Tue, 19 Mar 2024 20:45:09 -0500 Subject: [PATCH] Done: Validate misssing data --- README.md | 6 + lib/main.dart | 18 +- lib/src/config/app_constants.dart | 8 + lib/src/config/dependecy_injection.dart | 12 + lib/src/config/router.dart | 7 +- lib/src/data/api/member_api.dart | 106 ++++++ lib/src/data/firebase_collections.dart | 4 + .../domain/repository/member_repository.dart | 39 +++ lib/src/models/member.dart | 31 ++ lib/src/models/server_exception.dart | 11 + lib/src/models/timestamp_converter.dart | 14 + lib/src/notifiers/event_notifier.dart | 26 +- .../fci_editable_text_field.dart | 237 +++++++++++++ lib/src/ui/screens/person_screen.dart | 22 -- .../person_screen/person_notifier.dart | 86 +++++ .../screens/person_screen/person_screen.dart | 107 ++++++ lib/src/ui/utils/date_utils.dart | 7 + lib/src/ui/utils/dialogs.dart | 293 ++++++++++++++++ lib/src/ui/utils/string_utils.dart | 21 ++ .../ui/widgets/event_detail_body_widget.dart | 205 +++++++++--- pubspec.lock | 312 ++++++++++++++++++ pubspec.yaml | 7 + 22 files changed, 1494 insertions(+), 85 deletions(-) create mode 100644 lib/src/config/app_constants.dart create mode 100644 lib/src/config/dependecy_injection.dart create mode 100644 lib/src/data/api/member_api.dart create mode 100644 lib/src/data/firebase_collections.dart create mode 100644 lib/src/domain/repository/member_repository.dart create mode 100644 lib/src/models/member.dart create mode 100644 lib/src/models/server_exception.dart create mode 100644 lib/src/models/timestamp_converter.dart create mode 100644 lib/src/ui/fci_widgets/fci_editable_text_field/fci_editable_text_field.dart delete mode 100644 lib/src/ui/screens/person_screen.dart create mode 100644 lib/src/ui/screens/person_screen/person_notifier.dart create mode 100644 lib/src/ui/screens/person_screen/person_screen.dart create mode 100644 lib/src/ui/utils/date_utils.dart create mode 100644 lib/src/ui/utils/dialogs.dart create mode 100644 lib/src/ui/utils/string_utils.dart diff --git a/README.md b/README.md index 935bb54..0942278 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,9 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +## run this command to generate builders +- dart run build_runner build --delete-conflicting-outputs +then add this in the model file if you will use freezed +- part '{nameOfTheModel}.freezed.dart'; +- part '{nameOfTheModel}.g.dart'; \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index bf71364..d950cc5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:firebase_ui_oauth_google/firebase_ui_oauth_google.dart'; import 'package:flutter/material.dart'; import 'package:flutter_community_ibague/firebase_options.dart'; import 'package:flutter_community_ibague/src/config/app_colors.dart'; +import 'package:flutter_community_ibague/src/config/dependecy_injection.dart'; import 'package:flutter_community_ibague/src/config/es.dart'; import 'package:flutter_community_ibague/src/config/router.dart'; import 'package:intl/date_symbol_data_local.dart'; @@ -15,6 +16,7 @@ void main() async { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + DependecyInjection.registerInjections(); FirebaseUIAuth.configureProviders([ EmailAuthProvider(), GoogleProvider( @@ -27,28 +29,12 @@ void main() async { class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Flutter Community Ibagué', debugShowCheckedModeBanner: false, theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. colorScheme: ColorScheme.fromSeed(seedColor: AppColors.primary), useMaterial3: true, ), diff --git a/lib/src/config/app_constants.dart b/lib/src/config/app_constants.dart new file mode 100644 index 0000000..b9771bb --- /dev/null +++ b/lib/src/config/app_constants.dart @@ -0,0 +1,8 @@ +class AppConstants { + static const genderList = [ + 'Mujer', + 'Hombre', + 'Otro', + 'Perfiero no decir', + ]; +} diff --git a/lib/src/config/dependecy_injection.dart b/lib/src/config/dependecy_injection.dart new file mode 100644 index 0000000..93c9f95 --- /dev/null +++ b/lib/src/config/dependecy_injection.dart @@ -0,0 +1,12 @@ +import 'package:flutter_community_ibague/src/data/api/member_api.dart'; +import 'package:flutter_community_ibague/src/domain/repository/member_repository.dart'; +import 'package:get_it/get_it.dart'; + +final GetIt locator = GetIt.instance; + +class DependecyInjection { + static void registerInjections() { + locator.registerSingleton(MemberApiAdapter()); + locator.registerSingleton(MemberRepositoryAdapter()); + } +} diff --git a/lib/src/config/router.dart b/lib/src/config/router.dart index 7c4d5e3..62849fd 100644 --- a/lib/src/config/router.dart +++ b/lib/src/config/router.dart @@ -5,7 +5,8 @@ import 'package:flutter_community_ibague/src/notifiers/events_notifier.dart'; import 'package:flutter_community_ibague/src/ui/screens/event_detail_screen.dart'; import 'package:flutter_community_ibague/src/ui/screens/events_screen.dart'; import 'package:flutter_community_ibague/src/ui/screens/home_screen.dart'; -import 'package:flutter_community_ibague/src/ui/screens/person_screen.dart'; +import 'package:flutter_community_ibague/src/ui/screens/person_screen/person_notifier.dart'; +import 'package:flutter_community_ibague/src/ui/screens/person_screen/person_screen.dart'; import 'package:flutter_community_ibague/src/ui/screens/sponsors_screen.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; @@ -64,7 +65,9 @@ final GoRouter appRouter = GoRouter( name: Routes.person, path: Routes.person, builder: (context, state) { - return const PersonScreen(); + return ChangeNotifierProvider( + create: (_) => PersonNotifier()..init(), + child: const PersonScreen()); }, ), GoRoute( diff --git a/lib/src/data/api/member_api.dart b/lib/src/data/api/member_api.dart new file mode 100644 index 0000000..6545b23 --- /dev/null +++ b/lib/src/data/api/member_api.dart @@ -0,0 +1,106 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_community_ibague/src/data/firebase_collections.dart'; +import 'package:flutter_community_ibague/src/models/member.dart'; +import 'package:flutter_community_ibague/src/models/server_exception.dart'; +import 'package:multiple_result/multiple_result.dart'; + +abstract class MemberApi { + Future> manageMember(Member member); + + Future> getMemberByEmail({String? email}); + + Future> getCurrentUserLogged(); + + Future> createMember(); +} + +class MemberApiAdapter extends MemberApi { + final _firebaseDb = FirebaseFirestore.instance; + final _firebaseAuth = FirebaseAuth.instance; + + @override + Future> manageMember(Member member) async { + try { + var collectionRef = _firebaseDb.collection(FirebaseCollections.user); + await collectionRef.doc(member.uid).set(member.toJson()); + return Result.success(member); + } on FirebaseAuthException catch (e) { + return Result.error( + ServerException( + message: e.message, + codeError: e.code, + ), + ); + } catch (e) { + return Result.error( + ServerException( + //TODO CHANGE THIS IN THE FUTURE + codeError: 'Ups hubo un error', + ), + ); + } + } + + @override + Future> getMemberByEmail({String? email}) async { + try { + final userEmail = email ?? _firebaseAuth.currentUser?.email ?? ''; + final document = await _firebaseDb + .collection(FirebaseCollections.user) + .doc(userEmail) + .get(); + final event = Member.fromJson(document.data()!); + return Result.success(event); + } on FirebaseAuthException catch (e) { + return Result.error( + ServerException( + message: e.message, + codeError: e.code, + ), + ); + } catch (e) { + return Result.error( + ServerException( + //TODO CHANGE THIS IN THE FUTURE + codeError: 'Ups hubo un error', + ), + ); + } + } + + @override + Future> getCurrentUserLogged() async { + try { + final uid = _firebaseAuth.currentUser?.uid; + final document = + await _firebaseDb.collection(FirebaseCollections.user).doc(uid).get(); + final event = Member.fromJson(document.data()!); + return Result.success(event); + } on FirebaseAuthException catch (e) { + return Result.error( + ServerException( + message: e.message, + codeError: e.code, + ), + ); + } catch (e) { + return Result.error( + ServerException( + //TODO CHANGE THIS IN THE FUTURE + codeError: 'Ups hubo un error', + ), + ); + } + } + + @override + Future> createMember() async { + final memberToSave = Member( + uid: _firebaseAuth.currentUser!.uid, + email: _firebaseAuth.currentUser!.email!, + photoURL: _firebaseAuth.currentUser?.photoURL ?? '', + ); + return manageMember(memberToSave); + } +} diff --git a/lib/src/data/firebase_collections.dart b/lib/src/data/firebase_collections.dart new file mode 100644 index 0000000..f3fde8e --- /dev/null +++ b/lib/src/data/firebase_collections.dart @@ -0,0 +1,4 @@ +class FirebaseCollections { + static const user = 'users'; + static const events = 'events'; +} \ No newline at end of file diff --git a/lib/src/domain/repository/member_repository.dart b/lib/src/domain/repository/member_repository.dart new file mode 100644 index 0000000..d724f45 --- /dev/null +++ b/lib/src/domain/repository/member_repository.dart @@ -0,0 +1,39 @@ +import 'package:flutter_community_ibague/src/config/dependecy_injection.dart'; +import 'package:flutter_community_ibague/src/data/api/member_api.dart'; +import 'package:flutter_community_ibague/src/models/member.dart'; +import 'package:multiple_result/multiple_result.dart'; + +abstract class MemberRepository { + Future> manageMember(Member member); + + Future> createMember(); + + ///If the func does not have an email, it will get the member data with the current user logged + Future> getMemberByEmail({String? email}); + + Future> getCurrentUserLogged(); +} + +class MemberRepositoryAdapter extends MemberRepository { + final MemberApi _api = locator(); + + @override + Future> manageMember(Member member) { + return _api.manageMember(member); + } + + @override + Future> getMemberByEmail({String? email}) { + return _api.getMemberByEmail(email: email); + } + + @override + Future> createMember() { + return _api.createMember(); + } + + @override + Future> getCurrentUserLogged() { + return _api.getCurrentUserLogged(); + } +} diff --git a/lib/src/models/member.dart b/lib/src/models/member.dart new file mode 100644 index 0000000..8f4ef8a --- /dev/null +++ b/lib/src/models/member.dart @@ -0,0 +1,31 @@ +import 'package:flutter_community_ibague/src/models/timestamp_converter.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +part 'member.freezed.dart'; + +part 'member.g.dart'; + +@freezed +class Member with _$Member { + const factory Member({ + String? uid, + @Default('') String displayName, + String? photoURL, + required String email, + @Default('') String cellPhone, + @Default('') String gender, + @TimestampConverter() DateTime? dateOfBirth, + }) = _Member; + + factory Member.fromJson(Map json) => _$MemberFromJson(json); +} + +extension MemberExtension on Member { + bool get hasAllInformationCompleted { + return cellPhone.isNotEmpty && + gender.isNotEmpty && + dateOfBirth != null && + displayName.isNotEmpty; + } +} diff --git a/lib/src/models/server_exception.dart b/lib/src/models/server_exception.dart new file mode 100644 index 0000000..bff685c --- /dev/null +++ b/lib/src/models/server_exception.dart @@ -0,0 +1,11 @@ +class ServerException implements Exception { + final String? message; + final String? codeError; + final int? status; + + ServerException({ + this.message, + this.codeError, + this.status, + }); +} diff --git a/lib/src/models/timestamp_converter.dart b/lib/src/models/timestamp_converter.dart new file mode 100644 index 0000000..ae287ae --- /dev/null +++ b/lib/src/models/timestamp_converter.dart @@ -0,0 +1,14 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +class TimestampConverter implements JsonConverter { + const TimestampConverter(); + + @override + DateTime fromJson(Timestamp timestamp) { + return timestamp.toDate(); + } + + @override + Timestamp toJson(DateTime date) => Timestamp.fromDate(date); +} diff --git a/lib/src/notifiers/event_notifier.dart b/lib/src/notifiers/event_notifier.dart index cd09353..fae6a03 100644 --- a/lib/src/notifiers/event_notifier.dart +++ b/lib/src/notifiers/event_notifier.dart @@ -2,16 +2,23 @@ import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter_community_ibague/src/config/dependecy_injection.dart'; import 'package:flutter_community_ibague/src/data/events_repository.dart'; +import 'package:flutter_community_ibague/src/domain/repository/member_repository.dart'; import 'package:flutter_community_ibague/src/models/event.dart'; +import 'package:flutter_community_ibague/src/models/member.dart'; +import 'package:multiple_result/multiple_result.dart'; class EventNotifier extends ChangeNotifier { final EventsRepository _eventsRepository; + final MemberRepository _memberRepository = locator(); final FirebaseAuth _auth; Event event; + bool get attending => event.attendees.any((uid) => uid == _user?.uid); late final StreamSubscription _eventSubscription; + User? get _user => _auth.currentUser; EventNotifier({ @@ -33,12 +40,29 @@ class EventNotifier extends ChangeNotifier { } } - void notAttendEvent() { + void notAttendEvent() async { if (_user != null) { _eventsRepository.notAttendToEvent(event.id, _user!.uid); } } + Future> getCurrentUser() async { + final currentUser = await _memberRepository.getCurrentUserLogged(); + notifyListeners(); + return currentUser; + } + + Future> updateMember(Member member) async { + final currentUser = await _memberRepository.manageMember(member); + notifyListeners(); + return currentUser; + } + + //TODO move this to the Data layer + Future updateName(Member member) async { + await _auth.currentUser?.updateDisplayName(member.displayName); + } + @override void dispose() { _eventSubscription.cancel(); diff --git a/lib/src/ui/fci_widgets/fci_editable_text_field/fci_editable_text_field.dart b/lib/src/ui/fci_widgets/fci_editable_text_field/fci_editable_text_field.dart new file mode 100644 index 0000000..6fde87f --- /dev/null +++ b/lib/src/ui/fci_widgets/fci_editable_text_field/fci_editable_text_field.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart'; + +enum EditableType { + textField, + date, + selector, +} + +class FciEditableTextField extends StatefulWidget { + const FciEditableTextField({ + super.key, + this.content, + this.hintText = '', + this.labelText, + this.onEditPressed, + this.inputFormatters, + this.maxLength = 30, + this.minLength = 0, + this.onDataSelected, + this.errorText = '', + }) : type = EditableType.textField, + options = const []; + + const FciEditableTextField.datePicker({ + super.key, + this.content, + this.hintText = '', + this.labelText, + this.onDataSelected, + this.minLength = 0, + }) : onEditPressed = null, + inputFormatters = null, + errorText = '', + options = const [], + maxLength = 10, + type = EditableType.date; + + const FciEditableTextField.selector({ + super.key, + this.content, + this.hintText = '', + this.labelText, + this.onDataSelected, + this.minLength = 0, + this.options = const [], + }) : onEditPressed = null, + inputFormatters = null, + maxLength = 10, + errorText = '', + type = EditableType.selector; + + final String? content; + final String hintText; + final String errorText; + final String? labelText; + + final VoidCallback? onEditPressed; + final ValueChanged? onDataSelected; + + final EditableType type; + + final List? inputFormatters; + final int maxLength; + final int minLength; + final List options; + + @override + // ignore: library_private_types_in_public_api + _FciEditableTextFieldNameState createState() => + _FciEditableTextFieldNameState(); +} + +class _FciEditableTextFieldNameState extends State { + final ctrl = TextEditingController(); + + bool _editing = false; + bool _hasError = false; + + @override + void initState() { + super.initState(); + ctrl.text = widget.content ?? ''; + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + if (!_editing) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5.5), + child: IntrinsicWidth( + child: Row( + children: [ + Text( + ctrl.text.isEmpty ? widget.hintText : ctrl.text, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + ), + ), + IconButton( + icon: Icon(_editing ? Icons.check : Icons.edit), + color: theme.colorScheme.secondary, + onPressed: _handleOnPressed, + ), + ], + ), + ), + ); + } + return Row( + children: [ + Expanded( + child: TextField( + autofocus: true, + controller: ctrl, + maxLength: widget.maxLength, + inputFormatters: widget.inputFormatters, + decoration: InputDecoration( + hintText: widget.hintText, + labelText: widget.labelText, + errorText: _hasError ? widget.errorText : null, + ), + onSubmitted: (_) => _finishEditing(), + ), + ), + const SizedBox(width: 8), + SizedBox( + width: 50, + height: 32, + child: IconButton( + icon: Icon(_editing ? Icons.check : Icons.edit), + color: theme.colorScheme.secondary, + onPressed: _editing ? _finishEditing : _onEdit, + ), + ), + ], + ); + } + + void _handleOnPressed() { + switch (widget.type) { + case EditableType.textField: + _editing ? _finishEditing() : _onEdit(); + break; + case EditableType.date: + _selectDate(context); + break; + case EditableType.selector: + _showOptionsDialog(); + break; + default: + } + } + + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(1940, 1, 1), + lastDate: DateTime.now(), + ); + + if (picked != null) { + if (mounted) { + ctrl.clear(); + setState(() { + final dt = DateTime( + picked.year, + picked.month, + picked.day, + ); + ctrl.text = DateFormat.yMd('es').format(dt); + if (widget.onDataSelected != null) { + widget.onDataSelected!(ctrl.text); + } + }); + } + } + } + + void _showOptionsDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return SimpleDialog( + title: const Text('Selecciona una opción'), + children: widget.options + .map( + (option) => SimpleDialogOption( + child: Text(option), + onPressed: () { + setState(() { + if (widget.onDataSelected != null) { + widget.onDataSelected!(option); + } + ctrl.text = option; + }); + Navigator.pop(context); + }, + ), + ) + .toList(), + ); + }, + ); + } + + void _onEdit() { + setState(() { + _editing = true; + }); + } + + void _finishEditing() { + setState(() { + validateErrors(); + if (!_hasError) { + _hasError = false; + _editing = false; + if (widget.content == ctrl.text) return; + if (widget.onDataSelected != null) { + widget.onDataSelected!(ctrl.text); + } + } + }); + } + + void validateErrors() { + if (ctrl.text.length < widget.minLength) { + _hasError = true; + return; + } + _hasError = false; + } +} diff --git a/lib/src/ui/screens/person_screen.dart b/lib/src/ui/screens/person_screen.dart deleted file mode 100644 index 61e6143..0000000 --- a/lib/src/ui/screens/person_screen.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:firebase_ui_auth/firebase_ui_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_community_ibague/src/notifiers/auth_notifier.dart'; -import 'package:provider/provider.dart'; - -class PersonScreen extends StatelessWidget { - const PersonScreen({super.key}); - - @override - Widget build(BuildContext context) { - final authState = context.watch(); - if (authState.user == null) { - return const SignInScreen(); - } else { - return const ProfileScreen( - actions: [ - // SignedOutAction((_) => context.go(Routes.start)), - ], - ); - } - } -} diff --git a/lib/src/ui/screens/person_screen/person_notifier.dart b/lib/src/ui/screens/person_screen/person_notifier.dart new file mode 100644 index 0000000..82a5523 --- /dev/null +++ b/lib/src/ui/screens/person_screen/person_notifier.dart @@ -0,0 +1,86 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_community_ibague/src/config/dependecy_injection.dart'; +import 'package:flutter_community_ibague/src/domain/repository/member_repository.dart'; +import 'package:flutter_community_ibague/src/models/member.dart'; +import 'package:flutter_community_ibague/src/ui/utils/string_utils.dart'; +import 'package:multiple_result/multiple_result.dart'; + +enum FieldToUpdate { + dateOfBirth, + cellphone, + gender, + name, +} + +class PersonNotifier extends ChangeNotifier { + final MemberRepository _memberRepository = locator(); + Result? currentUser; + bool isLoading = false; + + Future init() async { + isLoading = true; + await getCurrentUser(); + isLoading = false; + notifyListeners(); + } + + Future> createMember() async { + isLoading = true; + final result = await _memberRepository.createMember(); + if (result.isSuccess()) { + currentUser = result; + } + notifyListeners(); + isLoading = false; + return result; + } + + Future getCurrentUser() async { + currentUser = await _memberRepository.getCurrentUserLogged(); + notifyListeners(); + } + + Future updateUser(FieldToUpdate fieldToUpdate, String value) async { + await getCurrentUser(); + if (currentUser != null && currentUser!.isSuccess()) { + final user = currentUser!.whenSuccess((member) => member); + if (user != null) { + final options = { + FieldToUpdate.cellphone: () { + return user.copyWith( + cellPhone: value, + ); + }, + FieldToUpdate.dateOfBirth: () { + return user.copyWith( + dateOfBirth: value.convertDMYToDate(), + ); + }, + FieldToUpdate.name: () { + return user.copyWith( + displayName: value, + ); + }, + FieldToUpdate.gender: () { + return user.copyWith( + gender: value, + ); + }, + }; + final userToUpdate = options[fieldToUpdate]!(); + manageUser(userToUpdate); + } + } + } + + Future> manageUser(Member user) async { + final result = await _memberRepository.manageMember(user); + notifyListeners(); + return result; + } + + void signedOut() { + currentUser = null; + notifyListeners(); + } +} diff --git a/lib/src/ui/screens/person_screen/person_screen.dart b/lib/src/ui/screens/person_screen/person_screen.dart new file mode 100644 index 0000000..fddb8e6 --- /dev/null +++ b/lib/src/ui/screens/person_screen/person_screen.dart @@ -0,0 +1,107 @@ +import 'package:firebase_ui_auth/firebase_ui_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_community_ibague/src/config/app_constants.dart'; +import 'package:flutter_community_ibague/src/notifiers/auth_notifier.dart'; +import 'package:flutter_community_ibague/src/ui/fci_widgets/fci_editable_text_field/fci_editable_text_field.dart'; +import 'package:flutter_community_ibague/src/ui/screens/person_screen/person_notifier.dart'; +import 'package:flutter_community_ibague/src/ui/utils/date_utils.dart'; +import 'package:provider/provider.dart'; + +class PersonScreen extends StatelessWidget { + const PersonScreen({super.key}); + + @override + Widget build(BuildContext context) { + final authState = context.watch(); + final personNotifier = context.read(); + if (authState.user == null) { + return SignInScreen( + actions: [ + AuthStateChangeAction((context, userCreated) async { + await personNotifier.createMember(); + }), + AuthStateChangeAction((context, userCreated) async { + await personNotifier.init(); + }), + ], + ); + } else { + return Consumer( + builder: ( + context, + vm, + _, + ) { + return ProfileScreen( + providers: const [], + actions: [ + DisplayNameChangedAction((context, oldName, newName) async { + personNotifier.updateUser(FieldToUpdate.name, newName); + }), + SignedOutAction((context) { + personNotifier.signedOut(); + }), + AccountDeletedAction((context, _) { + personNotifier.signedOut(); + }), + ], + showMFATile: false, + children: [ + if (vm.isLoading) + const Center( + child: Padding( + padding: EdgeInsets.all(60), + child: CircularProgressIndicator()), + ), + if (personNotifier.currentUser != null) + vm.currentUser!.when((user) { + return Column( + children: [ + FciEditableTextField( + labelText: 'Celular', + hintText: 'Ingrese su número de celular *', + errorText: 'Número de celular invalido', + maxLength: 10, + minLength: 10, + content: user.cellPhone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onDataSelected: (value) { + personNotifier.updateUser( + FieldToUpdate.cellphone, value); + }, + ), + const SizedBox(height: 12), + FciEditableTextField.selector( + labelText: 'Género', + hintText: + 'Ingrese el genero con el que se identifica *', + options: AppConstants.genderList, + content: user.gender, + onDataSelected: (genderSelected) { + personNotifier.updateUser( + FieldToUpdate.gender, genderSelected); + }, + ), + const SizedBox(height: 12), + FciEditableTextField.datePicker( + labelText: 'Edad', + hintText: 'Ingrese su edad *', + content: user.dateOfBirth?.dayMonthYear(), + onDataSelected: (dateSelected) { + personNotifier.updateUser( + FieldToUpdate.dateOfBirth, dateSelected); + }, + ), + ], + ); + }, (error) => const SizedBox.shrink()) + ], + ); + }, + ); + } + } +} diff --git a/lib/src/ui/utils/date_utils.dart b/lib/src/ui/utils/date_utils.dart new file mode 100644 index 0000000..6193faa --- /dev/null +++ b/lib/src/ui/utils/date_utils.dart @@ -0,0 +1,7 @@ +import 'package:intl/intl.dart'; + +extension DateExtensions on DateTime { + String dayMonthYear() { + return DateFormat('dd/MM/yyyy').format(this); + } +} diff --git a/lib/src/ui/utils/dialogs.dart b/lib/src/ui/utils/dialogs.dart new file mode 100644 index 0000000..24ae480 --- /dev/null +++ b/lib/src/ui/utils/dialogs.dart @@ -0,0 +1,293 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_community_ibague/src/config/app_colors.dart'; + +class Dialogs { + static Future showLoading(BuildContext context) async { + return showDialog( + barrierDismissible: false, + context: context, + builder: (_) { + return const _Dialog(); + }, + ); + } + + static void close( + BuildContext context, { + bool mounted = true, + }) { + if (!mounted) return; + Navigator.of(context, rootNavigator: true).pop(); + } + + static Future showErrorDialogWithMessage( + BuildContext context, String message) async { + return await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogCtx) { + return _Dialog( + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.error_outline_sharp, + color: Colors.red, + size: 60, + ), + const SizedBox( + height: 15, + ), + Text( + message, + textAlign: TextAlign.center, + ), + const SizedBox( + height: 15, + ), + TextButton( + onPressed: () { + Navigator.pop(dialogCtx); + }, + child: const Text('Aceptar'), + ), + ], + ), + ), + ); + }, + ); + } + + static Future showSuccessDialogWithMessage( + BuildContext context, + String message, { + Function()? callBack, + }) async { + return await showDialog( + barrierDismissible: false, + context: context, + builder: (_) { + return _Dialog( + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.check_circle_outline, + color: AppColors.primary, + size: 60, + ), + const SizedBox( + height: 15, + ), + Text( + message, + textAlign: TextAlign.center, + ), + const SizedBox( + height: 15, + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + if (callBack != null) { + callBack(); + } + }, + child: const Text('Aceptar'), + ), + ], + ), + ), + ); + }, + ); + } + + static Future showDialogWithMessage( + BuildContext context, + String message, { + Function()? callBack, + IconData? icon, + bool showCancelBtn = true, + Widget? title, + }) async { + return await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogCtx) { + return _AlertDialog( + title: title, + actions: [ + if (showCancelBtn) + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(dialogCtx).pop(); + }, + ), + TextButton( + onPressed: () { + Navigator.of(dialogCtx).pop(); + if (callBack != null) { + callBack(); + } + }, + child: const Text('Aceptar'), + ), + ], + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) + Icon( + icon, + color: AppColors.primary, + size: 60, + ), + const SizedBox( + height: 15, + ), + Text( + message, + textAlign: TextAlign.center, + ), + const SizedBox( + height: 15, + ), + ], + ), + ), + ); + }, + ); + } + + static Future showDialogWithContent( + BuildContext context, + Widget content, { + Function()? callBack, + IconData? icon, + bool showCancelBtn = true, + Widget? title, + }) async { + return await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogCtx) { + return _AlertDialog( + title: title, + actions: [ + if (showCancelBtn) + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(dialogCtx).pop(); + }, + ), + TextButton( + onPressed: () { + Navigator.of(dialogCtx).pop(); + if (callBack != null) { + callBack(); + } + }, + child: const Text('Aceptar'), + ), + ], + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) + Icon( + icon, + color: AppColors.primary, + size: 60, + ), + const SizedBox( + height: 15, + ), + content, + const SizedBox( + height: 15, + ), + ], + ), + ), + ); + }, + ); + } +} + +class _Dialog extends StatelessWidget { + const _Dialog({ + this.content, + }); + + final Widget? content; + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: content ?? + const Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + SizedBox( + height: 15, + ), + Text('Cargando') + ], + ), + ), + ); + } +} + +class _AlertDialog extends StatelessWidget { + const _AlertDialog({ + this.content, + this.actions, + this.title, + }); + + final Widget? content; + final Widget? title; + final List? actions; + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: Colors.white, + title: title, + actions: actions, + content: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: content ?? + const Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + SizedBox( + height: 15, + ), + Text('Cargando') + ], + ), + ), + ); + } +} diff --git a/lib/src/ui/utils/string_utils.dart b/lib/src/ui/utils/string_utils.dart new file mode 100644 index 0000000..11e6a63 --- /dev/null +++ b/lib/src/ui/utils/string_utils.dart @@ -0,0 +1,21 @@ +import 'package:intl/intl.dart'; + +extension EmailValidator on String { + bool isValidEmail() { + return RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') + .hasMatch(this); + } + + String firstLetterInCapitalize() => this[0].toUpperCase() + substring(1); + + DateTime convertToDate() { + DateTime date = DateFormat('dd/MM/yyyy hh:mm a').parse(this); + return date; + } + + DateTime convertDMYToDate() { + DateTime date = DateFormat('dd/MM/yyyy').parse(this); + return date; + } +} diff --git a/lib/src/ui/widgets/event_detail_body_widget.dart b/lib/src/ui/widgets/event_detail_body_widget.dart index a87c347..fe2e3c7 100644 --- a/lib/src/ui/widgets/event_detail_body_widget.dart +++ b/lib/src/ui/widgets/event_detail_body_widget.dart @@ -1,10 +1,17 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:firebase_ui_auth/firebase_ui_auth.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_community_ibague/src/config/app_assets.dart'; import 'package:flutter_community_ibague/src/config/app_colors.dart'; +import 'package:flutter_community_ibague/src/config/app_constants.dart'; +import 'package:flutter_community_ibague/src/models/member.dart'; import 'package:flutter_community_ibague/src/notifiers/auth_notifier.dart'; import 'package:flutter_community_ibague/src/notifiers/event_notifier.dart'; +import 'package:flutter_community_ibague/src/ui/fci_widgets/fci_editable_text_field/fci_editable_text_field.dart'; +import 'package:flutter_community_ibague/src/ui/utils/date_utils.dart'; +import 'package:flutter_community_ibague/src/ui/utils/dialogs.dart'; +import 'package:flutter_community_ibague/src/ui/utils/string_utils.dart'; import 'package:flutter_community_ibague/src/ui/widgets/event_detail_item_widget.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -24,7 +31,7 @@ class EventDetailBodyWidget extends StatelessWidget { Widget build(BuildContext context) { final notifier = context.watch(); final event = notifier.event; - + //TODO MOVE THESE LINES TO DATE UTILS // Convertir la fecha al formato deseado DateTime fecha = DateTime.parse(event.dateTime.toString()); String fechaFormateada = DateFormat('d MMMM, y', 'es').format(fecha); @@ -32,6 +39,8 @@ class EventDetailBodyWidget extends StatelessWidget { String horaFormateada = DateFormat('HH:mm').format(fecha); // Determinar el día de la semana String diaSemana = DateFormat('EEEE', 'es').format(fecha); + + ///TODO until here return Wrap( children: [ ConstrainedBox( @@ -159,50 +168,18 @@ class EventDetailBodyWidget extends StatelessWidget { return; } if (authNotifier.user == null) { - await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('Inicia sesión'), - content: const Text( - 'Debes iniciar sesión para poder registrarte al evento', - style: TextStyle( - fontSize: 16, - )), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(ctx); - }, - child: const Text('Ok')) - ], - )); - final logged = await showModalBottomSheet( - context: context, - builder: (ctx) { - late final Widget child; - - child = SignInScreen( - actions: [ - AuthStateChangeAction( - (context, state) { - Navigator.pop(ctx, true); - }), - AuthStateChangeAction( - (context, state) { - Navigator.pop(ctx, true); - }), - ], - ); - - return Scaffold(body: child); - }) ?? - false; - - if (logged) { - notifier.attendEvent(); - } + await Dialogs.showDialogWithMessage( + context, + 'Debes iniciar sesión para poder registrarte al evento', + title: const Text('Inicia sesión'), + showCancelBtn: false, + ); + await _showDoLogin(context, notifier); } else { - notifier.attendEvent(); + _validateUserData( + notifier, + context, + ); } }, child: Text( @@ -221,4 +198,144 @@ class EventDetailBodyWidget extends StatelessWidget { ], ); } + + Future _showDoLogin( + BuildContext context, + EventNotifier notifier, + ) async { + await showModalBottomSheet( + context: context, + builder: (ctx) { + final child = SignInScreen( + actions: [ + AuthStateChangeAction( + (context, state) async { + Navigator.pop(ctx, true); + await _validateUserData( + notifier, + context, + ); + }, + ), + AuthStateChangeAction( + (_, state) async { + Navigator.pop(ctx, true); + await _validateUserData( + notifier, + context, + ); + }, + ), + ], + ); + return Scaffold(body: child); + }, + ); + } + + Future _validateUserData( + EventNotifier notifier, BuildContext context) async { + Dialogs.showLoading(context); + final userResponse = await notifier.getCurrentUser(); + Dialogs.close(context); + userResponse.when( + (user) { + if (user.hasAllInformationCompleted) { + notifier.attendEvent(); + } else { + _showMissingDataDialog(user, context, notifier); + } + }, + (error) { + Dialogs.showErrorDialogWithMessage(context, 'Ups hubo un error'); + }, + ); + } + + void _showMissingDataDialog( + Member user, BuildContext context, EventNotifier notifier) { + var userToUpdate = user.copyWith(); + bool updateName = false; + Dialogs.showDialogWithContent( + context, + title: const Text('Completa tu perfil'), + Column( + children: [ + const Text( + 'Completa toda la informacion faltante de tu perfil para poder ir al evento'), + if (user.displayName.isEmpty) + FciEditableTextField( + labelText: 'Nombre completo', + hintText: 'Ingrese su nombre completo *', + errorText: 'Número de celular invalido', + minLength: 3, + content: user.displayName, + onDataSelected: (value) { + updateName = true; + userToUpdate = userToUpdate.copyWith( + displayName: value, + ); + }, + ), + if (user.cellPhone.isEmpty) + FciEditableTextField( + labelText: 'Celular', + hintText: 'Ingrese su número de celular *', + errorText: 'Número de celular invalido', + maxLength: 10, + minLength: 10, + content: user.cellPhone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onDataSelected: (value) { + userToUpdate = userToUpdate.copyWith( + cellPhone: value, + ); + }, + ), + if (user.gender.isEmpty) + FciEditableTextField.selector( + labelText: 'Género', + hintText: 'Ingrese el genero con el que se identifica *', + options: AppConstants.genderList, + content: user.gender, + onDataSelected: (genderSelected) { + userToUpdate = userToUpdate.copyWith( + gender: genderSelected, + ); + }, + ), + if (user.dateOfBirth == null) + FciEditableTextField.datePicker( + labelText: 'Edad', + hintText: 'Ingrese su edad *', + content: user.dateOfBirth?.dayMonthYear(), + onDataSelected: (dateSelected) { + userToUpdate = userToUpdate.copyWith( + dateOfBirth: dateSelected.convertDMYToDate(), + ); + }, + ), + ], + ), + callBack: () async { + Dialogs.showLoading(context); + final userResponse = await notifier.updateMember(userToUpdate); + if (updateName) { + await notifier.updateName(userToUpdate); + } + Dialogs.close(context); + userResponse.when((member) { + if (member.hasAllInformationCompleted) { + notifier.attendEvent(); + } else { + _showMissingDataDialog(member, context, notifier); + } + }, (error) { + Dialogs.showErrorDialogWithMessage(context, 'Ups hubo un error'); + }); + }, + ); + } } diff --git a/pubspec.lock b/pubspec.lock index f0c025e..174353b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" _flutterfire_internals: dependency: transitive description: @@ -9,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.25" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" args: dependency: transitive description: @@ -33,6 +49,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e + url: "https://pub.dev" + source: hosted + version: "8.9.1" cached_network_image: dependency: "direct main" description: @@ -65,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" clock: dependency: transitive description: @@ -97,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.10.8" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -105,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" crypto: dependency: transitive description: @@ -121,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" desktop_webview_auth: dependency: transitive description: @@ -317,6 +429,46 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed: + dependency: "direct main" + description: + name: freezed + sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" go_router: dependency: "direct main" description: @@ -373,6 +525,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.12.3+3" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" http: dependency: transitive description: @@ -381,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -397,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" js: dependency: transitive description: @@ -405,6 +581,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + url: "https://pub.dev" + source: hosted + version: "6.7.1" leak_tracker: dependency: transitive description: @@ -469,6 +661,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + multiple_result: + dependency: "direct main" + description: + name: multiple_result + sha256: a7a8aa7a068648521ebd41e8c7296a990cd7ec5e15e7efa210c26fefd6e4f193 + url: "https://pub.dev" + source: hosted + version: "5.1.0" nested: dependency: transitive description: @@ -485,6 +693,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -573,6 +789,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" provider: dependency: "direct main" description: @@ -581,6 +805,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" rxdart: dependency: transitive description: @@ -589,11 +829,43 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" source_span: dependency: transitive description: @@ -642,6 +914,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -674,6 +954,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" typed_data: dependency: transitive description: @@ -794,6 +1082,14 @@ packages: url: "https://pub.dev" source: hosted version: "13.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" web: dependency: transitive description: @@ -802,6 +1098,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" + url: "https://pub.dev" + source: hosted + version: "2.4.3" win32: dependency: transitive description: @@ -826,6 +1130,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index fba1abf..546e5c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,11 @@ dependencies: intl: ^0.18.1 provider: ^6.1.1 url_launcher: ^6.2.4 + freezed: ^2.4.7 + freezed_annotation: ^2.4.1 + json_annotation: ^4.8.1 + multiple_result: ^5.1.0 + get_it: ^7.6.7 dev_dependencies: @@ -59,6 +64,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 + build_runner: ^2.4.8 + json_serializable: ^6.7.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec