Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/gh 15/profiles #72

Open
wants to merge 6 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/app_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -32,6 +35,9 @@ class AppWidget extends StatelessWidget {
BlocProvider(
create: (_) => getIt<CrowdActionSelectedCubit>(),
),
BlocProvider(
create: (_) => getIt<ProfileBloc>(),
),
],
child: MultiBlocListener(
listeners: [
Expand Down
36 changes: 36 additions & 0 deletions lib/application/user/avatar/avatar_bloc.dart
Original file line number Diff line number Diff line change
@@ -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<AvatarEvent, AvatarState> {
final IProfileRepository profileRepository;

AvatarBloc(this.profileRepository) : super(const AvatarState.initial()) {
on<AvatarEvent>((event, emit) async {
await event.map(
uploadAvatar: (event) async => await _uploadAvatar(emit, event),
);
});
}

Future<void> _uploadAvatar(
Emitter<AvatarState> 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(),
),
);
}
}
6 changes: 6 additions & 0 deletions lib/application/user/avatar/avatar_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
part of 'avatar_bloc.dart';

@freezed
class AvatarEvent with _$AvatarEvent {
const factory AvatarEvent.uploadAvatar(File image) = _UploadAvatar;
}
12 changes: 12 additions & 0 deletions lib/application/user/avatar/avatar_state.dart
Original file line number Diff line number Diff line change
@@ -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;
}
76 changes: 76 additions & 0 deletions lib/application/user/profile/profile_bloc.dart
Original file line number Diff line number Diff line change
@@ -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<ProfileEvent, ProfileState> {
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<ProfileEvent>((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<void> _getUserProfile(
Emitter<ProfileState> emit,
_GetUserProfile event,
) async {
final userProfileOption = await profileRepository.getUserProfile();

emit(
userProfileOption.fold(
(failure) => const ProfileState.profileError(),
(userProfile) => ProfileState.profileFound(userProfile),
),
);
}

Future<void> _createUserProfile(
Emitter<ProfileState> emit,
_CreateUserProfile event,
) async {
final createUserOption = await profileRepository.createProfile();
emit(createUserOption.fold(
(failure) => const ProfileState.profileError(),
(success) => const ProfileState.profileFound(null),
));
}
}
7 changes: 7 additions & 0 deletions lib/application/user/profile/profile_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
part of 'profile_bloc.dart';

@freezed
class ProfileEvent with _$ProfileEvent {
const factory ProfileEvent.getUserProfile() = _GetUserProfile;
const factory ProfileEvent.createUserProfile() = _CreateUserProfile;
}
30 changes: 30 additions & 0 deletions lib/application/user/profile/profile_state.dart
Original file line number Diff line number Diff line change
@@ -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;
}
33 changes: 33 additions & 0 deletions lib/application/user/username/username_bloc.dart
Original file line number Diff line number Diff line change
@@ -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<UsernameEvent, UsernameState> {
final IProfileRepository profileRepository;

UsernameBloc(this.profileRepository) : super(const UsernameState.initial()) {
on<UsernameEvent>((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"),
),
);
});
});
}
}
9 changes: 9 additions & 0 deletions lib/application/user/username/username_event.dart
Original file line number Diff line number Diff line change
@@ -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;
}
9 changes: 9 additions & 0 deletions lib/application/user/username/username_state.dart
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 16 additions & 0 deletions lib/domain/profile/i_profile_repository.dart
Original file line number Diff line number Diff line change
@@ -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<Either<ProfileFailure, UserProfile>> getUserProfile();
Future<Either<ProfileFailure, Unit>> createProfile();
Future<Either<ProfileFailure, Unit>> updateUsername({
String? firstName,
String? lastName,
});
Future<Either<ProfileFailure, Unit>> uploadAvatar(File file);
}
18 changes: 18 additions & 0 deletions lib/domain/profile/profile.dart
Original file line number Diff line number Diff line change
@@ -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';
}
10 changes: 10 additions & 0 deletions lib/domain/profile/profile_failure.dart
Original file line number Diff line number Diff line change
@@ -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;
}
35 changes: 35 additions & 0 deletions lib/domain/profile/user_profile.dart
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
32 changes: 32 additions & 0 deletions lib/infrastructure/profile/profile_dto.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> json) =>
_$ProfileDtoFromJson(json);
}
Loading