Skip to content
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
17 changes: 17 additions & 0 deletions lib/repositories/user_repository.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter_base/network/api_client.dart';

import '../models/entities/user/user_entity.dart';

abstract class UserRepository {
Future<UserEntity> getProfile();

Future<UserEntity> updateProfile({required UserEntity userEntity});

Future<UserEntity?> uploadAvatar(File file);
}

class UserRepositoryImpl extends UserRepository {
Expand All @@ -23,4 +29,15 @@ class UserRepositoryImpl extends UserRepository {
Future<UserEntity> updateProfile({required UserEntity userEntity}) async {
return UserEntity.updateProfile(userEntity: userEntity);
}

@override
Future<UserEntity?> uploadAvatar(File file) async {
FormData.fromMap({
"file": await MultipartFile.fromFile(
file.path,
),
});
await Future.delayed(const Duration(seconds: 2));
return UserEntity.mockData();
}
}
28 changes: 28 additions & 0 deletions lib/ui/pages/profile/update_avatar/update_avatar_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import 'dart:io';

import 'package:equatable/equatable.dart';
import 'package:flutter_base/global_blocs/user/user_cubit.dart';
import 'package:flutter_base/models/entities/user/user_entity.dart';
import 'package:flutter_base/models/enums/load_status.dart';
import 'package:flutter_base/repositories/user_repository.dart';
import 'package:flutter_base/ui/pages/profile/update_avatar/update_avatar_navigator.dart';
import 'package:flutter_base/utils/logger.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

part 'update_avatar_state.dart';

class UpdateAvatarCubit extends Cubit<UpdateAvatarState> {
final UpdateAvatarNavigator navigator;
final UserRepository userRepository;
final UserCubit userCubit;

UpdateAvatarCubit({
required this.navigator,
required this.userRepository,
required this.userCubit,
}) : super(const UpdateAvatarState());

Future<void> updateImage(File file) async {
emit(state.copyWith(updateImageStatus: LoadStatus.loading));
try {
final result = await userRepository.uploadAvatar(file);
if (result != null) {
userCubit.updateUser(result);
emit(state.copyWith(
updateImageStatus: LoadStatus.success,
image: file,
));
navigator.showSuccessFlushbar(message: 'Update Avatar Successfully!');
} else {
emit(state.copyWith(updateImageStatus: LoadStatus.failure));
navigator.showErrorFlushbar(message: 'Update Avatar Failed!');
}
} catch (error) {
logger.e(error);
emit(state.copyWith(updateImageStatus: LoadStatus.failure));
}
}
}
10 changes: 10 additions & 0 deletions lib/ui/pages/profile/update_avatar/update_avatar_navigator.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_base/common/app_navigator.dart';

class UpdateAvatarNavigator extends AppNavigator {
UpdateAvatarNavigator({required super.context});

Future<void> showBottomSheet({required Widget child}) async {
await showModalBottomSheet(
context: context,
builder: (context) {
return child;
},
);
}
}
173 changes: 104 additions & 69 deletions lib/ui/pages/profile/update_avatar/update_avatar_page.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_base/global_blocs/user/user_cubit.dart';
import 'package:flutter_base/models/enums/load_status.dart';
import 'package:flutter_base/repositories/user_repository.dart';
import 'package:flutter_base/ui/pages/profile/update_avatar/update_avatar_navigator.dart';
import 'package:flutter_base/ui/widgets/appbar/app_bar_widget.dart';
import 'package:flutter_base/ui/widgets/divider/app_divider.dart';
import 'package:flutter_base/ui/widgets/images/app_circle_avatar.dart';
import 'package:flutter_base/ui/widgets/loading/app_loading_indicator.dart';
import 'package:flutter_base/ui/widgets/picker/app_image_picker.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'update_avatar_cubit.dart';
Expand All @@ -16,8 +23,12 @@ class UpdateAvatarPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
final userRepo = RepositoryProvider.of<UserRepository>(context);
final userCubit = RepositoryProvider.of<UserCubit>(context);
return UpdateAvatarCubit(
navigator: UpdateAvatarNavigator(context: context),
userRepository: userRepo,
userCubit: userCubit,
);
},
child: const UpdateAvatarChildPage(),
Expand All @@ -44,23 +55,27 @@ class _UpdateAvatarChildPageState extends State<UpdateAvatarChildPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Avatar"),
appBar: AppBarWidget(
title: "Avatar",
actions: [
BlocBuilder<UpdateAvatarCubit, UpdateAvatarState>(
builder: (context, state) {
return _cubit.state.image == null
? TextButton(
onPressed: () async {
showOption(
chooseImageCollection: () {},
chooseImageCamera: () {});
if (_cubit.state.updateImageStatus ==
LoadStatus.loading) return;
showOption();
},
child: const Text("thay đổi"),
child: const Text("Upload"),
)
: TextButton(
onPressed: () async {},
child: const Text("cập nhật"),
onPressed: () async {
if (_cubit.state.updateImageStatus ==
LoadStatus.loading) return;
showOption();
},
child: const Text("Update"),
);
},
)
Expand All @@ -76,72 +91,92 @@ class _UpdateAvatarChildPageState extends State<UpdateAvatarChildPage> {
return Center(
child: BlocBuilder<UpdateAvatarCubit, UpdateAvatarState>(
builder: (context, state) {
return state.image == null
? const AppCircleAvatar(size: Size(400, 400))
: ClipRRect(
borderRadius: BorderRadius.circular(200),
child: Image.file(
state.image ?? File(''),
width: 400,
height: 400,
fit: BoxFit.cover,
),
);
return state.updateImageStatus == LoadStatus.loading
? const AppCircularProgressIndicator()
: state.image == null
? const AppCircleAvatar(size: Size(400, 400))
: ClipRRect(
borderRadius: BorderRadius.circular(200),
child: Image.file(
state.image ?? File(''),
width: 400,
height: 400,
fit: BoxFit.cover,
),
);
},
),
);
}

Future<void> showOption({
required Function() chooseImageCollection,
required Function() chooseImageCamera,
}) async {
// AppBottomSheet.show(Container(
// height: 200,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(20),
// color: Colors.white,
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// InkWell(
// onTap: () {
// chooseImageCollection();
// },
// child: const Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(
// Icons.collections,
// size: 60,
// color: Colors.grey,
// ),
// Text(
// "choose from the collection",
// ),
// ],
// ),
// ),
// const InkWell(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(
// Icons.photo_camera,
// size: 60,
// color: Colors.grey,
// ),
// Text(
// 'take a photo',
// ),
// ],
// ),
// ),
// ],
// ),
// ));
Future<void> showOption() async {
await _cubit.navigator.showBottomSheet(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Edit Image",
),
const AppDivider(
height: 16,
thickness: 1,
),
ListTile(
onTap: () async {
await onChooseImageFromGallery();
},
leading: const Icon(
Icons.collections,
size: 24,
color: Colors.red,
),
title: const Text(
"Choose from the collection",
),
),
ListTile(
onTap: () async {
await onChooseImageFromCamera();
},
leading: const Icon(
Icons.photo_camera,
size: 24,
color: Colors.green,
),
title: const Text(
"Take a photo",
),
),
const SizedBox(height: 16),
],
),
),
);
}

