From dfd6c9e7d7491c94f61bc95cbdf8fca8369d8465 Mon Sep 17 00:00:00 2001 From: Thien Chi Vi Date: Thu, 25 Jun 2020 13:00:08 +0700 Subject: [PATCH] :tada: :tada: Complete upload avatar (#261) * use api get details account * Update dev module add account service authenticated * Complete upload avatar * Fix lock user intergate profile screen when uploading --- .../authentication_bloc.dart | 10 +- lib/profile/screen/profile_screen.dart | 132 +++++++++++++----- lib/profile/screen/screen.dart | 3 + .../widget/register_account_widget.dart | 6 +- petisland_core/lib/domain/login_data.dart | 3 +- .../lib/module/dev_module_core.dart | 16 +++ .../lib/repository/account_repository.dart | 9 ++ .../lib/repository/user_repository.dart | 14 +- .../lib/service/account_service.dart | 7 + petisland_core/lib/service/user_service.dart | 6 + 10 files changed, 165 insertions(+), 41 deletions(-) diff --git a/lib/authentication_bloc/authentication_bloc.dart b/lib/authentication_bloc/authentication_bloc.dart index 3998695..71730eb 100644 --- a/lib/authentication_bloc/authentication_bloc.dart +++ b/lib/authentication_bloc/authentication_bloc.dart @@ -18,6 +18,9 @@ class AuthenticationBloc extends TBloc @protected static final AccountService accountService = DI.get(AccountService); + static final AccountService accountServiceAuthenticated = + DI.get(DevModuleCore.account_service_authenticated); + @protected static final PetCategoryService categoryService = DI.get(PetCategoryService); @@ -59,8 +62,7 @@ class AuthenticationBloc extends TBloc case LoggedIn: _clearData(); - updateCurrentAccount(event); - + await updateCurrentAccount(); await reloadPetCategory(); await reloadReportContent(); yield Authenticated(); @@ -96,8 +98,8 @@ class AuthenticationBloc extends TBloc Log.info('loaded report'); } - void updateCurrentAccount(LoggedIn event) { - _currentAccount = event.dataLogin.account; + Future updateCurrentAccount() async { + _currentAccount = await accountServiceAuthenticated.getDetails(); } void _clearData() { diff --git a/lib/profile/screen/profile_screen.dart b/lib/profile/screen/profile_screen.dart index e89f96e..2bbe875 100644 --- a/lib/profile/screen/profile_screen.dart +++ b/lib/profile/screen/profile_screen.dart @@ -13,45 +13,53 @@ class ProfileScreen extends StatefulWidget { } class _ProfileScreenState extends TState { + bool isLoading = false; + final authen = DI.get(AuthenticationBloc); @override Widget build(BuildContext context) { final spacer = SizedBox(height: 5); - final Account account = DI.get(AuthenticationBloc).account; + final Account account = authen.account; final image = account.user?.avatar?.url; return Scaffold( - body: ListView( - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), - children: [ - const SizedBox(height: 25), - ChooseAvatarWidget( - avatar: CircleColorWidget( - padding: const EdgeInsets.all(5), - child: AvatarWidget( - url: image, - paddingDefaultImage: const EdgeInsets.all(5), + body: Stack( + children: [ + ListView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + children: [ + const SizedBox(height: 25), + ChooseAvatarWidget( + avatar: CircleColorWidget( + padding: const EdgeInsets.all(5), + child: AvatarWidget( + url: image, + paddingDefaultImage: const EdgeInsets.all(15), + ), + ), + onTapCamera: _handleOnTapCamera, ), - ), - ), - GestureDetector( - child: _buildName(context, account), - onTap: _onTapName, - ), - _buildDarkMode(), - Divider(), - ProfileDetailWidget( - onTapMyPost: _onTapMyPost, - onTapPostLiked: _onTapPostLiked, - onTapProfile: _onTapProfile, - ), - spacer, - Divider(), - BasicFunctionWidget( - onTapLogout: _onTapLogout, - onTapChangePassword: _onTapChangePassword, - onTapRating: _onTapRating, - onTapReport: _onTapReport, + GestureDetector( + child: _buildName(context, account), + onTap: _onTapName, + ), + _buildDarkMode(), + Divider(), + ProfileDetailWidget( + onTapMyPost: _onTapMyPost, + onTapPostLiked: _onTapPostLiked, + onTapProfile: _onTapProfile, + ), + spacer, + Divider(), + BasicFunctionWidget( + onTapLogout: _onTapLogout, + onTapChangePassword: _onTapChangePassword, + onTapRating: _onTapRating, + onTapReport: _onTapReport, + ), + ], ), + isLoading ? _buildLoading() : const SizedBox() ], ), ); @@ -130,4 +138,64 @@ class _ProfileScreenState extends TState { screen: NewProfileScreen(), ); } + + void _handleOnTapCamera() { + chooseImage() + .whenComplete(() => setState(() => isLoading = true)) + .then((value) => uploadImage(value)) + .then((value) => updateProfile(image: value)) + .then((value) => authen.account.user = value) + .catchError((ex) => Log.error('ChooseImageError ${ex.toString()}')) + .whenComplete(() => setState(() => isLoading = false)); + } + + Future chooseImage() { + return showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + builder: (_) => ImageChoosePopup(), + ).then((File file) { + if (file != null) { + return Future.value(file); + } else { + return retrieveLostData(); + } + }).then((value) { + if (value == null) { + throw Exception('Can\'t get image'); + } else + return value; + }); + } + + Future uploadImage(File value) async { + // TODO(TVC12): Move to bloc in the feature + final ImageService imageService = DI.get(ImageService); + final petImages = await imageService.upload([value.absolute.path]); + if (petImages?.isNotEmpty == true) { + return petImages.first; + } else { + throw PetException('Can\'t upload image'); + } + } + + Future updateProfile({PetImage image}) { + // TODO(TVC12): Move to bloc in the feature + final UserService userService = DI.get(UserService); + final AuthenticationBloc authenBloc = DI.get(AuthenticationBloc); + final userId = authenBloc.account.user?.id; + final oldAvatar = authenBloc.account.user?.avatar?.id; + if (userId != null) { + return userService.updateAvatar(userId, image.id, deleteImage: oldAvatar); + } else { + throw PetException('Can\t update profile'); + } + } + + Widget _buildLoading() { + return Scaffold( + backgroundColor: TColors.transparent, + body: Center(child: CircularProgressIndicator()), + ); + } } diff --git a/lib/profile/screen/screen.dart b/lib/profile/screen/screen.dart index 106f5b3..3d21873 100644 --- a/lib/profile/screen/screen.dart +++ b/lib/profile/screen/screen.dart @@ -1,5 +1,7 @@ library petisland.profile.screen; +import 'dart:io'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -10,6 +12,7 @@ import 'package:flutter_template/common/common.dart'; import 'package:flutter_template/common/widgets/widgets.dart'; import 'package:flutter_template/new_profile/screen/new_profile_screen.dart'; import 'package:flutter_template/pet_feed/widget/widget.dart'; +import 'package:flutter_template/post/post_edit/post_edit.dart'; import 'package:flutter_template/profile/bloc/bloc.dart'; import 'package:flutter_template/profile/widget/favorite_post_component.dart'; import 'package:flutter_template/profile/widget/widget.dart'; diff --git a/lib/register/widget/register_account_widget.dart b/lib/register/widget/register_account_widget.dart index 3112bb9..55264d4 100644 --- a/lib/register/widget/register_account_widget.dart +++ b/lib/register/widget/register_account_widget.dart @@ -152,7 +152,7 @@ class _RegisterAccountWidgetState extends TState { ), LocationSelectorWidget( isRequired: true, - onSelected: _onTextChanged, + onSelected: _onLocationChanged, padding: EdgeInsets.zero, selectedItem: locationController.text.isNotEmpty ? locationController.text : null, ) @@ -211,8 +211,10 @@ class _RegisterAccountWidgetState extends TState { bool get isValid => usernameController.text.isNotEmpty & passwordController.text.isNotEmpty; - void _onTextChanged(String str) { + void _onLocationChanged(String str) { locationController.text = str; + } + void _onTextChanged(String str) { setState(() {}); } } diff --git a/petisland_core/lib/domain/login_data.dart b/petisland_core/lib/domain/login_data.dart index d2604aa..4945b7d 100644 --- a/petisland_core/lib/domain/login_data.dart +++ b/petisland_core/lib/domain/login_data.dart @@ -15,8 +15,7 @@ class LoginData extends BaseModel { LoginData.fromJson(Map json) : super.fromJson(json) { token = json['token']; - account = - json['account'] != null ? Account.fromJson(json['account']) : null; + account = json['account'] != null ? Account.fromJson(json['account']) : null; } @override diff --git a/petisland_core/lib/module/dev_module_core.dart b/petisland_core/lib/module/dev_module_core.dart index aed2b8e..4eb8ec5 100644 --- a/petisland_core/lib/module/dev_module_core.dart +++ b/petisland_core/lib/module/dev_module_core.dart @@ -14,6 +14,7 @@ class DevModuleCore extends AbstractModule { static const String api_client = 'api_client'; static const String api_upload_image = 'api_upload_image'; static const String opencagedata_api = 'opencagedata_api'; + static const String account_service_authenticated = 'account_service_authenticated'; @override void init() async { @@ -26,6 +27,7 @@ class DevModuleCore extends AbstractModule { bind(opencagedata_api).to(_buildOpencagedataApi()); bind(AccountService).to(_buildAccountService()); + bind(account_service_authenticated).to(_buildAccountServiceAuthenticated()); bind(DIKeys.cache_image).to(await _buildCacheImage()); bind(ImageService).to(_buildImageService()); bind(PetCategoryService).to(_buildPetCategoryService()); @@ -37,6 +39,7 @@ class DevModuleCore extends AbstractModule { bind(LocationService).to(_buildLocationService()); bind(RescueRepository).to(_buildRescueRepository()); bind(RescueService).to(_buildRescueService()); + bind(UserService).to(_buildUserService()); } Future _buildLocalService() async { @@ -145,6 +148,12 @@ class DevModuleCore extends AbstractModule { return AccountServiceImpl(repository); } + AccountService _buildAccountServiceAuthenticated() { + final HttpClient client = get(api_client); + final AccountRepository repository = AccountReposityImpl(client); + return AccountServiceImpl(repository); + } + ImageService _buildImageService() { final HttpClient client = get(api_upload_image); final ImageRepository repository = ImageRepositoryImpl(client); @@ -202,4 +211,11 @@ class DevModuleCore extends AbstractModule { return RescueServiceImpl(repository); } + + UserService _buildUserService() { + final HttpClient client = get(api_client); + + final userRepository = UserRepositoryImpl(client); + return UserServiceImpl(userRepository); + } } diff --git a/petisland_core/lib/repository/account_repository.dart b/petisland_core/lib/repository/account_repository.dart index e3087d3..b557bda 100644 --- a/petisland_core/lib/repository/account_repository.dart +++ b/petisland_core/lib/repository/account_repository.dart @@ -15,6 +15,8 @@ abstract class AccountRepository { Future requireForgotPasswordCode(String email); Future forgotPassword(String email, String code, String password); + + Future getDetails(); } class AccountReposityImpl extends AccountRepository { @@ -96,4 +98,11 @@ class AccountReposityImpl extends AccountRepository { .get>('$path/forgot-password/require-code', params: params) .then((_) => true); } + + @override + Future getDetails() { + return client + .get>('$path/details') + .then((json) => Account.fromJson(json)); + } } diff --git a/petisland_core/lib/repository/user_repository.dart b/petisland_core/lib/repository/user_repository.dart index 84a4ffc..fc56e80 100644 --- a/petisland_core/lib/repository/user_repository.dart +++ b/petisland_core/lib/repository/user_repository.dart @@ -2,7 +2,7 @@ part of petisland_core.repository; abstract class UserRepository { Future createUser(User user); -// User getUser(); + Future updateAvatar(String userId, String newImage, {String deleteImage}); } class UserRepositoryImpl extends UserRepository { @@ -18,4 +18,16 @@ class UserRepositoryImpl extends UserRepository { .post>('/api/user', body) .then((Map json) => User.fromJson(json)); } + + @override + Future updateAvatar(String userId, String newImage, {String deleteImage}) { + final body = { + 'avatar': {'newImage': newImage, 'deleteImage': deleteImage} + ..removeWhere((key, value) => value == null) + }; + + return client + .put>('/api/user/${userId}', body) + .then((json) => User.fromJson(json)); + } } diff --git a/petisland_core/lib/service/account_service.dart b/petisland_core/lib/service/account_service.dart index d2f2499..2448b86 100644 --- a/petisland_core/lib/service/account_service.dart +++ b/petisland_core/lib/service/account_service.dart @@ -15,6 +15,8 @@ abstract class AccountService { Future requireForgotPasswordCode(String email); Future forgotPassword(String email, String code, String password); + + Future getDetails(); } class AccountServiceImpl extends AccountService { @@ -58,4 +60,9 @@ class AccountServiceImpl extends AccountService { Future requireForgotPasswordCode(String email) { return repository.requireForgotPasswordCode(email); } + + @override + Future getDetails() { + return repository.getDetails(); + } } diff --git a/petisland_core/lib/service/user_service.dart b/petisland_core/lib/service/user_service.dart index 6b0a6cd..423d482 100644 --- a/petisland_core/lib/service/user_service.dart +++ b/petisland_core/lib/service/user_service.dart @@ -2,6 +2,7 @@ part of petisland_core.service; abstract class UserService { Future createUser(User user); + Future updateAvatar(String userId, String newImage, {String deleteImage}); } class UserServiceImpl extends UserService { @@ -14,4 +15,9 @@ class UserServiceImpl extends UserService { Future createUser(User user) { return repository.createUser(user); } + + @override + Future updateAvatar(String userId, String newImage, {String deleteImage}) { + return repository.updateAvatar(userId, newImage, deleteImage: deleteImage); + } }