From 0fa8ce71de5f133ebd0ce73b9ecb3c70cee67502 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sat, 23 Mar 2024 23:06:59 -0300 Subject: [PATCH 01/50] build: install `g_recaptcha_v3` --- pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + 2 files changed, 9 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index df31c51..c2abeda 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -239,6 +239,14 @@ packages: description: flutter source: sdk version: "0.0.0" + g_recaptcha_v3: + dependency: "direct main" + description: + name: g_recaptcha_v3 + sha256: ee754d0a5621646e200b2d5d9c918d64725fb53d6a1b65cbd6f294c08e0fe529 + url: "https://pub.dev" + source: hosted + version: "0.0.5" get_it: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c0aff6b..2be6139 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: flutter_localizations: sdk: flutter flutter_svg: ^2.0.9 + g_recaptcha_v3: ^0.0.5 get_it: ^7.6.7 http: 1.2.0 intl: 0.18.1 From a90d2f87909ff239bb37f3fd2324009180b15423 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Mon, 8 Apr 2024 22:26:28 -0300 Subject: [PATCH 02/50] feature: added recaptcha to integrate with new service, and keys to use --- .env.example | 8 + .gitignore | 4 + .vscode/launch.json | 26 ++ l10n.yaml | 3 +- lib/app/features/home/home_page.dart | 8 +- .../home/widgets/contact/contact_widget.dart | 59 ++-- lib/data/constants/constants_api.dart | 8 + lib/data/services/recaptcha/recaptcha.dart | 2 + .../services/recaptcha/recaptcha_model.dart | 94 ++++++ .../services/recaptcha/recaptcha_service.dart | 53 +++ lib/infra/env/env.dart | 22 ++ lib/main.dart | 29 +- pubspec.lock | 304 ++++++++++++++++++ pubspec.yaml | 3 + web/index.html | 1 + 15 files changed, 591 insertions(+), 33 deletions(-) create mode 100644 .env.example create mode 100644 .vscode/launch.json create mode 100644 lib/data/services/recaptcha/recaptcha.dart create mode 100644 lib/data/services/recaptcha/recaptcha_model.dart create mode 100644 lib/data/services/recaptcha/recaptcha_service.dart create mode 100644 lib/infra/env/env.dart diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..768f62e --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# You can get these keys from https://www.google.com/recaptcha/admin +RECAPTCHA_PUBLIC_KEY=your_public_key +RECAPTCHA_SECRET_KEY=your_secret_key + +# Create your api key from AWS SES / API Gateway: +# - https://aws.amazon.com/ses/; +# - https://aws.amazon.com/api-gateway/ +API_SEND_MAIL=your_api_send_mail diff --git a/.gitignore b/.gitignore index f5ee892..84c66e6 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,7 @@ firebase.json firebase-config.js .firebase package.json + +# Environment configuration +.env +lib/infra/env/env.g.dart \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..cf00943 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "site", + "request": "launch", + "type": "dart", + "flutterMode": "debug" + }, + { + "name": "site (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "site (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml index f86dba8..2e0fdb4 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -3,5 +3,4 @@ output-dir: lib/app/core/l10n/localizations template-arb-file: app_pt.arb output-localization-file: app_localizations.dart output-class: AppLocalizations -### Uncomment when generating. -# synthetic-package: false \ No newline at end of file +synthetic-package: false \ No newline at end of file diff --git a/lib/app/features/home/home_page.dart b/lib/app/features/home/home_page.dart index 6cbc51c..1fe2b97 100644 --- a/lib/app/features/home/home_page.dart +++ b/lib/app/features/home/home_page.dart @@ -42,10 +42,6 @@ class _HomePageState extends State { void initState() { super.initState(); items = [ - Presentation(itemScrollController), - const Projects(), - const Experience(), - const Social(), ContactWidget( contactController: ContactController( contactRepository: ContactRepositoryImpl( @@ -54,6 +50,10 @@ class _HomePageState extends State { ), ), ), + Presentation(itemScrollController), + const Projects(), + const Experience(), + const Social(), const CustomFooter(), ]; } diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/home/widgets/contact/contact_widget.dart index f9c2d8a..71785b2 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/home/widgets/contact/contact_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:g_recaptcha_v3/g_recaptcha_v3.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; @@ -12,6 +13,7 @@ import 'package:site/app/features/home/widgets/contact/widgets/widgets.dart'; import 'package:site/app/widgets/snack_bars/snack_bars.dart'; import 'package:site/data/models/models.dart' as models; import 'package:site/data/repositories/contact/contact.dart'; +import 'package:site/data/services/recaptcha/recaptcha.dart'; class ContactWidget extends StatelessWidget { ContactWidget({ @@ -42,27 +44,39 @@ class ContactWidget extends StatelessWidget { emailController: emailController, subjectController: subjectController, messageController: messageController, - onPressed: () { + onPressed: () async { if (formKey.currentState?.validate() ?? false) { - appShowSnackBar( - context, - text: AppTexts.get(context).emailSendedWithSuccess, - icon: Icons.check, - color: AppColors.primaryDark, - width: 300, - ); - _contactController?.sendMail( - contact: models.Contact( - name: nameController.text, - email: emailController.text, - message: messageController.text, - subject: subjectController.text, - ), - ); - nameController.clear(); - emailController.clear(); - messageController.clear(); - subjectController.clear(); + final isNotABot = await RecaptchaService.isNotABot(); + + if (isNotABot) { + formKey.currentState!.save(); + if (context.mounted) { + appShowSnackBar( + context, + text: AppTexts.get(context).emailSendedWithSuccess, + icon: Icons.check, + color: AppColors.primaryDark, + width: 300, + ); + } + + _contactController?.sendMail( + contact: models.Contact( + name: nameController.text, + email: emailController.text, + message: messageController.text, + subject: subjectController.text, + ), + ); + for (var controller in [ + nameController, + emailController, + messageController, + subjectController, + ]) { + controller.clear(); + } + } } }, ); @@ -80,3 +94,8 @@ class ContactWidget extends StatelessWidget { ); } } + +Future generateToken() async { + final token = await GRecaptchaV3.execute('submit') ?? ''; + debugPrint('Token: $token'); +} diff --git a/lib/data/constants/constants_api.dart b/lib/data/constants/constants_api.dart index 88b49a9..5ab1b25 100644 --- a/lib/data/constants/constants_api.dart +++ b/lib/data/constants/constants_api.dart @@ -1,7 +1,15 @@ +import 'package:site/infra/env/env.dart'; + class ConstantsAPI { static const baseUrl = 'https://api.emailjs.com/api/v1.0/email/send'; static const headers = { 'origin': 'http://localhost', 'Content-Type': 'application/json', }; + + /// Recaptcha information. + static final recaptchaPublicKey = Env.recaptchaPublicKey; + static final recaptchaSecretKey = Env.recaptchaSecretKey; + static final recaptchaUrl = + Uri.parse('https://www.google.com/recaptcha/api/siteverify'); } diff --git a/lib/data/services/recaptcha/recaptcha.dart b/lib/data/services/recaptcha/recaptcha.dart new file mode 100644 index 0000000..bd6d9d0 --- /dev/null +++ b/lib/data/services/recaptcha/recaptcha.dart @@ -0,0 +1,2 @@ +export 'recaptcha_model.dart'; +export 'recaptcha_service.dart'; diff --git a/lib/data/services/recaptcha/recaptcha_model.dart b/lib/data/services/recaptcha/recaptcha_model.dart new file mode 100644 index 0000000..8093c59 --- /dev/null +++ b/lib/data/services/recaptcha/recaptcha_model.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +class RecaptchaResponse { + RecaptchaResponse({ + required this.success, + required this.challengeTimeStamp, + required this.hostName, + required this.score, + required this.action, + this.errorCodes = const [], + }); + + factory RecaptchaResponse.fromMap(Map json) { + return RecaptchaResponse( + success: json['success'] ?? false, + challengeTimeStamp: DateTime.parse(json['challenge_ts']), + hostName: json['hostname'] ?? '', + score: double.tryParse('${json['score']}') ?? 0.0, + action: json['action'] ?? '', + errorCodes: json['error-codes'] ?? [], + ); + } + + factory RecaptchaResponse.fromJson(String source) => + RecaptchaResponse.fromMap(json.decode(source)); + + final bool success; + final DateTime challengeTimeStamp; + final String hostName; + final double score; + final String action; + final List errorCodes; + + RecaptchaResponse copyWith({ + bool? success, + DateTime? challengeTimeStamp, + String? hostName, + double? score, + String? action, + List? errorCodes, + }) { + return RecaptchaResponse( + success: success ?? this.success, + challengeTimeStamp: challengeTimeStamp ?? this.challengeTimeStamp, + hostName: hostName ?? this.hostName, + score: score ?? this.score, + action: action ?? this.action, + errorCodes: errorCodes ?? this.errorCodes, + ); + } + + Map toMap() { + return { + 'success': success, + 'challenge_ts': challengeTimeStamp.millisecondsSinceEpoch, + 'hostname': hostName, + 'score': score, + 'action': action, + 'error-codes': errorCodes, + }; + } + + String toJson() => json.encode(toMap()); + + @override + String toString() { + return 'RecaptchaResponse(success: $success, challengeTimeStamp: $challengeTimeStamp, hostName: $hostName, score: $score, action: $action, errorCodes: $errorCodes)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is RecaptchaResponse && + other.success == success && + other.challengeTimeStamp == challengeTimeStamp && + other.hostName == hostName && + other.score == score && + other.action == action && + listEquals(other.errorCodes, errorCodes); + } + + @override + int get hashCode { + return success.hashCode ^ + challengeTimeStamp.hashCode ^ + hostName.hashCode ^ + score.hashCode ^ + action.hashCode ^ + errorCodes.hashCode; + } +} diff --git a/lib/data/services/recaptcha/recaptcha_service.dart b/lib/data/services/recaptcha/recaptcha_service.dart new file mode 100644 index 0000000..0ce95ed --- /dev/null +++ b/lib/data/services/recaptcha/recaptcha_service.dart @@ -0,0 +1,53 @@ +import 'dart:developer'; + +import 'package:g_recaptcha_v3/g_recaptcha_v3.dart'; +import 'package:http/http.dart' as http; +import 'package:site/data/constants/constants_api.dart'; +import 'package:site/data/services/recaptcha/recaptcha.dart'; + +class RecaptchaService { + RecaptchaService._(); + + static Future initiate() async => + await GRecaptchaV3.ready(ConstantsAPI.recaptchaPublicKey); + + static Future isNotABot() async { + final verificationResponse = await _getVerificationResponse(); + + if (verificationResponse == null) { + return false; + } + + final score = verificationResponse.score; + return score >= 0.5 && score < 1; + } + + static Future _getVerificationResponse() async { + try { + final token = await GRecaptchaV3.execute('submit') ?? ''; + + if (token.isNotEmpty) { + final response = await http.post( + ConstantsAPI.recaptchaUrl, + body: { + 'secret': ConstantsAPI.recaptchaSecretKey, + 'response': token, + }, + headers: { + 'Access-Control-Allow-Origin': '*', + }, + ); + final body = response.body; + return RecaptchaResponse.fromJson(body); + } else { + log('RecaptchaService._getVerificationResponse, token is empty'); + } + } catch (e, s) { + log( + 'RecaptchaService._getVerificationResponse, error: $e, stackTrace: $s', + ); + } + + return null; + } +} diff --git a/lib/infra/env/env.dart b/lib/infra/env/env.dart new file mode 100644 index 0000000..bcf2aad --- /dev/null +++ b/lib/infra/env/env.dart @@ -0,0 +1,22 @@ +import 'package:envied/envied.dart'; + +part 'env.g.dart'; + +// Rename and / or create .env file in the root of the project +// (already has a .env.example file to help) +// - Add the following variables. +// And run the following commands: +// dart run build_runner clean +// dart run build_runner build --delete-conflicting-outputs + +@Envied(path: '.env') +final class Env { + @EnviedField(varName: 'RECAPTCHA_PUBLIC_KEY', obfuscate: true) + static final String recaptchaPublicKey = _Env.recaptchaPublicKey; + + @EnviedField(varName: 'RECAPTCHA_SECRET_KEY', obfuscate: true) + static final String recaptchaSecretKey = _Env.recaptchaSecretKey; + + @EnviedField(varName: 'API_SEND_MAIL', obfuscate: true) + static final String apiSendMail = _Env.apiSendMail; +} diff --git a/lib/main.dart b/lib/main.dart index 6a2d862..c845c96 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,7 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:url_strategy/url_strategy.dart'; @@ -5,13 +9,24 @@ import 'package:url_strategy/url_strategy.dart'; import 'package:site/app/app_widget.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/data/services/firebase/firebase.dart'; +import 'package:site/data/services/recaptcha/recaptcha.dart'; Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await FirebaseServiceImpl().setUpInitialization(); - setPathUrlStrategy(); - configureDependencies(); - runApp( - AppWidget(), - ); + await runZonedGuarded(() async { + WidgetsFlutterBinding.ensureInitialized(); + await FirebaseServiceImpl().setUpInitialization(); + + if (kIsWeb) { + await RecaptchaService.initiate(); + } + + setPathUrlStrategy(); + configureDependencies(); + runApp( + AppWidget(), + ); + }, (error, stackTrace) { + log('runZonedGuarded: Caught error: $error'); + log('runZonedGuarded: StackTrace: $stackTrace'); + }); } diff --git a/pubspec.lock b/pubspec.lock index c2abeda..409bc9e 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" archive: dependency: transitive description: @@ -49,6 +65,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: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + url: "https://pub.dev" + source: hosted + version: "2.4.9" + 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: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" characters: dependency: transitive description: @@ -57,6 +137,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: @@ -65,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -89,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" email_validator: dependency: "direct main" description: @@ -97,6 +201,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.17" + envied: + dependency: "direct main" + description: + name: envied + sha256: bbff9c76120e4dc5e2e36a46690cf0a26feb65e7765633f4e8d916bcd173a450 + url: "https://pub.dev" + source: hosted + version: "0.5.4+1" + envied_generator: + dependency: "direct dev" + description: + name: envied_generator + sha256: "517b70de08d13dcd40e97b4e5347e216a0b1c75c99e704f3c85c0474a392d14a" + url: "https://pub.dev" + source: hosted + version: "0.5.4+1" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -193,6 +321,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.25" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -234,6 +370,14 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -255,6 +399,22 @@ packages: 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" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" http: dependency: "direct main" description: @@ -263,6 +423,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: @@ -284,6 +452,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: @@ -292,6 +468,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" leak_tracker: dependency: transitive description: @@ -324,6 +508,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" lottie: dependency: "direct main" description: @@ -356,6 +548,14 @@ 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" mocktail: dependency: "direct dev" description: @@ -364,6 +564,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_info_plus: dependency: "direct main" description: @@ -428,6 +636,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" process: dependency: transitive description: @@ -436,6 +652,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.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" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" scrollable_positioned_list: dependency: "direct main" description: @@ -444,11 +684,35 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.8" + 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_span: dependency: transitive description: @@ -473,6 +737,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: @@ -505,6 +777,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: @@ -625,6 +905,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: @@ -633,6 +921,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" webdriver: dependency: transitive description: @@ -657,6 +953,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.16.6" diff --git a/pubspec.yaml b/pubspec.yaml index 2be6139..9648b8c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: auto_size_text: ^3.0.0 email_validator: ^2.1.17 + envied: ^0.5.4+1 firebase_analytics: ^10.8.5 firebase_core: ^2.25.4 firebase_core_platform_interface: ^5.0.0 @@ -30,6 +31,8 @@ dependencies: url_strategy: ^0.2.0 dev_dependencies: + build_runner: ^2.4.9 + envied_generator: ^0.5.4+1 flutter_lints: ^3.0.1 flutter_test: sdk: flutter diff --git a/web/index.html b/web/index.html index 5aa5ed9..f3e6111 100644 --- a/web/index.html +++ b/web/index.html @@ -35,6 +35,7 @@ Felipe Sales | Social Links + From 0c7a5d1632d2e1ad75b4a7941479851bffe091c4 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Mon, 8 Apr 2024 22:33:43 -0300 Subject: [PATCH 03/50] feat: update to SES and testing --- lib/data/constants/constants_api.dart | 3 + lib/data/models/contact.dart | 9 +++ .../contact/contact_repository_impl.dart | 66 +++++++++++-------- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/lib/data/constants/constants_api.dart b/lib/data/constants/constants_api.dart index 5ab1b25..69d2837 100644 --- a/lib/data/constants/constants_api.dart +++ b/lib/data/constants/constants_api.dart @@ -7,6 +7,9 @@ class ConstantsAPI { 'Content-Type': 'application/json', }; + /// Send email endpoint. + static final apiSendMail = Env.apiSendMail; + /// Recaptcha information. static final recaptchaPublicKey = Env.recaptchaPublicKey; static final recaptchaSecretKey = Env.recaptchaSecretKey; diff --git a/lib/data/models/contact.dart b/lib/data/models/contact.dart index 07013d4..dae42d4 100644 --- a/lib/data/models/contact.dart +++ b/lib/data/models/contact.dart @@ -7,4 +7,13 @@ class Contact { }); final String name, email, message, subject; + + static Map toJson(Contact contact) { + return { + 'sender_name': contact.name, + 'source_email': contact.email, + 'template_subject': contact.subject, + 'template_body': contact.message, + }; + } } diff --git a/lib/data/repositories/contact/contact_repository_impl.dart b/lib/data/repositories/contact/contact_repository_impl.dart index d6d70a2..6ec97da 100644 --- a/lib/data/repositories/contact/contact_repository_impl.dart +++ b/lib/data/repositories/contact/contact_repository_impl.dart @@ -1,9 +1,10 @@ import 'dart:convert'; +import 'dart:developer'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:http/http.dart' as http; - import 'package:site/data/constants/constants_api.dart'; + import 'package:site/data/models/models.dart'; import 'package:site/data/repositories/contact/contact.dart'; @@ -21,31 +22,42 @@ class ContactRepositoryImpl implements ContactRepository { Future sendMail({ required Contact contact, }) async { - final serviceId = _firebaseRemoteConfig.getString('service_id'); - final templateId = _firebaseRemoteConfig.getString('template_id'); - final userId = _firebaseRemoteConfig.getString('user_id'); - final toEmail = _firebaseRemoteConfig.getString('to_email'); - final baseUrl = Uri.parse(ConstantsAPI.baseUrl); - - final response = await _httpClient.post( - baseUrl, - headers: ConstantsAPI.headers, - body: json.encode( - { - 'service_id': serviceId, - 'template_id': templateId, - 'user_id': userId, - 'template_params': { - 'user_name': contact.name, - 'user_email': contact.email, - 'user_subject': contact.email, - 'user_message': contact.message, - 'to_email': toEmail, - }, - }, - ), - ); - - return response; + // final serviceId = _firebaseRemoteConfig.getString('service_id'); + // final templateId = _firebaseRemoteConfig.getString('template_id'); + // final userId = _firebaseRemoteConfig.getString('user_id'); + // final toEmail = _firebaseRemoteConfig.getString('to_email'); + // final baseUrl = Uri.parse(ConstantsAPI.baseUrl); + + // final response = await _httpClient.post( + // baseUrl, + // headers: ConstantsAPI.headers, + // body: json.encode( + // { + // 'service_id': serviceId, + // 'template_id': templateId, + // 'user_id': userId, + // 'template_params': { + // 'user_name': contact.name, + // 'user_email': contact.email, + // 'user_subject': contact.email, + // 'user_message': contact.message, + // 'to_email': toEmail, + // }, + // }, + // ), + // ); + + try { + final apiEndpoint = ConstantsAPI.apiSendMail; + + final response = await _httpClient.post( + Uri.parse(apiEndpoint), + body: Contact.toJson(contact), + ); + + return response; + } catch (e, s) { + log('Error: $e', stackTrace: s); + } } } From 5a306b151c08c0d1c537c4346531e73d28e8c057 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 9 Apr 2024 21:40:45 -0300 Subject: [PATCH 04/50] feat: add error message --- l10n.yaml | 2 +- lib/app/core/l10n/localizations/app_localizations.dart | 6 ++++++ lib/app/core/l10n/localizations/app_localizations_en.dart | 3 +++ lib/app/core/l10n/localizations/app_localizations_es.dart | 3 +++ lib/app/core/l10n/localizations/app_localizations_pt.dart | 3 +++ lib/app/core/l10n/templates/app_en.arb | 1 + lib/app/core/l10n/templates/app_es.arb | 1 + lib/app/core/l10n/templates/app_pt.arb | 1 + lib/app/features/home/widgets/contact/contact_widget.dart | 8 ++++++++ lib/data/services/recaptcha/recaptcha_service.dart | 6 ++++++ 10 files changed, 33 insertions(+), 1 deletion(-) diff --git a/l10n.yaml b/l10n.yaml index 2e0fdb4..ae995ab 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -3,4 +3,4 @@ output-dir: lib/app/core/l10n/localizations template-arb-file: app_pt.arb output-localization-file: app_localizations.dart output-class: AppLocalizations -synthetic-package: false \ No newline at end of file +# synthetic-package: false \ No newline at end of file diff --git a/lib/app/core/l10n/localizations/app_localizations.dart b/lib/app/core/l10n/localizations/app_localizations.dart index 74fd434..546266c 100644 --- a/lib/app/core/l10n/localizations/app_localizations.dart +++ b/lib/app/core/l10n/localizations/app_localizations.dart @@ -329,6 +329,12 @@ abstract class AppLocalizations { /// **'E-mail enviado com sucesso!'** String get emailSendedWithSuccess; + /// No description provided for @emailNotSended. + /// + /// In pt, this message translates to: + /// **'Erro ao enviar e-mail!'** + String get emailNotSended; + /// No description provided for @letsChatCallMe. /// /// In pt, this message translates to: diff --git a/lib/app/core/l10n/localizations/app_localizations_en.dart b/lib/app/core/l10n/localizations/app_localizations_en.dart index 896e5a5..e8cf86c 100644 --- a/lib/app/core/l10n/localizations/app_localizations_en.dart +++ b/lib/app/core/l10n/localizations/app_localizations_en.dart @@ -121,6 +121,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get emailSendedWithSuccess => 'Email sent successfully!'; + @override + String get emailNotSended => 'Error to send email'; + @override String get letsChatCallMe => 'Let\'s chat, call me:'; diff --git a/lib/app/core/l10n/localizations/app_localizations_es.dart b/lib/app/core/l10n/localizations/app_localizations_es.dart index f3ab796..e13b259 100644 --- a/lib/app/core/l10n/localizations/app_localizations_es.dart +++ b/lib/app/core/l10n/localizations/app_localizations_es.dart @@ -121,6 +121,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get emailSendedWithSuccess => '¡Email enviado exitosamente!'; + @override + String get emailNotSended => '¡Error al enviar el email!'; + @override String get letsChatCallMe => 'Hablemos, llámame:'; diff --git a/lib/app/core/l10n/localizations/app_localizations_pt.dart b/lib/app/core/l10n/localizations/app_localizations_pt.dart index 8852f89..cea5bda 100644 --- a/lib/app/core/l10n/localizations/app_localizations_pt.dart +++ b/lib/app/core/l10n/localizations/app_localizations_pt.dart @@ -121,6 +121,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get emailSendedWithSuccess => 'E-mail enviado com sucesso!'; + @override + String get emailNotSended => 'Erro ao enviar e-mail!'; + @override String get letsChatCallMe => 'Vamos bater um papo, me chame:'; diff --git a/lib/app/core/l10n/templates/app_en.arb b/lib/app/core/l10n/templates/app_en.arb index 64ebbb8..f1ce24a 100644 --- a/lib/app/core/l10n/templates/app_en.arb +++ b/lib/app/core/l10n/templates/app_en.arb @@ -38,6 +38,7 @@ "text": "Text", "sendEmailUpper": "SEND EMAIL", "emailSendedWithSuccess": "Email sent successfully!", + "emailNotSended": "Error to send email", "letsChatCallMe": "Let's chat, call me:", "hyphen": " - ", "username": "@felipecastrosales", diff --git a/lib/app/core/l10n/templates/app_es.arb b/lib/app/core/l10n/templates/app_es.arb index ecb2e5f..10cc044 100644 --- a/lib/app/core/l10n/templates/app_es.arb +++ b/lib/app/core/l10n/templates/app_es.arb @@ -38,6 +38,7 @@ "text": "Texto", "sendEmailUpper": "ENVIAR EMAIL", "emailSendedWithSuccess": "¡Email enviado exitosamente!", + "emailNotSended": "¡Error al enviar el email!", "letsChatCallMe": "Hablemos, llámame:", "hyphen": " - ", "username": "@felipecastrosales", diff --git a/lib/app/core/l10n/templates/app_pt.arb b/lib/app/core/l10n/templates/app_pt.arb index a690b99..5817c24 100644 --- a/lib/app/core/l10n/templates/app_pt.arb +++ b/lib/app/core/l10n/templates/app_pt.arb @@ -38,6 +38,7 @@ "text": "Texto", "sendEmailUpper": "ENVIAR E-MAIL", "emailSendedWithSuccess": "E-mail enviado com sucesso!", + "emailNotSended": "Erro ao enviar e-mail!", "letsChatCallMe": "Vamos bater um papo, me chame:", "hyphen": " - ", "username": "@felipecastrosales", diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/home/widgets/contact/contact_widget.dart index 71785b2..f7154a7 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/home/widgets/contact/contact_widget.dart @@ -76,6 +76,14 @@ class ContactWidget extends StatelessWidget { ]) { controller.clear(); } + } else if (context.mounted) { + appShowSnackBar( + context, + text: AppTexts.get(context).emailNotSended, + icon: Icons.error, + color: AppColors.red, + width: 300, + ); } } }, diff --git a/lib/data/services/recaptcha/recaptcha_service.dart b/lib/data/services/recaptcha/recaptcha_service.dart index 0ce95ed..f5fb333 100644 --- a/lib/data/services/recaptcha/recaptcha_service.dart +++ b/lib/data/services/recaptcha/recaptcha_service.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:io'; import 'package:g_recaptcha_v3/g_recaptcha_v3.dart'; import 'package:http/http.dart' as http; @@ -14,6 +15,10 @@ class RecaptchaService { static Future isNotABot() async { final verificationResponse = await _getVerificationResponse(); + if (Platform.isAndroid || Platform.isIOS) { + return true; + } + if (verificationResponse == null) { return false; } @@ -38,6 +43,7 @@ class RecaptchaService { }, ); final body = response.body; + return RecaptchaResponse.fromJson(body); } else { log('RecaptchaService._getVerificationResponse, token is empty'); From 0fc31ae07a1d82dcaf199e0405bda862be9d5ea6 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Mon, 15 Apr 2024 20:12:41 -0300 Subject: [PATCH 05/50] docs: revert for be equal --- l10n.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/l10n.yaml b/l10n.yaml index ae995ab..f86dba8 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -3,4 +3,5 @@ output-dir: lib/app/core/l10n/localizations template-arb-file: app_pt.arb output-localization-file: app_localizations.dart output-class: AppLocalizations +### Uncomment when generating. # synthetic-package: false \ No newline at end of file From f77e2655a4d66930b43f8b053a4dfc26fd7dd689 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Mon, 15 Apr 2024 20:20:14 -0300 Subject: [PATCH 06/50] docs: update license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7fb594b..a6a4787 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2023 Felipe Sales +Copyright (c) 2022-2024 Felipe Sales Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 5ce8268af9b46c1670e6bb2bfb6fd4d0d2be45c8 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Mon, 15 Apr 2024 20:51:24 -0300 Subject: [PATCH 07/50] docs: fix typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc28bb0..05d03be 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ With a single codebase, you can access this example from mobile, web and even desktop. -I'm sure this will be one of the best examples of the Flutter Web project in a completely open-source way and with the amout of features that exist. +I'm sure this will be one of the best examples of the Flutter Web project in a completely open-source way and with the amount of features that exist. --- @@ -88,7 +88,7 @@ Challenges are always an opportunity for growth, and in this project that became One thing I realized was that Flutter Web still has a lot to evolve, but id does very well it proposes ([see here](https://docs.flutter.dev/development/platform-integration/web/faq#what-scenarios-are-ideal-for-flutter-on-the-web)). One of the main ones that still annoys me a little is the loading and rendering speed of the elements (there are some pre-load strategies, but that could be better and clearer IMHO. -I feel that the project can evolve a lot, and for that reason I was very carefull with its development. For it to be simple, scalable and capable of any developer, of any level, being able to use and understand it. Also, I'll always be on the lookout for issues and PRs to improve it. 🚀 +I feel that the project can evolve a lot, and for that reason I was very carefully with its development. For it to be simple, scalable and capable of any developer, of any level, being able to use and understand it. Also, I'll always be on the lookout for issues and PRs to improve it. 🚀 This project took me out of my comfort zone, and I'm very happy with the result. Also, releasing it to the community did me a lot of good. Let's grow together, because the **Forge is Daily**. 🏆 From 1e0a869ab65c8e366bfbe9f82625d73272dadda4 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Mon, 15 Apr 2024 21:15:18 -0300 Subject: [PATCH 08/50] feat: change `emailjs` to `aws ses` for sending emails --- lib/app/core/injections/injections.dart | 1 - lib/app/features/home/home_page.dart | 11 ++--- .../home/widgets/contact/contact_widget.dart | 6 ++- lib/data/constants/constants_api.dart | 8 +--- lib/data/models/contact.dart | 4 ++ .../contact/contact_repository_impl.dart | 41 +++---------------- .../firebase/firebase_service_impl.dart | 2 +- .../services/recaptcha/recaptcha_service.dart | 7 +++- .../controller/contact_controller_test.dart | 20 --------- .../contact/contact_repository_impl_test.dart | 17 -------- 10 files changed, 27 insertions(+), 90 deletions(-) diff --git a/lib/app/core/injections/injections.dart b/lib/app/core/injections/injections.dart index a48d7dd..c69b902 100644 --- a/lib/app/core/injections/injections.dart +++ b/lib/app/core/injections/injections.dart @@ -26,7 +26,6 @@ void configureDependencies() { if (!getIt.isRegistered()) { getIt.registerSingleton( ContactRepositoryImpl( - firebaseRemoteConfig: getIt(), httpClient: getIt(), ), ); diff --git a/lib/app/features/home/home_page.dart b/lib/app/features/home/home_page.dart index 1fe2b97..3d9ba53 100644 --- a/lib/app/features/home/home_page.dart +++ b/lib/app/features/home/home_page.dart @@ -25,6 +25,8 @@ class HomePage extends StatefulWidget { }) : _firebaseRemoteConfig = firebaseRemoteConfig ?? getIt(), _httpClient = httpClient ?? getIt(); + /// The [FirebaseRemoteConfig] instance is here to be used for future updates and configurations. + // ignore: unused_field final FirebaseRemoteConfig _firebaseRemoteConfig; final http.Client _httpClient; @@ -42,18 +44,17 @@ class _HomePageState extends State { void initState() { super.initState(); items = [ + Presentation(itemScrollController), + const Projects(), + const Experience(), + const Social(), ContactWidget( contactController: ContactController( contactRepository: ContactRepositoryImpl( - firebaseRemoteConfig: widget._firebaseRemoteConfig, httpClient: widget._httpClient, ), ), ), - Presentation(itemScrollController), - const Projects(), - const Experience(), - const Social(), const CustomFooter(), ]; } diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/home/widgets/contact/contact_widget.dart index f7154a7..3e40392 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/home/widgets/contact/contact_widget.dart @@ -22,7 +22,6 @@ class ContactWidget extends StatelessWidget { }) : _contactController = contactController ?? ContactController( contactRepository: ContactRepositoryImpl( - firebaseRemoteConfig: getIt(), httpClient: getIt(), ), ); @@ -49,7 +48,7 @@ class ContactWidget extends StatelessWidget { final isNotABot = await RecaptchaService.isNotABot(); if (isNotABot) { - formKey.currentState!.save(); + formKey.currentState?.save(); if (context.mounted) { appShowSnackBar( context, @@ -68,6 +67,7 @@ class ContactWidget extends StatelessWidget { subject: subjectController.text, ), ); + for (var controller in [ nameController, emailController, @@ -76,6 +76,8 @@ class ContactWidget extends StatelessWidget { ]) { controller.clear(); } + + formKey.currentState?.reset(); } else if (context.mounted) { appShowSnackBar( context, diff --git a/lib/data/constants/constants_api.dart b/lib/data/constants/constants_api.dart index 69d2837..805edd4 100644 --- a/lib/data/constants/constants_api.dart +++ b/lib/data/constants/constants_api.dart @@ -1,14 +1,8 @@ import 'package:site/infra/env/env.dart'; class ConstantsAPI { - static const baseUrl = 'https://api.emailjs.com/api/v1.0/email/send'; - static const headers = { - 'origin': 'http://localhost', - 'Content-Type': 'application/json', - }; - /// Send email endpoint. - static final apiSendMail = Env.apiSendMail; + static final apiSendMail = Uri.parse(Env.apiSendMail); /// Recaptcha information. static final recaptchaPublicKey = Env.recaptchaPublicKey; diff --git a/lib/data/models/contact.dart b/lib/data/models/contact.dart index dae42d4..38f00ad 100644 --- a/lib/data/models/contact.dart +++ b/lib/data/models/contact.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + class Contact { Contact({ required this.name, @@ -16,4 +18,6 @@ class Contact { 'template_body': contact.message, }; } + + static String toJsonString(Contact contact) => jsonEncode(toJson(contact)); } diff --git a/lib/data/repositories/contact/contact_repository_impl.dart b/lib/data/repositories/contact/contact_repository_impl.dart index 6ec97da..e6bdbbd 100644 --- a/lib/data/repositories/contact/contact_repository_impl.dart +++ b/lib/data/repositories/contact/contact_repository_impl.dart @@ -1,7 +1,5 @@ -import 'dart:convert'; import 'dart:developer'; -import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:http/http.dart' as http; import 'package:site/data/constants/constants_api.dart'; @@ -10,49 +8,22 @@ import 'package:site/data/repositories/contact/contact.dart'; class ContactRepositoryImpl implements ContactRepository { ContactRepositoryImpl({ - required FirebaseRemoteConfig firebaseRemoteConfig, required http.Client httpClient, - }) : _firebaseRemoteConfig = firebaseRemoteConfig, - _httpClient = httpClient; + }) : _httpClient = httpClient; - final FirebaseRemoteConfig _firebaseRemoteConfig; final http.Client _httpClient; @override Future sendMail({ required Contact contact, }) async { - // final serviceId = _firebaseRemoteConfig.getString('service_id'); - // final templateId = _firebaseRemoteConfig.getString('template_id'); - // final userId = _firebaseRemoteConfig.getString('user_id'); - // final toEmail = _firebaseRemoteConfig.getString('to_email'); - // final baseUrl = Uri.parse(ConstantsAPI.baseUrl); - - // final response = await _httpClient.post( - // baseUrl, - // headers: ConstantsAPI.headers, - // body: json.encode( - // { - // 'service_id': serviceId, - // 'template_id': templateId, - // 'user_id': userId, - // 'template_params': { - // 'user_name': contact.name, - // 'user_email': contact.email, - // 'user_subject': contact.email, - // 'user_message': contact.message, - // 'to_email': toEmail, - // }, - // }, - // ), - // ); - try { - final apiEndpoint = ConstantsAPI.apiSendMail; - final response = await _httpClient.post( - Uri.parse(apiEndpoint), - body: Contact.toJson(contact), + ConstantsAPI.apiSendMail, + body: Contact.toJsonString(contact), + headers: { + 'Content-Type': 'application/json', + }, ); return response; diff --git a/lib/data/services/firebase/firebase_service_impl.dart b/lib/data/services/firebase/firebase_service_impl.dart index dea81d3..1f31796 100644 --- a/lib/data/services/firebase/firebase_service_impl.dart +++ b/lib/data/services/firebase/firebase_service_impl.dart @@ -23,7 +23,7 @@ class FirebaseServiceImpl implements FirebaseService { try { await remoteConfig.setDefaults(remoteConfigKeys); await remoteConfig.setConfigSettings(remoteConfigSettings); - await remoteConfig.fetchAndActivate(); + // await remoteConfig.fetchAndActivate(); } catch (e, s) { developer.log( 'setUpRemoteConfig', diff --git a/lib/data/services/recaptcha/recaptcha_service.dart b/lib/data/services/recaptcha/recaptcha_service.dart index f5fb333..273cad3 100644 --- a/lib/data/services/recaptcha/recaptcha_service.dart +++ b/lib/data/services/recaptcha/recaptcha_service.dart @@ -1,6 +1,6 @@ import 'dart:developer'; -import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:g_recaptcha_v3/g_recaptcha_v3.dart'; import 'package:http/http.dart' as http; import 'package:site/data/constants/constants_api.dart'; @@ -15,7 +15,10 @@ class RecaptchaService { static Future isNotABot() async { final verificationResponse = await _getVerificationResponse(); - if (Platform.isAndroid || Platform.isIOS) { + if ([ + TargetPlatform.iOS, + TargetPlatform.android, + ].contains(defaultTargetPlatform)) { return true; } diff --git a/test/app/features/home/widgets/contact/controller/contact_controller_test.dart b/test/app/features/home/widgets/contact/controller/contact_controller_test.dart index 9d50e99..16017a0 100644 --- a/test/app/features/home/widgets/contact/controller/contact_controller_test.dart +++ b/test/app/features/home/widgets/contact/controller/contact_controller_test.dart @@ -12,39 +12,19 @@ import '../../../../../../utils/utils.dart'; void main() { setupFirebaseAuthMocks(); - - late MockFirebaseRemoteConfig mockFirebaseRemoteConfig; late MockHttpClient mockHttpClient; late ContactRepositoryImpl contactRepository; late ContactController contactController; setUp(() { - mockFirebaseRemoteConfig = MockFirebaseRemoteConfig(); mockHttpClient = MockHttpClient(); contactRepository = ContactRepositoryImpl( - firebaseRemoteConfig: mockFirebaseRemoteConfig, httpClient: mockHttpClient, ); contactController = ContactController( contactRepository: contactRepository, ); - when( - () => mockFirebaseRemoteConfig.getString('service_id'), - ).thenReturn('service_id'); - - when( - () => mockFirebaseRemoteConfig.getString('template_id'), - ).thenReturn('template_id'); - - when( - () => mockFirebaseRemoteConfig.getString('user_id'), - ).thenReturn('user_id'); - - when( - () => mockFirebaseRemoteConfig.getString('to_email'), - ).thenReturn('to_email'); - when( () => mockHttpClient.post( any(), diff --git a/test/data/repositories/contact/contact_repository_impl_test.dart b/test/data/repositories/contact/contact_repository_impl_test.dart index 775b745..ca48d91 100644 --- a/test/data/repositories/contact/contact_repository_impl_test.dart +++ b/test/data/repositories/contact/contact_repository_impl_test.dart @@ -16,26 +16,9 @@ void main() { mockHttpClient = MockHttpClient(); contactRepository = ContactRepositoryImpl( - firebaseRemoteConfig: mockFirebaseRemoteConfig, httpClient: mockHttpClient, ); - when( - () => mockFirebaseRemoteConfig.getString('service_id'), - ).thenReturn('service_id'); - - when( - () => mockFirebaseRemoteConfig.getString('template_id'), - ).thenReturn('template_id'); - - when( - () => mockFirebaseRemoteConfig.getString('user_id'), - ).thenReturn('user_id'); - - when( - () => mockFirebaseRemoteConfig.getString('to_email'), - ).thenReturn('to_email'); - when( () => mockHttpClient.post( any(), From c86387572cc3e8f2cd1b0f0ec9fa2531517b3f5a Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 16 Apr 2024 20:54:25 -0300 Subject: [PATCH 09/50] docs: removed references to email.js --- .github/docs/WANTTODO.md | 5 +++-- README.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/docs/WANTTODO.md b/.github/docs/WANTTODO.md index 3e67513..f050d3e 100644 --- a/.github/docs/WANTTODO.md +++ b/.github/docs/WANTTODO.md @@ -23,8 +23,9 @@ First, Follow the [🤔 How to Use](./README.md#-how-to-use) steps. ## Contact Form - Related to Contact Form: - - Create your account inside `emailjs` and make your changes. - - You can see [this video](https://www.youtube.com/watch?v=9HW3MZ_tsdo), to help you on practice. + - Check my api [here](https://github.com/felipecastrosales/site-api) and make your changes. + - You need configurate your AWS SES, and put your credentials inside the `.env` file. + - After that, configure AWS Lambda and API Gateway, and deploy your API. ## Firebase diff --git a/README.md b/README.md index 05d03be..94919f3 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ I'm sure this will be one of the best examples of the Flutter Web project in a c - All of them using [`mocktail`](https://pub.dev/packages/mocktail). - Internationalization: - With support to 3 languages: English, Portuguese and Spanish; -- Feature to send an email to the user using [`emailjs API`](https://www.emailjs.com/); +- Feature to send an email to the user using [`AWS SES`](https://aws.amazon.com/ses/), [`AWS Lambda`](https://aws.amazon.com/lambda/) and [`AWS API Gateway`](https://aws.amazon.com/api-gateway/); - Settings: - Firebase Hosting; - Google Domains; From f997a5f06cb58a1203e22e2f04fdb29942721945 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 16 Apr 2024 20:54:48 -0300 Subject: [PATCH 10/50] build: bump to 2.2.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9648b8c..f8d33c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: site description: Felipe Sales | Social Links. publish_to: 'none' -version: 2.1.2 +version: 2.2.0 environment: sdk: '>=3.3.0 <4.0.0' From c18b4377ac18ab2b80fd8bc1ad86738890538718 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 16 Apr 2024 21:23:06 -0300 Subject: [PATCH 11/50] wip --- lib/data/services/firebase/firebase_service_impl.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/data/services/firebase/firebase_service_impl.dart b/lib/data/services/firebase/firebase_service_impl.dart index 1f31796..ae6151d 100644 --- a/lib/data/services/firebase/firebase_service_impl.dart +++ b/lib/data/services/firebase/firebase_service_impl.dart @@ -23,6 +23,7 @@ class FirebaseServiceImpl implements FirebaseService { try { await remoteConfig.setDefaults(remoteConfigKeys); await remoteConfig.setConfigSettings(remoteConfigSettings); + // as long as no fetchAndActivate is needed, this is not necessary // await remoteConfig.fetchAndActivate(); } catch (e, s) { developer.log( From e13da16c913fcd5941656015caf422a4cad19fb4 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 17 Apr 2024 21:07:40 -0300 Subject: [PATCH 12/50] chore: removed save and reset of form --- lib/app/features/home/widgets/contact/contact_widget.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/home/widgets/contact/contact_widget.dart index 3e40392..9be6e29 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/home/widgets/contact/contact_widget.dart @@ -48,7 +48,6 @@ class ContactWidget extends StatelessWidget { final isNotABot = await RecaptchaService.isNotABot(); if (isNotABot) { - formKey.currentState?.save(); if (context.mounted) { appShowSnackBar( context, @@ -76,8 +75,6 @@ class ContactWidget extends StatelessWidget { ]) { controller.clear(); } - - formKey.currentState?.reset(); } else if (context.mounted) { appShowSnackBar( context, From dca05a15437ef00493328a29b96543143ee35728 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 17 Apr 2024 21:12:21 -0300 Subject: [PATCH 13/50] chore: add early return --- lib/data/services/recaptcha/recaptcha_service.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/data/services/recaptcha/recaptcha_service.dart b/lib/data/services/recaptcha/recaptcha_service.dart index 273cad3..c34fcd8 100644 --- a/lib/data/services/recaptcha/recaptcha_service.dart +++ b/lib/data/services/recaptcha/recaptcha_service.dart @@ -13,8 +13,6 @@ class RecaptchaService { await GRecaptchaV3.ready(ConstantsAPI.recaptchaPublicKey); static Future isNotABot() async { - final verificationResponse = await _getVerificationResponse(); - if ([ TargetPlatform.iOS, TargetPlatform.android, @@ -22,6 +20,8 @@ class RecaptchaService { return true; } + final verificationResponse = await _getVerificationResponse(); + if (verificationResponse == null) { return false; } @@ -43,8 +43,12 @@ class RecaptchaService { }, headers: { 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST', + 'Access-Control-Allow-Headers': + 'Origin, X-Requested-With, Content-Type, Accept', }, ); + final body = response.body; return RecaptchaResponse.fromJson(body); From 5a54ba49f54bb3e6ce70a7374be0bb88ad9d1e6e Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 17 Apr 2024 21:36:06 -0300 Subject: [PATCH 14/50] chore: add headers --- lib/data/repositories/contact/contact_repository_impl.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/data/repositories/contact/contact_repository_impl.dart b/lib/data/repositories/contact/contact_repository_impl.dart index e6bdbbd..5410093 100644 --- a/lib/data/repositories/contact/contact_repository_impl.dart +++ b/lib/data/repositories/contact/contact_repository_impl.dart @@ -23,6 +23,7 @@ class ContactRepositoryImpl implements ContactRepository { body: Contact.toJsonString(contact), headers: { 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', }, ); From 0dceb84ef03815d7476605cfcf9da7aebb5c5d43 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sun, 21 Apr 2024 21:31:24 -0300 Subject: [PATCH 15/50] chore: unused code --- lib/app/features/home/widgets/contact/contact_widget.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/home/widgets/contact/contact_widget.dart index 9be6e29..29f7d28 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/home/widgets/contact/contact_widget.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:g_recaptcha_v3/g_recaptcha_v3.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; @@ -101,8 +100,3 @@ class ContactWidget extends StatelessWidget { ); } } - -Future generateToken() async { - final token = await GRecaptchaV3.execute('submit') ?? ''; - debugPrint('Token: $token'); -} From 5689d53f9d5cbea25c33b4efd21554d8efde8b92 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 24 Apr 2024 18:26:44 -0300 Subject: [PATCH 16/50] style: comment dart_code_metrics usage --- analysis_options.yaml | 102 +++++++++++++++++++++--------------------- pubspec.lock | 10 ++++- pubspec.yaml | 4 +- 3 files changed, 62 insertions(+), 54 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 01e5e46..a2cf24c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,55 +1,55 @@ include: package:flutter_lints/flutter.yaml -analyzer: - plugins: - - dart_code_metrics - exclude: - - lib/generated_plugin_registrant.dart - - test/** - - lib/app/core/l10n/localizations/** # Generated file - - lib/data/services/firebase/firebase_set_defaults.dart # To use dynamic type (because really need it) +# analyzer: +# plugins: +# - dart_code_metrics +# exclude: +# - lib/generated_plugin_registrant.dart +# - test/** +# - lib/app/core/l10n/localizations/** # Generated file +# - lib/data/services/firebase/firebase_set_defaults.dart # To use dynamic type (because really need it) -linter: - rules: - - always_declare_return_types - - always_use_package_imports - - avoid_dynamic_calls - - avoid_relative_lib_imports - - implementation_imports - - require_trailing_commas - - sized_box_for_whitespace - - sized_box_shrink_expand - - sort_child_properties_last - - sort_constructors_first - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_literals_to_create_immutables - - prefer_const_declarations - - prefer_final_fields - - prefer_single_quotes +# linter: +# rules: +# - always_declare_return_types +# - always_use_package_imports +# - avoid_dynamic_calls +# - avoid_relative_lib_imports +# - implementation_imports +# - require_trailing_commas +# - sized_box_for_whitespace +# - sized_box_shrink_expand +# - sort_child_properties_last +# - sort_constructors_first +# - prefer_const_constructors +# - prefer_const_constructors_in_immutables +# - prefer_const_literals_to_create_immutables +# - prefer_const_declarations +# - prefer_final_fields +# - prefer_single_quotes -dart_code_metrics: - metrics: - cyclomatic-complexity: 20 - number-of-parameters: 4 - maximum-nesting-level: 5 - metrics-exclude: - - test/** - rules: - - avoid-dynamic - - avoid-redundant-async - - avoid-passing-async-when-sync-expected - - avoid-redundant-async - - avoid-unnecessary-type-assertions - - avoid-unnecessary-type-casts - - avoid-unrelated-type-assertions - - avoid-unused-parameters - - avoid-nested-conditional-expressions - - newline-before-return - - no-boolean-literal-compare - - no-empty-block - - prefer-trailing-comma - - prefer-conditional-expressions - - no-equal-then-else - - prefer-moving-to-variable - - prefer-match-file-name \ No newline at end of file +# dart_code_metrics: +# metrics: +# cyclomatic-complexity: 20 +# number-of-parameters: 4 +# maximum-nesting-level: 5 +# metrics-exclude: +# - test/** +# rules: +# - avoid-dynamic +# - avoid-redundant-async +# - avoid-passing-async-when-sync-expected +# - avoid-redundant-async +# - avoid-unnecessary-type-assertions +# - avoid-unnecessary-type-casts +# - avoid-unrelated-type-assertions +# - avoid-unused-parameters +# - avoid-nested-conditional-expressions +# - newline-before-return +# - no-boolean-literal-compare +# - no-empty-block +# - prefer-trailing-comma +# - prefer-conditional-expressions +# - no-equal-then-else +# - prefer-moving-to-variable +# - prefer-match-file-name \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 409bc9e..9517b16 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -193,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.6" + dio: + dependency: "direct main" + description: + name: dio + sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" + url: "https://pub.dev" + source: hosted + version: "5.4.3+1" email_validator: dependency: "direct main" description: @@ -416,7 +424,7 @@ packages: source: hosted version: "2.3.1" http: - dependency: "direct main" + dependency: transitive description: name: http sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba diff --git a/pubspec.yaml b/pubspec.yaml index f8d33c5..8ef8c64 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,13 +1,14 @@ name: site description: Felipe Sales | Social Links. publish_to: 'none' -version: 2.2.0 +version: 2.2.1 environment: sdk: '>=3.3.0 <4.0.0' dependencies: auto_size_text: ^3.0.0 + dio: ^5.4.3+1 email_validator: ^2.1.17 envied: ^0.5.4+1 firebase_analytics: ^10.8.5 @@ -22,7 +23,6 @@ dependencies: flutter_svg: ^2.0.9 g_recaptcha_v3: ^0.0.5 get_it: ^7.6.7 - http: 1.2.0 intl: 0.18.1 lottie: ^3.0.0 package_info_plus: ^5.0.1 From 5aaa69fb34ccaf7277ac0d703f045d3feec6a89d Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 24 Apr 2024 19:34:59 -0300 Subject: [PATCH 17/50] fix: set primary as false to avoid error when typing form --- lib/app/features/home/widgets/contact/contact_mobile.dart | 1 + .../utils_widgets/single_child_scroll_view_without_scroll.dart | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib/app/features/home/widgets/contact/contact_mobile.dart b/lib/app/features/home/widgets/contact/contact_mobile.dart index 38798de..af946c2 100644 --- a/lib/app/features/home/widgets/contact/contact_mobile.dart +++ b/lib/app/features/home/widgets/contact/contact_mobile.dart @@ -40,6 +40,7 @@ class ContactMobile extends StatelessWidget { ), ), SingleChildScrollViewWithoutScroll( + primary: false, child: Column( children: [ MobileBody( diff --git a/lib/app/widgets/utils_widgets/single_child_scroll_view_without_scroll.dart b/lib/app/widgets/utils_widgets/single_child_scroll_view_without_scroll.dart index be78e5a..17d9001 100644 --- a/lib/app/widgets/utils_widgets/single_child_scroll_view_without_scroll.dart +++ b/lib/app/widgets/utils_widgets/single_child_scroll_view_without_scroll.dart @@ -17,14 +17,17 @@ class SingleChildScrollViewWithoutScroll extends StatelessWidget { const SingleChildScrollViewWithoutScroll({ super.key, required this.child, + this.primary, }); final Widget child; + final bool? primary; @override Widget build(BuildContext context) { return SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), + primary: primary, child: child, ); } From 88bde04e21c4bc6dc94cabd9cdf3f276d3c7a00c Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 24 Apr 2024 19:56:35 -0300 Subject: [PATCH 18/50] chore: removed temp recaptcha, change http to dio and init to fix test --- lib/app/app_widget.dart | 9 +++--- lib/app/core/injections/injections.dart | 8 ++--- lib/app/features/home/home_page.dart | 9 +++--- .../home/widgets/contact/contact_widget.dart | 7 +++-- .../widgets/contact/widgets/custom_form.dart | 26 ++++++++++++++-- lib/app/utils/contact_validators.dart | 2 ++ lib/app/widgets/buttons/app_text_button.dart | 3 ++ lib/data/constants/constants_api.dart | 2 +- .../contact/contact_repository_impl.dart | 14 +++------ .../services/recaptcha/recaptcha_service.dart | 31 +++++++++---------- lib/main.dart | 8 +++-- .../controller/contact_controller_test.dart | 12 ++++--- .../contact/contact_repository_impl_test.dart | 16 +++++----- test/utils/mocks/mock_http_client.dart | 4 +-- web/index.html | 2 +- 15 files changed, 89 insertions(+), 64 deletions(-) diff --git a/lib/app/app_widget.dart b/lib/app/app_widget.dart index 1aca53b..916135d 100644 --- a/lib/app/app_widget.dart +++ b/lib/app/app_widget.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; - +import 'package:dio/dio.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; -import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; @@ -12,12 +11,12 @@ class AppWidget extends StatelessWidget { AppWidget({ super.key, FirebaseRemoteConfig? firebaseRemoteConfig, - http.Client? httpClient, + Dio? httpClient, }) : _firebaseRemoteConfig = firebaseRemoteConfig ?? getIt(), _httpClient = httpClient ?? getIt(); final FirebaseRemoteConfig _firebaseRemoteConfig; - final http.Client _httpClient; + final Dio _httpClient; @override Widget build(BuildContext context) { diff --git a/lib/app/core/injections/injections.dart b/lib/app/core/injections/injections.dart index c69b902..140c8c4 100644 --- a/lib/app/core/injections/injections.dart +++ b/lib/app/core/injections/injections.dart @@ -1,6 +1,6 @@ +import 'package:dio/dio.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:get_it/get_it.dart'; -import 'package:http/http.dart' as http; import 'package:site/data/repositories/contact/contact.dart'; import 'package:site/data/services/firebase/firebase.dart'; @@ -8,9 +8,9 @@ import 'package:site/data/services/firebase/firebase.dart'; final getIt = GetIt.I; void configureDependencies() { - if (!getIt.isRegistered()) { - getIt.registerSingleton( - http.Client(), + if (!getIt.isRegistered()) { + getIt.registerSingleton( + Dio(), ); } if (!getIt.isRegistered()) { diff --git a/lib/app/features/home/home_page.dart b/lib/app/features/home/home_page.dart index 3d9ba53..9c95d5a 100644 --- a/lib/app/features/home/home_page.dart +++ b/lib/app/features/home/home_page.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; - +import 'package:dio/dio.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; -import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:site/app/core/injections/injections.dart'; @@ -21,14 +20,14 @@ class HomePage extends StatefulWidget { HomePage({ super.key, FirebaseRemoteConfig? firebaseRemoteConfig, - http.Client? httpClient, + Dio? httpClient, }) : _firebaseRemoteConfig = firebaseRemoteConfig ?? getIt(), _httpClient = httpClient ?? getIt(); /// The [FirebaseRemoteConfig] instance is here to be used for future updates and configurations. // ignore: unused_field final FirebaseRemoteConfig _firebaseRemoteConfig; - final http.Client _httpClient; + final Dio _httpClient; @override State createState() => _HomePageState(); diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/home/widgets/contact/contact_widget.dart index 29f7d28..e9f5bd8 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/home/widgets/contact/contact_widget.dart @@ -44,9 +44,10 @@ class ContactWidget extends StatelessWidget { messageController: messageController, onPressed: () async { if (formKey.currentState?.validate() ?? false) { - final isNotABot = await RecaptchaService.isNotABot(); + // final isNotABot = await RecaptchaService.isNotABot(); - if (isNotABot) { + // if (isNotABot) { + if (true) { if (context.mounted) { appShowSnackBar( context, @@ -60,7 +61,7 @@ class ContactWidget extends StatelessWidget { _contactController?.sendMail( contact: models.Contact( name: nameController.text, - email: emailController.text, + email: emailController.text.trim(), message: messageController.text, subject: subjectController.text, ), diff --git a/lib/app/features/home/widgets/contact/widgets/custom_form.dart b/lib/app/features/home/widgets/contact/widgets/custom_form.dart index fa633d0..eeeebc3 100644 --- a/lib/app/features/home/widgets/contact/widgets/custom_form.dart +++ b/lib/app/features/home/widgets/contact/widgets/custom_form.dart @@ -1,13 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; +import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; import 'package:site/app/features/home/widgets/contact/widgets/widgets.dart'; import 'package:site/app/utils/contact_validators.dart'; import 'package:site/app/widgets/buttons/buttons.dart'; import 'package:site/app/widgets/dividers/dividers.dart'; +import 'package:site/data/repositories/contact/contact_repository_impl.dart'; +import 'package:site/data/models/models.dart' as models; class CustomForm extends StatelessWidget { - const CustomForm({ + CustomForm({ super.key, required this.formKey, required this.nameController, @@ -15,7 +19,13 @@ class CustomForm extends StatelessWidget { required this.subjectController, required this.messageController, required this.onPressed, - }); + ContactController? contactController, + }) : _contactController = contactController ?? + ContactController( + contactRepository: ContactRepositoryImpl( + httpClient: getIt(), + ), + ); final GlobalKey formKey; final TextEditingController nameController; @@ -24,6 +34,8 @@ class CustomForm extends StatelessWidget { final TextEditingController messageController; final VoidCallback onPressed; + final ContactController? _contactController; + @override Widget build(BuildContext context) { return Form( @@ -69,6 +81,16 @@ class CustomForm extends StatelessWidget { child: AppTextButton( text: AppTexts.get(context).sendEmailUpper, onPressed: onPressed, + onLongPress: () { + _contactController?.sendMail( + contact: models.Contact( + name: 'felipe sales', + email: 'soufeliposales@gmail.com', + message: 'teste de mensagem', + subject: 'vamos testar, mais uma vez', + ), + ); + }, ), ), const SizedBox(height: 60), diff --git a/lib/app/utils/contact_validators.dart b/lib/app/utils/contact_validators.dart index 266107e..62e2767 100644 --- a/lib/app/utils/contact_validators.dart +++ b/lib/app/utils/contact_validators.dart @@ -30,6 +30,8 @@ class ContactValidators { } static String? email(String? value, [BuildContext? context]) { + value = value?.trim(); + if (value == null || value.isEmpty) { return AppTexts.get(context!).insertValidEmail; } diff --git a/lib/app/widgets/buttons/app_text_button.dart b/lib/app/widgets/buttons/app_text_button.dart index af2ca9b..530c5c7 100644 --- a/lib/app/widgets/buttons/app_text_button.dart +++ b/lib/app/widgets/buttons/app_text_button.dart @@ -7,15 +7,18 @@ class AppTextButton extends StatelessWidget { super.key, required this.onPressed, required this.text, + this.onLongPress, }); final VoidCallback onPressed; + final VoidCallback? onLongPress; final String text; @override Widget build(BuildContext context) { return TextButton( onPressed: onPressed, + onLongPress: onLongPress, style: TextButton.styleFrom( minimumSize: Size.zero, padding: EdgeInsets.zero, diff --git a/lib/data/constants/constants_api.dart b/lib/data/constants/constants_api.dart index 805edd4..5026927 100644 --- a/lib/data/constants/constants_api.dart +++ b/lib/data/constants/constants_api.dart @@ -2,7 +2,7 @@ import 'package:site/infra/env/env.dart'; class ConstantsAPI { /// Send email endpoint. - static final apiSendMail = Uri.parse(Env.apiSendMail); + static final apiSendMail = Env.apiSendMail; /// Recaptcha information. static final recaptchaPublicKey = Env.recaptchaPublicKey; diff --git a/lib/data/repositories/contact/contact_repository_impl.dart b/lib/data/repositories/contact/contact_repository_impl.dart index 5410093..f19089b 100644 --- a/lib/data/repositories/contact/contact_repository_impl.dart +++ b/lib/data/repositories/contact/contact_repository_impl.dart @@ -1,6 +1,6 @@ import 'dart:developer'; -import 'package:http/http.dart' as http; +import 'package:dio/dio.dart'; import 'package:site/data/constants/constants_api.dart'; import 'package:site/data/models/models.dart'; @@ -8,10 +8,10 @@ import 'package:site/data/repositories/contact/contact.dart'; class ContactRepositoryImpl implements ContactRepository { ContactRepositoryImpl({ - required http.Client httpClient, + required Dio httpClient, }) : _httpClient = httpClient; - final http.Client _httpClient; + final Dio _httpClient; @override Future sendMail({ @@ -20,14 +20,10 @@ class ContactRepositoryImpl implements ContactRepository { try { final response = await _httpClient.post( ConstantsAPI.apiSendMail, - body: Contact.toJsonString(contact), - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - }, + data: Contact.toJson(contact), ); - return response; + return response.data; } catch (e, s) { log('Error: $e', stackTrace: s); } diff --git a/lib/data/services/recaptcha/recaptcha_service.dart b/lib/data/services/recaptcha/recaptcha_service.dart index c34fcd8..564b394 100644 --- a/lib/data/services/recaptcha/recaptcha_service.dart +++ b/lib/data/services/recaptcha/recaptcha_service.dart @@ -2,7 +2,6 @@ import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:g_recaptcha_v3/g_recaptcha_v3.dart'; -import 'package:http/http.dart' as http; import 'package:site/data/constants/constants_api.dart'; import 'package:site/data/services/recaptcha/recaptcha.dart'; @@ -35,23 +34,23 @@ class RecaptchaService { final token = await GRecaptchaV3.execute('submit') ?? ''; if (token.isNotEmpty) { - final response = await http.post( - ConstantsAPI.recaptchaUrl, - body: { - 'secret': ConstantsAPI.recaptchaSecretKey, - 'response': token, - }, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST', - 'Access-Control-Allow-Headers': - 'Origin, X-Requested-With, Content-Type, Accept', - }, - ); + // final response = await http.post( + // ConstantsAPI.recaptchaUrl, + // body: { + // 'secret': ConstantsAPI.recaptchaSecretKey, + // 'response': token, + // }, + // headers: { + // 'Access-Control-Allow-Origin': '*', + // 'Access-Control-Allow-Methods': 'POST', + // 'Access-Control-Allow-Headers': + // 'Origin, X-Requested-With, Content-Type, Accept', + // }, + // ); - final body = response.body; + // final body = response.body; - return RecaptchaResponse.fromJson(body); + // return RecaptchaResponse.fromJson(body); } else { log('RecaptchaService._getVerificationResponse, token is empty'); } diff --git a/lib/main.dart b/lib/main.dart index c845c96..4529749 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,10 +16,12 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); await FirebaseServiceImpl().setUpInitialization(); - if (kIsWeb) { - await RecaptchaService.initiate(); - } + // if (kIsWeb) { + // debugPrint('main: isWeb'); + // await RecaptchaService.initiate(); + // } + debugPrint('main: runZonedGuarded 2'); setPathUrlStrategy(); configureDependencies(); runApp( diff --git a/test/app/features/home/widgets/contact/controller/contact_controller_test.dart b/test/app/features/home/widgets/contact/controller/contact_controller_test.dart index 16017a0..29949f8 100644 --- a/test/app/features/home/widgets/contact/controller/contact_controller_test.dart +++ b/test/app/features/home/widgets/contact/controller/contact_controller_test.dart @@ -1,8 +1,8 @@ +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; @@ -28,11 +28,15 @@ void main() { when( () => mockHttpClient.post( any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + data: any(named: 'data'), + options: any(named: 'options'), ), ).thenAnswer( - (_) async => http.Response('', 200), + (_) async => Response( + data: {}, + statusCode: 200, + requestOptions: RequestOptions(path: ''), + ), ); }); diff --git a/test/data/repositories/contact/contact_repository_impl_test.dart b/test/data/repositories/contact/contact_repository_impl_test.dart index ca48d91..056feba 100644 --- a/test/data/repositories/contact/contact_repository_impl_test.dart +++ b/test/data/repositories/contact/contact_repository_impl_test.dart @@ -1,5 +1,5 @@ +import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; import 'package:site/data/repositories/contact/contact.dart'; @@ -7,14 +7,11 @@ import 'package:site/data/repositories/contact/contact.dart'; import '../../../utils/utils.dart'; void main() { - late MockFirebaseRemoteConfig mockFirebaseRemoteConfig; late MockHttpClient mockHttpClient; late ContactRepositoryImpl contactRepository; setUp(() { - mockFirebaseRemoteConfig = MockFirebaseRemoteConfig(); mockHttpClient = MockHttpClient(); - contactRepository = ContactRepositoryImpl( httpClient: mockHttpClient, ); @@ -22,13 +19,14 @@ void main() { when( () => mockHttpClient.post( any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + data: any(named: 'data'), + options: any(named: 'options'), ), ).thenAnswer( - (_) async => http.Response( - '', - 200, + (_) async => Response( + data: {}, + statusCode: 200, + requestOptions: RequestOptions(path: ''), ), ); }); diff --git a/test/utils/mocks/mock_http_client.dart b/test/utils/mocks/mock_http_client.dart index 254543e..50480cc 100644 --- a/test/utils/mocks/mock_http_client.dart +++ b/test/utils/mocks/mock_http_client.dart @@ -1,4 +1,4 @@ -import 'package:http/http.dart' as http; +import 'package:dio/dio.dart'; import 'package:mocktail/mocktail.dart'; -class MockHttpClient extends Mock implements http.Client {} +class MockHttpClient extends Mock implements Dio {} diff --git a/web/index.html b/web/index.html index f3e6111..755ded1 100644 --- a/web/index.html +++ b/web/index.html @@ -35,7 +35,7 @@ Felipe Sales | Social Links - + From a77256280650c009114b0e4cca03668f140dac50 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 24 Apr 2024 21:14:04 -0300 Subject: [PATCH 19/50] chore: temporarily made recaptcha unavailable --- lib/data/constants/constants_api.dart | 3 +- .../services/recaptcha/recaptcha_service.dart | 35 +++++++++++-------- lib/main.dart | 3 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/lib/data/constants/constants_api.dart b/lib/data/constants/constants_api.dart index 5026927..3350fae 100644 --- a/lib/data/constants/constants_api.dart +++ b/lib/data/constants/constants_api.dart @@ -7,6 +7,5 @@ class ConstantsAPI { /// Recaptcha information. static final recaptchaPublicKey = Env.recaptchaPublicKey; static final recaptchaSecretKey = Env.recaptchaSecretKey; - static final recaptchaUrl = - Uri.parse('https://www.google.com/recaptcha/api/siteverify'); + static const recaptchaUrl = 'https://www.google.com/recaptcha/api/siteverify'; } diff --git a/lib/data/services/recaptcha/recaptcha_service.dart b/lib/data/services/recaptcha/recaptcha_service.dart index 564b394..82c9b53 100644 --- a/lib/data/services/recaptcha/recaptcha_service.dart +++ b/lib/data/services/recaptcha/recaptcha_service.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:g_recaptcha_v3/g_recaptcha_v3.dart'; import 'package:site/data/constants/constants_api.dart'; @@ -34,23 +35,27 @@ class RecaptchaService { final token = await GRecaptchaV3.execute('submit') ?? ''; if (token.isNotEmpty) { - // final response = await http.post( - // ConstantsAPI.recaptchaUrl, - // body: { - // 'secret': ConstantsAPI.recaptchaSecretKey, - // 'response': token, - // }, - // headers: { - // 'Access-Control-Allow-Origin': '*', - // 'Access-Control-Allow-Methods': 'POST', - // 'Access-Control-Allow-Headers': - // 'Origin, X-Requested-With, Content-Type, Accept', - // }, - // ); + final response = await Dio().post( + ConstantsAPI.recaptchaUrl, + // body: { + // 'secret': ConstantsAPI.recaptchaSecretKey, + // 'response': token, + // }, + // headers: { + // 'Access-Control-Allow-Origin': '*', + // 'Access-Control-Allow-Methods': 'POST', + // 'Access-Control-Allow-Headers': + // 'Origin, X-Requested-With, Content-Type, Accept', + // }, + data: { + 'secret': ConstantsAPI.recaptchaSecretKey, + 'response': token, + }, + ); - // final body = response.body; + final body = response.data; - // return RecaptchaResponse.fromJson(body); + return RecaptchaResponse.fromJson(body); } else { log('RecaptchaService._getVerificationResponse, token is empty'); } diff --git a/lib/main.dart b/lib/main.dart index 4529749..8078d89 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,7 @@ import 'package:url_strategy/url_strategy.dart'; import 'package:site/app/app_widget.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/data/services/firebase/firebase.dart'; -import 'package:site/data/services/recaptcha/recaptcha.dart'; +// import 'package:site/data/services/recaptcha/recaptcha.dart'; Future main() async { await runZonedGuarded(() async { @@ -17,7 +17,6 @@ Future main() async { await FirebaseServiceImpl().setUpInitialization(); // if (kIsWeb) { - // debugPrint('main: isWeb'); // await RecaptchaService.initiate(); // } From 02e63713c6ecc43e96375e962dfa98115e497ff3 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 19:05:50 -0300 Subject: [PATCH 20/50] chore: removed runZonedGuarded for prevent twice on app --- lib/main.dart | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8078d89..7c15565 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,33 +1,23 @@ import 'dart:async'; -import 'dart:developer'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; - import 'package:url_strategy/url_strategy.dart'; import 'package:site/app/app_widget.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/data/services/firebase/firebase.dart'; -// import 'package:site/data/services/recaptcha/recaptcha.dart'; Future main() async { - await runZonedGuarded(() async { - WidgetsFlutterBinding.ensureInitialized(); - await FirebaseServiceImpl().setUpInitialization(); + WidgetsFlutterBinding.ensureInitialized(); + await FirebaseServiceImpl().setUpInitialization(); - // if (kIsWeb) { - // await RecaptchaService.initiate(); - // } + // if (kIsWeb) { + // await RecaptchaService.initiate(); + // } - debugPrint('main: runZonedGuarded 2'); - setPathUrlStrategy(); - configureDependencies(); - runApp( - AppWidget(), - ); - }, (error, stackTrace) { - log('runZonedGuarded: Caught error: $error'); - log('runZonedGuarded: StackTrace: $stackTrace'); - }); + setPathUrlStrategy(); + configureDependencies(); + runApp( + AppWidget(), + ); } From 1ee7ffade70c24a13372b7270c9b38c2b1afd4ed Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 19:28:47 -0300 Subject: [PATCH 21/50] chore: fix overflow --- .../platform_info/widgets/platform_info_widget.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/app/core/platform_info/widgets/platform_info_widget.dart b/lib/app/core/platform_info/widgets/platform_info_widget.dart index 104833a..dc66a3c 100644 --- a/lib/app/core/platform_info/widgets/platform_info_widget.dart +++ b/lib/app/core/platform_info/widgets/platform_info_widget.dart @@ -30,18 +30,11 @@ class _PlatformInfoWidgetState extends State { return FutureBuilder( future: packageInfo, builder: (context, snapshot) { - if ([ - ConnectionState.none, - ConnectionState.active, - ConnectionState.waiting, - ].contains(snapshot.connectionState)) { - return const CircularProgressIndicator(); - } - if (snapshot.hasData && snapshot.data != null) { final data = snapshot.data!; + final buildNumber = data.buildNumber; final text = - 'v${data.version}${data.buildNumber.isEmpty ? '' : '+${data.buildNumber}'}'; + 'v${data.version}${buildNumber.isEmpty ? '' : '+$buildNumber'}'; return Padding( padding: padding, From 0cfa23ca99a68c6559c29c6812a385c698dabc49 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 19:33:59 -0300 Subject: [PATCH 22/50] refactor: removed help method --- .../home/widgets/contact/contact_widget.dart | 95 +++++++++---------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/home/widgets/contact/contact_widget.dart index e9f5bd8..736229c 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/home/widgets/contact/contact_widget.dart @@ -12,7 +12,7 @@ import 'package:site/app/features/home/widgets/contact/widgets/widgets.dart'; import 'package:site/app/widgets/snack_bars/snack_bars.dart'; import 'package:site/data/models/models.dart' as models; import 'package:site/data/repositories/contact/contact.dart'; -import 'package:site/data/services/recaptcha/recaptcha.dart'; +// import 'package:site/data/services/recaptcha/recaptcha.dart'; class ContactWidget extends StatelessWidget { ContactWidget({ @@ -34,66 +34,61 @@ class ContactWidget extends StatelessWidget { final emailController = TextEditingController(); final messageController = TextEditingController(); final subjectController = TextEditingController(); + final form = CustomForm( + formKey: formKey, + nameController: nameController, + emailController: emailController, + subjectController: subjectController, + messageController: messageController, + onPressed: () async { + if (formKey.currentState?.validate() ?? false) { + // final isNotABot = await RecaptchaService.isNotABot(); - Widget contactForm() { - return CustomForm( - formKey: formKey, - nameController: nameController, - emailController: emailController, - subjectController: subjectController, - messageController: messageController, - onPressed: () async { - if (formKey.currentState?.validate() ?? false) { - // final isNotABot = await RecaptchaService.isNotABot(); - - // if (isNotABot) { - if (true) { - if (context.mounted) { - appShowSnackBar( - context, - text: AppTexts.get(context).emailSendedWithSuccess, - icon: Icons.check, - color: AppColors.primaryDark, - width: 300, - ); - } - - _contactController?.sendMail( - contact: models.Contact( - name: nameController.text, - email: emailController.text.trim(), - message: messageController.text, - subject: subjectController.text, - ), - ); - - for (var controller in [ - nameController, - emailController, - messageController, - subjectController, - ]) { - controller.clear(); - } - } else if (context.mounted) { + // if (isNotABot) { + if (true) { + if (context.mounted) { appShowSnackBar( context, - text: AppTexts.get(context).emailNotSended, - icon: Icons.error, - color: AppColors.red, + text: AppTexts.get(context).emailSendedWithSuccess, + icon: Icons.check, + color: AppColors.primaryDark, width: 300, ); } + + _contactController?.sendMail( + contact: models.Contact( + name: nameController.text, + email: emailController.text.trim(), + message: messageController.text, + subject: subjectController.text, + ), + ); + + for (var controller in [ + nameController, + emailController, + messageController, + subjectController, + ]) { + controller.clear(); + } + } else if (context.mounted) { + appShowSnackBar( + context, + text: AppTexts.get(context).emailNotSended, + icon: Icons.error, + color: AppColors.red, + width: 300, + ); } - }, - ); - } + } + }, + ); return LayoutBuilder( key: AppKeys.contact, builder: (context, constraints) { - final form = contactForm(); - return constraints.maxWidth < Breakpoints.contact ? ContactMobile(form) : ContactWeb(form); From 301b559e10125b06346adc8dce6933072b8ac66c Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 19:34:11 -0300 Subject: [PATCH 23/50] build: add flutter_bloc --- pubspec.lock | 32 ++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 2 files changed, 33 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index 9517b16..a3ea46b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" boolean_selector: dependency: transitive description: @@ -342,6 +350,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + url: "https://pub.dev" + source: hosted + version: "8.1.5" flutter_driver: dependency: transitive description: flutter @@ -572,6 +588,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -660,6 +684,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.2" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" pub_semver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8ef8c64..4af0111 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: firebase_remote_config: ^4.3.13 flutter: sdk: flutter + flutter_bloc: ^8.1.5 flutter_localizations: sdk: flutter flutter_svg: ^2.0.9 From afedae4b08ddf24b2577bb32fcd35db41b7b57d5 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 19:42:44 -0300 Subject: [PATCH 24/50] refactor!: change architecture to be more generic and make more implementations on contact flow --- .../presentation}/controller/contact_controller.dart | 0 .../presentation/widgets/form}/custom_form.dart | 4 ++-- .../widgets/form}/custom_text_form_field.dart | 0 .../presentation/widgets/ui}/contact_mobile.dart | 0 .../presentation/widgets/ui}/contact_web.dart | 0 .../presentation/widgets/ui}/contact_widget.dart | 6 ++---- .../contact/presentation/widgets/widgets.dart | 5 +++++ lib/app/features/home/home_page.dart | 4 ++-- .../home/widgets/contact/widgets/widgets.dart | 2 -- .../controller/contact_controller_test.dart | 4 ++-- .../presentation/widgets/form}/custom_form_test.dart | 2 +- .../widgets/form}/custom_text_form_field_test.dart | 2 +- .../widgets/ui}/contact_mobile_test.dart | 4 ++-- .../presentation/widgets/ui}/contact_web_test.dart | 4 ++-- .../widgets/ui}/contact_widget_test.dart | 12 ++++++------ test/utils/mocks/mock_contact_controller.dart | 2 +- 16 files changed, 26 insertions(+), 25 deletions(-) rename lib/app/features/{home/widgets/contact => contact/presentation}/controller/contact_controller.dart (100%) rename lib/app/features/{home/widgets/contact/widgets => contact/presentation/widgets/form}/custom_form.dart (96%) rename lib/app/features/{home/widgets/contact/widgets => contact/presentation/widgets/form}/custom_text_form_field.dart (100%) rename lib/app/features/{home/widgets/contact => contact/presentation/widgets/ui}/contact_mobile.dart (100%) rename lib/app/features/{home/widgets/contact => contact/presentation/widgets/ui}/contact_web.dart (100%) rename lib/app/features/{home/widgets/contact => contact/presentation/widgets/ui}/contact_widget.dart (91%) create mode 100644 lib/app/features/contact/presentation/widgets/widgets.dart delete mode 100644 lib/app/features/home/widgets/contact/widgets/widgets.dart rename test/app/features/{home/widgets/contact => contact/presentation}/controller/contact_controller_test.dart (93%) rename test/app/features/{home/widgets/contact/widgets => contact/presentation/widgets/form}/custom_form_test.dart (93%) rename test/app/features/{home/widgets/contact/widgets => contact/presentation/widgets/form}/custom_text_form_field_test.dart (92%) rename test/app/features/{home/widgets/contact => contact/presentation/widgets/ui}/contact_mobile_test.dart (72%) rename test/app/features/{home/widgets/contact => contact/presentation/widgets/ui}/contact_web_test.dart (72%) rename test/app/features/{home/widgets/contact => contact/presentation/widgets/ui}/contact_widget_test.dart (82%) diff --git a/lib/app/features/home/widgets/contact/controller/contact_controller.dart b/lib/app/features/contact/presentation/controller/contact_controller.dart similarity index 100% rename from lib/app/features/home/widgets/contact/controller/contact_controller.dart rename to lib/app/features/contact/presentation/controller/contact_controller.dart diff --git a/lib/app/features/home/widgets/contact/widgets/custom_form.dart b/lib/app/features/contact/presentation/widgets/form/custom_form.dart similarity index 96% rename from lib/app/features/home/widgets/contact/widgets/custom_form.dart rename to lib/app/features/contact/presentation/widgets/form/custom_form.dart index eeeebc3..d5f10b0 100644 --- a/lib/app/features/home/widgets/contact/widgets/custom_form.dart +++ b/lib/app/features/contact/presentation/widgets/form/custom_form.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; -import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; -import 'package:site/app/features/home/widgets/contact/widgets/widgets.dart'; +import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; +import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; import 'package:site/app/utils/contact_validators.dart'; import 'package:site/app/widgets/buttons/buttons.dart'; import 'package:site/app/widgets/dividers/dividers.dart'; diff --git a/lib/app/features/home/widgets/contact/widgets/custom_text_form_field.dart b/lib/app/features/contact/presentation/widgets/form/custom_text_form_field.dart similarity index 100% rename from lib/app/features/home/widgets/contact/widgets/custom_text_form_field.dart rename to lib/app/features/contact/presentation/widgets/form/custom_text_form_field.dart diff --git a/lib/app/features/home/widgets/contact/contact_mobile.dart b/lib/app/features/contact/presentation/widgets/ui/contact_mobile.dart similarity index 100% rename from lib/app/features/home/widgets/contact/contact_mobile.dart rename to lib/app/features/contact/presentation/widgets/ui/contact_mobile.dart diff --git a/lib/app/features/home/widgets/contact/contact_web.dart b/lib/app/features/contact/presentation/widgets/ui/contact_web.dart similarity index 100% rename from lib/app/features/home/widgets/contact/contact_web.dart rename to lib/app/features/contact/presentation/widgets/ui/contact_web.dart diff --git a/lib/app/features/home/widgets/contact/contact_widget.dart b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart similarity index 91% rename from lib/app/features/home/widgets/contact/contact_widget.dart rename to lib/app/features/contact/presentation/widgets/ui/contact_widget.dart index 736229c..5d21d3c 100644 --- a/lib/app/features/home/widgets/contact/contact_widget.dart +++ b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart @@ -5,10 +5,8 @@ import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/responsive/responsive.dart'; import 'package:site/app/core/shared/app_keys.dart'; import 'package:site/app/core/tokens/tokens.dart'; -import 'package:site/app/features/home/widgets/contact/contact_mobile.dart'; -import 'package:site/app/features/home/widgets/contact/contact_web.dart'; -import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; -import 'package:site/app/features/home/widgets/contact/widgets/widgets.dart'; +import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; +import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; import 'package:site/app/widgets/snack_bars/snack_bars.dart'; import 'package:site/data/models/models.dart' as models; import 'package:site/data/repositories/contact/contact.dart'; diff --git a/lib/app/features/contact/presentation/widgets/widgets.dart b/lib/app/features/contact/presentation/widgets/widgets.dart new file mode 100644 index 0000000..1d60b1d --- /dev/null +++ b/lib/app/features/contact/presentation/widgets/widgets.dart @@ -0,0 +1,5 @@ +export 'ui/contact_mobile.dart'; +export 'ui/contact_web.dart'; +export 'ui/contact_widget.dart'; +export 'form/custom_form.dart'; +export 'form/custom_text_form_field.dart'; diff --git a/lib/app/features/home/home_page.dart b/lib/app/features/home/home_page.dart index 9c95d5a..87ce5cd 100644 --- a/lib/app/features/home/home_page.dart +++ b/lib/app/features/home/home_page.dart @@ -5,8 +5,8 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/responsive/responsive.dart'; -import 'package:site/app/features/home/widgets/contact/contact_widget.dart'; -import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; +import 'package:site/app/features/contact/presentation/widgets/ui/contact_widget.dart'; +import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; import 'package:site/app/features/home/widgets/experience/experience.dart'; import 'package:site/app/features/home/widgets/footer/footer.dart'; import 'package:site/app/features/home/widgets/presentation/presentation.dart'; diff --git a/lib/app/features/home/widgets/contact/widgets/widgets.dart b/lib/app/features/home/widgets/contact/widgets/widgets.dart deleted file mode 100644 index 5a4cca3..0000000 --- a/lib/app/features/home/widgets/contact/widgets/widgets.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'custom_form.dart'; -export 'custom_text_form_field.dart'; diff --git a/test/app/features/home/widgets/contact/controller/contact_controller_test.dart b/test/app/features/contact/presentation/controller/contact_controller_test.dart similarity index 93% rename from test/app/features/home/widgets/contact/controller/contact_controller_test.dart rename to test/app/features/contact/presentation/controller/contact_controller_test.dart index 29949f8..46a44cb 100644 --- a/test/app/features/home/widgets/contact/controller/contact_controller_test.dart +++ b/test/app/features/contact/presentation/controller/contact_controller_test.dart @@ -5,10 +5,10 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; +import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; import 'package:site/data/repositories/contact/contact.dart'; -import '../../../../../../utils/utils.dart'; +import '../../../../../utils/utils.dart'; void main() { setupFirebaseAuthMocks(); diff --git a/test/app/features/home/widgets/contact/widgets/custom_form_test.dart b/test/app/features/contact/presentation/widgets/form/custom_form_test.dart similarity index 93% rename from test/app/features/home/widgets/contact/widgets/custom_form_test.dart rename to test/app/features/contact/presentation/widgets/form/custom_form_test.dart index 7d7cfc4..823aa43 100644 --- a/test/app/features/home/widgets/contact/widgets/custom_form_test.dart +++ b/test/app/features/contact/presentation/widgets/form/custom_form_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:site/app/features/home/widgets/contact/widgets/widgets.dart'; +import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; import '../../../../../../flutter_test_config.dart'; diff --git a/test/app/features/home/widgets/contact/widgets/custom_text_form_field_test.dart b/test/app/features/contact/presentation/widgets/form/custom_text_form_field_test.dart similarity index 92% rename from test/app/features/home/widgets/contact/widgets/custom_text_form_field_test.dart rename to test/app/features/contact/presentation/widgets/form/custom_text_form_field_test.dart index 7c91d0b..3dda0de 100644 --- a/test/app/features/home/widgets/contact/widgets/custom_text_form_field_test.dart +++ b/test/app/features/contact/presentation/widgets/form/custom_text_form_field_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:site/app/features/home/widgets/contact/widgets/widgets.dart'; +import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; import 'package:site/app/utils/utils.dart'; import '../../../../../../flutter_test_config.dart'; diff --git a/test/app/features/home/widgets/contact/contact_mobile_test.dart b/test/app/features/contact/presentation/widgets/ui/contact_mobile_test.dart similarity index 72% rename from test/app/features/home/widgets/contact/contact_mobile_test.dart rename to test/app/features/contact/presentation/widgets/ui/contact_mobile_test.dart index f4775f5..fe01033 100644 --- a/test/app/features/home/widgets/contact/contact_mobile_test.dart +++ b/test/app/features/contact/presentation/widgets/ui/contact_mobile_test.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:site/app/features/home/widgets/contact/contact_mobile.dart'; +import 'package:site/app/features/contact/presentation/widgets/ui/contact_mobile.dart'; -import '../../../../../flutter_test_config.dart'; +import '../../../../../../flutter_test_config.dart'; void main() { testWidgets('Should renders ContactMobile', (tester) async { diff --git a/test/app/features/home/widgets/contact/contact_web_test.dart b/test/app/features/contact/presentation/widgets/ui/contact_web_test.dart similarity index 72% rename from test/app/features/home/widgets/contact/contact_web_test.dart rename to test/app/features/contact/presentation/widgets/ui/contact_web_test.dart index cdce7f9..09e5e52 100644 --- a/test/app/features/home/widgets/contact/contact_web_test.dart +++ b/test/app/features/contact/presentation/widgets/ui/contact_web_test.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:site/app/features/home/widgets/contact/contact_web.dart'; +import 'package:site/app/features/contact/presentation/widgets/ui/contact_web.dart'; -import '../../../../../flutter_test_config.dart'; +import '../../../../../../flutter_test_config.dart'; void main() { testWidgets('Should renders ContactWeb', (tester) async { diff --git a/test/app/features/home/widgets/contact/contact_widget_test.dart b/test/app/features/contact/presentation/widgets/ui/contact_widget_test.dart similarity index 82% rename from test/app/features/home/widgets/contact/contact_widget_test.dart rename to test/app/features/contact/presentation/widgets/ui/contact_widget_test.dart index 6b95821..3f9f6e3 100644 --- a/test/app/features/home/widgets/contact/contact_widget_test.dart +++ b/test/app/features/contact/presentation/widgets/ui/contact_widget_test.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:site/app/features/home/widgets/contact/contact_mobile.dart'; -import 'package:site/app/features/home/widgets/contact/contact_web.dart'; -import 'package:site/app/features/home/widgets/contact/contact_widget.dart'; -import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; +import 'package:site/app/features/contact/presentation/widgets/ui/contact_mobile.dart'; +import 'package:site/app/features/contact/presentation/widgets/ui/contact_web.dart'; +import 'package:site/app/features/contact/presentation/widgets/ui/contact_widget.dart'; +import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; -import '../../../../../flutter_test_config.dart'; -import '../../../../../utils/utils.dart'; +import '../../../../../../flutter_test_config.dart'; +import '../../../../../../utils/utils.dart'; void main() { late ContactController contactController; diff --git a/test/utils/mocks/mock_contact_controller.dart b/test/utils/mocks/mock_contact_controller.dart index 8d03a1b..e3d74f3 100644 --- a/test/utils/mocks/mock_contact_controller.dart +++ b/test/utils/mocks/mock_contact_controller.dart @@ -1,5 +1,5 @@ import 'package:mocktail/mocktail.dart'; -import 'package:site/app/features/home/widgets/contact/controller/contact_controller.dart'; +import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; class MockContactController extends Mock implements ContactController {} From c8168f0d6e5b3d94bdd71f06bdcab983454ddfc8 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 19:52:36 -0300 Subject: [PATCH 25/50] refactor: move Contact to inside specific feature --- .../features/contact/domain}/models/contact.dart | 0 .../contact/presentation/controller/contact_controller.dart | 4 ++-- .../contact/presentation/widgets/form/custom_form.dart | 6 +++--- .../contact/presentation/widgets/ui/contact_widget.dart | 4 ++-- lib/data/models/models.dart | 1 - lib/data/repositories/contact/contact_repository.dart | 2 +- lib/data/repositories/contact/contact_repository_impl.dart | 4 ++-- test/utils/fixtures/app_fixtures.dart | 2 +- 8 files changed, 11 insertions(+), 12 deletions(-) rename lib/{data => app/features/contact/domain}/models/contact.dart (100%) delete mode 100644 lib/data/models/models.dart diff --git a/lib/data/models/contact.dart b/lib/app/features/contact/domain/models/contact.dart similarity index 100% rename from lib/data/models/contact.dart rename to lib/app/features/contact/domain/models/contact.dart diff --git a/lib/app/features/contact/presentation/controller/contact_controller.dart b/lib/app/features/contact/presentation/controller/contact_controller.dart index 273e62f..f57a286 100644 --- a/lib/app/features/contact/presentation/controller/contact_controller.dart +++ b/lib/app/features/contact/presentation/controller/contact_controller.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:site/app/core/injections/injections.dart'; -import 'package:site/data/models/models.dart'; +import 'package:site/app/core/injections/injections.dart'; +import 'package:site/app/features/contact/domain/models/contact.dart'; import 'package:site/data/repositories/contact/contact.dart'; class ContactController extends ChangeNotifier { diff --git a/lib/app/features/contact/presentation/widgets/form/custom_form.dart b/lib/app/features/contact/presentation/widgets/form/custom_form.dart index d5f10b0..d67b932 100644 --- a/lib/app/features/contact/presentation/widgets/form/custom_form.dart +++ b/lib/app/features/contact/presentation/widgets/form/custom_form.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:site/app/core/injections/injections.dart'; +import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; +import 'package:site/app/features/contact/domain/models/contact.dart'; import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; import 'package:site/app/utils/contact_validators.dart'; import 'package:site/app/widgets/buttons/buttons.dart'; import 'package:site/app/widgets/dividers/dividers.dart'; import 'package:site/data/repositories/contact/contact_repository_impl.dart'; -import 'package:site/data/models/models.dart' as models; class CustomForm extends StatelessWidget { CustomForm({ @@ -83,7 +83,7 @@ class CustomForm extends StatelessWidget { onPressed: onPressed, onLongPress: () { _contactController?.sendMail( - contact: models.Contact( + contact: Contact( name: 'felipe sales', email: 'soufeliposales@gmail.com', message: 'teste de mensagem', diff --git a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart index 5d21d3c..c4349cd 100644 --- a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart +++ b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart @@ -5,10 +5,10 @@ import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/responsive/responsive.dart'; import 'package:site/app/core/shared/app_keys.dart'; import 'package:site/app/core/tokens/tokens.dart'; +import 'package:site/app/features/contact/domain/models/contact.dart'; import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; import 'package:site/app/widgets/snack_bars/snack_bars.dart'; -import 'package:site/data/models/models.dart' as models; import 'package:site/data/repositories/contact/contact.dart'; // import 'package:site/data/services/recaptcha/recaptcha.dart'; @@ -55,7 +55,7 @@ class ContactWidget extends StatelessWidget { } _contactController?.sendMail( - contact: models.Contact( + contact: Contact( name: nameController.text, email: emailController.text.trim(), message: messageController.text, diff --git a/lib/data/models/models.dart b/lib/data/models/models.dart deleted file mode 100644 index 4c72b19..0000000 --- a/lib/data/models/models.dart +++ /dev/null @@ -1 +0,0 @@ -export 'contact.dart'; diff --git a/lib/data/repositories/contact/contact_repository.dart b/lib/data/repositories/contact/contact_repository.dart index d0dc29b..3149b57 100644 --- a/lib/data/repositories/contact/contact_repository.dart +++ b/lib/data/repositories/contact/contact_repository.dart @@ -1,4 +1,4 @@ -import 'package:site/data/models/models.dart'; +import 'package:site/app/features/contact/domain/models/contact.dart'; abstract class ContactRepository { Future sendMail({required Contact contact}); diff --git a/lib/data/repositories/contact/contact_repository_impl.dart b/lib/data/repositories/contact/contact_repository_impl.dart index f19089b..a022095 100644 --- a/lib/data/repositories/contact/contact_repository_impl.dart +++ b/lib/data/repositories/contact/contact_repository_impl.dart @@ -1,9 +1,9 @@ import 'dart:developer'; import 'package:dio/dio.dart'; -import 'package:site/data/constants/constants_api.dart'; -import 'package:site/data/models/models.dart'; +import 'package:site/app/features/contact/domain/models/contact.dart'; +import 'package:site/data/constants/constants_api.dart'; import 'package:site/data/repositories/contact/contact.dart'; class ContactRepositoryImpl implements ContactRepository { diff --git a/test/utils/fixtures/app_fixtures.dart b/test/utils/fixtures/app_fixtures.dart index 5109316..d7d2ede 100644 --- a/test/utils/fixtures/app_fixtures.dart +++ b/test/utils/fixtures/app_fixtures.dart @@ -1,4 +1,4 @@ -import 'package:site/data/models/models.dart'; +import 'package:site/app/features/contact/domain/models/contact.dart'; class AppFixtures { final tContact = Contact( From 5f821e63a243c97b212e8f409bcd303d1d007a48 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 20:24:33 -0300 Subject: [PATCH 26/50] feat: add result related (looks like dartz and fpdart - but native using dart 3.0.0) --- lib/app/core/result/result.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/app/core/result/result.dart diff --git a/lib/app/core/result/result.dart b/lib/app/core/result/result.dart new file mode 100644 index 0000000..c129102 --- /dev/null +++ b/lib/app/core/result/result.dart @@ -0,0 +1,15 @@ +sealed class Result { + const Result(); +} + +final class Success extends Result { + const Success(this.object) : super(); + + final T object; +} + +final class Failure extends Result { + const Failure(this.error) : super(); + + final R error; +} From 2130ab7d551b2342afe4bd191e4f6f77fa3795e5 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 21:48:07 -0300 Subject: [PATCH 27/50] feat: init cubit construction and add equatable --- .../presentation/cubit/contact_cubit.dart | 59 +++++++++++++++++++ .../presentation/cubit/contact_state.dart | 49 +++++++++++++++ pubspec.lock | 2 +- pubspec.yaml | 1 + 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 lib/app/features/contact/presentation/cubit/contact_cubit.dart create mode 100644 lib/app/features/contact/presentation/cubit/contact_state.dart diff --git a/lib/app/features/contact/presentation/cubit/contact_cubit.dart b/lib/app/features/contact/presentation/cubit/contact_cubit.dart new file mode 100644 index 0000000..ee2e989 --- /dev/null +++ b/lib/app/features/contact/presentation/cubit/contact_cubit.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:site/app/features/contact/contact.dart'; + +part 'contact_state.dart'; + +class ContactCubit extends Cubit { + ContactCubit({ + required ContactRepositoryImpl contactRepository, + }) : _contactRepository = contactRepository, + super( + const ContactInitial(), + ); + + final ContactRepositoryImpl _contactRepository; + + Future sendMail({ + required Contact contact, + }) async { + emit(const ContactLoading()); + + try { + await _contactRepository.sendMail( + contact: contact, + ); + emit( + ContactSuccess( + contact: contact, + message: 'Email sent successfully', + ), + ); + } catch (e) { + emit( + ContactError( + contact: contact, + message: 'Error sending email', + ), + ); + } + } +} + + +// class ContactController extends ChangeNotifier { +// ContactController({ +// ContactRepositoryImpl? contactRepository, +// }) : _contactRepository = contactRepository ?? getIt(); + +// final ContactRepositoryImpl _contactRepository; + +// Future sendMail({ +// required Contact contact, +// }) { +// return _contactRepository.sendMail( +// contact: contact, +// ); +// } +// } diff --git a/lib/app/features/contact/presentation/cubit/contact_state.dart b/lib/app/features/contact/presentation/cubit/contact_state.dart new file mode 100644 index 0000000..c566dbe --- /dev/null +++ b/lib/app/features/contact/presentation/cubit/contact_state.dart @@ -0,0 +1,49 @@ +part of 'contact_cubit.dart'; + +@immutable +sealed class ContactState extends Equatable { + const ContactState(); + + @override + List get props => []; +} + +final class ContactInitial extends ContactState { + const ContactInitial(); + + @override + List get props => []; +} + +final class ContactLoading extends ContactState { + const ContactLoading(); + + @override + List get props => []; +} + +final class ContactSuccess extends ContactState { + const ContactSuccess({ + required this.contact, + required this.message, + }); + + final Contact contact; + final String message; + + @override + List get props => [contact, message]; +} + +final class ContactError extends ContactState { + const ContactError({ + required this.contact, + required this.message, + }); + + final Contact contact; + final String message; + + @override + List get props => [contact, message]; +} diff --git a/pubspec.lock b/pubspec.lock index a3ea46b..3bae08f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -234,7 +234,7 @@ packages: source: hosted version: "0.5.4+1" equatable: - dependency: transitive + dependency: "direct main" description: name: equatable sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 diff --git a/pubspec.yaml b/pubspec.yaml index 4af0111..5f0d780 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: dio: ^5.4.3+1 email_validator: ^2.1.17 envied: ^0.5.4+1 + equatable: ^2.0.5 firebase_analytics: ^10.8.5 firebase_core: ^2.25.4 firebase_core_platform_interface: ^5.0.0 From 4abde1ed01ab3ac07866b806dc8f0fe5a836142a Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 21:48:36 -0300 Subject: [PATCH 28/50] refactor!: apply clean architecture and separate better layers; also add barrel files --- lib/app/core/injections/injections.dart | 2 +- lib/app/features/contact/contact.dart | 3 +++ lib/app/features/contact/data/data.dart | 1 + .../data/repositories}/contact_repository_impl.dart | 3 +-- .../features/contact/data/repositories/repositories.dart} | 1 - lib/app/features/contact/domain/domain.dart | 2 ++ lib/app/features/contact/domain/models/models.dart | 1 + .../contact/domain/repositories}/contact_repository.dart | 0 .../features/contact/domain/repositories/repositories.dart | 1 + .../presentation/controller/contact_controller.dart | 3 +-- .../contact/presentation/controller/controller.dart | 1 + lib/app/features/contact/presentation/presentation.dart | 2 ++ .../contact/presentation/widgets/form/custom_form.dart | 2 +- .../features/contact/presentation/widgets/form/form.dart | 2 ++ .../contact/presentation/widgets/ui/contact_widget.dart | 5 +---- lib/app/features/contact/presentation/widgets/ui/ui.dart | 3 +++ lib/app/features/contact/presentation/widgets/widgets.dart | 7 ++----- lib/app/features/home/home_page.dart | 4 +--- .../data/repositories}/contact_repository_impl_test.dart | 5 ++--- .../presentation/controller/contact_controller_test.dart | 4 +--- 20 files changed, 27 insertions(+), 25 deletions(-) create mode 100644 lib/app/features/contact/contact.dart create mode 100644 lib/app/features/contact/data/data.dart rename lib/{data/repositories/contact => app/features/contact/data/repositories}/contact_repository_impl.dart (81%) rename lib/{data/repositories/contact/contact.dart => app/features/contact/data/repositories/repositories.dart} (53%) create mode 100644 lib/app/features/contact/domain/domain.dart create mode 100644 lib/app/features/contact/domain/models/models.dart rename lib/{data/repositories/contact => app/features/contact/domain/repositories}/contact_repository.dart (100%) create mode 100644 lib/app/features/contact/domain/repositories/repositories.dart create mode 100644 lib/app/features/contact/presentation/controller/controller.dart create mode 100644 lib/app/features/contact/presentation/presentation.dart create mode 100644 lib/app/features/contact/presentation/widgets/form/form.dart create mode 100644 lib/app/features/contact/presentation/widgets/ui/ui.dart rename test/{data/repositories/contact => app/features/contact/data/repositories}/contact_repository_impl_test.dart (90%) diff --git a/lib/app/core/injections/injections.dart b/lib/app/core/injections/injections.dart index 140c8c4..9a46a38 100644 --- a/lib/app/core/injections/injections.dart +++ b/lib/app/core/injections/injections.dart @@ -1,8 +1,8 @@ import 'package:dio/dio.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:site/app/features/contact/contact.dart'; import 'package:get_it/get_it.dart'; -import 'package:site/data/repositories/contact/contact.dart'; import 'package:site/data/services/firebase/firebase.dart'; final getIt = GetIt.I; diff --git a/lib/app/features/contact/contact.dart b/lib/app/features/contact/contact.dart new file mode 100644 index 0000000..aeeb708 --- /dev/null +++ b/lib/app/features/contact/contact.dart @@ -0,0 +1,3 @@ +export 'data/data.dart'; +export 'domain/domain.dart'; +export 'presentation/presentation.dart'; diff --git a/lib/app/features/contact/data/data.dart b/lib/app/features/contact/data/data.dart new file mode 100644 index 0000000..d08598b --- /dev/null +++ b/lib/app/features/contact/data/data.dart @@ -0,0 +1 @@ +export 'repositories/repositories.dart'; diff --git a/lib/data/repositories/contact/contact_repository_impl.dart b/lib/app/features/contact/data/repositories/contact_repository_impl.dart similarity index 81% rename from lib/data/repositories/contact/contact_repository_impl.dart rename to lib/app/features/contact/data/repositories/contact_repository_impl.dart index a022095..daadc72 100644 --- a/lib/data/repositories/contact/contact_repository_impl.dart +++ b/lib/app/features/contact/data/repositories/contact_repository_impl.dart @@ -2,9 +2,8 @@ import 'dart:developer'; import 'package:dio/dio.dart'; -import 'package:site/app/features/contact/domain/models/contact.dart'; +import 'package:site/app/features/contact/contact.dart'; import 'package:site/data/constants/constants_api.dart'; -import 'package:site/data/repositories/contact/contact.dart'; class ContactRepositoryImpl implements ContactRepository { ContactRepositoryImpl({ diff --git a/lib/data/repositories/contact/contact.dart b/lib/app/features/contact/data/repositories/repositories.dart similarity index 53% rename from lib/data/repositories/contact/contact.dart rename to lib/app/features/contact/data/repositories/repositories.dart index dd4eaeb..16b0082 100644 --- a/lib/data/repositories/contact/contact.dart +++ b/lib/app/features/contact/data/repositories/repositories.dart @@ -1,2 +1 @@ -export 'contact_repository.dart'; export 'contact_repository_impl.dart'; diff --git a/lib/app/features/contact/domain/domain.dart b/lib/app/features/contact/domain/domain.dart new file mode 100644 index 0000000..b9314f1 --- /dev/null +++ b/lib/app/features/contact/domain/domain.dart @@ -0,0 +1,2 @@ +export 'models/models.dart'; +export 'repositories/repositories.dart'; diff --git a/lib/app/features/contact/domain/models/models.dart b/lib/app/features/contact/domain/models/models.dart new file mode 100644 index 0000000..4c72b19 --- /dev/null +++ b/lib/app/features/contact/domain/models/models.dart @@ -0,0 +1 @@ +export 'contact.dart'; diff --git a/lib/data/repositories/contact/contact_repository.dart b/lib/app/features/contact/domain/repositories/contact_repository.dart similarity index 100% rename from lib/data/repositories/contact/contact_repository.dart rename to lib/app/features/contact/domain/repositories/contact_repository.dart diff --git a/lib/app/features/contact/domain/repositories/repositories.dart b/lib/app/features/contact/domain/repositories/repositories.dart new file mode 100644 index 0000000..7f88437 --- /dev/null +++ b/lib/app/features/contact/domain/repositories/repositories.dart @@ -0,0 +1 @@ +export 'contact_repository.dart'; diff --git a/lib/app/features/contact/presentation/controller/contact_controller.dart b/lib/app/features/contact/presentation/controller/contact_controller.dart index f57a286..dc7486c 100644 --- a/lib/app/features/contact/presentation/controller/contact_controller.dart +++ b/lib/app/features/contact/presentation/controller/contact_controller.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:site/app/core/injections/injections.dart'; -import 'package:site/app/features/contact/domain/models/contact.dart'; -import 'package:site/data/repositories/contact/contact.dart'; +import 'package:site/app/features/contact/contact.dart'; class ContactController extends ChangeNotifier { ContactController({ diff --git a/lib/app/features/contact/presentation/controller/controller.dart b/lib/app/features/contact/presentation/controller/controller.dart new file mode 100644 index 0000000..8550c05 --- /dev/null +++ b/lib/app/features/contact/presentation/controller/controller.dart @@ -0,0 +1 @@ +export 'contact_controller.dart'; diff --git a/lib/app/features/contact/presentation/presentation.dart b/lib/app/features/contact/presentation/presentation.dart new file mode 100644 index 0000000..b944e92 --- /dev/null +++ b/lib/app/features/contact/presentation/presentation.dart @@ -0,0 +1,2 @@ +export 'controller/controller.dart'; +export 'widgets/widgets.dart'; diff --git a/lib/app/features/contact/presentation/widgets/form/custom_form.dart b/lib/app/features/contact/presentation/widgets/form/custom_form.dart index d67b932..929efa2 100644 --- a/lib/app/features/contact/presentation/widgets/form/custom_form.dart +++ b/lib/app/features/contact/presentation/widgets/form/custom_form.dart @@ -8,7 +8,7 @@ import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; import 'package:site/app/utils/contact_validators.dart'; import 'package:site/app/widgets/buttons/buttons.dart'; import 'package:site/app/widgets/dividers/dividers.dart'; -import 'package:site/data/repositories/contact/contact_repository_impl.dart'; +import 'package:site/app/features/contact/data/repositories/contact_repository_impl.dart'; class CustomForm extends StatelessWidget { CustomForm({ diff --git a/lib/app/features/contact/presentation/widgets/form/form.dart b/lib/app/features/contact/presentation/widgets/form/form.dart new file mode 100644 index 0000000..5a4cca3 --- /dev/null +++ b/lib/app/features/contact/presentation/widgets/form/form.dart @@ -0,0 +1,2 @@ +export 'custom_form.dart'; +export 'custom_text_form_field.dart'; diff --git a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart index c4349cd..76ae4dd 100644 --- a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart +++ b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart @@ -5,11 +5,8 @@ import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/responsive/responsive.dart'; import 'package:site/app/core/shared/app_keys.dart'; import 'package:site/app/core/tokens/tokens.dart'; -import 'package:site/app/features/contact/domain/models/contact.dart'; -import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; -import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; +import 'package:site/app/features/contact/contact.dart'; import 'package:site/app/widgets/snack_bars/snack_bars.dart'; -import 'package:site/data/repositories/contact/contact.dart'; // import 'package:site/data/services/recaptcha/recaptcha.dart'; class ContactWidget extends StatelessWidget { diff --git a/lib/app/features/contact/presentation/widgets/ui/ui.dart b/lib/app/features/contact/presentation/widgets/ui/ui.dart new file mode 100644 index 0000000..15ec899 --- /dev/null +++ b/lib/app/features/contact/presentation/widgets/ui/ui.dart @@ -0,0 +1,3 @@ +export 'contact_mobile.dart'; +export 'contact_web.dart'; +export 'contact_widget.dart'; diff --git a/lib/app/features/contact/presentation/widgets/widgets.dart b/lib/app/features/contact/presentation/widgets/widgets.dart index 1d60b1d..f3a2eb9 100644 --- a/lib/app/features/contact/presentation/widgets/widgets.dart +++ b/lib/app/features/contact/presentation/widgets/widgets.dart @@ -1,5 +1,2 @@ -export 'ui/contact_mobile.dart'; -export 'ui/contact_web.dart'; -export 'ui/contact_widget.dart'; -export 'form/custom_form.dart'; -export 'form/custom_text_form_field.dart'; +export 'form/form.dart'; +export 'ui/ui.dart'; diff --git a/lib/app/features/home/home_page.dart b/lib/app/features/home/home_page.dart index 87ce5cd..cd8763f 100644 --- a/lib/app/features/home/home_page.dart +++ b/lib/app/features/home/home_page.dart @@ -5,8 +5,7 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/responsive/responsive.dart'; -import 'package:site/app/features/contact/presentation/widgets/ui/contact_widget.dart'; -import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; +import 'package:site/app/features/contact/contact.dart'; import 'package:site/app/features/home/widgets/experience/experience.dart'; import 'package:site/app/features/home/widgets/footer/footer.dart'; import 'package:site/app/features/home/widgets/presentation/presentation.dart'; @@ -14,7 +13,6 @@ import 'package:site/app/features/home/widgets/projects/projects.dart'; import 'package:site/app/features/home/widgets/social/social.dart'; import 'package:site/app/widgets/app_bar/app_bar.dart'; import 'package:site/app/widgets/drawer/drawer.dart'; -import 'package:site/data/repositories/contact/contact.dart'; class HomePage extends StatefulWidget { HomePage({ diff --git a/test/data/repositories/contact/contact_repository_impl_test.dart b/test/app/features/contact/data/repositories/contact_repository_impl_test.dart similarity index 90% rename from test/data/repositories/contact/contact_repository_impl_test.dart rename to test/app/features/contact/data/repositories/contact_repository_impl_test.dart index 056feba..1ebb109 100644 --- a/test/data/repositories/contact/contact_repository_impl_test.dart +++ b/test/app/features/contact/data/repositories/contact_repository_impl_test.dart @@ -1,10 +1,9 @@ import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:site/app/features/contact/contact.dart'; -import 'package:site/data/repositories/contact/contact.dart'; - -import '../../../utils/utils.dart'; +import '../../../../../utils/utils.dart'; void main() { late MockHttpClient mockHttpClient; diff --git a/test/app/features/contact/presentation/controller/contact_controller_test.dart b/test/app/features/contact/presentation/controller/contact_controller_test.dart index 46a44cb..0408c85 100644 --- a/test/app/features/contact/presentation/controller/contact_controller_test.dart +++ b/test/app/features/contact/presentation/controller/contact_controller_test.dart @@ -4,9 +4,7 @@ import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; - -import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; -import 'package:site/data/repositories/contact/contact.dart'; +import 'package:site/app/features/contact/contact.dart'; import '../../../../../utils/utils.dart'; From b4ef9bd1aa8b677d336bdff87f8af21e1e8b04b3 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Tue, 7 May 2024 23:04:27 -0300 Subject: [PATCH 29/50] feat: init to work with abstractions and complexities for ensure and improve flexibility --- analysis_options.yaml | 36 +++++++++---------- lib/app/features/contact/data/data.dart | 1 + .../repositories/contact_repository_impl.dart | 12 ++++--- .../data/results/contact_failed_result.dart | 5 +++ .../contact/data/results/results.dart | 1 + .../contact/domain/models/contact.dart | 23 ------------ .../contact/domain/models/contact_answer.dart | 26 ++++++++++++++ .../contact/domain/models/contact_model.dart | 10 ++++++ .../contact/domain/models/contact_user.dart | 24 +++++++++++++ .../contact/domain/models/models.dart | 4 ++- .../repositories/contact_repository.dart | 7 ++-- .../controller/contact_controller.dart | 2 +- .../presentation/cubit/contact_cubit.dart | 2 +- .../presentation/cubit/contact_state.dart | 4 +-- .../widgets/form/custom_form.dart | 7 ++-- .../widgets/ui/contact_widget.dart | 2 +- test/utils/fixtures/app_fixtures.dart | 4 +-- 17 files changed, 110 insertions(+), 60 deletions(-) create mode 100644 lib/app/features/contact/data/results/contact_failed_result.dart create mode 100644 lib/app/features/contact/data/results/results.dart delete mode 100644 lib/app/features/contact/domain/models/contact.dart create mode 100644 lib/app/features/contact/domain/models/contact_answer.dart create mode 100644 lib/app/features/contact/domain/models/contact_model.dart create mode 100644 lib/app/features/contact/domain/models/contact_user.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index a2cf24c..ab644f4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -9,24 +9,24 @@ include: package:flutter_lints/flutter.yaml # - lib/app/core/l10n/localizations/** # Generated file # - lib/data/services/firebase/firebase_set_defaults.dart # To use dynamic type (because really need it) -# linter: -# rules: -# - always_declare_return_types -# - always_use_package_imports -# - avoid_dynamic_calls -# - avoid_relative_lib_imports -# - implementation_imports -# - require_trailing_commas -# - sized_box_for_whitespace -# - sized_box_shrink_expand -# - sort_child_properties_last -# - sort_constructors_first -# - prefer_const_constructors -# - prefer_const_constructors_in_immutables -# - prefer_const_literals_to_create_immutables -# - prefer_const_declarations -# - prefer_final_fields -# - prefer_single_quotes +linter: + rules: + - always_declare_return_types + - always_use_package_imports + - avoid_dynamic_calls + - avoid_relative_lib_imports + - implementation_imports + - require_trailing_commas + - sized_box_for_whitespace + - sized_box_shrink_expand + - sort_child_properties_last + - sort_constructors_first + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_literals_to_create_immutables + - prefer_const_declarations + - prefer_final_fields + - prefer_single_quotes # dart_code_metrics: # metrics: diff --git a/lib/app/features/contact/data/data.dart b/lib/app/features/contact/data/data.dart index d08598b..5066431 100644 --- a/lib/app/features/contact/data/data.dart +++ b/lib/app/features/contact/data/data.dart @@ -1 +1,2 @@ export 'repositories/repositories.dart'; +export 'results/results.dart'; \ No newline at end of file diff --git a/lib/app/features/contact/data/repositories/contact_repository_impl.dart b/lib/app/features/contact/data/repositories/contact_repository_impl.dart index daadc72..3dfba86 100644 --- a/lib/app/features/contact/data/repositories/contact_repository_impl.dart +++ b/lib/app/features/contact/data/repositories/contact_repository_impl.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'package:dio/dio.dart'; +import 'package:site/app/core/result/result.dart'; import 'package:site/app/features/contact/contact.dart'; import 'package:site/data/constants/constants_api.dart'; @@ -13,18 +14,21 @@ class ContactRepositoryImpl implements ContactRepository { final Dio _httpClient; @override - Future sendMail({ - required Contact contact, + Future> sendMail({ + required ContactModel contact, }) async { try { final response = await _httpClient.post( ConstantsAPI.apiSendMail, - data: Contact.toJson(contact), + data: ContactUser.toJson(contact), ); - return response.data; + // return response.data; + return Success(ContactAnswer.fromJson(response.data)); } catch (e, s) { log('Error: $e', stackTrace: s); + + return const Failure(ContactFailedResult.unknown); } } } diff --git a/lib/app/features/contact/data/results/contact_failed_result.dart b/lib/app/features/contact/data/results/contact_failed_result.dart new file mode 100644 index 0000000..13bc4af --- /dev/null +++ b/lib/app/features/contact/data/results/contact_failed_result.dart @@ -0,0 +1,5 @@ +enum ContactFailedResult { + tooManyRequests, + unauthorized, + unknown, +} diff --git a/lib/app/features/contact/data/results/results.dart b/lib/app/features/contact/data/results/results.dart new file mode 100644 index 0000000..5380ab9 --- /dev/null +++ b/lib/app/features/contact/data/results/results.dart @@ -0,0 +1 @@ +export 'contact_failed_result.dart'; diff --git a/lib/app/features/contact/domain/models/contact.dart b/lib/app/features/contact/domain/models/contact.dart deleted file mode 100644 index 38f00ad..0000000 --- a/lib/app/features/contact/domain/models/contact.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:convert'; - -class Contact { - Contact({ - required this.name, - required this.email, - required this.message, - required this.subject, - }); - - final String name, email, message, subject; - - static Map toJson(Contact contact) { - return { - 'sender_name': contact.name, - 'source_email': contact.email, - 'template_subject': contact.subject, - 'template_body': contact.message, - }; - } - - static String toJsonString(Contact contact) => jsonEncode(toJson(contact)); -} diff --git a/lib/app/features/contact/domain/models/contact_answer.dart b/lib/app/features/contact/domain/models/contact_answer.dart new file mode 100644 index 0000000..760a24a --- /dev/null +++ b/lib/app/features/contact/domain/models/contact_answer.dart @@ -0,0 +1,26 @@ +import 'package:site/app/features/contact/domain/models/contact_model.dart'; + +class ContactAnswer extends ContactModel { + factory ContactAnswer.fromJson(Map json) { + return ContactAnswer( + name: json['sender_name'], + email: json['source_email'], + message: json['template_body'], + subject: json['template_subject'], + response: json['response'], + status: json['status'], + ); + } + + ContactAnswer({ + required super.name, + required super.email, + required super.message, + required super.subject, + required this.response, + required this.status, + }); + + final String response; + final String status; +} diff --git a/lib/app/features/contact/domain/models/contact_model.dart b/lib/app/features/contact/domain/models/contact_model.dart new file mode 100644 index 0000000..7246294 --- /dev/null +++ b/lib/app/features/contact/domain/models/contact_model.dart @@ -0,0 +1,10 @@ +abstract class ContactModel { + ContactModel({ + required this.name, + required this.email, + required this.message, + required this.subject, + }); + + final String name, email, message, subject; +} diff --git a/lib/app/features/contact/domain/models/contact_user.dart b/lib/app/features/contact/domain/models/contact_user.dart new file mode 100644 index 0000000..0aa423d --- /dev/null +++ b/lib/app/features/contact/domain/models/contact_user.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; + +import 'package:site/app/features/contact/contact.dart'; + +class ContactUser extends ContactModel { + ContactUser({ + required super.name, + required super.email, + required super.message, + required super.subject, + }); + + static Map toJson(ContactModel contact) { + return { + 'sender_name': contact.name, + 'source_email': contact.email, + 'template_subject': contact.subject, + 'template_body': contact.message, + }; + } + + static String toJsonString(ContactModel contact) => + jsonEncode(toJson(contact)); +} diff --git a/lib/app/features/contact/domain/models/models.dart b/lib/app/features/contact/domain/models/models.dart index 4c72b19..d3da820 100644 --- a/lib/app/features/contact/domain/models/models.dart +++ b/lib/app/features/contact/domain/models/models.dart @@ -1 +1,3 @@ -export 'contact.dart'; +export 'contact_answer.dart'; +export 'contact_user.dart'; +export 'contact_model.dart'; diff --git a/lib/app/features/contact/domain/repositories/contact_repository.dart b/lib/app/features/contact/domain/repositories/contact_repository.dart index 3149b57..c44b821 100644 --- a/lib/app/features/contact/domain/repositories/contact_repository.dart +++ b/lib/app/features/contact/domain/repositories/contact_repository.dart @@ -1,5 +1,8 @@ -import 'package:site/app/features/contact/domain/models/contact.dart'; +import 'package:site/app/core/result/result.dart'; +import 'package:site/app/features/contact/contact.dart'; abstract class ContactRepository { - Future sendMail({required Contact contact}); + Future> sendMail({ + required ContactModel contact, + }); } diff --git a/lib/app/features/contact/presentation/controller/contact_controller.dart b/lib/app/features/contact/presentation/controller/contact_controller.dart index dc7486c..b772287 100644 --- a/lib/app/features/contact/presentation/controller/contact_controller.dart +++ b/lib/app/features/contact/presentation/controller/contact_controller.dart @@ -11,7 +11,7 @@ class ContactController extends ChangeNotifier { final ContactRepositoryImpl _contactRepository; Future sendMail({ - required Contact contact, + required ContactModel contact, }) { return _contactRepository.sendMail( contact: contact, diff --git a/lib/app/features/contact/presentation/cubit/contact_cubit.dart b/lib/app/features/contact/presentation/cubit/contact_cubit.dart index ee2e989..7627775 100644 --- a/lib/app/features/contact/presentation/cubit/contact_cubit.dart +++ b/lib/app/features/contact/presentation/cubit/contact_cubit.dart @@ -16,7 +16,7 @@ class ContactCubit extends Cubit { final ContactRepositoryImpl _contactRepository; Future sendMail({ - required Contact contact, + required ContactModel contact, }) async { emit(const ContactLoading()); diff --git a/lib/app/features/contact/presentation/cubit/contact_state.dart b/lib/app/features/contact/presentation/cubit/contact_state.dart index c566dbe..20690a1 100644 --- a/lib/app/features/contact/presentation/cubit/contact_state.dart +++ b/lib/app/features/contact/presentation/cubit/contact_state.dart @@ -28,7 +28,7 @@ final class ContactSuccess extends ContactState { required this.message, }); - final Contact contact; + final ContactModel contact; final String message; @override @@ -41,7 +41,7 @@ final class ContactError extends ContactState { required this.message, }); - final Contact contact; + final ContactModel contact; final String message; @override diff --git a/lib/app/features/contact/presentation/widgets/form/custom_form.dart b/lib/app/features/contact/presentation/widgets/form/custom_form.dart index 929efa2..32c733a 100644 --- a/lib/app/features/contact/presentation/widgets/form/custom_form.dart +++ b/lib/app/features/contact/presentation/widgets/form/custom_form.dart @@ -2,13 +2,10 @@ import 'package:flutter/material.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; -import 'package:site/app/features/contact/domain/models/contact.dart'; -import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; -import 'package:site/app/features/contact/presentation/widgets/widgets.dart'; +import 'package:site/app/features/contact/contact.dart'; import 'package:site/app/utils/contact_validators.dart'; import 'package:site/app/widgets/buttons/buttons.dart'; import 'package:site/app/widgets/dividers/dividers.dart'; -import 'package:site/app/features/contact/data/repositories/contact_repository_impl.dart'; class CustomForm extends StatelessWidget { CustomForm({ @@ -83,7 +80,7 @@ class CustomForm extends StatelessWidget { onPressed: onPressed, onLongPress: () { _contactController?.sendMail( - contact: Contact( + contact: ContactUser( name: 'felipe sales', email: 'soufeliposales@gmail.com', message: 'teste de mensagem', diff --git a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart index 76ae4dd..36b271c 100644 --- a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart +++ b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart @@ -52,7 +52,7 @@ class ContactWidget extends StatelessWidget { } _contactController?.sendMail( - contact: Contact( + contact: ContactUser( name: nameController.text, email: emailController.text.trim(), message: messageController.text, diff --git a/test/utils/fixtures/app_fixtures.dart b/test/utils/fixtures/app_fixtures.dart index d7d2ede..fee255c 100644 --- a/test/utils/fixtures/app_fixtures.dart +++ b/test/utils/fixtures/app_fixtures.dart @@ -1,7 +1,7 @@ -import 'package:site/app/features/contact/domain/models/contact.dart'; +import 'package:site/app/features/contact/contact.dart'; class AppFixtures { - final tContact = Contact( + final tContact = ContactUser( name: 'felipecastrosales', email: 'fakeemail@gmail.com', message: 'Hello, World!', From 3a5b06f2b2ae0b97884fd6427ab9d0813920fdce Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sat, 11 May 2024 22:53:25 -0300 Subject: [PATCH 30/50] chore: removed old ContactController --- .../controller/contact_controller.dart | 20 ------ .../presentation/controller/controller.dart | 1 - .../controller/contact_controller_test.dart | 71 ------------------- test/utils/mocks/mock_contact_controller.dart | 5 -- 4 files changed, 97 deletions(-) delete mode 100644 lib/app/features/contact/presentation/controller/contact_controller.dart delete mode 100644 lib/app/features/contact/presentation/controller/controller.dart delete mode 100644 test/app/features/contact/presentation/controller/contact_controller_test.dart delete mode 100644 test/utils/mocks/mock_contact_controller.dart diff --git a/lib/app/features/contact/presentation/controller/contact_controller.dart b/lib/app/features/contact/presentation/controller/contact_controller.dart deleted file mode 100644 index b772287..0000000 --- a/lib/app/features/contact/presentation/controller/contact_controller.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:site/app/core/injections/injections.dart'; -import 'package:site/app/features/contact/contact.dart'; - -class ContactController extends ChangeNotifier { - ContactController({ - ContactRepositoryImpl? contactRepository, - }) : _contactRepository = contactRepository ?? getIt(); - - final ContactRepositoryImpl _contactRepository; - - Future sendMail({ - required ContactModel contact, - }) { - return _contactRepository.sendMail( - contact: contact, - ); - } -} diff --git a/lib/app/features/contact/presentation/controller/controller.dart b/lib/app/features/contact/presentation/controller/controller.dart deleted file mode 100644 index 8550c05..0000000 --- a/lib/app/features/contact/presentation/controller/controller.dart +++ /dev/null @@ -1 +0,0 @@ -export 'contact_controller.dart'; diff --git a/test/app/features/contact/presentation/controller/contact_controller_test.dart b/test/app/features/contact/presentation/controller/contact_controller_test.dart deleted file mode 100644 index 0408c85..0000000 --- a/test/app/features/contact/presentation/controller/contact_controller_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; - -import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:site/app/features/contact/contact.dart'; - -import '../../../../../utils/utils.dart'; - -void main() { - setupFirebaseAuthMocks(); - late MockHttpClient mockHttpClient; - late ContactRepositoryImpl contactRepository; - late ContactController contactController; - - setUp(() { - mockHttpClient = MockHttpClient(); - contactRepository = ContactRepositoryImpl( - httpClient: mockHttpClient, - ); - contactController = ContactController( - contactRepository: contactRepository, - ); - - when( - () => mockHttpClient.post( - any(), - data: any(named: 'data'), - options: any(named: 'options'), - ), - ).thenAnswer( - (_) async => Response( - data: {}, - statusCode: 200, - requestOptions: RequestOptions(path: ''), - ), - ); - }); - - setUpAll(() async { - if (Firebase.apps.isEmpty) await Firebase.initializeApp(); - registerFallbackValue(UriFake()); - }); - - test('ContactController', () { - expect( - contactController, - isInstanceOf(), - ); - - expect( - contactController, - isInstanceOf(), - ); - - expect( - contactController.sendMail, - isInstanceOf(), - ); - - var sendMail = contactController.sendMail( - contact: AppFixtures().tContact, - ); - - expect( - () => sendMail, - isInstanceOf(), - ); - }); -} diff --git a/test/utils/mocks/mock_contact_controller.dart b/test/utils/mocks/mock_contact_controller.dart deleted file mode 100644 index e3d74f3..0000000 --- a/test/utils/mocks/mock_contact_controller.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:mocktail/mocktail.dart'; - -import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; - -class MockContactController extends Mock implements ContactController {} From d12547c8fc21af946dd065508df924fe3acab1b0 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sat, 11 May 2024 22:54:19 -0300 Subject: [PATCH 31/50] chore: removed deprecated properties --- .../widgets/ui/contact_widget_test.dart | 36 +++++++------------ test/app/features/home/home_page_test.dart | 19 ++++------ .../presentation/presentation_test.dart | 21 ++++------- 3 files changed, 25 insertions(+), 51 deletions(-) diff --git a/test/app/features/contact/presentation/widgets/ui/contact_widget_test.dart b/test/app/features/contact/presentation/widgets/ui/contact_widget_test.dart index 3f9f6e3..21615f0 100644 --- a/test/app/features/contact/presentation/widgets/ui/contact_widget_test.dart +++ b/test/app/features/contact/presentation/widgets/ui/contact_widget_test.dart @@ -1,27 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; - -import 'package:site/app/features/contact/presentation/widgets/ui/contact_mobile.dart'; -import 'package:site/app/features/contact/presentation/widgets/ui/contact_web.dart'; -import 'package:site/app/features/contact/presentation/widgets/ui/contact_widget.dart'; -import 'package:site/app/features/contact/presentation/controller/contact_controller.dart'; +import 'package:site/app/features/contact/contact.dart'; import '../../../../../../flutter_test_config.dart'; import '../../../../../../utils/utils.dart'; void main() { - late ContactController contactController; + late ContactCubit contactCubit; setUp(() { - contactController = MockContactController(); + contactCubit = MockContactCubit(); }); testWidgets('Should renders Contact', (tester) async { await appWidgetTest( tester: tester, widget: ContactWidget( - contactController: contactController, + contactCubit: contactCubit, ), ); @@ -36,46 +32,38 @@ void main() { testWidgets( 'ContactMobile when constraints is less than Breakpoints.contact', (tester) async { - final widthLargeSize = - tester.binding.window.physicalSizeTestValue = const Size(200, 400); + tester.view.physicalSize = const Size(200, 400); await appWidgetTest( tester: tester, - widget: MediaQuery( - data: MediaQueryData(size: widthLargeSize), - child: ContactWidget( - contactController: contactController, - ), + widget: ContactWidget( + contactCubit: contactCubit, ), ); expect(contactMobile, findsOneWidget); expect(contactWeb, findsNothing); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }, ); testWidgets( 'ContactWeb when constraints is greater than Breakpoints.contact', (tester) async { - final widthLargeSize = - tester.binding.window.physicalSizeTestValue = const Size(2000, 400); + tester.view.physicalSize = const Size(2000, 400); await appWidgetTest( tester: tester, - widget: MediaQuery( - data: MediaQueryData(size: widthLargeSize), - child: ContactWidget( - contactController: contactController, - ), + widget: ContactWidget( + contactCubit: contactCubit, ), ); expect(contactMobile, findsNothing); expect(contactWeb, findsOneWidget); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }, ); }); diff --git a/test/app/features/home/home_page_test.dart b/test/app/features/home/home_page_test.dart index 674c4d2..dc41886 100644 --- a/test/app/features/home/home_page_test.dart +++ b/test/app/features/home/home_page_test.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; - import 'package:flutter_test/flutter_test.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; -import 'package:site/app/core/responsive/responsive.dart'; +import 'package:site/app/core/responsive/responsive.dart'; import 'package:site/app/features/home/home_page.dart'; import 'package:site/app/widgets/drawer/drawer.dart'; @@ -55,23 +54,19 @@ void main() { ); testWidgets('Find CustomDrawer when is to show', (tester) async { - final isToShowDrawerWidth = tester.binding.window.physicalSizeTestValue = - Size(Breakpoints.appBar.toDouble(), 400); + tester.view.physicalSize = Size(Breakpoints.appBar.toDouble(), 400); await appWidgetTest( tester: tester, - widget: MediaQuery( - data: MediaQueryData(size: isToShowDrawerWidth), - child: HomePage( - firebaseRemoteConfig: mockFirebaseRemoteConfig, - httpClient: mockHttpClient, - ), + widget: HomePage( + firebaseRemoteConfig: mockFirebaseRemoteConfig, + httpClient: mockHttpClient, ), ); expect(findDrawer, findsOneWidget); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }); testWidgets('Not find Drawer when is not to show', (tester) async { @@ -85,7 +80,7 @@ void main() { expect(findDrawer, findsNothing); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }); }); } diff --git a/test/app/features/home/widgets/presentation/presentation_test.dart b/test/app/features/home/widgets/presentation/presentation_test.dart index 6e553b8..20a68d2 100644 --- a/test/app/features/home/widgets/presentation/presentation_test.dart +++ b/test/app/features/home/widgets/presentation/presentation_test.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import 'package:flutter_test/flutter_test.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; @@ -28,42 +27,34 @@ void main() { testWidgets( 'PresentationMobile when constraints is less than Breakpoints.presentation', (tester) async { - final widthSmallSize = tester.binding.window.physicalSizeTestValue = - const Size(400, 400); + tester.view.physicalSize = const Size(400, 400); await appWidgetTest( tester: tester, - widget: MediaQuery( - data: MediaQueryData(size: widthSmallSize), - child: Presentation(ItemScrollController()), - ), + widget: Presentation(ItemScrollController()), ); expect(presentationMobile, findsOneWidget); expect(presentationWeb, findsNothing); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }, ); testWidgets( 'PresentationWeb when constraints is greater than Breakpoints.presentation', (tester) async { - final widthLargeSize = tester.binding.window.physicalSizeTestValue = - const Size(2000, 1000); + tester.view.physicalSize = const Size(2000, 1000); await appWidgetTest( tester: tester, - widget: MediaQuery( - data: MediaQueryData(size: widthLargeSize), - child: Presentation(ItemScrollController()), - ), + widget: Presentation(ItemScrollController()), ); expect(presentationMobile, findsNothing); expect(presentationWeb, findsOneWidget); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }, ); }); From 44bb6849612497e278b8ec084509b84096eb71a9 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sat, 11 May 2024 22:55:20 -0300 Subject: [PATCH 32/50] style: ignore only localizations --- analysis_options.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index ab644f4..d905d8a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,12 +1,12 @@ include: package:flutter_lints/flutter.yaml -# analyzer: +analyzer: # plugins: # - dart_code_metrics -# exclude: -# - lib/generated_plugin_registrant.dart -# - test/** -# - lib/app/core/l10n/localizations/** # Generated file + exclude: + # - lib/generated_plugin_registrant.dart + # - test/** + - lib/app/core/l10n/localizations/** # Generated file # - lib/data/services/firebase/firebase_set_defaults.dart # To use dynamic type (because really need it) linter: From ff4e89d2f8d1e3be39339cf476e5b2635a5df8c6 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sun, 12 May 2024 10:06:09 -0300 Subject: [PATCH 33/50] feat: enhance cubit with more control and models related for return types --- .../repositories/contact_repository_impl.dart | 16 ++- .../contact/domain/models/contact_answer.dart | 22 ++-- .../presentation/cubit/contact_cubit.dart | 46 +++++--- .../contact/presentation/presentation.dart | 2 +- .../widgets/form/custom_form.dart | 25 ++-- .../widgets/ui/contact_widget.dart | 107 ++++++++---------- lib/app/features/home/home_page.dart | 11 +- .../snack_bars/app_show_snack_bar.dart | 27 +++++ .../cubit/contact_cubit_test.dart | 9 ++ test/utils/mocks/mock_contact_cubit.dart | 5 + test/utils/mocks/mocks.dart | 2 +- 11 files changed, 171 insertions(+), 101 deletions(-) create mode 100644 test/app/features/contact/presentation/cubit/contact_cubit_test.dart create mode 100644 test/utils/mocks/mock_contact_cubit.dart diff --git a/lib/app/features/contact/data/repositories/contact_repository_impl.dart b/lib/app/features/contact/data/repositories/contact_repository_impl.dart index 3dfba86..99be92a 100644 --- a/lib/app/features/contact/data/repositories/contact_repository_impl.dart +++ b/lib/app/features/contact/data/repositories/contact_repository_impl.dart @@ -18,17 +18,23 @@ class ContactRepositoryImpl implements ContactRepository { required ContactModel contact, }) async { try { - final response = await _httpClient.post( + final Response response = await _httpClient.post( ConstantsAPI.apiSendMail, data: ContactUser.toJson(contact), ); - // return response.data; - return Success(ContactAnswer.fromJson(response.data)); + return Success( + ContactAnswer.fromResponse( + contact: contact, + response: response, + ), + ); } catch (e, s) { - log('Error: $e', stackTrace: s); + log('[Error]: ContactRepositoryImpl.sendMail', error: e, stackTrace: s); - return const Failure(ContactFailedResult.unknown); + return const Failure( + ContactFailedResult.unknown, + ); } } } diff --git a/lib/app/features/contact/domain/models/contact_answer.dart b/lib/app/features/contact/domain/models/contact_answer.dart index 760a24a..5833be3 100644 --- a/lib/app/features/contact/domain/models/contact_answer.dart +++ b/lib/app/features/contact/domain/models/contact_answer.dart @@ -1,14 +1,18 @@ +import 'package:dio/dio.dart'; import 'package:site/app/features/contact/domain/models/contact_model.dart'; class ContactAnswer extends ContactModel { - factory ContactAnswer.fromJson(Map json) { + factory ContactAnswer.fromResponse({ + required ContactModel contact, + required Response response, + }) { return ContactAnswer( - name: json['sender_name'], - email: json['source_email'], - message: json['template_body'], - subject: json['template_subject'], - response: json['response'], - status: json['status'], + name: contact.name, + email: contact.email, + message: contact.message, + subject: contact.subject, + status: response.statusMessage ?? '', + response: '${response.statusCode}', ); } @@ -17,10 +21,10 @@ class ContactAnswer extends ContactModel { required super.email, required super.message, required super.subject, - required this.response, required this.status, + required this.response, }); - final String response; final String status; + final String response; } diff --git a/lib/app/features/contact/presentation/cubit/contact_cubit.dart b/lib/app/features/contact/presentation/cubit/contact_cubit.dart index 7627775..cd02b86 100644 --- a/lib/app/features/contact/presentation/cubit/contact_cubit.dart +++ b/lib/app/features/contact/presentation/cubit/contact_cubit.dart @@ -1,19 +1,22 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:site/app/core/result/result.dart'; import 'package:site/app/features/contact/contact.dart'; part 'contact_state.dart'; class ContactCubit extends Cubit { ContactCubit({ - required ContactRepositoryImpl contactRepository, + required ContactRepository contactRepository, }) : _contactRepository = contactRepository, super( const ContactInitial(), ); - final ContactRepositoryImpl _contactRepository; + final ContactRepository _contactRepository; Future sendMail({ required ContactModel contact, @@ -21,16 +24,31 @@ class ContactCubit extends Cubit { emit(const ContactLoading()); try { - await _contactRepository.sendMail( - contact: contact, - ); - emit( - ContactSuccess( - contact: contact, - message: 'Email sent successfully', - ), - ); - } catch (e) { + final result = await _contactRepository.sendMail(contact: contact); + + switch (result) { + case Success(object: final contactAnswer): + emit( + ContactSuccess( + contact: contactAnswer, + message: 'Email sent successfully', + ), + ); + case Failure(error: final contactFailedResult): + emit( + ContactError( + contact: contact, + message: switch (contactFailedResult) { + ContactFailedResult.unauthorized => 'Unauthorized', + ContactFailedResult.tooManyRequests => + 'Too many requests, try again later', + ContactFailedResult.unknown => 'Error sending email', + }, + ), + ); + } + } catch (e, s) { + log('[Error]: ContactCubit.sendMail', error: e, stackTrace: s); emit( ContactError( contact: contact, @@ -42,8 +60,8 @@ class ContactCubit extends Cubit { } -// class ContactController extends ChangeNotifier { -// ContactController({ +// class ContactCubit extends ChangeNotifier { +// ContactCubit({ // ContactRepositoryImpl? contactRepository, // }) : _contactRepository = contactRepository ?? getIt(); diff --git a/lib/app/features/contact/presentation/presentation.dart b/lib/app/features/contact/presentation/presentation.dart index b944e92..ac29eaf 100644 --- a/lib/app/features/contact/presentation/presentation.dart +++ b/lib/app/features/contact/presentation/presentation.dart @@ -1,2 +1,2 @@ -export 'controller/controller.dart'; export 'widgets/widgets.dart'; +export 'cubit/contact_cubit.dart'; \ No newline at end of file diff --git a/lib/app/features/contact/presentation/widgets/form/custom_form.dart b/lib/app/features/contact/presentation/widgets/form/custom_form.dart index 32c733a..6ade631 100644 --- a/lib/app/features/contact/presentation/widgets/form/custom_form.dart +++ b/lib/app/features/contact/presentation/widgets/form/custom_form.dart @@ -16,9 +16,9 @@ class CustomForm extends StatelessWidget { required this.subjectController, required this.messageController, required this.onPressed, - ContactController? contactController, - }) : _contactController = contactController ?? - ContactController( + ContactCubit? contactCubit, + }) : _contactCubit = contactCubit ?? + ContactCubit( contactRepository: ContactRepositoryImpl( httpClient: getIt(), ), @@ -30,8 +30,7 @@ class CustomForm extends StatelessWidget { final TextEditingController subjectController; final TextEditingController messageController; final VoidCallback onPressed; - - final ContactController? _contactController; + final ContactCubit? _contactCubit; @override Widget build(BuildContext context) { @@ -77,9 +76,21 @@ class CustomForm extends StatelessWidget { Center( child: AppTextButton( text: AppTexts.get(context).sendEmailUpper, - onPressed: onPressed, + + /// TODO: + // onPressed: onPressed, + onPressed: () { + _contactCubit?.sendMail( + contact: ContactUser( + name: 'felipe sales', + email: 'soufeliposales@gmail.com', + message: 'teste de mensagem', + subject: 'vamos testar, mais uma vez', + ), + ); + }, onLongPress: () { - _contactController?.sendMail( + _contactCubit?.sendMail( contact: ContactUser( name: 'felipe sales', email: 'soufeliposales@gmail.com', diff --git a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart index 36b271c..7371389 100644 --- a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart +++ b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart @@ -1,26 +1,25 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:site/app/core/injections/injections.dart'; -import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/responsive/responsive.dart'; import 'package:site/app/core/shared/app_keys.dart'; -import 'package:site/app/core/tokens/tokens.dart'; import 'package:site/app/features/contact/contact.dart'; import 'package:site/app/widgets/snack_bars/snack_bars.dart'; -// import 'package:site/data/services/recaptcha/recaptcha.dart'; class ContactWidget extends StatelessWidget { ContactWidget({ super.key, - ContactController? contactController, - }) : _contactController = contactController ?? - ContactController( - contactRepository: ContactRepositoryImpl( - httpClient: getIt(), - ), - ); + ContactCubit? contactCubit, + }) : _contactCubit = contactCubit ?? _cubit; - final ContactController? _contactController; + final ContactCubit? _contactCubit; + + static final _cubit = ContactCubit( + contactRepository: ContactRepositoryImpl( + httpClient: getIt(), + ), + ); @override Widget build(BuildContext context) { @@ -29,56 +28,46 @@ class ContactWidget extends StatelessWidget { final emailController = TextEditingController(); final messageController = TextEditingController(); final subjectController = TextEditingController(); - final form = CustomForm( - formKey: formKey, - nameController: nameController, - emailController: emailController, - subjectController: subjectController, - messageController: messageController, - onPressed: () async { - if (formKey.currentState?.validate() ?? false) { - // final isNotABot = await RecaptchaService.isNotABot(); - - // if (isNotABot) { - if (true) { - if (context.mounted) { - appShowSnackBar( - context, - text: AppTexts.get(context).emailSendedWithSuccess, - icon: Icons.check, - color: AppColors.primaryDark, - width: 300, - ); - } - - _contactController?.sendMail( - contact: ContactUser( - name: nameController.text, - email: emailController.text.trim(), - message: messageController.text, - subject: subjectController.text, - ), - ); - for (var controller in [ - nameController, - emailController, - messageController, - subjectController, - ]) { - controller.clear(); - } - } else if (context.mounted) { - appShowSnackBar( - context, - text: AppTexts.get(context).emailNotSended, - icon: Icons.error, - color: AppColors.red, - width: 300, - ); + final form = BlocProvider( + create: (context) => _contactCubit ?? _cubit, + child: BlocConsumer( + listener: (context, state) { + if ([ContactSuccess, ContactError].contains(state.runtimeType)) { + appShowSnackBarFromContact(context, state); } - } - }, + }, + builder: (context, state) { + return CustomForm( + formKey: formKey, + nameController: nameController, + emailController: emailController, + subjectController: subjectController, + messageController: messageController, + onPressed: () { + if (formKey.currentState?.validate() ?? false) { + _contactCubit?.sendMail( + contact: ContactUser( + name: nameController.text, + email: emailController.text, + subject: subjectController.text, + message: messageController.text, + ), + ); + + for (var controller in [ + nameController, + emailController, + messageController, + subjectController, + ]) { + controller.clear(); + } + } + }, + ); + }, + ), ); return LayoutBuilder( diff --git a/lib/app/features/home/home_page.dart b/lib/app/features/home/home_page.dart index cd8763f..751310d 100644 --- a/lib/app/features/home/home_page.dart +++ b/lib/app/features/home/home_page.dart @@ -41,17 +41,18 @@ class _HomePageState extends State { void initState() { super.initState(); items = [ - Presentation(itemScrollController), - const Projects(), - const Experience(), - const Social(), + /// TODO: ContactWidget( - contactController: ContactController( + contactCubit: ContactCubit( contactRepository: ContactRepositoryImpl( httpClient: widget._httpClient, ), ), ), + Presentation(itemScrollController), + const Projects(), + const Experience(), + const Social(), const CustomFooter(), ]; } diff --git a/lib/app/widgets/snack_bars/app_show_snack_bar.dart b/lib/app/widgets/snack_bars/app_show_snack_bar.dart index 07c7e7d..4f1988e 100644 --- a/lib/app/widgets/snack_bars/app_show_snack_bar.dart +++ b/lib/app/widgets/snack_bars/app_show_snack_bar.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:auto_size_text/auto_size_text.dart'; +import 'package:site/app/core/l10n/localizations/localizations.dart'; import 'package:site/app/core/tokens/tokens.dart'; +import 'package:site/app/features/contact/presentation/presentation.dart'; ScaffoldFeatureController appShowSnackBar( BuildContext context, { @@ -44,3 +46,28 @@ ScaffoldFeatureController appShowSnackBar( ), ); } + +ScaffoldFeatureController appShowSnackBarFromContact( + BuildContext context, + ContactState state, +) { + return appShowSnackBar( + context, + width: 300, + text: switch (state) { + ContactSuccess() => AppTexts.get(context).emailSendedWithSuccess, + ContactError() => AppTexts.get(context).emailNotSended, + _ => '', + }, + icon: switch (state) { + ContactSuccess() => Icons.check, + ContactError() => Icons.error, + _ => Icons.error, + }, + color: switch (state) { + ContactSuccess() => AppColors.primaryDark, + ContactError() => AppColors.red, + _ => AppColors.red, + }, + ); +} diff --git a/test/app/features/contact/presentation/cubit/contact_cubit_test.dart b/test/app/features/contact/presentation/cubit/contact_cubit_test.dart new file mode 100644 index 0000000..55f33b9 --- /dev/null +++ b/test/app/features/contact/presentation/cubit/contact_cubit_test.dart @@ -0,0 +1,9 @@ +import 'package:flutter_test/flutter_test.dart'; + +/// TODO: + +void main() { + testWidgets('contact cubit ...', (tester) async { + // TODO: Implement test + }); +} diff --git a/test/utils/mocks/mock_contact_cubit.dart b/test/utils/mocks/mock_contact_cubit.dart new file mode 100644 index 0000000..98e226e --- /dev/null +++ b/test/utils/mocks/mock_contact_cubit.dart @@ -0,0 +1,5 @@ +import 'package:mocktail/mocktail.dart'; + +import 'package:site/app/features/contact/contact.dart'; + +class MockContactCubit extends Mock implements ContactCubit {} diff --git a/test/utils/mocks/mocks.dart b/test/utils/mocks/mocks.dart index b68f775..160c14f 100644 --- a/test/utils/mocks/mocks.dart +++ b/test/utils/mocks/mocks.dart @@ -1,3 +1,3 @@ -export 'mock_contact_controller.dart'; +export 'mock_contact_cubit.dart'; export 'mock_firebase_remote_config.dart'; export 'mock_http_client.dart'; From 98869c05b9d4f01e31514468a2ec5be70c97340c Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sun, 12 May 2024 11:59:34 -0300 Subject: [PATCH 34/50] feat: update bloc and repository layers for better control --- lib/app/core/shared/shared.dart | 1 + .../repositories/contact_repository_impl.dart | 28 +++++++++++++------ .../data/results/contact_failed_result.dart | 1 + .../contact/domain/models/contact_answer.dart | 13 +++++---- .../presentation/cubit/contact_cubit.dart | 4 ++- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lib/app/core/shared/shared.dart b/lib/app/core/shared/shared.dart index 07d8ef8..9243f7a 100644 --- a/lib/app/core/shared/shared.dart +++ b/lib/app/core/shared/shared.dart @@ -2,3 +2,4 @@ export 'app_assets.dart'; export 'app_datas.dart'; export 'app_keys.dart'; export 'app_urls.dart'; +export 'app_values.dart'; diff --git a/lib/app/features/contact/data/repositories/contact_repository_impl.dart b/lib/app/features/contact/data/repositories/contact_repository_impl.dart index 99be92a..2cc20b7 100644 --- a/lib/app/features/contact/data/repositories/contact_repository_impl.dart +++ b/lib/app/features/contact/data/repositories/contact_repository_impl.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:io'; import 'package:dio/dio.dart'; import 'package:site/app/core/result/result.dart'; @@ -18,22 +19,33 @@ class ContactRepositoryImpl implements ContactRepository { required ContactModel contact, }) async { try { - final Response response = await _httpClient.post( + final response = await _httpClient.post( ConstantsAPI.apiSendMail, data: ContactUser.toJson(contact), ); - return Success( - ContactAnswer.fromResponse( - contact: contact, - response: response, - ), - ); + return switch (response.statusCode) { + HttpStatus.ok => Success( + ContactAnswer.fromResponse( + contact: contact, + response: response, + ), + ), + HttpStatus.unauthorized => const Failure( + ContactFailedResult.unauthorized, + ), + HttpStatus.tooManyRequests => const Failure( + ContactFailedResult.tooManyRequests, + ), + _ => const Failure( + ContactFailedResult.unknown, + ), + }; } catch (e, s) { log('[Error]: ContactRepositoryImpl.sendMail', error: e, stackTrace: s); return const Failure( - ContactFailedResult.unknown, + ContactFailedResult.error, ); } } diff --git a/lib/app/features/contact/data/results/contact_failed_result.dart b/lib/app/features/contact/data/results/contact_failed_result.dart index 13bc4af..58ba4d7 100644 --- a/lib/app/features/contact/data/results/contact_failed_result.dart +++ b/lib/app/features/contact/data/results/contact_failed_result.dart @@ -2,4 +2,5 @@ enum ContactFailedResult { tooManyRequests, unauthorized, unknown, + error, } diff --git a/lib/app/features/contact/domain/models/contact_answer.dart b/lib/app/features/contact/domain/models/contact_answer.dart index 5833be3..488a81c 100644 --- a/lib/app/features/contact/domain/models/contact_answer.dart +++ b/lib/app/features/contact/domain/models/contact_answer.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:site/app/core/shared/shared.dart'; import 'package:site/app/features/contact/domain/models/contact_model.dart'; class ContactAnswer extends ContactModel { @@ -11,8 +12,8 @@ class ContactAnswer extends ContactModel { email: contact.email, message: contact.message, subject: contact.subject, - status: response.statusMessage ?? '', - response: '${response.statusCode}', + statusCode: response.statusCode ?? AppValues.invalidValue, + responseMessage: '${(response.data as Map)['message']}', ); } @@ -21,10 +22,10 @@ class ContactAnswer extends ContactModel { required super.email, required super.message, required super.subject, - required this.status, - required this.response, + required this.statusCode, + required this.responseMessage, }); - final String status; - final String response; + final int statusCode; + final String responseMessage; } diff --git a/lib/app/features/contact/presentation/cubit/contact_cubit.dart b/lib/app/features/contact/presentation/cubit/contact_cubit.dart index cd02b86..6a3d3df 100644 --- a/lib/app/features/contact/presentation/cubit/contact_cubit.dart +++ b/lib/app/features/contact/presentation/cubit/contact_cubit.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -42,7 +43,8 @@ class ContactCubit extends Cubit { ContactFailedResult.unauthorized => 'Unauthorized', ContactFailedResult.tooManyRequests => 'Too many requests, try again later', - ContactFailedResult.unknown => 'Error sending email', + ContactFailedResult.unknown => 'Unknown error', + ContactFailedResult.error => 'Error sending email', }, ), ); From a2cc20caceec945e150ac32ab645f3d05ce95613 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 15 May 2024 19:39:34 -0300 Subject: [PATCH 35/50] feat: add missing file --- lib/app/core/shared/app_values.dart | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/app/core/shared/app_values.dart diff --git a/lib/app/core/shared/app_values.dart b/lib/app/core/shared/app_values.dart new file mode 100644 index 0000000..855e7b7 --- /dev/null +++ b/lib/app/core/shared/app_values.dart @@ -0,0 +1,3 @@ +class AppValues { + static const invalidValue = -1; +} From 9032cc23a54692d9ae99ac03ea4c73f78129ba6a Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 15 May 2024 21:01:38 -0300 Subject: [PATCH 36/50] feat: add global context for use in controllers --- lib/app/app_widget.dart | 2 ++ lib/app/core/globals/globals.dart | 1 + lib/app/core/globals/navigation_service.dart | 6 ++++ .../core/l10n/localizations/app_texts.dart | 6 +++- .../data/results/contact_failed_result.dart | 16 +++++++++- .../presentation/cubit/contact_cubit.dart | 14 ++++----- pubspec.lock | 30 +++++++++---------- pubspec.yaml | 2 +- 8 files changed, 50 insertions(+), 27 deletions(-) create mode 100644 lib/app/core/globals/globals.dart create mode 100644 lib/app/core/globals/navigation_service.dart diff --git a/lib/app/app_widget.dart b/lib/app/app_widget.dart index 916135d..aabf082 100644 --- a/lib/app/app_widget.dart +++ b/lib/app/app_widget.dart @@ -2,6 +2,7 @@ import 'package:dio/dio.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/material.dart'; +import 'package:site/app/core/globals/globals.dart'; import 'package:site/app/core/injections/injections.dart'; import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/themes/app_theme.dart'; @@ -21,6 +22,7 @@ class AppWidget extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + navigatorKey: NavigationService.navigatorKey, onGenerateTitle: (context) => AppTexts.get(context).projectTitle, debugShowCheckedModeBanner: false, theme: AppTheme.theme, diff --git a/lib/app/core/globals/globals.dart b/lib/app/core/globals/globals.dart new file mode 100644 index 0000000..a952f65 --- /dev/null +++ b/lib/app/core/globals/globals.dart @@ -0,0 +1 @@ +export 'navigation_service.dart'; diff --git a/lib/app/core/globals/navigation_service.dart b/lib/app/core/globals/navigation_service.dart new file mode 100644 index 0000000..1620c34 --- /dev/null +++ b/lib/app/core/globals/navigation_service.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + static final navigatorKey = GlobalKey(); + static final navigatorKeyContext = navigatorKey.currentContext; +} diff --git a/lib/app/core/l10n/localizations/app_texts.dart b/lib/app/core/l10n/localizations/app_texts.dart index 1c71d1d..5caf1df 100644 --- a/lib/app/core/l10n/localizations/app_texts.dart +++ b/lib/app/core/l10n/localizations/app_texts.dart @@ -3,7 +3,11 @@ import 'package:flutter/material.dart'; import 'package:site/app/core/l10n/l10n.dart'; class AppTexts { - static AppLocalizations get(BuildContext context) { + static AppLocalizations get(BuildContext? context) { + if (context == null) { + throw Exception('AppTexts.get: context is null'); + } + return Localizations.of(context, AppLocalizations)!; } } diff --git a/lib/app/features/contact/data/results/contact_failed_result.dart b/lib/app/features/contact/data/results/contact_failed_result.dart index 58ba4d7..9cf79f4 100644 --- a/lib/app/features/contact/data/results/contact_failed_result.dart +++ b/lib/app/features/contact/data/results/contact_failed_result.dart @@ -1,6 +1,20 @@ +import 'package:site/app/core/globals/globals.dart'; +import 'package:site/app/core/l10n/l10n.dart'; + enum ContactFailedResult { tooManyRequests, unauthorized, unknown, - error, + error; + + String get message { + final appTexts = AppTexts.get(NavigationService.navigatorKeyContext); + + return switch (this) { + ContactFailedResult.tooManyRequests => appTexts.emailNotSended, + ContactFailedResult.unauthorized => appTexts.emailNotSended, + ContactFailedResult.unknown => appTexts.emailNotSended, + ContactFailedResult.error => appTexts.emailNotSended, + }; + } } diff --git a/lib/app/features/contact/presentation/cubit/contact_cubit.dart b/lib/app/features/contact/presentation/cubit/contact_cubit.dart index 6a3d3df..996d4c0 100644 --- a/lib/app/features/contact/presentation/cubit/contact_cubit.dart +++ b/lib/app/features/contact/presentation/cubit/contact_cubit.dart @@ -1,9 +1,10 @@ import 'dart:developer'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:site/app/core/globals/navigation_service.dart'; +import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/result/result.dart'; import 'package:site/app/features/contact/contact.dart'; @@ -32,20 +33,15 @@ class ContactCubit extends Cubit { emit( ContactSuccess( contact: contactAnswer, - message: 'Email sent successfully', + message: AppTexts.get(NavigationService.navigatorKeyContext) + .emailSendedWithSuccess, ), ); case Failure(error: final contactFailedResult): emit( ContactError( contact: contact, - message: switch (contactFailedResult) { - ContactFailedResult.unauthorized => 'Unauthorized', - ContactFailedResult.tooManyRequests => - 'Too many requests, try again later', - ContactFailedResult.unknown => 'Unknown error', - ContactFailedResult.error => 'Error sending email', - }, + message: contactFailedResult.message, ), ); } diff --git a/pubspec.lock b/pubspec.lock index 3bae08f..e311440 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -472,10 +472,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" io: dependency: transitive description: @@ -504,26 +504,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -568,10 +568,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: transitive description: @@ -813,10 +813,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" timing: dependency: transitive description: @@ -941,10 +941,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" watcher: dependency: transitive description: @@ -1003,4 +1003,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.16.6" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 5f0d780..401a4af 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: flutter_svg: ^2.0.9 g_recaptcha_v3: ^0.0.5 get_it: ^7.6.7 - intl: 0.18.1 + intl: ^0.19.0 lottie: ^3.0.0 package_info_plus: ^5.0.1 scrollable_positioned_list: ^0.3.8 From 229cdc2d8154e457264f97fa8ab8afb9778ee8f6 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 15 May 2024 21:24:12 -0300 Subject: [PATCH 37/50] chore: clear only after success --- .../widgets/ui/contact_widget.dart | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart index 7371389..ae60ee5 100644 --- a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart +++ b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart @@ -36,6 +36,17 @@ class ContactWidget extends StatelessWidget { if ([ContactSuccess, ContactError].contains(state.runtimeType)) { appShowSnackBarFromContact(context, state); } + + if (state is ContactSuccess) { + for (var controller in [ + nameController, + emailController, + messageController, + subjectController, + ]) { + controller.clear(); + } + } }, builder: (context, state) { return CustomForm( @@ -54,15 +65,6 @@ class ContactWidget extends StatelessWidget { message: messageController.text, ), ); - - for (var controller in [ - nameController, - emailController, - messageController, - subjectController, - ]) { - controller.clear(); - } } }, ); From 946b6fcb6efaeffc707fa1f9d203e256b23a4b49 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Wed, 15 May 2024 21:24:33 -0300 Subject: [PATCH 38/50] chore: use WidgetState instead of MaterialState --- .../features/home/widgets/footer/widgets/text_with_link.dart | 2 +- lib/app/widgets/app_bar/widgets/web_app_bar_title.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/app/features/home/widgets/footer/widgets/text_with_link.dart b/lib/app/features/home/widgets/footer/widgets/text_with_link.dart index eb673a7..65c4d04 100644 --- a/lib/app/features/home/widgets/footer/widgets/text_with_link.dart +++ b/lib/app/features/home/widgets/footer/widgets/text_with_link.dart @@ -20,7 +20,7 @@ class TextWithLink extends StatelessWidget { clipBehavior: Clip.antiAlias, child: InkWell( splashColor: AppColors.primary, - overlayColor: MaterialStateProperty.all( + overlayColor: WidgetStateProperty.all( AppColors.primary.withOpacity(0.25), ), onTap: () => LaunchUrls().launchURL(link), diff --git a/lib/app/widgets/app_bar/widgets/web_app_bar_title.dart b/lib/app/widgets/app_bar/widgets/web_app_bar_title.dart index 7cb7fb6..d2a8b13 100644 --- a/lib/app/widgets/app_bar/widgets/web_app_bar_title.dart +++ b/lib/app/widgets/app_bar/widgets/web_app_bar_title.dart @@ -26,7 +26,7 @@ class WebAppBarTitle extends StatelessWidget with ScrollToMixin { clipBehavior: Clip.antiAlias, child: InkWell( splashColor: AppColors.primary, - overlayColor: MaterialStateProperty.all( + overlayColor: WidgetStateProperty.all( AppColors.primary.withOpacity(0.25), ), onTap: () => scrollTo(index, itemScrollController), From c9a21f0b802562509f2a4f6933031b3ccb7e4654 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sat, 18 May 2024 20:46:58 -0300 Subject: [PATCH 39/50] feat: add messages and use --- .../l10n/localizations/app_localizations.dart | 18 ++++++++++++++++++ .../localizations/app_localizations_en.dart | 9 +++++++++ .../localizations/app_localizations_es.dart | 9 +++++++++ .../localizations/app_localizations_pt.dart | 9 +++++++++ lib/app/core/l10n/localizations/app_texts.dart | 17 +++++++++++++++-- lib/app/core/l10n/templates/app_en.arb | 5 ++++- lib/app/core/l10n/templates/app_es.arb | 3 +++ lib/app/core/l10n/templates/app_pt.arb | 3 +++ .../data/results/contact_failed_result.dart | 11 +++++------ .../presentation/cubit/contact_cubit.dart | 4 +--- .../widgets/snack_bars/app_show_snack_bar.dart | 8 +++----- 11 files changed, 79 insertions(+), 17 deletions(-) diff --git a/lib/app/core/l10n/localizations/app_localizations.dart b/lib/app/core/l10n/localizations/app_localizations.dart index 546266c..cc27e5b 100644 --- a/lib/app/core/l10n/localizations/app_localizations.dart +++ b/lib/app/core/l10n/localizations/app_localizations.dart @@ -335,6 +335,24 @@ abstract class AppLocalizations { /// **'Erro ao enviar e-mail!'** String get emailNotSended; + /// No description provided for @emailTooManyRequests. + /// + /// In pt, this message translates to: + /// **'Tente novamente mais tarde!'** + String get emailTooManyRequests; + + /// No description provided for @emailUnauthorized. + /// + /// In pt, this message translates to: + /// **'O envio não foi autorizado!'** + String get emailUnauthorized; + + /// No description provided for @emailUnknowError. + /// + /// In pt, this message translates to: + /// **'Tente enviar de outra forma!'** + String get emailUnknowError; + /// No description provided for @letsChatCallMe. /// /// In pt, this message translates to: diff --git a/lib/app/core/l10n/localizations/app_localizations_en.dart b/lib/app/core/l10n/localizations/app_localizations_en.dart index e8cf86c..c914756 100644 --- a/lib/app/core/l10n/localizations/app_localizations_en.dart +++ b/lib/app/core/l10n/localizations/app_localizations_en.dart @@ -124,6 +124,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get emailNotSended => 'Error to send email'; + @override + String get emailTooManyRequests => 'Please try again later!'; + + @override + String get emailUnauthorized => 'Sending was not authorized!'; + + @override + String get emailUnknowError => 'Try sending in another way!'; + @override String get letsChatCallMe => 'Let\'s chat, call me:'; diff --git a/lib/app/core/l10n/localizations/app_localizations_es.dart b/lib/app/core/l10n/localizations/app_localizations_es.dart index e13b259..2e83e80 100644 --- a/lib/app/core/l10n/localizations/app_localizations_es.dart +++ b/lib/app/core/l10n/localizations/app_localizations_es.dart @@ -124,6 +124,15 @@ class AppLocalizationsEs extends AppLocalizations { @override String get emailNotSended => '¡Error al enviar el email!'; + @override + String get emailTooManyRequests => '¡Inténtalo de nuevo más tarde!'; + + @override + String get emailUnauthorized => '¡El envío no fue autorizado!'; + + @override + String get emailUnknowError => '¡Intenta enviar de otra manera!'; + @override String get letsChatCallMe => 'Hablemos, llámame:'; diff --git a/lib/app/core/l10n/localizations/app_localizations_pt.dart b/lib/app/core/l10n/localizations/app_localizations_pt.dart index cea5bda..b2dc128 100644 --- a/lib/app/core/l10n/localizations/app_localizations_pt.dart +++ b/lib/app/core/l10n/localizations/app_localizations_pt.dart @@ -124,6 +124,15 @@ class AppLocalizationsPt extends AppLocalizations { @override String get emailNotSended => 'Erro ao enviar e-mail!'; + @override + String get emailTooManyRequests => 'Tente novamente mais tarde!'; + + @override + String get emailUnauthorized => 'O envio não foi autorizado!'; + + @override + String get emailUnknowError => 'Tente enviar de outra forma!'; + @override String get letsChatCallMe => 'Vamos bater um papo, me chame:'; diff --git a/lib/app/core/l10n/localizations/app_texts.dart b/lib/app/core/l10n/localizations/app_texts.dart index 5caf1df..1a489c6 100644 --- a/lib/app/core/l10n/localizations/app_texts.dart +++ b/lib/app/core/l10n/localizations/app_texts.dart @@ -1,13 +1,26 @@ import 'package:flutter/material.dart'; - +import 'package:site/app/core/globals/globals.dart'; +import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/l10n/l10n.dart'; class AppTexts { - static AppLocalizations get(BuildContext? context) { + static AppLocalizations get(BuildContext context) { if (context == null) { throw Exception('AppTexts.get: context is null'); } return Localizations.of(context, AppLocalizations)!; } + + static AppLocalizations get getViaNavigatorKey { + final navigatorKey = NavigationService.navigatorKeyContext; + + if (navigatorKey == null) { + throw Exception( + 'AppTexts.getViaNavigatorKey: navigatorKeyContext is null', + ); + } + + return Localizations.of(navigatorKey!, AppLocalizations)!; + } } diff --git a/lib/app/core/l10n/templates/app_en.arb b/lib/app/core/l10n/templates/app_en.arb index f1ce24a..e6da626 100644 --- a/lib/app/core/l10n/templates/app_en.arb +++ b/lib/app/core/l10n/templates/app_en.arb @@ -38,7 +38,10 @@ "text": "Text", "sendEmailUpper": "SEND EMAIL", "emailSendedWithSuccess": "Email sent successfully!", - "emailNotSended": "Error to send email", + "emailNotSended": "Error to send email", + "emailTooManyRequests": "Please try again later!", + "emailUnauthorized": "Sending was not authorized!", + "emailUnknowError": "Try sending in another way!", "letsChatCallMe": "Let's chat, call me:", "hyphen": " - ", "username": "@felipecastrosales", diff --git a/lib/app/core/l10n/templates/app_es.arb b/lib/app/core/l10n/templates/app_es.arb index 10cc044..4ae2410 100644 --- a/lib/app/core/l10n/templates/app_es.arb +++ b/lib/app/core/l10n/templates/app_es.arb @@ -39,6 +39,9 @@ "sendEmailUpper": "ENVIAR EMAIL", "emailSendedWithSuccess": "¡Email enviado exitosamente!", "emailNotSended": "¡Error al enviar el email!", + "emailTooManyRequests": "¡Inténtalo de nuevo más tarde!", + "emailUnauthorized": "¡El envío no fue autorizado!", + "emailUnknowError": "¡Intenta enviar de otra manera!", "letsChatCallMe": "Hablemos, llámame:", "hyphen": " - ", "username": "@felipecastrosales", diff --git a/lib/app/core/l10n/templates/app_pt.arb b/lib/app/core/l10n/templates/app_pt.arb index 5817c24..5664511 100644 --- a/lib/app/core/l10n/templates/app_pt.arb +++ b/lib/app/core/l10n/templates/app_pt.arb @@ -39,6 +39,9 @@ "sendEmailUpper": "ENVIAR E-MAIL", "emailSendedWithSuccess": "E-mail enviado com sucesso!", "emailNotSended": "Erro ao enviar e-mail!", + "emailTooManyRequests": "Tente novamente mais tarde!", + "emailUnauthorized": "O envio não foi autorizado!", + "emailUnknowError": "Tente enviar de outra forma!", "letsChatCallMe": "Vamos bater um papo, me chame:", "hyphen": " - ", "username": "@felipecastrosales", diff --git a/lib/app/features/contact/data/results/contact_failed_result.dart b/lib/app/features/contact/data/results/contact_failed_result.dart index 9cf79f4..71fc109 100644 --- a/lib/app/features/contact/data/results/contact_failed_result.dart +++ b/lib/app/features/contact/data/results/contact_failed_result.dart @@ -1,4 +1,3 @@ -import 'package:site/app/core/globals/globals.dart'; import 'package:site/app/core/l10n/l10n.dart'; enum ContactFailedResult { @@ -8,13 +7,13 @@ enum ContactFailedResult { error; String get message { - final appTexts = AppTexts.get(NavigationService.navigatorKeyContext); + final texts = AppTexts.getViaNavigatorKey; return switch (this) { - ContactFailedResult.tooManyRequests => appTexts.emailNotSended, - ContactFailedResult.unauthorized => appTexts.emailNotSended, - ContactFailedResult.unknown => appTexts.emailNotSended, - ContactFailedResult.error => appTexts.emailNotSended, + ContactFailedResult.tooManyRequests => texts.emailTooManyRequests, + ContactFailedResult.unauthorized => texts.emailUnauthorized, + ContactFailedResult.unknown => texts.emailUnknowError, + ContactFailedResult.error => texts.emailNotSended, }; } } diff --git a/lib/app/features/contact/presentation/cubit/contact_cubit.dart b/lib/app/features/contact/presentation/cubit/contact_cubit.dart index 996d4c0..b2e0d0b 100644 --- a/lib/app/features/contact/presentation/cubit/contact_cubit.dart +++ b/lib/app/features/contact/presentation/cubit/contact_cubit.dart @@ -3,7 +3,6 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:site/app/core/globals/navigation_service.dart'; import 'package:site/app/core/l10n/l10n.dart'; import 'package:site/app/core/result/result.dart'; import 'package:site/app/features/contact/contact.dart'; @@ -33,8 +32,7 @@ class ContactCubit extends Cubit { emit( ContactSuccess( contact: contactAnswer, - message: AppTexts.get(NavigationService.navigatorKeyContext) - .emailSendedWithSuccess, + message: AppTexts.getViaNavigatorKey.emailSendedWithSuccess, ), ); case Failure(error: final contactFailedResult): diff --git a/lib/app/widgets/snack_bars/app_show_snack_bar.dart b/lib/app/widgets/snack_bars/app_show_snack_bar.dart index 4f1988e..ebb55a9 100644 --- a/lib/app/widgets/snack_bars/app_show_snack_bar.dart +++ b/lib/app/widgets/snack_bars/app_show_snack_bar.dart @@ -1,7 +1,5 @@ -import 'package:flutter/material.dart'; - import 'package:auto_size_text/auto_size_text.dart'; -import 'package:site/app/core/l10n/localizations/localizations.dart'; +import 'package:flutter/material.dart'; import 'package:site/app/core/tokens/tokens.dart'; import 'package:site/app/features/contact/presentation/presentation.dart'; @@ -55,8 +53,8 @@ ScaffoldFeatureController appShowSnackBarFromContact( context, width: 300, text: switch (state) { - ContactSuccess() => AppTexts.get(context).emailSendedWithSuccess, - ContactError() => AppTexts.get(context).emailNotSended, + ContactSuccess() => state.message, + ContactError() => state.message, _ => '', }, icon: switch (state) { From cb46529ce7f6703928123919807deb32f93907a0 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sat, 25 May 2024 23:52:06 -0300 Subject: [PATCH 40/50] build: bump packages to latest version --- pubspec.lock | 92 ++++++++++++++++++++++++++-------------------------- pubspec.yaml | 22 ++++++------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index e311440..3402a42 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "4eec93681221723a686ad580c2e7d960e1017cf1a4e0a263c2573c2c6b0bf5cd" + sha256: e4be6711f96d3d4eebe79728897d645b7a5585bbfdd6d534878d202c171266d7 url: "https://pub.dev" source: hosted - version: "1.3.25" + version: "1.3.34" analyzer: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "1414d6d733a85d8ad2f1dfcb3ea7945759e35a123cb99ccfac75d0758f75edfa" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.10" build_runner_core: dependency: transitive description: @@ -269,34 +269,34 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: b13cbf1ee78744ca5e6b762e9218db3bd3967a0edfed75f58339907892a2ccb9 + sha256: "08f98034f51c8018d08cd56ac51f44c63ab684f85a7b8f0ede92c4acfb23a2bc" url: "https://pub.dev" source: hosted - version: "10.8.9" + version: "10.10.6" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: "416b33d62033db5ecd2df719fcb657ad04e9995fa0fc392ffdab4ca0e76cb679" + sha256: "4de04e25bd739184eb2cfcd76c2f336c9e03bf65457e1e17d027d65f2344a2df" url: "https://pub.dev" source: hosted - version: "3.9.9" + version: "3.10.7" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "9dca9d8d468172444ef18cabb73fe99f7aae24733bfad67115bd36bffd2d65c1" + sha256: "77ded8cb214193cc816395625ab8a031539cdd870d667123d1b9d53c7303a82d" url: "https://pub.dev" source: hosted - version: "0.5.5+21" + version: "0.5.7+6" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "53316975310c8af75a96e365f9fccb67d1c544ef0acdbf0d88bbe30eedd1c4f9" + sha256: "4b5100e2dbc3fe72c2d4241a046d3f01457fe11293283a324f5c52575e3406f8" url: "https://pub.dev" source: hosted - version: "2.27.0" + version: "2.31.1" firebase_core_platform_interface: dependency: "direct main" description: @@ -309,34 +309,34 @@ packages: dependency: "direct main" description: name: firebase_core_web - sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492 + sha256: "43d9e951ac52b87ae9cc38ecdcca1e8fa7b52a1dd26a96085ba41ce5108db8e9" url: "https://pub.dev" source: hosted - version: "2.11.5" + version: "2.17.0" firebase_remote_config: dependency: "direct main" description: name: firebase_remote_config - sha256: b085a72c007bd8f177a7ab98b8292d764659b07fb6b0561b84125239ee656efc + sha256: e685eb602a528b9ad2ae5c09f244728a484c4289dbf273af03042e737e752a6e url: "https://pub.dev" source: hosted - version: "4.3.17" + version: "4.4.6" firebase_remote_config_platform_interface: dependency: transitive description: name: firebase_remote_config_platform_interface - sha256: c589e007156b2c9f903253764c108abb96c1b56dd17cf0b91afc4b72ccab7bb6 + sha256: "86621fa95c515491d1914cee690475c84dc46dd87a71cf16a8df4e280aa1c123" url: "https://pub.dev" source: hosted - version: "1.4.25" + version: "1.4.34" firebase_remote_config_web: dependency: transitive description: name: firebase_remote_config_web - sha256: "92443c70e2721ab9d4beb23eb1d9f971da7381332451daee04f619b0f9204569" + sha256: ee05916a72d9630a838df0cc581b006991eee949690a9f212731a981be864234 url: "https://pub.dev" source: hosted - version: "1.4.25" + version: "1.6.6" fixnum: dependency: transitive description: @@ -367,10 +367,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -411,10 +411,10 @@ packages: dependency: "direct main" description: name: g_recaptcha_v3 - sha256: ee754d0a5621646e200b2d5d9c918d64725fb53d6a1b65cbd6f294c08e0fe529 + sha256: b493d9bbad64bb4631a2b7bb86f7b4c6c6a3c2b327729c4130b3c95817bedf29 url: "https://pub.dev" source: hosted - version: "0.0.5" + version: "0.0.6" get_it: dependency: "direct main" description: @@ -443,10 +443,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -488,10 +488,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: @@ -528,10 +528,10 @@ packages: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" logging: dependency: transitive description: @@ -544,10 +544,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: ce2bb2605753915080e4ee47f036a64228c88dc7f56f7bc1dbe912d75b55b1e2 + sha256: "6a24ade5d3d918c306bb1c21a6b9a04aab0489d51a2582522eea820b4093b62b" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" matcher: dependency: transitive description: @@ -608,18 +608,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "8.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" path: dependency: transitive description: @@ -837,10 +837,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.2.6" url_launcher_android: dependency: transitive description: @@ -885,10 +885,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.1" url_launcher_windows: dependency: transitive description: @@ -957,18 +957,18 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.5" webdriver: dependency: transitive description: @@ -1002,5 +1002,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 401a4af..cd3fe37 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,30 +12,30 @@ dependencies: email_validator: ^2.1.17 envied: ^0.5.4+1 equatable: ^2.0.5 - firebase_analytics: ^10.8.5 - firebase_core: ^2.25.4 + firebase_analytics: ^10.10.6 + firebase_core: ^2.31.1 firebase_core_platform_interface: ^5.0.0 - firebase_core_web: ^2.11.4 - firebase_remote_config: ^4.3.13 + firebase_core_web: ^2.17.0 + firebase_remote_config: ^4.4.6 flutter: sdk: flutter flutter_bloc: ^8.1.5 flutter_localizations: sdk: flutter - flutter_svg: ^2.0.9 - g_recaptcha_v3: ^0.0.5 + flutter_svg: ^2.0.10+1 + g_recaptcha_v3: ^0.0.6 get_it: ^7.6.7 intl: ^0.19.0 - lottie: ^3.0.0 - package_info_plus: ^5.0.1 + lottie: ^3.1.2 + package_info_plus: ^8.0.0 scrollable_positioned_list: ^0.3.8 - url_launcher: ^6.2.4 + url_launcher: ^6.2.6 url_strategy: ^0.2.0 dev_dependencies: - build_runner: ^2.4.9 + build_runner: ^2.4.10 envied_generator: ^0.5.4+1 - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter integration_test: From b35bb51038013143e5fc1022ea538905ced9c7c4 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sun, 26 May 2024 10:09:30 -0300 Subject: [PATCH 41/50] chore: optimizing build, using BlocListener instead of BlocConsumer --- .../widgets/ui/contact_widget.dart | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart index ae60ee5..85eef4e 100644 --- a/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart +++ b/lib/app/features/contact/presentation/widgets/ui/contact_widget.dart @@ -31,7 +31,7 @@ class ContactWidget extends StatelessWidget { final form = BlocProvider( create: (context) => _contactCubit ?? _cubit, - child: BlocConsumer( + child: BlocListener( listener: (context, state) { if ([ContactSuccess, ContactError].contains(state.runtimeType)) { appShowSnackBarFromContact(context, state); @@ -48,27 +48,25 @@ class ContactWidget extends StatelessWidget { } } }, - builder: (context, state) { - return CustomForm( - formKey: formKey, - nameController: nameController, - emailController: emailController, - subjectController: subjectController, - messageController: messageController, - onPressed: () { - if (formKey.currentState?.validate() ?? false) { - _contactCubit?.sendMail( - contact: ContactUser( - name: nameController.text, - email: emailController.text, - subject: subjectController.text, - message: messageController.text, - ), - ); - } - }, - ); - }, + child: CustomForm( + formKey: formKey, + nameController: nameController, + emailController: emailController, + subjectController: subjectController, + messageController: messageController, + onPressed: () { + if (formKey.currentState?.validate() ?? false) { + _contactCubit?.sendMail( + contact: ContactUser( + name: nameController.text, + email: emailController.text, + subject: subjectController.text, + message: messageController.text, + ), + ); + } + }, + ), ), ); From 49c3b3297c2c1fb2ad6d4fb0782fff3dd1328460 Mon Sep 17 00:00:00 2001 From: Felipe Sales Date: Sun, 26 May 2024 10:41:48 -0300 Subject: [PATCH 42/50] build: bump web (index.html) to Flutter 3.22.0 --- web/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/index.html b/web/index.html index 755ded1..b2b724a 100644 --- a/web/index.html +++ b/web/index.html @@ -76,12 +76,12 @@