Future<void> onChooseImageFromGallery() async {
final result = await AppImagePicker.getImageFromGallery(context);
_cubit.navigator.pop();
if (result != null) {
final file = File(result.path);
await _cubit.updateImage(file);
}
}

Future<void> onChooseImageFromCamera() async {
final result = await AppImagePicker.getImageFromCamera(context);
_cubit.navigator.pop();
if (result != null) {
final file = File(result.path);
await _cubit.updateImage(file);
}
}

@override
Expand Down
16 changes: 5 additions & 11 deletions lib/ui/pages/profile/update_avatar/update_avatar_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,34 @@ class UpdateAvatarState extends Equatable {
final LoadStatus userStatus;
final UserEntity? user;
final File? image;
final LoadStatus imageCollectionStatus;
final LoadStatus imageCameraStatus;
final LoadStatus updateImageStatus;

const UpdateAvatarState({
this.userStatus = LoadStatus.initial,
this.user,
this.image,
this.imageCollectionStatus = LoadStatus.initial,
this.imageCameraStatus = LoadStatus.initial,
this.updateImageStatus = LoadStatus.initial,
});

@override
List<Object?> get props => [
userStatus,
user,
image,
imageCollectionStatus,
imageCameraStatus,
updateImageStatus,
];

UpdateAvatarState copyWith({
LoadStatus? userStatus,
UserEntity? user,
File? image,
LoadStatus? imageCollectionStatus,
LoadStatus? imageCameraStatus,
LoadStatus? updateImageStatus,
}) {
return UpdateAvatarState(
userStatus: userStatus ?? this.userStatus,
user: user ?? this.user,
image: image ?? this.image,
imageCollectionStatus:
imageCollectionStatus ?? this.imageCollectionStatus,
imageCameraStatus: imageCameraStatus ?? this.imageCameraStatus,
updateImageStatus: updateImageStatus ?? this.updateImageStatus,
);
}
}
3 changes: 3 additions & 0 deletions lib/ui/widgets/appbar/app_bar_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
final String title;
final bool showBackButton;
final VoidCallback? onBackPressed;
final List<Widget>? actions;

const AppBarWidget({
super.key,
required this.title,
this.showBackButton = true,
this.onBackPressed,
this.actions,
});

@override
Expand Down Expand Up @@ -49,6 +51,7 @@ class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
),
)
: const SizedBox(),
actions: actions,
),
);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/widgets/divider/app_divider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ class AppDivider extends Divider {
super.key,
double super.indent = 0,
double super.endIndent = 0,
double super.thickness = 0,
double super.height = 0,
}) : super(
color: AppColors.divider,
height: 1,
);
}
Loading