diff --git a/lib/application/user/profile/profile_bloc.dart b/lib/application/user/profile/profile_bloc.dart index 99784a81..fe7ce549 100644 --- a/lib/application/user/profile/profile_bloc.dart +++ b/lib/application/user/profile/profile_bloc.dart @@ -25,24 +25,45 @@ class ProfileBloc extends Bloc { await userOrFailure.fold( (failure) => const ProfileState( userProfile: null, - isEditing: false, + isPicEditing: false, + isBioEditing: false, ), (userProfile) => state.copyWith( userProfile: userProfile, - isEditing: false, + isPicEditing: false, + isBioEditing: false, ), ), ); }); - on((event, emit) { - emit(state.copyWith(isEditing: true)); + on((event, emit) { + emit(state.copyWith(isBioEditing: true)); }); - on((event, emit) async { + on((event, emit) { + emit(state.copyWith(isPicEditing: true)); + }); + + on((event, emit) async { if (event.bio != null) { await _profileRepository.saveProfile(bio: event.bio); } + + final userOrFailure = await _profileRepository.getUserProfile(); + + emit( + userOrFailure.fold( + (failure) => state.copyWith(isBioEditing: false), + (userProfile) => state.copyWith( + userProfile: userProfile, + isBioEditing: false, + ), + ), + ); + }); + + on((event, emit) async { final wasImageUpdated = event.image != null; if (wasImageUpdated) { await _avatarRepository.uploadAvatar(event.image!); @@ -53,18 +74,18 @@ class ProfileBloc extends Bloc { emit( userOrFailure.fold( - (failure) => state.copyWith(isEditing: false), + (failure) => state.copyWith(isPicEditing: false), (userProfile) => state.copyWith( userProfile: userProfile, - isEditing: false, + isPicEditing: false, wasProfilePictureUpdated: wasImageUpdated, ), ), ); }); - on((event, emit) { - emit(state.copyWith(isEditing: false)); + on((event, emit) { + emit(state.copyWith(isPicEditing: false)); }); } } diff --git a/lib/application/user/profile/profile_event.dart b/lib/application/user/profile/profile_event.dart index 4780c65e..a7f0dac6 100644 --- a/lib/application/user/profile/profile_event.dart +++ b/lib/application/user/profile/profile_event.dart @@ -4,16 +4,25 @@ abstract class ProfileEvent {} class GetUserProfile extends ProfileEvent {} -class EditProfile extends ProfileEvent {} +class EditBio extends ProfileEvent {} -class SaveProfile extends ProfileEvent { +class SaveBio extends ProfileEvent { final String? bio; + SaveBio({ + this.bio, + }); +} + +class CancelBio extends ProfileEvent {} + +class EditProfilePic extends ProfileEvent {} + +class SaveProfilePic extends ProfileEvent { final File? image; - SaveProfile({ - this.bio, + SaveProfilePic({ this.image, }); } -class CancelEditProfile extends ProfileEvent {} +class CancelEditProfilePic extends ProfileEvent {} diff --git a/lib/application/user/profile/profile_state.dart b/lib/application/user/profile/profile_state.dart index 57646fcf..da4fbbd1 100644 --- a/lib/application/user/profile/profile_state.dart +++ b/lib/application/user/profile/profile_state.dart @@ -3,33 +3,39 @@ part of 'profile_bloc.dart'; class ProfileState extends Equatable { const ProfileState({ required this.userProfile, - this.isEditing, + this.isPicEditing, + this.isBioEditing, this.wasProfilePictureUpdated, }); final UserProfile? userProfile; - final bool? isEditing; + final bool? isPicEditing; + final bool? isBioEditing; final bool? wasProfilePictureUpdated; factory ProfileState.initial() => const ProfileState( userProfile: null, - isEditing: false, + isPicEditing: false, + isBioEditing: false, wasProfilePictureUpdated: false, ); ProfileState copyWith({ UserProfile? userProfile, - bool? isEditing, + bool? isPicEditing, + bool? isBioEditing, bool? wasProfilePictureUpdated, }) { return ProfileState( userProfile: userProfile ?? this.userProfile, - isEditing: isEditing ?? this.isEditing, + isPicEditing: isPicEditing ?? this.isPicEditing, + isBioEditing: isBioEditing ?? this.isBioEditing, wasProfilePictureUpdated: wasProfilePictureUpdated ?? false, // Reset state implicitly ); } @override - List get props => [userProfile, isEditing, wasProfilePictureUpdated]; + List get props => + [userProfile, isPicEditing, isBioEditing, wasProfilePictureUpdated]; } diff --git a/lib/presentation/profile/profile_screen.dart b/lib/presentation/profile/profile_screen.dart index 43035b7c..ea260b62 100644 --- a/lib/presentation/profile/profile_screen.dart +++ b/lib/presentation/profile/profile_screen.dart @@ -129,7 +129,7 @@ class _UserProfilePageState extends State { maxRadius: 50, ), ), - if (state.isEditing == true) ...[ + if (state.userProfile != null) ...[ Positioned( bottom: 0, right: 0, @@ -139,11 +139,17 @@ class _UserProfilePageState extends State { context, onSelected: (image) { setState(() => _image = image); + BlocProvider.of(context) + .add( + SaveProfilePic(image: image), + ); }, ), backgroundColor: kAccentColor, mini: true, - child: const Icon(Icons.add), + child: const Icon( + Icons.drive_file_rename_outline, + ), ), ), ] @@ -162,17 +168,58 @@ class _UserProfilePageState extends State { ), if (state.userProfile != null) ...[ const SizedBox(height: 40), - const Text( - 'About me', - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 11, - color: Color(0xFF666666), - ), - textAlign: TextAlign.left, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'About me', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 11, + color: Color(0xFF666666), + ), + textAlign: TextAlign.left, + ), + TextButton( + key: const Key('save_edit_bio_button'), + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(200), + ), + ), + ), + onPressed: () { + if (state.isBioEditing == true) { + /// TODO: Implement save profile image + BlocProvider.of(context).add( + SaveBio( + bio: bioController.text, + ), + ); + } else { + context + .read() + .add(EditBio()); + } + }, + child: Text( + state.isBioEditing == true + ? 'Save' + : 'Edit', + style: const TextStyle( + fontSize: 11, + color: kAccentColor, + fontWeight: FontWeight.w700, + ), + ), + ), + ], ), const SizedBox(height: 10), - if (state.isEditing == true) ...[ + if (state.isBioEditing == true) ...[ + //idhr bio editing start Row( children: [ Expanded( @@ -215,8 +262,9 @@ class _UserProfilePageState extends State { ), const SizedBox(height: 4), Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - const SizedBox(width: 16), Text( 'Maximum 150 characters', style: Theme.of(context) @@ -265,90 +313,6 @@ class _UserProfilePageState extends State { ], ), ), - const SizedBox(height: 40), - TextButton( - key: const Key('save_edit_button'), - style: ButtonStyle( - overlayColor: MaterialStateColor.resolveWith( - (states) => state.isEditing == true - ? Colors.white.withOpacity(0.1) - : kAccentColor.withOpacity(0.1), - ), - backgroundColor: state.isEditing == true - ? MaterialStateProperty.all( - kAccentColor, - ) - : null, - minimumSize: MaterialStateProperty.all( - const Size(double.infinity * 0.75, 52), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(200), - side: const BorderSide( - color: kAccentColor, - ), - ), - ), - ), - onPressed: () { - if (state.isEditing == true) { - BlocProvider.of(context).add( - SaveProfile( - bio: bioController.text, - image: _image, - ), - ); - } else { - context - .read() - .add(EditProfile()); - } - }, - child: Text( - state.isEditing == true - ? 'Save changes' - : 'Edit profile', - style: TextStyle( - color: state.isEditing == true - ? Colors.white - : kAccentColor, - fontWeight: FontWeight.w700, - ), - ), - ), - if (state.isEditing == true) ...[ - const SizedBox(height: 10), - TextButton( - key: const Key('cancel_edit_button'), - style: ButtonStyle( - minimumSize: MaterialStateProperty.all( - const Size(double.infinity * 0.75, 52), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(200), - ), - ), - ), - onPressed: () { - BlocProvider.of(context) - .add(CancelEditProfile()); - - _image = null; - bioController.value = TextEditingValue( - text: state.userProfile?.profile.bio ?? '', - ); - }, - child: const Text( - 'Cancel', - style: TextStyle( - color: kAccentColor, - fontWeight: FontWeight.w700, - ), - ), - ), - ], ] else ...[ const SizedBox(height: 40), PillButton( diff --git a/test/application/user/profile/profile_bloc_test.dart b/test/application/user/profile/profile_bloc_test.dart deleted file mode 100644 index 5183cb8d..00000000 --- a/test/application/user/profile/profile_bloc_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:collaction_app/application/user/profile/profile_bloc.dart'; -import 'package:collaction_app/domain/user/i_avatar_repository.dart'; -import 'package:collaction_app/domain/user/i_profile_repository.dart'; -import 'package:collaction_app/domain/user/profile_failure.dart'; -import 'package:dartz/dartz.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../domain/user/user_fixture.dart'; -import '../../../test_utilities.dart'; - -void main() { - late IProfileRepository profileRepository; - late IAvatarRepository avatarRepository; - late ProfileBloc profileBloc; - - const userProfile = cUserProfile; - - setUp(() { - profileRepository = MockProfileRepository(); - avatarRepository = MockAvatarRepository(); - profileBloc = ProfileBloc(profileRepository, avatarRepository); - }); - - test('should have [ProfileState.initial()] as initial state', () async { - /// assert - expect(profileBloc.state, equals(ProfileState.initial())); - }); - - blocTest( - 'should return [ProfileState] with user profile ' - 'when profile retrieval is successful', - setUp: () { - when(() => profileRepository.getUserProfile()) - .thenAnswer((_) async => const Right(userProfile)); - }, - build: () => profileBloc, - act: (bloc) => [bloc.add(GetUserProfile())], - expect: () => const [ - ProfileState( - userProfile: userProfile, - isEditing: false, - wasProfilePictureUpdated: false, - ) - ], - verify: (_) => verify(() => profileRepository.getUserProfile()).called(1), - ); - - blocTest( - 'should return [ProfileState] with null user profile ' - 'when profile retrieval is not successful', - setUp: () { - when(() => profileRepository.getUserProfile()) - .thenAnswer((_) async => const Left(ProfileFailure.unexpected())); - }, - build: () => profileBloc, - act: (bloc) => [bloc.add(GetUserProfile())], - expect: () => const [ - ProfileState( - userProfile: null, - isEditing: false, - ) - ], - verify: (_) => verify(() => profileRepository.getUserProfile()).called(1), - ); - - /// TODO(obella): Add more tests -} diff --git a/test/presentation/profile/profile_screen_test.dart b/test/presentation/profile/profile_screen_test.dart index 45960ed8..3de6e058 100644 --- a/test/presentation/profile/profile_screen_test.dart +++ b/test/presentation/profile/profile_screen_test.dart @@ -156,9 +156,9 @@ void main() { expect(find.byType(UserProfilePage), findsOneWidget); - profileBloc.add(EditProfile()); + profileBloc.add(EditBio()); await tester.pumpAndSettle(); - expect(profileBloc.state.isEditing, true); + expect(profileBloc.state.isBioEditing, true); }); }