From 9f7a21fb6c8bfabc3d9e5d358836f98b597bb3f2 Mon Sep 17 00:00:00 2001 From: Andre Hui Date: Mon, 29 May 2023 23:03:35 -0700 Subject: [PATCH 1/6] feat: added blocs for profile management --- lib/app_widget.dart | 6 ++ lib/application/user/avatar/avatar_bloc.dart | 36 +++++++++ lib/application/user/avatar/avatar_event.dart | 6 ++ lib/application/user/avatar/avatar_state.dart | 12 +++ .../user/profile/profile_bloc.dart | 76 +++++++++++++++++++ .../user/profile/profile_event.dart | 7 ++ .../user/profile/profile_state.dart | 30 ++++++++ .../user/username/username_bloc.dart | 33 ++++++++ .../user/username/username_event.dart | 9 +++ .../user/username/username_state.dart | 9 +++ 10 files changed, 224 insertions(+) create mode 100644 lib/application/user/avatar/avatar_bloc.dart create mode 100644 lib/application/user/avatar/avatar_event.dart create mode 100644 lib/application/user/avatar/avatar_state.dart create mode 100644 lib/application/user/profile/profile_bloc.dart create mode 100644 lib/application/user/profile/profile_event.dart create mode 100644 lib/application/user/profile/profile_state.dart create mode 100644 lib/application/user/username/username_bloc.dart create mode 100644 lib/application/user/username/username_event.dart create mode 100644 lib/application/user/username/username_state.dart diff --git a/lib/app_widget.dart b/lib/app_widget.dart index b36efca..d8f0e7a 100644 --- a/lib/app_widget.dart +++ b/lib/app_widget.dart @@ -2,6 +2,9 @@ import 'package:collaction_cms/application/crowdaction/crowdaction_creation/comm import 'package:collaction_cms/application/crowdaction/crowdaction_getter/crowdaction_getter_bloc.dart'; import 'package:collaction_cms/application/crowdaction/crowdaction_selected/crowdaction_selected_cubit.dart'; import 'package:collaction_cms/application/crowdaction/pagination/pagination_cubit.dart'; +import 'package:collaction_cms/application/user/avatar/avatar_bloc.dart'; +import 'package:collaction_cms/application/user/profile/profile_bloc.dart'; +import 'package:collaction_cms/application/user/username/username_bloc.dart'; import 'package:collaction_cms/infrastructure/core/injection.dart'; import 'package:collaction_cms/presentation/theme/theme.dart'; import 'package:flutter/material.dart'; @@ -32,6 +35,9 @@ class AppWidget extends StatelessWidget { BlocProvider( create: (_) => getIt(), ), + BlocProvider( + create: (_) => getIt(), + ), ], child: MultiBlocListener( listeners: [ diff --git a/lib/application/user/avatar/avatar_bloc.dart b/lib/application/user/avatar/avatar_bloc.dart new file mode 100644 index 0000000..f2c6d54 --- /dev/null +++ b/lib/application/user/avatar/avatar_bloc.dart @@ -0,0 +1,36 @@ +import 'dart:io'; +import 'package:bloc/bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; +import 'package:collaction_cms/domain/profile/i_profile_repository.dart'; + +part 'avatar_bloc.freezed.dart'; +part 'avatar_event.dart'; +part 'avatar_state.dart'; + +@injectable +class AvatarBloc extends Bloc { + final IProfileRepository profileRepository; + + AvatarBloc(this.profileRepository) : super(const AvatarState.initial()) { + on((event, emit) async { + await event.map( + uploadAvatar: (event) async => await _uploadAvatar(emit, event), + ); + }); + } + + Future _uploadAvatar( + Emitter emit, + _UploadAvatar event, + ) async { + emit(const AvatarState.uploading()); + final result = await profileRepository.uploadAvatar(event.image); + emit( + result.fold( + (failure) => const AvatarState.uploadFail(), + (success) => const AvatarState.uploadSuccess(), + ), + ); + } +} diff --git a/lib/application/user/avatar/avatar_event.dart b/lib/application/user/avatar/avatar_event.dart new file mode 100644 index 0000000..e3807de --- /dev/null +++ b/lib/application/user/avatar/avatar_event.dart @@ -0,0 +1,6 @@ +part of 'avatar_bloc.dart'; + +@freezed +class AvatarEvent with _$AvatarEvent { + const factory AvatarEvent.uploadAvatar(File image) = _UploadAvatar; +} diff --git a/lib/application/user/avatar/avatar_state.dart b/lib/application/user/avatar/avatar_state.dart new file mode 100644 index 0000000..8475557 --- /dev/null +++ b/lib/application/user/avatar/avatar_state.dart @@ -0,0 +1,12 @@ +part of 'avatar_bloc.dart'; + +@freezed +class AvatarState with _$AvatarState { + const factory AvatarState.initial() = _Initial; + + const factory AvatarState.uploading() = _Uploading; + + const factory AvatarState.uploadSuccess() = _UploadSuccess; + + const factory AvatarState.uploadFail() = _UploadFail; +} diff --git a/lib/application/user/profile/profile_bloc.dart b/lib/application/user/profile/profile_bloc.dart new file mode 100644 index 0000000..a07be7c --- /dev/null +++ b/lib/application/user/profile/profile_bloc.dart @@ -0,0 +1,76 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:collaction_cms/application/user/username/username_bloc.dart'; +import 'package:collaction_cms/domain/profile/i_profile_repository.dart'; +import 'package:collaction_cms/domain/profile/user_profile.dart'; +import 'package:collaction_cms/application/user/avatar/avatar_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +part 'profile_bloc.freezed.dart'; +part 'profile_event.dart'; +part 'profile_state.dart'; + +@injectable +class ProfileBloc extends Bloc { + final IProfileRepository profileRepository; + final AvatarBloc _avatarBloc; + final UsernameBloc _usernameBloc; + late StreamSubscription avatarBlocSubscription; + late StreamSubscription usernameBlocSubscription; + + UserProfile? userProfile; + + ProfileBloc(this.profileRepository, this._avatarBloc, this._usernameBloc) + : super(const ProfileState.initial()) { + on((event, emit) async { + await event.map( + getUserProfile: (event) async => await _getUserProfile(emit, event), + createUserProfile: (event) async => await _createUserProfile( + emit, + event, + ), + ); + }); + + avatarBlocSubscription = _avatarBloc.stream.listen((state) { + state.mapOrNull( + uploadSuccess: (_) { + add(const ProfileEvent.getUserProfile()); + }, + ); + }); + + usernameBlocSubscription = _usernameBloc.stream.listen((state) { + state.mapOrNull( + updateSuccess: (_) => add(const ProfileEvent.getUserProfile()), + ); + }); + } + + Future _getUserProfile( + Emitter emit, + _GetUserProfile event, + ) async { + final userProfileOption = await profileRepository.getUserProfile(); + + emit( + userProfileOption.fold( + (failure) => const ProfileState.profileError(), + (userProfile) => ProfileState.profileFound(userProfile), + ), + ); + } + + Future _createUserProfile( + Emitter emit, + _CreateUserProfile event, + ) async { + final createUserOption = await profileRepository.createProfile(); + emit(createUserOption.fold( + (failure) => const ProfileState.profileError(), + (success) => const ProfileState.profileFound(null), + )); + } +} diff --git a/lib/application/user/profile/profile_event.dart b/lib/application/user/profile/profile_event.dart new file mode 100644 index 0000000..514b81b --- /dev/null +++ b/lib/application/user/profile/profile_event.dart @@ -0,0 +1,7 @@ +part of 'profile_bloc.dart'; + +@freezed +class ProfileEvent with _$ProfileEvent { + const factory ProfileEvent.getUserProfile() = _GetUserProfile; + const factory ProfileEvent.createUserProfile() = _CreateUserProfile; +} diff --git a/lib/application/user/profile/profile_state.dart b/lib/application/user/profile/profile_state.dart new file mode 100644 index 0000000..e89617f --- /dev/null +++ b/lib/application/user/profile/profile_state.dart @@ -0,0 +1,30 @@ +part of 'profile_bloc.dart'; + +/*class ProfileState { + const ProfileState({ + required this.userProfile, + }); + + final UserProfile? userProfile; + + factory ProfileState.initial() => const ProfileState( + userProfile: null, + ); + + ProfileState copyWith({ + UserProfile? userProfile, + }) { + return ProfileState( + userProfile: userProfile ?? this.userProfile, + ); + } +}*/ +@freezed +class ProfileState with _$ProfileState { + const factory ProfileState.initial() = _Initial; + + const factory ProfileState.profileError() = _ProfileError; + + const factory ProfileState.profileFound(UserProfile? userProfile) = + _ProfileFound; +} diff --git a/lib/application/user/username/username_bloc.dart b/lib/application/user/username/username_bloc.dart new file mode 100644 index 0000000..554b775 --- /dev/null +++ b/lib/application/user/username/username_bloc.dart @@ -0,0 +1,33 @@ +import 'package:bloc/bloc.dart'; +import 'package:collaction_cms/domain/profile/i_profile_repository.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +part 'username_event.dart'; +part 'username_state.dart'; +part 'username_bloc.freezed.dart'; + +@injectable +class UsernameBloc extends Bloc { + final IProfileRepository profileRepository; + + UsernameBloc(this.profileRepository) : super(const UsernameState.initial()) { + on((event, emit) async { + await event.when(updateUsername: (firstName, lastName) async { + emit(const UsernameState.updating()); + + final unitOrFailure = await profileRepository.updateUsername( + firstName: firstName, + lastName: lastName, + ); + + emit( + unitOrFailure.fold( + (failure) => const UsernameState.updateFailure(), + (_) => UsernameState.updateSuccess("$firstName $lastName"), + ), + ); + }); + }); + } +} diff --git a/lib/application/user/username/username_event.dart b/lib/application/user/username/username_event.dart new file mode 100644 index 0000000..d03a993 --- /dev/null +++ b/lib/application/user/username/username_event.dart @@ -0,0 +1,9 @@ +part of 'username_bloc.dart'; + +@freezed +class UsernameEvent with _$UsernameEvent { + const factory UsernameEvent.updateUsername({ + required String firstName, + required String lastName, + }) = _UpdateUsername; +} diff --git a/lib/application/user/username/username_state.dart b/lib/application/user/username/username_state.dart new file mode 100644 index 0000000..65cdbe6 --- /dev/null +++ b/lib/application/user/username/username_state.dart @@ -0,0 +1,9 @@ +part of 'username_bloc.dart'; + +@freezed +class UsernameState with _$UsernameState { + const factory UsernameState.initial() = _Initial; + const factory UsernameState.updating() = _Updating; + const factory UsernameState.updateSuccess(String fullname) = _UpdateSuccess; + const factory UsernameState.updateFailure() = _UpdateFailure; +} From c8786e0ed92edfc724a5ed22c1bbcb36d8d6c505 Mon Sep 17 00:00:00 2001 From: Andre Hui Date: Mon, 29 May 2023 23:04:08 -0700 Subject: [PATCH 2/6] feat: created profile repository --- lib/domain/profile/i_profile_repository.dart | 16 ++ lib/domain/profile/profile_failure.dart | 10 + .../profile/profile_repository.dart | 215 ++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 lib/domain/profile/i_profile_repository.dart create mode 100644 lib/domain/profile/profile_failure.dart create mode 100644 lib/infrastructure/profile/profile_repository.dart diff --git a/lib/domain/profile/i_profile_repository.dart b/lib/domain/profile/i_profile_repository.dart new file mode 100644 index 0000000..9c5ec7c --- /dev/null +++ b/lib/domain/profile/i_profile_repository.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +import 'package:dartz/dartz.dart'; + +import '../profile/user_profile.dart'; +import 'profile_failure.dart'; + +abstract class IProfileRepository { + Future> getUserProfile(); + Future> createProfile(); + Future> updateUsername({ + String? firstName, + String? lastName, + }); + Future> uploadAvatar(File file); +} diff --git a/lib/domain/profile/profile_failure.dart b/lib/domain/profile/profile_failure.dart new file mode 100644 index 0000000..3a44cd1 --- /dev/null +++ b/lib/domain/profile/profile_failure.dart @@ -0,0 +1,10 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'profile_failure.freezed.dart'; + +@freezed +class ProfileFailure with _$ProfileFailure { + const factory ProfileFailure.unexpected() = Unexpected; + const factory ProfileFailure.noUser() = NoUser; + const factory ProfileFailure.avatarUploadFailure() = AvatarUploadFailure; +} diff --git a/lib/infrastructure/profile/profile_repository.dart b/lib/infrastructure/profile/profile_repository.dart new file mode 100644 index 0000000..bab9e76 --- /dev/null +++ b/lib/infrastructure/profile/profile_repository.dart @@ -0,0 +1,215 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:collaction_cms/infrastructure/auth/firebase/firebase_auth_mapper.dart'; +import 'package:dartz/dartz.dart'; +import 'package:http/http.dart' as http; +import 'package:injectable/injectable.dart'; +import 'package:image/image.dart'; +import 'package:path/path.dart'; +import 'package:http_parser/http_parser.dart'; + +import '../../domain/auth/i_auth_client_repository.dart'; +import '../../domain/core/i_settings_repository.dart'; +import '../../domain/profile/user_profile.dart'; +import '../../domain/profile/i_profile_repository.dart'; +import '../../domain/profile/profile_failure.dart'; +import '../../domain/user/user.dart'; +import '../../infrastructure/profile/profile_dto.dart'; + +@LazySingleton(as: IProfileRepository) +class ProfileRepository implements IProfileRepository { + final IAuthClientRepository _authRepository; + final http.Client _client; + final ISettingsRepository _settingsRepository; + + const ProfileRepository( + this._authRepository, + this._client, + this._settingsRepository, + ); + + @override + Future> getUserProfile() async { + try { + final userOption = await _authRepository.user.first; + + return await userOption.fold( + () => left(const ProfileFailure.noUser()), + (user) async { + var userProfile = await _getUserProfile(user.toDomain()); + + if (userProfile != null) { + return right(userProfile); + } + + final resultOrFailure = await createProfile(); + + if (resultOrFailure.isRight()) { + userProfile = await _getUserProfile(user.toDomain()); + return right(userProfile!); + } + + return left(const ProfileFailure.noUser()); + }, + ); + } catch (_) { + return left(const ProfileFailure.unexpected()); + } + } + + @override + Future> createProfile() async { + try { + final userOption = await _authRepository.user.first; + + return await userOption.fold( + () => left(const ProfileFailure.noUser()), + (user) async { + final tokenId = await user.getIdToken(); + + final response = await _client.post( + Uri.parse( + '${await _settingsRepository.baseApiEndpointUrl}/v1/profiles', + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $tokenId', + }, + body: jsonEncode({ + "firstName": "John", + "lastName": "Doe", + "country": "NL", + "bio": "My bio is currently empty", + }), + ); + + if (response.statusCode == 201) { + return right(unit); + } + + return left(const ProfileFailure.unexpected()); + }, + ); + } catch (_) { + return left(const ProfileFailure.unexpected()); + } + } + + @override + Future> updateUsername({ + String? firstName, + String? lastName, + }) async { + try { + final userOption = await _authRepository.user.first; + + return await userOption.fold( + () => left(const ProfileFailure.noUser()), + (user) async { + final token = await user.getIdToken(); + + final response = await _client.put( + Uri.parse( + '${await _settingsRepository.baseApiEndpointUrl}/v1/profiles', + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + body: jsonEncode({ + "firstName": firstName, + "lastName": lastName, + }), + ); + + if (response.statusCode == 200) { + return right(unit); + } + + return left(const ProfileFailure.unexpected()); + }, + ); + } catch (_) { + return left(const ProfileFailure.unexpected()); + } + } + + @override + Future> uploadAvatar(File imageFile) async { + try { + final imageBytes = await imageFile.readAsBytes(); + final imageResized = copyResize(decodeImage(imageBytes)!, width: 200); + final resizedBytes = encodePng(imageResized); + + final userOption = await _authRepository.user.first; + return await userOption.fold(() => left(const ProfileFailure.noUser()), + (user) async { + final token = await user.getIdToken(); + final request = http.MultipartRequest( + 'POST', + Uri.parse( + '${await _settingsRepository.baseApiEndpointUrl}/v1/profiles/me/image', + ), + ); + + request.headers['Authorization'] = 'Bearer $token'; + request.files.add( + http.MultipartFile.fromBytes( + 'file', + resizedBytes, + contentType: MediaType('image', 'png'), + filename: basename(imageFile.path), + ), + ); + + final response = await request.send(); + + if ([201, 200].contains(response.statusCode)) { + return right(unit); + } else { + return left(const ProfileFailure.avatarUploadFailure()); + } + }); + } catch (_) { + return left(const ProfileFailure.avatarUploadFailure()); + } + } + + /// Get user profile from api + Future _getUserProfile(User user) async { + final tokenId = await user.getIdToken(); + + final response = await _client.get( + Uri.parse( + '${await _settingsRepository.baseApiEndpointUrl}/v1/profiles/me', + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $tokenId' + }, + ); + + final roleOption = await _authRepository.roleOption.first; + roleOption.fold( + () => null, + (role) { + final roleEnum = Role.enumOf(role); + if (roleEnum == null) return null; + + /// Return profile if request is successful + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + + final profile = + ProfileDto.fromJson(json as Map).toDomain(); + + return UserProfile(user: user, profile: profile, role: roleEnum); + } + }, + ); + + return null; + } +} From 1dc58c8357df455a85fa8f67ce77825e907c4d68 Mon Sep 17 00:00:00 2001 From: Andre Hui Date: Mon, 29 May 2023 23:04:25 -0700 Subject: [PATCH 3/6] feat: create profile objects --- lib/domain/profile/profile.dart | 18 +++++++++++ lib/domain/profile/user_profile.dart | 35 +++++++++++++++++++++ lib/infrastructure/profile/profile_dto.dart | 32 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 lib/domain/profile/profile.dart create mode 100644 lib/domain/profile/user_profile.dart create mode 100644 lib/infrastructure/profile/profile_dto.dart diff --git a/lib/domain/profile/profile.dart b/lib/domain/profile/profile.dart new file mode 100644 index 0000000..3255533 --- /dev/null +++ b/lib/domain/profile/profile.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'profile.freezed.dart'; + +@freezed +class Profile with _$Profile { + const Profile._(); + + const factory Profile({ + required String userId, + required String firstName, + required String lastName, + required String avatar, + String? bio, + }) = _Profile; + + String get fullName => '$firstName $lastName'; +} diff --git a/lib/domain/profile/user_profile.dart b/lib/domain/profile/user_profile.dart new file mode 100644 index 0000000..b1af18f --- /dev/null +++ b/lib/domain/profile/user_profile.dart @@ -0,0 +1,35 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../user/user.dart'; +import 'profile.dart'; + +part 'user_profile.freezed.dart'; + +@freezed +class UserProfile with _$UserProfile { + const UserProfile._(); + + const factory UserProfile({ + required User user, + required Profile profile, + required Role role, + }) = _UserProfile; +} + +enum Role { + @JsonValue('ADMIN') + admin, + @JsonValue("MODERATOR") + moderator; + + static Role? enumOf(String? input) { + switch (input) { + case "ADMIN": + return Role.admin; + case "MODERATOR": + return Role.moderator; + default: + return null; + } + } +} diff --git a/lib/infrastructure/profile/profile_dto.dart b/lib/infrastructure/profile/profile_dto.dart new file mode 100644 index 0000000..bc2f036 --- /dev/null +++ b/lib/infrastructure/profile/profile_dto.dart @@ -0,0 +1,32 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../domain/profile/profile.dart'; + +part 'profile_dto.freezed.dart'; +part 'profile_dto.g.dart'; + +@freezed +class ProfileDto with _$ProfileDto { + const ProfileDto._(); + + const factory ProfileDto({ + required String userId, + required String firstName, + required String lastName, + required String avatar, + String? bio, + }) = _ProfileDto; + + Profile toDomain() { + return Profile( + userId: userId, + firstName: firstName, + lastName: lastName, + avatar: avatar, + bio: bio, + ); + } + + factory ProfileDto.fromJson(Map json) => + _$ProfileDtoFromJson(json); +} From ec77489fccaf9ff88a94e631ddaef83b86d61b83 Mon Sep 17 00:00:00 2001 From: Andre Hui Date: Mon, 29 May 2023 23:04:43 -0700 Subject: [PATCH 4/6] feat: integrated profile bloc --- lib/presentation/navigation/top_bar.dart | 96 ++++++++++++++---------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/lib/presentation/navigation/top_bar.dart b/lib/presentation/navigation/top_bar.dart index c0f210b..0ffe9ea 100644 --- a/lib/presentation/navigation/top_bar.dart +++ b/lib/presentation/navigation/top_bar.dart @@ -1,54 +1,72 @@ +import 'package:collaction_cms/application/user/profile/profile_bloc.dart'; +import 'package:collaction_cms/domain/profile/user_profile.dart'; import 'package:collaction_cms/presentation/theme/constants.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -class TopBar extends StatelessWidget { +class TopBar extends StatefulWidget { final GlobalKey scaffoldKey; final bool isSmallScreen; + const TopBar({ - Key? key, + super.key, this.isSmallScreen = false, required this.scaffoldKey, - }) : super(key: key); + }); + + @override + State createState() => _TopBarState(); +} + +class _TopBarState extends State { + late UserProfile? userProfile; @override Widget build(BuildContext context) { - return Column( - children: [ - SizedBox( - height: 75.0, - child: Row( - children: [ - Container( - height: 40, - width: 40, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: kBorderColor), - borderRadius: BorderRadius.circular(8.0), + return BlocBuilder(builder: (context, state) { + state.maybeMap( + profileFound: (value) => userProfile = value.userProfile, + orElse: () => userProfile = null, + ); + return Column( + children: [ + SizedBox( + height: 75.0, + child: Row( + children: [ + Container( + height: 40, + width: 40, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: kBorderColor), + borderRadius: BorderRadius.circular(8.0), + ), + child: !widget.isSmallScreen + ? const Icon( + Icons.devices_other_outlined, + color: kNavItemActiveColor, + ) + : IconButton( + icon: const Icon(Icons.menu), + onPressed: () => + widget.scaffoldKey.currentState?.openDrawer(), + ), + ), + const Expanded(child: SizedBox()), + Row( + children: [ + Text(userProfile?.profile.firstName ?? 'User'), + const Icon(Icons.arrow_drop_down), + ], ), - child: !isSmallScreen - ? const Icon( - Icons.devices_other_outlined, - color: kNavItemActiveColor, - ) - : IconButton( - icon: const Icon(Icons.menu), - onPressed: () => scaffoldKey.currentState?.openDrawer(), - ), - ), - const Expanded(child: SizedBox()), - Row( - children: const [ - Text('Mathias'), - Icon(Icons.arrow_drop_down), - ], - ), - ], + ], + ), ), - ), - const Divider(height: 0, color: kBorderColor), - ], - ); + const Divider(height: 0, color: kBorderColor), + ], + ); + }); } -} \ No newline at end of file +} From ca0e921ae8ab8338f1f4d6b994fd5ed2ef408a15 Mon Sep 17 00:00:00 2001 From: Andre Hui Date: Mon, 29 May 2023 23:04:55 -0700 Subject: [PATCH 5/6] feat: profile bloc tests --- .../user/profile/profile_bloc_test.dart | 171 ++++++++++++++++++ test/test_utilities.dart | 34 ++++ 2 files changed, 205 insertions(+) create mode 100644 test/application/user/profile/profile_bloc_test.dart diff --git a/test/application/user/profile/profile_bloc_test.dart b/test/application/user/profile/profile_bloc_test.dart new file mode 100644 index 0000000..d18bdde --- /dev/null +++ b/test/application/user/profile/profile_bloc_test.dart @@ -0,0 +1,171 @@ +import 'dart:io'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:collaction_cms/application/user/avatar/avatar_bloc.dart'; +import 'package:collaction_cms/application/user/profile/profile_bloc.dart'; +import 'package:collaction_cms/application/user/username/username_bloc.dart'; +import 'package:collaction_cms/domain/profile/profile_failure.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../test_utilities.dart'; + +void main() { + group("Testing profile bloc events and stream listeners", () { + late final MockProfileRepository profileRepository; + late AvatarBloc avatarBloc; + late UsernameBloc usernameBloc; + late ProfileBloc profileBloc; + final File file = File('tFile'); + + setUpAll(() { + profileRepository = MockProfileRepository(); + }); + + setUp(() { + avatarBloc = AvatarBloc(profileRepository); + usernameBloc = UsernameBloc(profileRepository); + profileBloc = ProfileBloc(profileRepository, avatarBloc, usernameBloc); + }); + + test('Initial state', () { + expect(profileBloc.state, const ProfileState.initial()); + }); + + blocTest( + 'createUserProfile success', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.createProfile()).thenAnswer( + (_) => Future.value( + right(unit), + ), + ); + + profileBloc.add(const ProfileEvent.createUserProfile()); + }, + expect: () => [const ProfileState.profileFound(null)], + ); + + blocTest( + 'createUserProfile fail', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.createProfile()).thenAnswer( + (_) => Future.value( + left(const ProfileFailure.unexpected()), + ), + ); + + profileBloc.add(const ProfileEvent.createUserProfile()); + }, + expect: () => [const ProfileState.profileError()], + ); + + blocTest( + 'getUserProfile fail', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.getUserProfile()).thenAnswer( + (_) => Future.value( + left(const ProfileFailure.noUser()), + ), + ); + + profileBloc.add(const ProfileEvent.getUserProfile()); + }, + expect: () => [const ProfileState.profileError()], + ); + + blocTest( + 'getUserProfile success', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.getUserProfile()).thenAnswer( + (_) => Future.value( + right(tAdminUserProfile), + ), + ); + + profileBloc.add(const ProfileEvent.getUserProfile()); + }, + expect: () => [const ProfileState.profileFound(tAdminUserProfile)], + ); + + blocTest( + 'getUserProfile event triggers when AvatarBloc emits success', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.getUserProfile()) + .thenAnswer((_) => Future.value(right(tAdminUserProfile))); + when(() => profileRepository.uploadAvatar(file)).thenAnswer( + (_) => Future.value(right(unit)), + ); + avatarBloc.add(AvatarEvent.uploadAvatar(file)); + }, + expect: () => [ + const ProfileState.profileFound(tAdminUserProfile), + ], + ); + + blocTest( + 'getUserProfile event does not trigger when AvatarBloc emits failure', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.getUserProfile()) + .thenAnswer((_) => Future.value(right(tAdminUserProfile))); + when(() => profileRepository.uploadAvatar(file)).thenAnswer( + (_) => Future.value(left(const ProfileFailure.avatarUploadFailure())), + ); + avatarBloc.add(AvatarEvent.uploadAvatar(file)); + }, + expect: () => [], + ); + + blocTest( + 'getUserProfile event triggers when UsernameBloc emits success', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.getUserProfile()).thenAnswer( + (_) => Future.value(right(tAdminUserProfile)), + ); + when( + () => profileRepository.updateUsername( + firstName: "tFirstName", + lastName: "tLastName", + ), + ).thenAnswer( + (_) => Future.value(right(unit)), + ); + usernameBloc.add(const UsernameEvent.updateUsername( + firstName: "tFirstName", + lastName: "tLastName", + )); + }, + expect: () => [const ProfileState.profileFound(tAdminUserProfile)], + ); + + blocTest( + 'getUserProfile event does not trigger when UsernameBloc emits failure', + build: () => profileBloc, + act: (profileBloc) { + when(() => profileRepository.getUserProfile()) + .thenAnswer((_) => Future.value(right(tAdminUserProfile))); + when( + () => profileRepository.updateUsername( + firstName: "tFirstName", + lastName: "tLastName", + ), + ).thenAnswer( + (_) => Future.value(left(const ProfileFailure.avatarUploadFailure())), + ); + usernameBloc.add(const UsernameEvent.updateUsername( + firstName: "tFirstName", + lastName: "tLastName", + )); + }, + expect: () => [], + ); + }); +} diff --git a/test/test_utilities.dart b/test/test_utilities.dart index ecf04ac..d246307 100644 --- a/test/test_utilities.dart +++ b/test/test_utilities.dart @@ -3,6 +3,16 @@ import 'dart:typed_data'; import 'package:collaction_cms/domain/crowdaction/crowdaction.dart'; import 'package:collaction_cms/domain/crowdaction/crowdaction_utility/crowdaction_images.dart'; import 'package:collaction_cms/domain/crowdaction/crowdaction_utility/crowdaction_info.dart'; +import 'package:collaction_cms/domain/auth/i_auth_client_repository.dart'; +import 'package:collaction_cms/domain/profile/i_profile_repository.dart'; +import 'package:collaction_cms/domain/profile/profile.dart'; +import 'package:collaction_cms/domain/profile/user_profile.dart'; +import 'package:collaction_cms/domain/user/user.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAuthClientRepository extends Mock implements IAuthClientRepository {} + +class MockProfileRepository extends Mock implements IProfileRepository {} final tCrowdActionInfo = CrowdActionInfo( title: "tTitle", @@ -68,3 +78,27 @@ const tCommitmentsList = [tCommitment1, tCommitment2, tCommitment3]; const Location tLocation = Location(code: 'tCode', name: 'tName'); final List tCommitment = ['tCommitment']; + +const tUser = User( + id: 'tId', + getIdToken: User.getAnonymousIdToken, +); + +const tProfile = Profile( + userId: 'tId', + firstName: 'tFirstName', + lastName: 'tLastName', + avatar: 'tAvatar', +); + +const tAdminUserProfile = UserProfile( + user: tUser, + profile: tProfile, + role: Role.admin, +); + +const tModeratorUserProfile = UserProfile( + user: tUser, + profile: tProfile, + role: Role.moderator, +); From 0462d293812d6b1804c6fb0cdab82266c5af1ffb Mon Sep 17 00:00:00 2001 From: Andre Hui Date: Sun, 18 Jun 2023 22:14:58 -0700 Subject: [PATCH 6/6] fix: flutter pub upgrade --- pubspec.lock | 286 ++++++++++++++++++++++++++------------------------- 1 file changed, 147 insertions(+), 139 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 313136a..6c93850 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a url: "https://pub.dev" source: hosted - version: "47.0.0" + version: "61.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "2f428053492f92303e42c9afa8e3a78ad1886760e7b594e2b5a6b6ee47376360" + url: "https://pub.dev" + source: hosted + version: "1.0.2" analyzer: dependency: transitive description: name: analyzer - sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "5.13.0" archive: dependency: transitive description: @@ -29,10 +37,10 @@ packages: dependency: transitive description: name: args - sha256: b003c3098049a51720352d219b0bb5f219b60fbfb68e7a4748139a06a5676515 + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.2" async: dependency: transitive description: @@ -45,18 +53,18 @@ packages: dependency: "direct main" description: name: bloc - sha256: "658a5ae59edcf1e58aac98b000a71c762ad8f46f1394c34a52050cafb3e11a80" + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.1.2" bloc_test: dependency: "direct main" description: name: bloc_test - sha256: ffbb60c17ee3d8e3784cb78071088e353199057233665541e8ac6cd438dca8ad + sha256: "43d5b2f3d09ba768d6b611151bdf20ca141ffb46e795eb9550a58c9c2f4eae3f" url: "https://pub.dev" source: hosted - version: "9.1.1" + version: "9.1.3" boolean_selector: dependency: transitive description: @@ -77,42 +85,42 @@ packages: dependency: transitive description: name: build_config - sha256: "5b7355c14258f5e7df24bad1566f7b991de3e54aeacfb94e1a65e5233d9739c1" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.2.0" build_runner: dependency: "direct dev" description: name: build_runner - sha256: e3b4e8eab6b9bdb57059e9290a7ddc0ec61b617ac862fe4d973bf6eb90475b85 + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.3" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "409c20ff6b6a9c9f4152fc9fcbf16440fedf02fcacc0fb26ea3b8eab9a860a40" + sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" url: "https://pub.dev" source: hosted - version: "7.2.4" + version: "7.2.7+1" built_collection: dependency: transitive description: @@ -125,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: d7a9cd57c215bdf8d502772447aa6b52a8ab3f956d25d5fdea6ef1df2d2dad60 + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" url: "https://pub.dev" source: hosted - version: "8.4.1" + version: "8.6.1" characters: dependency: transitive description: @@ -141,10 +149,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: dd007e4fb8270916820a0d66e24f619266b60773cddd082c6439341645af2659 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" clock: dependency: transitive description: @@ -157,34 +165,34 @@ packages: dependency: "direct main" description: name: cloud_firestore - sha256: "0405c92c7e131a76700a489b2ee2cce8bd4d75b7c19d94f2f32f6a5a850d4291" + sha256: "9194bf1845ee073bea64ec94e72c9dcf5d15f755e96496d8ccfee9def38b666f" url: "https://pub.dev" source: hosted - version: "3.4.8" + version: "3.5.1" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - sha256: "0fdb431954a4d95e0b2f461982f50a2742526934ba399039e8a47733595e1cec" + sha256: d023142c18c28b2610c23c196e829c96976569cc2aa2f8e45328ae8a64c428d1 url: "https://pub.dev" source: hosted - version: "5.7.4" + version: "5.7.7" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - sha256: "4b71c1ade1def7d036996885574746432f6a028bee3270037ed4f54ca70542c9" + sha256: "3d7d4fa8c1dc5a1f7cb33985ae0ab9924d33d76d4959fe26aed84b7d282887e3" url: "https://pub.dev" source: hosted - version: "2.8.7" + version: "2.8.10" code_builder: dependency: transitive description: name: code_builder - sha256: "02ce3596b459c666530f045ad6f96209474e8fee6e4855940a3cee65fb872ec5" + sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.5.0" collection: dependency: transitive description: @@ -197,10 +205,10 @@ packages: dependency: transitive description: name: convert - sha256: "196284f26f69444b7f5c50692b55ec25da86d9e500451dc09333bf2e3ad69259" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.1" country_codes: dependency: "direct main" description: @@ -221,10 +229,10 @@ packages: dependency: transitive description: name: coverage - sha256: d2494157c32b303f47dedee955b1479f2979c4ff66934eb7c0def44fd9e0267a + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "1.6.3" crop_your_image: dependency: "direct main" description: @@ -238,10 +246,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -254,10 +262,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.1" dartz: dependency: "direct main" description: @@ -286,10 +294,10 @@ packages: dependency: transitive description: name: dotenv - sha256: "301007cddf9995f8e4430d2f4840045dfbd57a5720b1a88ecd4ff9085ce2be79" + sha256: e169b516bc7b88801919e1c508772bcb8e3d0d1776a43f74ab692c57e741cd8a url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.1.0" dotted_border: dependency: "direct main" description: @@ -310,10 +318,10 @@ packages: dependency: "direct dev" description: name: envied_generator - sha256: "1d1264439857ca4b40f3b664b0c79d85b84c043358d1bdd92f3ea108fc1c47e6" + sha256: ebcf7d808a563383aa683899b212357dda7a2c4fa087a0c287b69dceee740f46 url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.3+1" equatable: dependency: "direct main" description: @@ -342,58 +350,58 @@ packages: dependency: "direct main" description: name: firebase_auth - sha256: "0c6365fe4e41a4d2fe27c9f416417ebbd8fa5fc2f9d7c80c148217e69a057b5b" + sha256: ca3034d35d6ca894487ec80aa1162a135fef7c5d0abef8154789cbeea3a6deaf url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.11.2" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "4c2fdc4a7d10b3cecbc093e0534a396bc26bcb1108651238e5066c7698dbb0d6" + sha256: ab20ecbc411726e139250a49fa03fe1ae0105fd990c5330b2a148ec08dfb140b url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.10.1" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: f4486f9872091a7e59e3c2c3004dd6e3891613ba187cefac1dac94f6d44499b7 + sha256: bbe4f4fffcc378ca05c3d8ff33853be86dd27d0fafc85a953acaf5190531b6f9 url: "https://pub.dev" source: hosted - version: "4.4.1" + version: "4.6.1" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "4267ef73eec5b0a0dcd030c2b702518f615812029bbd380dc4a66e0eb95d34ce" + sha256: "4f1d7c13a909e82ff026679c9b8493cdeb35a9c76bc46c42bf9e2240c9e57e80" url: "https://pub.dev" source: hosted - version: "1.23.0" + version: "1.24.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: b51257a8b4388565cd66062d727d3e60067b5f5cc3390eb0ecd20b8f97741bdb + sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.8.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "26f5ed9c1eb209464ef77c0da41f3273e713f63d7d96bf8b40f0d614c99f0389" + sha256: "839f1b48032a61962792cea1225fae030d4f27163867f181d6d2072dd40acbee" url: "https://pub.dev" source: hosted - version: "1.7.2" + version: "1.7.3" fixnum: dependency: transitive description: name: fixnum - sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -403,18 +411,18 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "890c51c8007f0182360e523518a0c732efb89876cb4669307af7efada5b55557" + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.1.3" flutter_dotenv: dependency: "direct main" description: name: flutter_dotenv - sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3 + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.0" flutter_dropzone: dependency: "direct main" description: @@ -451,10 +459,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: f2aa23d9d1721cd5c2c5097558368d2a1f85be35a85a6cf26a626e80c369a47c + sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.6" flutter_test: dependency: "direct dev" description: flutter @@ -469,66 +477,66 @@ packages: dependency: "direct dev" description: name: freezed - sha256: b8f1017d491344ef41045d3fe85950404c49e74eeaf84a84d7b8b67ac24dfb91 + sha256: a9520490532087cf38bf3f7de478ab6ebeb5f68bb1eb2641546d92719b224445 url: "https://pub.dev" source: hosted - version: "2.1.0+1" + version: "2.3.5" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: "625eb228fd9f00f952b7cd245be34791434fad48375f74e46f97dea4b4e11678" + sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "4f4a162323c86ffc1245765cfe138872b8f069deb42f7dbb36115fa27f31469b" + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.2.0" get_it: dependency: "direct main" description: name: get_it - sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7" + sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468" url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.6.0" glob: dependency: transitive description: name: glob - sha256: c51b4fdfee4d281f49b8c957f1add91b815473597f76bcf07377987f66a55729 + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" go_router: dependency: "direct main" description: name: go_router - sha256: "2049b076eaec30c695d76623c8a40b482c4bde8451d655ddef506d051522eb53" + sha256: "6d1607d76bb3a6ef52947ddae870948b9fd5bc7a5d1ef992ec4bed2fc2006fc3" url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.2.4" graphs: dependency: transitive description: name: graphs - sha256: ae0b3d956ff324c6f8671f08dcb2dbd71c99cdbf2aa3ca63a14190c47aa6679c + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.1" http: dependency: "direct main" description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "0.13.6" http_multi_server: dependency: transitive description: @@ -541,26 +549,26 @@ packages: dependency: transitive description: name: http_parser - sha256: db3060f22889f3d9d55f6a217565486737037eec3609f7f3eca4d0c67ee0d8a0 + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" image: dependency: transitive description: name: image - sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227" + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf url: "https://pub.dev" source: hosted - version: "4.0.15" + version: "4.0.17" image_network: dependency: "direct main" description: name: image_network - sha256: "5f780e4549749efba5280a32fd540e77117b76910357f8a0f1c210230aeb8ef1" + sha256: "0e1f927385af35954f93d18f2240cf6c87a3f5840caa1bb80b2c4714dc5f0460" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.5.2" injectable: dependency: "direct main" description: @@ -589,10 +597,10 @@ packages: dependency: transitive description: name: io - sha256: "0d4c73c3653ab85bf696d51a9657604c900a370549196a91f33e4c39af760852" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" js: dependency: transitive description: @@ -605,18 +613,18 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: "3520fa844009431b5d4491a5a778603520cdc399ab3406332dcc50f93547258c" + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: f3c2c18a7889580f71926f30c1937727c8c7d4f3a435f8f5e8b0ddd25253ef5d + sha256: "43793352f90efa5d8b251893a63d767b2f7c833120e3cc02adad55eefec04dc7" url: "https://pub.dev" source: hosted - version: "6.5.4" + version: "6.6.2" lint: dependency: "direct dev" description: @@ -629,18 +637,18 @@ packages: dependency: transitive description: name: lints - sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" logging: dependency: transitive description: name: logging - sha256: "293ae2d49fd79d4c04944c3a26dfd313382d5f52e821ec57119230ae16031ad4" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.2.0" matcher: dependency: transitive description: @@ -669,10 +677,10 @@ packages: dependency: transitive description: name: mime - sha256: dab22e92b41aa1255ea90ddc4bc2feaf35544fd0728e209638cad041a6e3928a + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" mocktail: dependency: "direct main" description: @@ -701,10 +709,10 @@ packages: dependency: transitive description: name: node_preamble - sha256: "8ebdbaa3b96d5285d068f80772390d27c21e1fa10fb2df6627b1b9415043608d" + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" package_config: dependency: transitive description: @@ -741,18 +749,18 @@ packages: dependency: transitive description: name: petitparser - sha256: "2ebb289dc4764ec397f5cd3ca9881c6d17196130a7d646ed022a0dd9c2e25a71" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pointer_interceptor: dependency: transitive description: @@ -781,26 +789,26 @@ packages: dependency: transitive description: name: provider - sha256: "8d7d4c2df46d6a6270a4e10404bfecb18a937e3e00f710c260d0a10415ce6b7b" + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.0.5" pub_semver: dependency: transitive description: name: pub_semver - sha256: "816c1a640e952d213ddd223b3e7aafae08cd9f8e1f6864eed304cc13b0272b07" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" rive: dependency: "direct main" description: @@ -813,34 +821,34 @@ packages: dependency: transitive description: name: shelf - sha256: "8ec607599dd0a78931a5114cdac7d609b6dbbf479a38acc9a6dba024b2a30ea0" + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.1" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler - sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" shelf_static: dependency: transitive description: name: shelf_static - sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "6db16374bc3497d21aa0eebe674d3db9fdf82082aac0f04dc7b44e4af5b08afc" + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -850,10 +858,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "0f5cf4243080b2c57650128ac1841498282da5ca1da0e826a03c9e208186c59d" + sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.2" source_helper: dependency: transitive description: @@ -874,10 +882,10 @@ packages: dependency: transitive description: name: source_maps - sha256: "490098075234dcedb83c5d949b4c93dad5e6b7702748de000be2b57b8e6b2427" + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" url: "https://pub.dev" source: hosted - version: "0.10.11" + version: "0.10.12" source_span: dependency: transitive description: @@ -906,10 +914,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: ed464977cb26a1f41537e177e190c67223dbd9f4f683489b6ab2e5d211ec564e + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: @@ -922,10 +930,10 @@ packages: dependency: "direct main" description: name: tap_canvas - sha256: "23893d16031a8a02bb92ba57237e8328378f78c296a681f0120e674400479921" + sha256: b5287b04f1efca06f0169a3591f30a8f556df3d39a7638638d7f4a24e0fa24f9 url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.5" term_glyph: dependency: transitive description: @@ -962,18 +970,18 @@ packages: dependency: transitive description: name: timing - sha256: c386d07d7f5efc613479a7c4d9d64b03710b03cfaa7e8ad5f2bfb295a1f0dfad + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" uuid: dependency: "direct main" description: @@ -1002,18 +1010,18 @@ packages: dependency: transitive description: name: watcher - sha256: e42dfcc48f67618344da967b10f62de57e04bae01d9d3af4c2596f3712a88c99 + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "3a969ddcc204a3e34e863d204b29c0752716f78b6f9cc8235083208d268a4ccd" + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.0" webkit_inspection_protocol: dependency: transitive description: @@ -1066,18 +1074,18 @@ packages: dependency: transitive description: name: xml - sha256: ac0e3f4bf00ba2708c33fbabbbe766300e509f8c82dbd4ab6525039813f7e2fb + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.2" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.3.0"