From 9788f9c09b37c4427a8327b058e40aeb2d9effa3 Mon Sep 17 00:00:00 2001 From: dungngminh <63831488+dungngminh@users.noreply.github.com> Date: Wed, 31 Jan 2024 02:06:13 +0700 Subject: [PATCH 01/20] add chopper --- mobile/build.yaml | 10 + .../remote/auth/auth_service.chopper.dart | 34 ++ mobile/lib/data/remote/auth/auth_service.dart | 18 + .../auth/requests/login_request_body.dart | 13 + .../auth/requests/login_request_body.g.dart | 13 + .../auth/requests/register_request_body.dart | 19 + .../requests/register_request_body.g.dart | 16 + .../remote/auth/responses/login_response.dart | 14 + .../auth/responses/login_response.g.dart | 13 + mobile/lib/di/di.config.dart | 50 +++ mobile/lib/di/di.dart | 12 + mobile/lib/di/modules/local_module.dart | 0 mobile/lib/di/modules/network_module.dart | 34 ++ .../packages/http_client_handler/pubspec.yaml | 8 +- mobile/pubspec.lock | 406 +++++++++++++----- mobile/pubspec.yaml | 7 + 16 files changed, 557 insertions(+), 110 deletions(-) create mode 100644 mobile/build.yaml create mode 100644 mobile/lib/data/remote/auth/auth_service.chopper.dart create mode 100644 mobile/lib/data/remote/auth/auth_service.dart create mode 100644 mobile/lib/data/remote/auth/requests/login_request_body.dart create mode 100644 mobile/lib/data/remote/auth/requests/login_request_body.g.dart create mode 100644 mobile/lib/data/remote/auth/requests/register_request_body.dart create mode 100644 mobile/lib/data/remote/auth/requests/register_request_body.g.dart create mode 100644 mobile/lib/data/remote/auth/responses/login_response.dart create mode 100644 mobile/lib/data/remote/auth/responses/login_response.g.dart create mode 100644 mobile/lib/di/di.config.dart create mode 100644 mobile/lib/di/modules/local_module.dart create mode 100644 mobile/lib/di/modules/network_module.dart diff --git a/mobile/build.yaml b/mobile/build.yaml new file mode 100644 index 0000000..83f1cca --- /dev/null +++ b/mobile/build.yaml @@ -0,0 +1,10 @@ +targets: + $default: + builders: + json_serializable: + options: + # Options configure how source code is generated for every + # `@JsonSerializable`-annotated class in the package. + # + # The default value for each is listed. + field_rename: snake \ No newline at end of file diff --git a/mobile/lib/data/remote/auth/auth_service.chopper.dart b/mobile/lib/data/remote/auth/auth_service.chopper.dart new file mode 100644 index 0000000..20b5d83 --- /dev/null +++ b/mobile/lib/data/remote/auth/auth_service.chopper.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_service.dart'; + +// ************************************************************************** +// ChopperGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +final class _$AuthService extends AuthService { + _$AuthService([ChopperClient? client]) { + if (client == null) return; + this.client = client; + } + + @override + final Type definitionType = AuthService; + + @override + Future login(LoginRequestBody body) async { + final Uri $url = Uri.parse('api/auth/login'); + final $body = body; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + final Response $response = + await client.send($request); + return $response.bodyOrThrow; + } +} diff --git a/mobile/lib/data/remote/auth/auth_service.dart b/mobile/lib/data/remote/auth/auth_service.dart new file mode 100644 index 0000000..e28638a --- /dev/null +++ b/mobile/lib/data/remote/auth/auth_service.dart @@ -0,0 +1,18 @@ +import 'package:chopper/chopper.dart'; +import 'package:injectable/injectable.dart'; +import 'package:very_good_blog_app/data/remote/auth/requests/login_request_body.dart'; +import 'package:very_good_blog_app/data/remote/auth/responses/login_response.dart'; + +part 'auth_service.chopper.dart'; + +@lazySingleton +@ChopperApi(baseUrl: 'api/auth') +abstract class AuthService extends ChopperService { + @factoryMethod + static AuthService create(@Named('unAuthClient') ChopperClient client) { + return _$AuthService(client); + } + + @Post(path: 'login') + Future login(@Body() LoginRequestBody body); +} diff --git a/mobile/lib/data/remote/auth/requests/login_request_body.dart b/mobile/lib/data/remote/auth/requests/login_request_body.dart new file mode 100644 index 0000000..a36e49f --- /dev/null +++ b/mobile/lib/data/remote/auth/requests/login_request_body.dart @@ -0,0 +1,13 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'login_request_body.g.dart'; + +@JsonSerializable(createFactory: false) +class LoginRequestBody { + LoginRequestBody({required this.email, required this.password}); + + final String email; + final String password; + + Map toJson() => _$LoginRequestBodyToJson(this); +} diff --git a/mobile/lib/data/remote/auth/requests/login_request_body.g.dart b/mobile/lib/data/remote/auth/requests/login_request_body.g.dart new file mode 100644 index 0000000..c0c019b --- /dev/null +++ b/mobile/lib/data/remote/auth/requests/login_request_body.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_request_body.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Map _$LoginRequestBodyToJson(LoginRequestBody instance) => + { + 'email': instance.email, + 'password': instance.password, + }; diff --git a/mobile/lib/data/remote/auth/requests/register_request_body.dart b/mobile/lib/data/remote/auth/requests/register_request_body.dart new file mode 100644 index 0000000..c0d4af0 --- /dev/null +++ b/mobile/lib/data/remote/auth/requests/register_request_body.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'register_request_body.g.dart'; + +@JsonSerializable(createFactory: false) +class RegisterRequestBody { + RegisterRequestBody({ + required this.email, + required this.password, + required this.confirmationPassword, + required this.fullName, + }); + final String email; + final String password; + final String confirmationPassword; + final String fullName; + + Map toJson() => _$RegisterRequestBodyToJson(this); +} diff --git a/mobile/lib/data/remote/auth/requests/register_request_body.g.dart b/mobile/lib/data/remote/auth/requests/register_request_body.g.dart new file mode 100644 index 0000000..346bfeb --- /dev/null +++ b/mobile/lib/data/remote/auth/requests/register_request_body.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'register_request_body.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Map _$RegisterRequestBodyToJson( + RegisterRequestBody instance) => + { + 'email': instance.email, + 'password': instance.password, + 'confirmation_password': instance.confirmationPassword, + 'full_name': instance.fullName, + }; diff --git a/mobile/lib/data/remote/auth/responses/login_response.dart b/mobile/lib/data/remote/auth/responses/login_response.dart new file mode 100644 index 0000000..f9ef1e4 --- /dev/null +++ b/mobile/lib/data/remote/auth/responses/login_response.dart @@ -0,0 +1,14 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'login_response.g.dart'; + +@JsonSerializable(createToJson: false) +class LoginResponse { + LoginResponse({required this.id, required this.token}); + + factory LoginResponse.fromJson(Map json) => + _$LoginResponseFromJson(json); + + final String id; + final String token; +} diff --git a/mobile/lib/data/remote/auth/responses/login_response.g.dart b/mobile/lib/data/remote/auth/responses/login_response.g.dart new file mode 100644 index 0000000..71a181d --- /dev/null +++ b/mobile/lib/data/remote/auth/responses/login_response.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LoginResponse _$LoginResponseFromJson(Map json) => + LoginResponse( + id: json['id'] as String, + token: json['token'] as String, + ); diff --git a/mobile/lib/di/di.config.dart b/mobile/lib/di/di.config.dart new file mode 100644 index 0000000..d8401a2 --- /dev/null +++ b/mobile/lib/di/di.config.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// InjectableConfigGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:chopper/chopper.dart' as _i3; +import 'package:get_it/get_it.dart' as _i1; +import 'package:injectable/injectable.dart' as _i2; + +import '../data/remote/auth/auth_service.dart' as _i4; +import 'modules/network_module.dart' as _i5; + +extension GetItInjectableX on _i1.GetIt { +// initializes the registration of main-scope dependencies inside of GetIt + _i1.GetIt init({ + String? environment, + _i2.EnvironmentFilter? environmentFilter, + }) { + final gh = _i2.GetItHelper( + this, + environment, + environmentFilter, + ); + final networkModule = _$NetworkModule(); + gh.factory( + () => networkModule.baseUrl, + instanceName: 'baseUrl', + ); + gh.lazySingleton<_i3.ChopperClient>( + () => networkModule + .unAuthChopperClient(gh(instanceName: 'baseUrl')), + instanceName: 'unAuthClient', + ); + gh.lazySingleton<_i3.ChopperClient>( + () => + networkModule.authChopperClient(gh(instanceName: 'baseUrl')), + instanceName: 'authClient', + ); + gh.lazySingleton<_i4.AuthService>(() => _i4.AuthService.create( + gh<_i3.ChopperClient>(instanceName: 'unAuthClient'))); + return this; + } +} + +class _$NetworkModule extends _i5.NetworkModule {} diff --git a/mobile/lib/di/di.dart b/mobile/lib/di/di.dart index d1dae81..7a20bd4 100644 --- a/mobile/lib/di/di.dart +++ b/mobile/lib/di/di.dart @@ -1,14 +1,26 @@ import 'package:authentication_data_source/authentication_data_source.dart'; import 'package:blog_data_source/blog_data_source.dart'; import 'package:bookmark_data_source/bookmark_data_source.dart'; +import 'package:chopper/chopper.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:hive_flutter/hive_flutter.dart'; +import 'package:injectable/injectable.dart'; import 'package:user_repository/user_repository.dart'; import 'package:very_good_blog_app/app/app.dart'; +import 'package:very_good_blog_app/data/remote/auth/auth_service.dart'; + +import 'di.config.dart'; final injector = GetIt.instance; +@InjectableInit( + initializerName: 'init', // default + preferRelativeImports: true, // default + asExtension: true, // default +) +void configureDependencies() => injector.init(); + Future initServices() async { await Hive.openBox(HiveBox.userPrefs); final _bookmarkBox = await Hive.openBox(HiveBox.bookmark); diff --git a/mobile/lib/di/modules/local_module.dart b/mobile/lib/di/modules/local_module.dart new file mode 100644 index 0000000..e69de29 diff --git a/mobile/lib/di/modules/network_module.dart b/mobile/lib/di/modules/network_module.dart new file mode 100644 index 0000000..b3f7922 --- /dev/null +++ b/mobile/lib/di/modules/network_module.dart @@ -0,0 +1,34 @@ +import 'package:chopper/chopper.dart'; +import 'package:injectable/injectable.dart'; + +@module +abstract class NetworkModule { + @Named('baseUrl') + String get baseUrl => 'https://very-good-blog-app.up.railway.app'; + + @Named('unAuthClient') + @lazySingleton + ChopperClient unAuthChopperClient(@Named('baseUrl') String baseUrl) { + return ChopperClient( + baseUrl: Uri.parse(baseUrl), + converter: const JsonConverter(), + interceptors: [ + CurlInterceptor(), + HttpLoggingInterceptor(), + ], + ); + } + + @Named('authClient') + @lazySingleton + ChopperClient authChopperClient(@Named('baseUrl') String baseUrl) { + return ChopperClient( + baseUrl: Uri.parse(baseUrl), + converter: const JsonConverter(), + interceptors: [ + CurlInterceptor(), + HttpLoggingInterceptor(), + ], + ); + } +} diff --git a/mobile/packages/http_client_handler/pubspec.yaml b/mobile/packages/http_client_handler/pubspec.yaml index c12f529..bff9f3a 100644 --- a/mobile/packages/http_client_handler/pubspec.yaml +++ b/mobile/packages/http_client_handler/pubspec.yaml @@ -4,10 +4,12 @@ version: 0.1.0+1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=3.1.0 <4.0.0" + +dependencies: + http: ^1.2.0 + -dependencies: {http: ^0.13.5} - dev_dependencies: mocktail: ^0.3.0 diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 8e81575..d4ae9b8 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "64.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: eb0ac20f704799b986049fbb3c1c16421eca319a1b872378d669513e12452ba5 + sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7 url: "https://pub.dev" source: hosted - version: "1.3.14" + version: "1.3.16" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.2.0" args: dependency: transitive description: @@ -107,30 +107,94 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + url: "https://pub.dev" + source: hosted + version: "7.2.11" + 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: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 + url: "https://pub.dev" + source: hosted + version: "8.9.0" cached_network_image: dependency: "direct main" description: name: cached_network_image - sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" characters: dependency: transitive description: @@ -147,6 +211,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + chopper: + dependency: "direct main" + description: + name: chopper + sha256: d1457197abb29ee354889fda35a7ac2e752eb5a687562148afb1faa78a61e36d + url: "https://pub.dev" + source: hosted + version: "7.1.1" + chopper_generator: + dependency: "direct dev" + description: + name: chopper_generator + sha256: "31e74e3b0cf6d694822c9fea0d28e28dc05e8523336c7aa109e40a61c467c048" + url: "https://pub.dev" + source: hosted + version: "7.1.1" clock: dependency: transitive description: @@ -162,6 +250,14 @@ packages: relative: true source: path version: "1.1.0-dev.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -198,18 +294,18 @@ packages: dependency: transitive description: name: coverage - sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9 + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" url: "https://pub.dev" source: hosted - version: "1.7.1" + version: "1.7.2" cross_file: dependency: transitive description: name: cross_file - sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e url: "https://pub.dev" source: hosted - version: "0.3.3+7" + version: "0.3.3+8" crypto: dependency: transitive description: @@ -234,6 +330,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + url: "https://pub.dev" + source: hosted + version: "2.3.4" dbus: dependency: transitive description: @@ -334,10 +438,10 @@ packages: dependency: transitive description: name: file_selector_platform_interface - sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262" + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.6.2" file_selector_windows: dependency: transitive description: @@ -350,10 +454,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: d301561d614487688d797717bef013a264c517d1d09e4c5c1325c3a64c835efb + sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb" url: "https://pub.dev" source: hosted - version: "2.24.0" + version: "2.24.2" firebase_core_platform_interface: dependency: transitive description: @@ -366,50 +470,50 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "10159d9ee42c79f4548971d92f3f0fcd5791f6738cda3583a4e3b2c8b244c018" + sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0 url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" firebase_messaging: dependency: transitive description: name: firebase_messaging - sha256: "260064e1b512a9e1970b5964d645eba888208ca3de42459c38e484c8ecdc37a9" + sha256: "980259425fa5e2afc03e533f33723335731d21a56fd255611083bceebf4373a8" url: "https://pub.dev" source: hosted - version: "14.7.6" + version: "14.7.10" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "81fb8c983356dd75ee660f276c918380325df7a1ab1e981ede911809e9ddff30" + sha256: "54e283a0e41d81d854636ad0dad73066adc53407a60a7c3189c9656e2f1b6107" url: "https://pub.dev" source: hosted - version: "4.5.15" + version: "4.5.18" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "1c5d9b6cf929ab471300143059d1641a26b73c9c24adb5266e241aea23c090aa" + sha256: "90dc7ed885e90a24bb0e56d661d4d2b5f84429697fd2cbb9e5890a0ca370e6f4" url: "https://pub.dev" source: hosted - version: "3.5.15" + version: "3.5.18" firebase_storage: dependency: "direct main" description: name: firebase_storage - sha256: ef1974043d48b0aa081ad055944ec74cad3a665545849cc6232f8ac1691fd901 + sha256: "75e6cb6bed65138b5bbd86bfd7cf9bc9a175fb0c31aacc400e9203df117ffbe6" url: "https://pub.dev" source: hosted - version: "11.5.3" + version: "11.6.0" firebase_storage_platform_interface: dependency: transitive description: name: firebase_storage_platform_interface - sha256: "7b5aa0bf53de4a983aa3df57ab2bc0509d75fe2d269a0029a814aa84484798d5" + sha256: "545a3a8edf337850403bb0fa03c8074a53deb87c0107d19755c77a82ce07919e" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.1.3" firebase_storage_service: dependency: "direct main" description: @@ -421,10 +525,18 @@ packages: dependency: transitive description: name: firebase_storage_web - sha256: bc5ede7fd6dfe7e23821cce007cd551cf77f47f44f8c3ea4f270dea0372051dc + sha256: ee6870ff79aa304b8996ba18a4aefe1e8b3fc31fd385eab6574180267aa8d393 url: "https://pub.dev" source: hosted - version: "3.6.15" + version: "3.6.17" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -657,10 +769,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: f79870884de16d689cf9a7d15eedf31ed61d750e813c538a6efb92660fea83c3 + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 url: "https://pub.dev" source: hosted - version: "7.6.4" + version: "7.6.7" glob: dependency: transitive description: @@ -673,10 +785,18 @@ packages: dependency: "direct main" description: name: go_router - sha256: "3b40e751eaaa855179b416974d59d29669e750d2e50fcdb2b37f1cb0ca8c803a" + sha256: "07ee2436909f749d606f53521dc1725dd738dc5196e5ff815bc254253c594075" url: "https://pub.dev" source: hosted - version: "13.0.1" + version: "13.1.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" hive: dependency: "direct main" description: @@ -713,10 +833,10 @@ packages: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.2.0" http_client_handler: dependency: "direct main" description: @@ -744,34 +864,34 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84" + sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.7" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: d6a6e78821086b0b737009b09363018309bbc6de3fd88cc5c26bc2bb44a4957f + sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1" url: "https://pub.dev" source: hosted - version: "0.8.8+2" + version: "0.8.9+3" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "50bc9ae6a77eea3a8b11af5eb6c661eeb858fdd2f734c2a4fd17086922347ef7" + sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "76ec722aeea419d03aa915c2c96bf5b47214b053899088c9abb4086ceecf97a7" + sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3 url: "https://pub.dev" source: hosted - version: "0.8.8+4" + version: "0.8.9+1" image_picker_linux: dependency: transitive description: @@ -792,10 +912,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514 + sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.9.3" image_picker_windows: dependency: transitive description: @@ -804,6 +924,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1+1" + injectable: + dependency: "direct main" + description: + name: injectable + sha256: cd3c422e13270c81f64ab73c80406b2b2ed563fe59d0ff2093eb7eee63d0bbeb + url: "https://pub.dev" + source: hosted + version: "2.3.2" + injectable_generator: + dependency: "direct dev" + description: + name: injectable_generator + sha256: f9d3c05f0938403f79ad6c6d23ec8e37a7a05ad980b1bf9399493f3e41845788 + url: "https://pub.dev" + source: hosted + version: "2.4.1" intl: dependency: "direct main" description: @@ -845,21 +981,29 @@ packages: source: hosted version: "0.6.7" json_annotation: - dependency: transitive + dependency: "direct main" description: name: json_annotation sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted version: "4.8.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + url: "https://pub.dev" + source: hosted + version: "6.7.1" lazy_load_indexed_stack: dependency: "direct main" description: name: lazy_load_indexed_stack - sha256: c56c9c292048ecd8c86008617c7d77aa4057cafcdae1c5269aec6fb75019eebf + sha256: e581426391c44708210b1f571046320ea52dea46740d4972d9e1820b4ef94827 url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" logger: dependency: transitive description: @@ -912,18 +1056,18 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mocktail: dependency: "direct dev" description: name: mocktail - sha256: bac151b31e4ed78bd59ab89aa4c0928f297b1180186d5daf03734519e5f596c1 + sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.3" models: dependency: "direct main" description: @@ -991,26 +1135,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -1023,10 +1167,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -1039,10 +1183,10 @@ packages: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" phosphor_flutter: dependency: "direct main" description: @@ -1063,18 +1207,18 @@ packages: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pool: dependency: transitive description: @@ -1107,6 +1251,14 @@ packages: 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" quiver: dependency: transitive description: @@ -1115,6 +1267,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" rxdart: dependency: transitive description: @@ -1175,6 +1335,22 @@ packages: description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" source_map_stack_trace: dependency: transitive description: @@ -1211,18 +1387,18 @@ packages: dependency: transitive description: name: sqflite - sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" url: "https://pub.dev" source: hosted - version: "2.5.0+2" + version: "2.5.3" stack_trace: dependency: transitive description: @@ -1239,6 +1415,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: @@ -1267,10 +1451,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -1307,10 +1491,10 @@ packages: dependency: "direct main" description: name: timeago - sha256: c44b80cbc6b44627c00d76960f2af571f6f50e5dbedef4d9215d455e4335165b + sha256: d3204eb4c788214883380253da7f23485320a58c11d145babc82ad16bf4e7764 url: "https://pub.dev" source: hosted - version: "3.6.0" + version: "3.6.1" timezone: dependency: transitive description: @@ -1319,6 +1503,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.2" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" typed_data: dependency: transitive description: @@ -1331,34 +1523,34 @@ packages: dependency: transitive description: name: url_launcher - sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -1371,26 +1563,26 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7" + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" user_data_source: dependency: "direct main" description: @@ -1409,34 +1601,34 @@ packages: dependency: transitive description: name: uuid - sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "4.3.3" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -1497,18 +1689,18 @@ packages: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.2.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" xdg_directories: dependency: transitive description: @@ -1521,10 +1713,10 @@ packages: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" yaml: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 1dc957b..0db216f 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: bookmark_repository: path: packages/bookmark_repository cached_network_image: ^3.2.3 + chopper: ^7.1.1 cloud_messaging_service: path: packages/cloud_messaging_service connectivity_plus: ^5.0.2 @@ -51,7 +52,9 @@ dependencies: http_client_handler: path: packages/http_client_handler image_picker: ^1.0.4 + injectable: ^2.3.2 intl: ^0.18.1 + json_annotation: ^4.8.1 lazy_load_indexed_stack: ^1.0.1 models: path: packages/models @@ -68,8 +71,12 @@ dependencies: dev_dependencies: bloc_test: ^9.1.0 + build_runner: ^2.4.8 + chopper_generator: ^7.1.1 flutter_test: sdk: flutter + injectable_generator: ^2.4.1 + json_serializable: ^6.7.1 mocktail: ^1.0.1 very_good_analysis: ^5.1.0 From 733e6d204b4d1169d2ee761b9a1d59b3f719546a Mon Sep 17 00:00:00 2001 From: dungngminh <63831488+dungngminh@users.noreply.github.com> Date: Wed, 31 Jan 2024 02:10:37 +0700 Subject: [PATCH 02/20] add register by chopper --- .../lib/data/remote/auth/auth_service.chopper.dart | 14 ++++++++++++++ mobile/lib/data/remote/auth/auth_service.dart | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/mobile/lib/data/remote/auth/auth_service.chopper.dart b/mobile/lib/data/remote/auth/auth_service.chopper.dart index 20b5d83..dc281ac 100644 --- a/mobile/lib/data/remote/auth/auth_service.chopper.dart +++ b/mobile/lib/data/remote/auth/auth_service.chopper.dart @@ -31,4 +31,18 @@ final class _$AuthService extends AuthService { await client.send($request); return $response.bodyOrThrow; } + + @override + Future register(RegisterRequestBody body) async { + final Uri $url = Uri.parse('api/auth/register'); + final $body = body; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + final Response $response = await client.send($request); + return $response.bodyOrThrow; + } } diff --git a/mobile/lib/data/remote/auth/auth_service.dart b/mobile/lib/data/remote/auth/auth_service.dart index e28638a..34f4a9d 100644 --- a/mobile/lib/data/remote/auth/auth_service.dart +++ b/mobile/lib/data/remote/auth/auth_service.dart @@ -1,6 +1,7 @@ import 'package:chopper/chopper.dart'; import 'package:injectable/injectable.dart'; import 'package:very_good_blog_app/data/remote/auth/requests/login_request_body.dart'; +import 'package:very_good_blog_app/data/remote/auth/requests/register_request_body.dart'; import 'package:very_good_blog_app/data/remote/auth/responses/login_response.dart'; part 'auth_service.chopper.dart'; @@ -15,4 +16,7 @@ abstract class AuthService extends ChopperService { @Post(path: 'login') Future login(@Body() LoginRequestBody body); + + @Post(path: 'register') + Future register(@Body() RegisterRequestBody body); } From 3ffb9d7f783f217f69107389a04fd710dfc2ed51 Mon Sep 17 00:00:00 2001 From: dungngminh <63831488+dungngminh@users.noreply.github.com> Date: Thu, 1 Feb 2024 01:11:06 +0700 Subject: [PATCH 03/20] di and inject local storage --- .../local/user_secure_storage.dart | 42 +++++++++++++++ .../remote/auth/auth_service.chopper.dart | 0 .../remote/auth/auth_service.dart | 6 +-- .../auth/requests/login_request_body.dart | 0 .../auth/requests/login_request_body.g.dart | 0 .../auth/requests/register_request_body.dart | 0 .../requests/register_request_body.g.dart | 0 .../remote/auth/responses/login_response.dart | 0 .../auth/responses/login_response.g.dart | 0 .../repositories/auth_repository_impl.dart | 51 +++++++++++++++++++ mobile/lib/di/di.config.dart | 31 ++++++++--- mobile/lib/di/di.dart | 17 +++---- mobile/lib/di/modules/local_module.dart | 10 ++++ .../domain/repositories/auth_repository.dart | 12 +++++ mobile/pubspec.lock | 2 +- mobile/pubspec.yaml | 1 + 16 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 mobile/lib/data/datasources/local/user_secure_storage.dart rename mobile/lib/data/{ => datasources}/remote/auth/auth_service.chopper.dart (100%) rename mobile/lib/data/{ => datasources}/remote/auth/auth_service.dart (63%) rename mobile/lib/data/{ => datasources}/remote/auth/requests/login_request_body.dart (100%) rename mobile/lib/data/{ => datasources}/remote/auth/requests/login_request_body.g.dart (100%) rename mobile/lib/data/{ => datasources}/remote/auth/requests/register_request_body.dart (100%) rename mobile/lib/data/{ => datasources}/remote/auth/requests/register_request_body.g.dart (100%) rename mobile/lib/data/{ => datasources}/remote/auth/responses/login_response.dart (100%) rename mobile/lib/data/{ => datasources}/remote/auth/responses/login_response.g.dart (100%) create mode 100644 mobile/lib/data/repositories/auth_repository_impl.dart create mode 100644 mobile/lib/domain/repositories/auth_repository.dart diff --git a/mobile/lib/data/datasources/local/user_secure_storage.dart b/mobile/lib/data/datasources/local/user_secure_storage.dart new file mode 100644 index 0000000..8e5ac20 --- /dev/null +++ b/mobile/lib/data/datasources/local/user_secure_storage.dart @@ -0,0 +1,42 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:injectable/injectable.dart'; + +const accessTokenKey = 'ACCESS_TOKEN'; +const userIdKey = 'USER_ID'; + +abstract class UserSecureStorage { + Future get accessToken; + + Future get userId; + + Future setUserId(String userId); + + Future setAccessToken(String accessToken); + + Future removeAllKeys(); +} + +@LazySingleton(as: UserSecureStorage) +class UserSecureStorageImpl implements UserSecureStorage { + UserSecureStorageImpl({required FlutterSecureStorage secureStorage}) + : _secureStorage = secureStorage; + + final FlutterSecureStorage _secureStorage; + + @override + Future get accessToken => _secureStorage.read(key: accessTokenKey); + + @override + Future get userId => _secureStorage.read(key: userIdKey); + + @override + Future setAccessToken(String accessToken) => + _secureStorage.write(key: accessTokenKey, value: accessToken); + + @override + Future setUserId(String userId) => + _secureStorage.write(key: userIdKey, value: userId); + + @override + Future removeAllKeys() => _secureStorage.deleteAll(); +} diff --git a/mobile/lib/data/remote/auth/auth_service.chopper.dart b/mobile/lib/data/datasources/remote/auth/auth_service.chopper.dart similarity index 100% rename from mobile/lib/data/remote/auth/auth_service.chopper.dart rename to mobile/lib/data/datasources/remote/auth/auth_service.chopper.dart diff --git a/mobile/lib/data/remote/auth/auth_service.dart b/mobile/lib/data/datasources/remote/auth/auth_service.dart similarity index 63% rename from mobile/lib/data/remote/auth/auth_service.dart rename to mobile/lib/data/datasources/remote/auth/auth_service.dart index 34f4a9d..0945a11 100644 --- a/mobile/lib/data/remote/auth/auth_service.dart +++ b/mobile/lib/data/datasources/remote/auth/auth_service.dart @@ -1,8 +1,8 @@ import 'package:chopper/chopper.dart'; import 'package:injectable/injectable.dart'; -import 'package:very_good_blog_app/data/remote/auth/requests/login_request_body.dart'; -import 'package:very_good_blog_app/data/remote/auth/requests/register_request_body.dart'; -import 'package:very_good_blog_app/data/remote/auth/responses/login_response.dart'; +import 'package:very_good_blog_app/data/datasources/remote/auth/requests/login_request_body.dart'; +import 'package:very_good_blog_app/data/datasources/remote/auth/requests/register_request_body.dart'; +import 'package:very_good_blog_app/data/datasources/remote/auth/responses/login_response.dart'; part 'auth_service.chopper.dart'; diff --git a/mobile/lib/data/remote/auth/requests/login_request_body.dart b/mobile/lib/data/datasources/remote/auth/requests/login_request_body.dart similarity index 100% rename from mobile/lib/data/remote/auth/requests/login_request_body.dart rename to mobile/lib/data/datasources/remote/auth/requests/login_request_body.dart diff --git a/mobile/lib/data/remote/auth/requests/login_request_body.g.dart b/mobile/lib/data/datasources/remote/auth/requests/login_request_body.g.dart similarity index 100% rename from mobile/lib/data/remote/auth/requests/login_request_body.g.dart rename to mobile/lib/data/datasources/remote/auth/requests/login_request_body.g.dart diff --git a/mobile/lib/data/remote/auth/requests/register_request_body.dart b/mobile/lib/data/datasources/remote/auth/requests/register_request_body.dart similarity index 100% rename from mobile/lib/data/remote/auth/requests/register_request_body.dart rename to mobile/lib/data/datasources/remote/auth/requests/register_request_body.dart diff --git a/mobile/lib/data/remote/auth/requests/register_request_body.g.dart b/mobile/lib/data/datasources/remote/auth/requests/register_request_body.g.dart similarity index 100% rename from mobile/lib/data/remote/auth/requests/register_request_body.g.dart rename to mobile/lib/data/datasources/remote/auth/requests/register_request_body.g.dart diff --git a/mobile/lib/data/remote/auth/responses/login_response.dart b/mobile/lib/data/datasources/remote/auth/responses/login_response.dart similarity index 100% rename from mobile/lib/data/remote/auth/responses/login_response.dart rename to mobile/lib/data/datasources/remote/auth/responses/login_response.dart diff --git a/mobile/lib/data/remote/auth/responses/login_response.g.dart b/mobile/lib/data/datasources/remote/auth/responses/login_response.g.dart similarity index 100% rename from mobile/lib/data/remote/auth/responses/login_response.g.dart rename to mobile/lib/data/datasources/remote/auth/responses/login_response.g.dart diff --git a/mobile/lib/data/repositories/auth_repository_impl.dart b/mobile/lib/data/repositories/auth_repository_impl.dart new file mode 100644 index 0000000..b8c4784 --- /dev/null +++ b/mobile/lib/data/repositories/auth_repository_impl.dart @@ -0,0 +1,51 @@ +import 'package:injectable/injectable.dart'; +import 'package:very_good_blog_app/data/datasources/local/user_secure_storage.dart'; +import 'package:very_good_blog_app/data/datasources/remote/auth/auth_service.dart'; +import 'package:very_good_blog_app/data/datasources/remote/auth/requests/login_request_body.dart'; +import 'package:very_good_blog_app/data/datasources/remote/auth/requests/register_request_body.dart'; +import 'package:very_good_blog_app/domain/repositories/auth_repository.dart'; + +@LazySingleton(as: AuthRepository) +class AuthRepositoryImpl implements AuthRepository { + AuthRepositoryImpl({ + required AuthService authService, + required UserSecureStorage secureStorage, + }) : _authService = authService, + _secureStorage = secureStorage; + + final AuthService _authService; + final UserSecureStorage _secureStorage; + + @override + Future login({required String email, required String password}) { + return _authService + .login(LoginRequestBody(email: email, password: password)) + .then((response) { + _secureStorage + ..setAccessToken(response.token) + ..setUserId(response.id); + }); + } + + @override + Future register({ + required String email, + required String password, + required String confirmationPassword, + required String fullName, + }) { + return _authService.register( + RegisterRequestBody( + email: email, + password: password, + confirmationPassword: confirmationPassword, + fullName: fullName, + ), + ); + } + + @override + Future signOut() { + return _secureStorage.removeAllKeys(); + } +} diff --git a/mobile/lib/di/di.config.dart b/mobile/lib/di/di.config.dart index d8401a2..0efe1a6 100644 --- a/mobile/lib/di/di.config.dart +++ b/mobile/lib/di/di.config.dart @@ -8,12 +8,17 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:chopper/chopper.dart' as _i3; +import 'package:chopper/chopper.dart' as _i5; +import 'package:flutter_secure_storage/flutter_secure_storage.dart' as _i3; import 'package:get_it/get_it.dart' as _i1; import 'package:injectable/injectable.dart' as _i2; -import '../data/remote/auth/auth_service.dart' as _i4; -import 'modules/network_module.dart' as _i5; +import '../data/datasources/local/user_secure_storage.dart' as _i4; +import '../data/datasources/remote/auth/auth_service.dart' as _i6; +import '../data/repositories/auth_repository_impl.dart' as _i8; +import '../domain/repositories/auth_repository.dart' as _i7; +import 'modules/local_module.dart' as _i9; +import 'modules/network_module.dart' as _i10; extension GetItInjectableX on _i1.GetIt { // initializes the registration of main-scope dependencies inside of GetIt @@ -26,25 +31,35 @@ extension GetItInjectableX on _i1.GetIt { environment, environmentFilter, ); + final localModule = _$LocalModule(); final networkModule = _$NetworkModule(); + gh.lazySingleton<_i3.FlutterSecureStorage>(() => localModule.secureStorage); gh.factory( () => networkModule.baseUrl, instanceName: 'baseUrl', ); - gh.lazySingleton<_i3.ChopperClient>( + gh.lazySingleton<_i4.UserSecureStorage>(() => _i4.UserSecureStorageImpl( + secureStorage: gh<_i3.FlutterSecureStorage>())); + gh.lazySingleton<_i5.ChopperClient>( () => networkModule .unAuthChopperClient(gh(instanceName: 'baseUrl')), instanceName: 'unAuthClient', ); - gh.lazySingleton<_i3.ChopperClient>( + gh.lazySingleton<_i5.ChopperClient>( () => networkModule.authChopperClient(gh(instanceName: 'baseUrl')), instanceName: 'authClient', ); - gh.lazySingleton<_i4.AuthService>(() => _i4.AuthService.create( - gh<_i3.ChopperClient>(instanceName: 'unAuthClient'))); + gh.lazySingleton<_i6.AuthService>(() => _i6.AuthService.create( + gh<_i5.ChopperClient>(instanceName: 'unAuthClient'))); + gh.lazySingleton<_i7.AuthRepository>(() => _i8.AuthRepositoryImpl( + authService: gh<_i6.AuthService>(), + secureStorage: gh<_i4.UserSecureStorage>(), + )); return this; } } -class _$NetworkModule extends _i5.NetworkModule {} +class _$LocalModule extends _i9.LocalModule {} + +class _$NetworkModule extends _i10.NetworkModule {} diff --git a/mobile/lib/di/di.dart b/mobile/lib/di/di.dart index 7a20bd4..5a50772 100644 --- a/mobile/lib/di/di.dart +++ b/mobile/lib/di/di.dart @@ -1,25 +1,22 @@ import 'package:authentication_data_source/authentication_data_source.dart'; import 'package:blog_data_source/blog_data_source.dart'; import 'package:bookmark_data_source/bookmark_data_source.dart'; -import 'package:chopper/chopper.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:injectable/injectable.dart'; import 'package:user_repository/user_repository.dart'; import 'package:very_good_blog_app/app/app.dart'; -import 'package:very_good_blog_app/data/remote/auth/auth_service.dart'; - -import 'di.config.dart'; +import 'package:very_good_blog_app/di/di.config.dart'; final injector = GetIt.instance; -@InjectableInit( - initializerName: 'init', // default - preferRelativeImports: true, // default - asExtension: true, // default -) -void configureDependencies() => injector.init(); +@InjectableInit( + initializerName: 'init', // default + preferRelativeImports: true, // default + asExtension: true, // default +) +void configureDependencies() => injector.init(); Future initServices() async { await Hive.openBox(HiveBox.userPrefs); diff --git a/mobile/lib/di/modules/local_module.dart b/mobile/lib/di/modules/local_module.dart index e69de29..9d1bf3d 100644 --- a/mobile/lib/di/modules/local_module.dart +++ b/mobile/lib/di/modules/local_module.dart @@ -0,0 +1,10 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:injectable/injectable.dart'; + +@module +abstract class LocalModule { + @lazySingleton + FlutterSecureStorage get secureStorage => const FlutterSecureStorage( + aOptions: AndroidOptions(encryptedSharedPreferences: true), + ); +} diff --git a/mobile/lib/domain/repositories/auth_repository.dart b/mobile/lib/domain/repositories/auth_repository.dart new file mode 100644 index 0000000..6b14988 --- /dev/null +++ b/mobile/lib/domain/repositories/auth_repository.dart @@ -0,0 +1,12 @@ +abstract class AuthRepository { + Future login({required String email, required String password}); + + Future register({ + required String email, + required String password, + required String confirmationPassword, + required String fullName, + }); + + Future signOut(); +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index d4ae9b8..ec7f053 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -668,7 +668,7 @@ packages: source: hosted version: "9.2.10" flutter_secure_storage: - dependency: transitive + dependency: "direct main" description: name: flutter_secure_storage sha256: f2afec1f1762c040a349ea2a588e32f442da5d0db3494a52a929a97c9e550bc5 diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 0db216f..945173e 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: flutter_localizations: sdk: flutter flutter_quill: ^9.2.10 + flutter_secure_storage: ^7.0.1 flutter_slidable: ^3.0.1 flutter_svg: ^2.0.9 fluttertoast: ^8.1.2 From e71b985a41c6cf9f8569bd9221250ce3162c6103 Mon Sep 17 00:00:00 2001 From: dungngminh <63831488+dungngminh@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:53:09 +0700 Subject: [PATCH 04/20] refactor: login bloc --- .../config/extensions/context_extension.dart | 5 + .../config/extensions/string_extension.dart | 13 ++ mobile/lib/login/bloc/login_bloc.dart | 28 +-- mobile/lib/login/bloc/login_event.dart | 8 +- mobile/lib/login/bloc/login_state.dart | 10 +- mobile/lib/login/model/email.dart | 21 ++ mobile/lib/login/model/models.dart | 2 +- mobile/lib/login/model/username.dart | 13 -- mobile/lib/login/view/login_form.dart | 194 ++++++++---------- mobile/lib/login/view/login_view.dart | 5 +- mobile/pubspec.lock | 24 +++ mobile/pubspec.yaml | 2 + mobile/test/login/bloc/login_bloc_test.dart | 41 ++-- mobile/test/login/bloc/login_event_test.dart | 4 +- mobile/test/login/bloc/login_state_test.dart | 6 +- mobile/test/login/model/username_test.dart | 10 +- mobile/test/register/model/username_test.dart | 10 +- 17 files changed, 211 insertions(+), 185 deletions(-) create mode 100644 mobile/lib/app/config/extensions/string_extension.dart create mode 100644 mobile/lib/login/model/email.dart delete mode 100644 mobile/lib/login/model/username.dart diff --git a/mobile/lib/app/config/extensions/context_extension.dart b/mobile/lib/app/config/extensions/context_extension.dart index c3fe3e8..6cf5157 100644 --- a/mobile/lib/app/config/extensions/context_extension.dart +++ b/mobile/lib/app/config/extensions/context_extension.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; @@ -17,4 +18,8 @@ extension ContextExtension on BuildContext { String get currentLocation { return GoRouterState.of(this).uri.toString(); } + + ThemeData get theme { + return Theme.of(this); + } } diff --git a/mobile/lib/app/config/extensions/string_extension.dart b/mobile/lib/app/config/extensions/string_extension.dart new file mode 100644 index 0000000..2f7e3e9 --- /dev/null +++ b/mobile/lib/app/config/extensions/string_extension.dart @@ -0,0 +1,13 @@ +import 'package:dartx/dartx.dart'; + +extension StringExtension on String? { + bool isEmail() { + if (isNullOrEmpty || isNullOrBlank) { + return false; + } + final emailRegex = RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$', + ); + return emailRegex.hasMatch(this!); + } +} diff --git a/mobile/lib/login/bloc/login_bloc.dart b/mobile/lib/login/bloc/login_bloc.dart index 6789f9f..eb2bedb 100644 --- a/mobile/lib/login/bloc/login_bloc.dart +++ b/mobile/lib/login/bloc/login_bloc.dart @@ -1,7 +1,7 @@ -import 'package:authentication_repository/authentication_repository.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:formz/formz.dart'; +import 'package:very_good_blog_app/domain/repositories/auth_repository.dart'; import 'package:very_good_blog_app/login/model/models.dart'; part 'login_event.dart'; @@ -9,28 +9,28 @@ part 'login_state.dart'; class LoginBloc extends Bloc { LoginBloc({ - required AuthenticationRepository authenticationRepository, - }) : _authenticationRepository = authenticationRepository, + required AuthRepository authRepository, + }) : _authRepository = authRepository, super(const LoginState()) { - on(_onUsernameChanged); + on(_onEmailChanged); on(_onPasswordChanged); on(_onSubmitted); } - final AuthenticationRepository _authenticationRepository; + final AuthRepository _authRepository; - void _onUsernameChanged( - LoginUsernameChanged event, + void _onEmailChanged( + LoginEmailChanged event, Emitter emit, ) { - final username = Username.dirty(event.username); + final email = Email.dirty(event.email); emit( state.copyWith( - username: username, + email: email, password: state.password, isValid: Formz.validate( [ - username, + email, state.password, ], ), @@ -45,9 +45,9 @@ class LoginBloc extends Bloc { final password = Password.dirty(event.password); emit( state.copyWith( - username: state.username, + email: state.email, password: password, - isValid: Formz.validate([state.username, password]), + isValid: Formz.validate([state.email, password]), ), ); } @@ -58,8 +58,8 @@ class LoginBloc extends Bloc { ) async { if (!state.isValid) return; emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); - return _authenticationRepository - .login(password: state.password.value, username: state.username.value) + await _authRepository + .login(password: state.password.value, email: state.email.value) .then( (_) => emit(state.copyWith(status: FormzSubmissionStatus.success)), ) diff --git a/mobile/lib/login/bloc/login_event.dart b/mobile/lib/login/bloc/login_event.dart index 7c63fbf..04cc3b7 100644 --- a/mobile/lib/login/bloc/login_event.dart +++ b/mobile/lib/login/bloc/login_event.dart @@ -7,13 +7,13 @@ abstract class LoginEvent extends Equatable { List get props => []; } -class LoginUsernameChanged extends LoginEvent { - const LoginUsernameChanged(this.username); +class LoginEmailChanged extends LoginEvent { + const LoginEmailChanged(this.email); - final String username; + final String email; @override - List get props => [username]; + List get props => [email]; } class LoginPasswordChanged extends LoginEvent { diff --git a/mobile/lib/login/bloc/login_state.dart b/mobile/lib/login/bloc/login_state.dart index 53c4844..a6e365a 100644 --- a/mobile/lib/login/bloc/login_state.dart +++ b/mobile/lib/login/bloc/login_state.dart @@ -3,30 +3,30 @@ part of 'login_bloc.dart'; class LoginState extends Equatable { const LoginState({ this.status = FormzSubmissionStatus.initial, - this.username = const Username.isPure(), + this.email = const Email.isPure(), this.password = const Password.isPure(), this.isValid = false, }); final FormzSubmissionStatus status; - final Username username; + final Email email; final Password password; final bool isValid; LoginState copyWith({ FormzSubmissionStatus? status, - Username? username, + Email? email, Password? password, bool? isValid, }) { return LoginState( status: status ?? this.status, - username: username ?? this.username, + email: email ?? this.email, password: password ?? this.password, isValid: isValid ?? this.isValid, ); } @override - List get props => [status, username, password, isValid]; + List get props => [status, email, password, isValid]; } diff --git a/mobile/lib/login/model/email.dart b/mobile/lib/login/model/email.dart new file mode 100644 index 0000000..3ad5f6f --- /dev/null +++ b/mobile/lib/login/model/email.dart @@ -0,0 +1,21 @@ +import 'package:dartx/dartx.dart'; +import 'package:formz/formz.dart'; +import 'package:very_good_blog_app/app/config/extensions/string_extension.dart'; + +enum EmailValidationError { empty, invalid } + +class Email extends FormzInput { + const Email.isPure() : super.pure(''); + const Email.dirty([super.value = '']) : super.dirty(); + + @override + EmailValidationError? validator(String? value) { + if (value.isNullOrEmpty) { + return EmailValidationError.empty; + } + if (!value.isEmail()) { + return EmailValidationError.invalid; + } + return null; + } +} diff --git a/mobile/lib/login/model/models.dart b/mobile/lib/login/model/models.dart index 58be313..31cc892 100644 --- a/mobile/lib/login/model/models.dart +++ b/mobile/lib/login/model/models.dart @@ -1,2 +1,2 @@ +export 'email.dart'; export 'password.dart'; -export 'username.dart'; diff --git a/mobile/lib/login/model/username.dart b/mobile/lib/login/model/username.dart deleted file mode 100644 index b2a58f7..0000000 --- a/mobile/lib/login/model/username.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:formz/formz.dart'; - -enum UsernameValidationError { empty } - -class Username extends FormzInput { - const Username.isPure() : super.pure(''); - const Username.dirty([super.value = '']) : super.dirty(); - - @override - UsernameValidationError? validator(String? value) { - return value?.isNotEmpty == true ? null : UsernameValidationError.empty; - } -} diff --git a/mobile/lib/login/view/login_form.dart b/mobile/lib/login/view/login_form.dart index e575287..d654d1b 100644 --- a/mobile/lib/login/view/login_form.dart +++ b/mobile/lib/login/view/login_form.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; +import 'package:gap/gap.dart'; import 'package:very_good_blog_app/app/app.dart'; import 'package:very_good_blog_app/authentication/authentication.dart'; import 'package:very_good_blog_app/l10n/l10n.dart'; @@ -14,22 +15,21 @@ class LoginForm extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; + void showSnackbarError(String message) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(message), + ), + ); + } + return BlocListener( listener: (context, state) { if (state.status.isFailure) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Builder( - builder: (context) { - final message = - context.watch().state.status; - return Text(message.toString()); - }, - ), - ), - ); + final message = context.watch().state.status; + showSnackbarError(message.toString()); } }, child: Padding( @@ -38,13 +38,11 @@ class LoginForm extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TitleOfTextField(l10n.username), - _UsernameInput(), - const Padding(padding: EdgeInsets.all(12)), + _EmailInput(), + const Gap(24), TitleOfTextField(l10n.password), _PasswordInput(), - const SizedBox( - height: 24, - ), + const Gap(24), Align( alignment: Alignment.centerRight, child: Text( @@ -55,10 +53,8 @@ class LoginForm extends StatelessWidget { ), ), ), - const Padding(padding: EdgeInsets.all(12)), - Center( - child: _LoginButton(), - ), + const Gap(24), + Center(child: _LoginButton()), ], ), ), @@ -66,30 +62,26 @@ class LoginForm extends StatelessWidget { } } -class _UsernameInput extends StatelessWidget { +class _EmailInput extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - return BlocBuilder( - buildWhen: (previous, current) => previous.username != current.username, - builder: (context, state) { - return TextFieldDecoration( - child: TextField( - key: const Key('loginForm_usernameInput_textField'), - onChanged: (username) => - context.read().add(LoginUsernameChanged(username)), - decoration: InputDecoration( - contentPadding: const EdgeInsets.only(left: 16, right: 16), - border: InputBorder.none, - hintText: l10n.usernameHint, - errorText: state.username.displayError != null - ? l10n.usernameEmpty - : null, - ), - textInputAction: TextInputAction.next, - ), - ); - }, + final email = context.select((LoginBloc bloc) => bloc.state.email); + return TextFieldDecoration( + child: TextField( + key: const Key('loginForm_usernameInput_textField'), + onChanged: (value) { + if (value.trim().isEmpty) return; + context.read().add(LoginEmailChanged(value)); + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only(left: 16, right: 16), + border: InputBorder.none, + hintText: l10n.usernameHint, + errorText: email.displayError != null ? l10n.usernameEmpty : null, + ), + textInputAction: TextInputAction.next, + ), ); } } @@ -100,47 +92,37 @@ class _PasswordInput extends StatefulWidget { } class _PasswordInputState extends State<_PasswordInput> { - late bool _isHidePassword; - - @override - void initState() { - _isHidePassword = true; - super.initState(); - } + bool _isHidePassword = true; @override Widget build(BuildContext context) { final l10n = context.l10n; - return BlocBuilder( - buildWhen: (previous, current) => previous.password != current.password, - builder: (context, state) { - return TextFieldDecoration( - child: TextField( - key: const Key('loginForm_passwordInput_textField'), - onChanged: (password) => - context.read().add(LoginPasswordChanged(password)), - obscureText: _isHidePassword, - decoration: InputDecoration( - contentPadding: const EdgeInsets.only(left: 16, right: 16), - border: InputBorder.none, - hintText: l10n.passwordHint, - errorText: getErrorMessage(state.password.displayError, l10n), - suffixIcon: IconButton( - icon: _isHidePassword - ? Assets.icons.show.svg(color: AppPalette.primaryColor) - : Assets.icons.hide.svg(color: AppPalette.primaryColor), - onPressed: () { - setState(() { - _isHidePassword = !_isHidePassword; - }); - }, - splashRadius: 24, - ), - ), - textAlignVertical: TextAlignVertical.center, + final password = context.select((LoginBloc bloc) => bloc.state.password); + final showOrHideIcon = + _isHidePassword ? Assets.icons.show.svg() : Assets.icons.hide.svg(); + return TextFieldDecoration( + child: TextField( + key: const Key('loginForm_passwordInput_textField'), + onChanged: (password) => + context.read().add(LoginPasswordChanged(password)), + obscureText: _isHidePassword, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only(left: 16, right: 16), + border: InputBorder.none, + hintText: l10n.passwordHint, + errorText: getErrorMessage(password.displayError, l10n), + suffixIcon: IconButton( + icon: showOrHideIcon, + onPressed: () { + setState(() { + _isHidePassword = !_isHidePassword; + }); + }, + splashRadius: 24, ), - ); - }, + ), + textAlignVertical: TextAlignVertical.center, + ), ); } @@ -160,38 +142,30 @@ class _LoginButton extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; + final formStatus = context.select((LoginBloc bloc) => bloc.state.status); + if (formStatus.isInProgress) { + return const CircularProgressIndicator( + color: AppPalette.primaryColor, + ); + } + void onLoginPressed() { + context.read().add(const LoginSubmitted()); + } - return BlocBuilder( - buildWhen: (previous, current) => previous.status != current.status, - builder: (context, state) { - return state.status.isInProgress - ? const CircularProgressIndicator( - color: AppPalette.primaryColor, - ) - : ElevatedButton( - key: const Key('loginForm_continue_raisedButton'), - onPressed: state.status.isInitial - ? () { - context.read().add(const LoginSubmitted()); - } - : null, - style: ElevatedButton.styleFrom( - fixedSize: const Size(130, 50), - backgroundColor: Theme.of(context).primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - ), - child: Text( - l10n.login, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: AppPalette.whiteBackgroundColor, - ), - ), - ); - }, + return ElevatedButton( + key: const Key('loginForm_continue_raisedButton'), + onPressed: onLoginPressed, + style: ElevatedButton.styleFrom( + backgroundColor: context.theme.primaryColor, + ), + child: Text( + l10n.login, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: AppPalette.whiteBackgroundColor, + ), + ), ); } } diff --git a/mobile/lib/login/view/login_view.dart b/mobile/lib/login/view/login_view.dart index 8f45287..b113738 100644 --- a/mobile/lib/login/view/login_view.dart +++ b/mobile/lib/login/view/login_view.dart @@ -5,6 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:very_good_blog_app/app/app.dart'; import 'package:very_good_blog_app/authentication/authentication.dart'; +import 'package:very_good_blog_app/di/di.dart'; +import 'package:very_good_blog_app/domain/repositories/auth_repository.dart'; import 'package:very_good_blog_app/l10n/l10n.dart'; import 'package:very_good_blog_app/login/login.dart'; import 'package:very_good_blog_app/widgets/dismiss_focus_keyboard.dart'; @@ -45,8 +47,7 @@ class LoginView extends StatelessWidget { height: context.screenHeight * 0.65, child: BlocProvider( create: (context) => LoginBloc( - authenticationRepository: - context.read(), + authRepository: injector(), ), child: const LoginForm(), ), diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index ec7f053..01b4e44 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -338,6 +338,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.4" + dartx: + dependency: "direct main" + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" dbus: dependency: transitive description: @@ -765,6 +773,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + gap: + dependency: "direct main" + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + url: "https://pub.dev" + source: hosted + version: "3.0.1" get_it: dependency: "direct main" description: @@ -1487,6 +1503,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.9" + time: + dependency: transitive + description: + name: time + sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + url: "https://pub.dev" + source: hosted + version: "2.1.4" timeago: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 945173e..caff2b6 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: cloud_messaging_service: path: packages/cloud_messaging_service connectivity_plus: ^5.0.2 + dartx: ^1.2.0 draggable_home: ^1.0.5 easy_debounce: ^2.0.3 equatable: ^2.0.5 @@ -46,6 +47,7 @@ dependencies: flutter_svg: ^2.0.9 fluttertoast: ^8.1.2 formz: ^0.6.1 + gap: ^3.0.1 get_it: ^7.2.0 go_router: ^13.0.1 hive: ^2.2.3 diff --git a/mobile/test/login/bloc/login_bloc_test.dart b/mobile/test/login/bloc/login_bloc_test.dart index 3eeb4fa..4ef7e93 100644 --- a/mobile/test/login/bloc/login_bloc_test.dart +++ b/mobile/test/login/bloc/login_bloc_test.dart @@ -1,24 +1,23 @@ -import 'package:authentication_repository/authentication_repository.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:formz/formz.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:very_good_blog_app/domain/repositories/auth_repository.dart'; import 'package:very_good_blog_app/login/login.dart'; -class MockAuthenticationRepository extends Mock - implements AuthenticationRepository {} +class MockAuthRepository extends Mock implements AuthRepository {} void main() { - late AuthenticationRepository authenticationRepository; + late AuthRepository authenticationRepository; setUp(() { - authenticationRepository = MockAuthenticationRepository(); + authenticationRepository = MockAuthRepository(); }); group('LoginBloc', () { test('initial state is LoginState', () { final loginBloc = LoginBloc( - authenticationRepository: authenticationRepository, + authRepository: authenticationRepository, ); expect(loginBloc.state, const LoginState()); }); @@ -30,43 +29,43 @@ void main() { setUp: () { when( () => authenticationRepository.login( - username: 'username', + email: 'username', password: 'password', ), // ignore: void_checks ).thenAnswer((_) => Future.value('user')); }, build: () => LoginBloc( - authenticationRepository: authenticationRepository, + authRepository: authenticationRepository, ), act: (bloc) { bloc - ..add(const LoginUsernameChanged('username')) + ..add(const LoginEmailChanged('username')) ..add(const LoginPasswordChanged('password')) ..add(const LoginSubmitted()); }, expect: () => const [ LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), ), LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), password: Password.dirty('password'), isValid: true, ), LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), password: Password.dirty('password'), isValid: true, status: FormzSubmissionStatus.inProgress, ), LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), password: Password.dirty('password'), status: FormzSubmissionStatus.inProgress, ), LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), password: Password.dirty('password'), status: FormzSubmissionStatus.success, ), @@ -78,37 +77,37 @@ void main() { setUp: () { when( () => authenticationRepository.login( - username: 'username', + email: 'username', password: 'password', ), ).thenThrow(Exception('oops')); }, build: () => LoginBloc( - authenticationRepository: authenticationRepository, + authRepository: authenticationRepository, ), act: (bloc) { bloc - ..add(const LoginUsernameChanged('username')) + ..add(const LoginEmailChanged('username')) ..add(const LoginPasswordChanged('password')) ..add(const LoginSubmitted()); }, expect: () => const [ LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), ), LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), password: Password.dirty('password'), isValid: true, ), LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), password: Password.dirty('password'), isValid: true, status: FormzSubmissionStatus.inProgress, ), LoginState( - username: Username.dirty('username'), + email: Email.dirty('username'), password: Password.dirty('password'), isValid: true, status: FormzSubmissionStatus.failure, diff --git a/mobile/test/login/bloc/login_event_test.dart b/mobile/test/login/bloc/login_event_test.dart index ba0c23d..e158075 100644 --- a/mobile/test/login/bloc/login_event_test.dart +++ b/mobile/test/login/bloc/login_event_test.dart @@ -8,8 +8,8 @@ void main() { group('LoginUsernameChanged', () { test('supports value comparisons', () { expect( - const LoginUsernameChanged(username), - const LoginUsernameChanged(username), + const LoginEmailChanged(username), + const LoginEmailChanged(username), ); }); }); diff --git a/mobile/test/login/bloc/login_state_test.dart b/mobile/test/login/bloc/login_state_test.dart index bdec8bd..02fa8bc 100644 --- a/mobile/test/login/bloc/login_state_test.dart +++ b/mobile/test/login/bloc/login_state_test.dart @@ -2,7 +2,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:very_good_blog_app/login/login.dart'; void main() { - const username = Username.dirty('username'); + const username = Email.dirty('username'); const password = Password.dirty('password'); group('LoginState', () { test('supports value comparisons', () { @@ -22,8 +22,8 @@ void main() { test('returns object with updated username when username is passed', () { expect( - const LoginState().copyWith(username: username), - const LoginState(username: username), + const LoginState().copyWith(email: username), + const LoginState(email: username), ); }); diff --git a/mobile/test/login/model/username_test.dart b/mobile/test/login/model/username_test.dart index 8d72023..ff9bee9 100644 --- a/mobile/test/login/model/username_test.dart +++ b/mobile/test/login/model/username_test.dart @@ -6,13 +6,13 @@ void main() { group('Login Username', () { group('constructors', () { test('pure creates correct instance', () { - const username = Username.isPure(); + const username = Email.isPure(); expect(username.value, ''); expect(username.isPure, true); }); test('dirty creates correct instance', () { - const username = Username.dirty(usernameString); + const username = Email.dirty(usernameString); expect(username.value, usernameString); expect(username.isPure, false); }); @@ -21,14 +21,14 @@ void main() { group('validator', () { test('returns empty error when username is empty', () { expect( - const Username.dirty().error, - UsernameValidationError.empty, + const Email.dirty().error, + EmailValidationError.empty, ); }); test('is valid when username is not empty', () { expect( - const Username.dirty(usernameString).error, + const Email.dirty(usernameString).error, isNull, ); }); diff --git a/mobile/test/register/model/username_test.dart b/mobile/test/register/model/username_test.dart index 8d72023..ff9bee9 100644 --- a/mobile/test/register/model/username_test.dart +++ b/mobile/test/register/model/username_test.dart @@ -6,13 +6,13 @@ void main() { group('Login Username', () { group('constructors', () { test('pure creates correct instance', () { - const username = Username.isPure(); + const username = Email.isPure(); expect(username.value, ''); expect(username.isPure, true); }); test('dirty creates correct instance', () { - const username = Username.dirty(usernameString); + const username = Email.dirty(usernameString); expect(username.value, usernameString); expect(username.isPure, false); }); @@ -21,14 +21,14 @@ void main() { group('validator', () { test('returns empty error when username is empty', () { expect( - const Username.dirty().error, - UsernameValidationError.empty, + const Email.dirty().error, + EmailValidationError.empty, ); }); test('is valid when username is not empty', () { expect( - const Username.dirty(usernameString).error, + const Email.dirty(usernameString).error, isNull, ); }); From 5e5b30e9c32c7464a3a218dabd2966dbd1e6831b Mon Sep 17 00:00:00 2001 From: dungngminh <63831488+dungngminh@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:00:15 +0700 Subject: [PATCH 05/20] add some stuff --- mobile/.gitignore | 3 +++ mobile/lib/di/modules/network_module.dart | 2 +- mobile/lib/login/view/login_view.dart | 7 ++----- .../lib/src/authentication_status.dart | 17 +++++++++++++++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/mobile/.gitignore b/mobile/.gitignore index 318877e..8c5bce3 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -129,3 +129,6 @@ app.*.map.json !.idea/runConfigurations/ .mason + + +**/config.json \ No newline at end of file diff --git a/mobile/lib/di/modules/network_module.dart b/mobile/lib/di/modules/network_module.dart index b3f7922..cbea99f 100644 --- a/mobile/lib/di/modules/network_module.dart +++ b/mobile/lib/di/modules/network_module.dart @@ -4,7 +4,7 @@ import 'package:injectable/injectable.dart'; @module abstract class NetworkModule { @Named('baseUrl') - String get baseUrl => 'https://very-good-blog-app.up.railway.app'; + String get baseUrl => const String.fromEnvironment('BASE_URL'); @Named('unAuthClient') @lazySingleton diff --git a/mobile/lib/login/view/login_view.dart b/mobile/lib/login/view/login_view.dart index b113738..e485525 100644 --- a/mobile/lib/login/view/login_view.dart +++ b/mobile/lib/login/view/login_view.dart @@ -1,4 +1,3 @@ -import 'package:authentication_repository/authentication_repository.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -19,7 +18,7 @@ class LoginView extends StatelessWidget { final l10n = context.l10n; return BlocListener( listener: (context, state) { - if (state.status == AuthenticationStatus.authenticated) { + if (state.status.isAuthenticated) { context.go(AppRoutes.home); } }, @@ -40,9 +39,7 @@ class LoginView extends StatelessWidget { ), ), ), - const SizedBox( - height: 24, - ), + const SizedBox(height: 24), SizedBox( height: context.screenHeight * 0.65, child: BlocProvider( diff --git a/mobile/packages/authentication_repository/lib/src/authentication_status.dart b/mobile/packages/authentication_repository/lib/src/authentication_status.dart index acb777d..782453a 100644 --- a/mobile/packages/authentication_repository/lib/src/authentication_status.dart +++ b/mobile/packages/authentication_repository/lib/src/authentication_status.dart @@ -22,4 +22,21 @@ enum AuthenticationStatus { /// A value that is used to indicate that the user /// is not successfully registered. unsuccessfullyRegistered; + + bool get isUnknown => this == AuthenticationStatus.unknown; + + bool get isAuthenticated => this == AuthenticationStatus.authenticated; + + bool get isAuthenticatedOffline => + this == AuthenticationStatus.authenticatedOffline; + + bool get isUnauthenticated => this == AuthenticationStatus.unauthenticated; + + bool get isSuccessfullyRegistered => + this == AuthenticationStatus.successfullyRegistered; + + bool get isExisted => this == AuthenticationStatus.existed; + + bool get isUnsuccessfullyRegistered => + this == AuthenticationStatus.unsuccessfullyRegistered; } From 4cde3fcfe5108c691b1493a41aa638e109a2fb1c Mon Sep 17 00:00:00 2001 From: dungngminh <63831488+dungngminh@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:07:54 +0700 Subject: [PATCH 06/20] update dependabot for whole project --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/dependabot.yaml | 10 + .github/workflows/main.yaml | 18 - .vscode/configurationCache.log | 1 - .vscode/dryrun.log | 6 - .vscode/settings.json | 3 - .vscode/targets.log | 315 ------------------ mobile/.github/dependabot.yaml | 10 - .../cache_manager/app_cache_manager.dart | 1 + 9 files changed, 12 insertions(+), 354 deletions(-) create mode 100644 .github/dependabot.yaml delete mode 100644 .github/workflows/main.yaml delete mode 100644 .vscode/configurationCache.log delete mode 100644 .vscode/dryrun.log delete mode 100644 .vscode/settings.json delete mode 100644 .vscode/targets.log delete mode 100644 mobile/.github/dependabot.yaml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2a98433..f9d8d29 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@