From a5f4ac88894582dfa39f6072b1016ebd0b5bf2ef Mon Sep 17 00:00:00 2001 From: ya_yo0 Date: Thu, 25 Sep 2025 15:53:51 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/constants/endpoint.dart | 3 +++ .../preparation_remote_data_source.dart | 19 ++++++++++++++++++ .../update_spare_time_request_model.dart | 13 ++++++++++++ .../preparation_repository_impl.dart | 8 ++++++++ .../repositories/preparation_repository.dart | 2 ++ .../use-cases/update_spare_time_use_case.dart | 13 ++++++++++++ ...ault_preparation_spare_time_form_bloc.dart | 20 +++++++++++-------- 7 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 lib/data/models/update_spare_time_request_model.dart create mode 100644 lib/domain/use-cases/update_spare_time_use_case.dart diff --git a/lib/core/constants/endpoint.dart b/lib/core/constants/endpoint.dart index acf590d4..3eb1a7f2 100644 --- a/lib/core/constants/endpoint.dart +++ b/lib/core/constants/endpoint.dart @@ -48,6 +48,9 @@ class Endpoint { static get updateDefaultPreparation => _defaultPreparation; + static const _updateSpareTime = '/users/me/spare-time'; + static get updateSpareTime => _updateSpareTime; + static const _fcmToken = '/firebase-token'; // 사용자 fcm 토큰 등록 static get fcmTokenRegister => _fcmToken; } diff --git a/lib/data/data_sources/preparation_remote_data_source.dart b/lib/data/data_sources/preparation_remote_data_source.dart index 6d5d0a75..f6b46dfe 100644 --- a/lib/data/data_sources/preparation_remote_data_source.dart +++ b/lib/data/data_sources/preparation_remote_data_source.dart @@ -8,6 +8,7 @@ import 'package:on_time_front/domain/entities/preparation_entity.dart'; import 'package:on_time_front/data/models/create_preparation_schedule_request_model.dart'; import 'package:on_time_front/data/models/create_defualt_preparation_request_model.dart'; import 'package:on_time_front/data/models/get_preparation_step_response_model.dart'; +import 'package:on_time_front/data/models/update_spare_time_request_model.dart'; abstract interface class PreparationRemoteDataSource { Future createDefaultPreparation( @@ -24,6 +25,8 @@ abstract interface class PreparationRemoteDataSource { Future getPreparationByScheduleId(String scheduleId); Future getDefualtPreparation(); + + Future updateSpareTime(Duration newSpareTime); } @Injectable(as: PreparationRemoteDataSource) @@ -154,4 +157,20 @@ class PreparationRemoteDataSourceImpl implements PreparationRemoteDataSource { rethrow; } } + + @override + Future updateSpareTime(Duration newSpareTime) async { + try { + final body = UpdateSpareTimeRequestModel.fromDuration(newSpareTime); + final result = await dio.put( + Endpoint.updateSpareTime, + data: body.toJson(), + ); + if (result.statusCode != 200) { + throw Exception('Error updating spare time'); + } + } catch (e) { + rethrow; + } + } } diff --git a/lib/data/models/update_spare_time_request_model.dart b/lib/data/models/update_spare_time_request_model.dart new file mode 100644 index 00000000..8b34aa69 --- /dev/null +++ b/lib/data/models/update_spare_time_request_model.dart @@ -0,0 +1,13 @@ +class UpdateSpareTimeRequestModel { + final int newSpareTime; + + UpdateSpareTimeRequestModel({required this.newSpareTime}); + + Map toJson() => { + 'newSpareTime': newSpareTime, + }; + + factory UpdateSpareTimeRequestModel.fromDuration(Duration duration) { + return UpdateSpareTimeRequestModel(newSpareTime: duration.inMinutes); + } +} diff --git a/lib/data/repositories/preparation_repository_impl.dart b/lib/data/repositories/preparation_repository_impl.dart index cc2672f6..ff42ecac 100644 --- a/lib/data/repositories/preparation_repository_impl.dart +++ b/lib/data/repositories/preparation_repository_impl.dart @@ -91,4 +91,12 @@ class PreparationRepositoryImpl implements PreparationRepository { rethrow; } } + + Future updateSpareTime(Duration newSpareTime) async { + try { + await preparationRemoteDataSource.updateSpareTime(newSpareTime); + } catch (e) { + rethrow; + } + } } diff --git a/lib/domain/repositories/preparation_repository.dart b/lib/domain/repositories/preparation_repository.dart index 10edde1f..d6b94af3 100644 --- a/lib/domain/repositories/preparation_repository.dart +++ b/lib/domain/repositories/preparation_repository.dart @@ -17,4 +17,6 @@ abstract interface class PreparationRepository { Future updatePreparationByScheduleId( PreparationEntity preparationEntity, String scheduleId); + + Future updateSpareTime(Duration newSpareTime); } diff --git a/lib/domain/use-cases/update_spare_time_use_case.dart b/lib/domain/use-cases/update_spare_time_use_case.dart new file mode 100644 index 00000000..2bf97c79 --- /dev/null +++ b/lib/domain/use-cases/update_spare_time_use_case.dart @@ -0,0 +1,13 @@ +import 'package:injectable/injectable.dart'; +import 'package:on_time_front/domain/repositories/preparation_repository.dart'; + +@Injectable() +class UpdateSpareTimeUseCase { + final PreparationRepository _preparationRepository; + + UpdateSpareTimeUseCase(this._preparationRepository); + + Future call(Duration newSpareTime) async { + await _preparationRepository.updateSpareTime(newSpareTime); + } +} diff --git a/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart b/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart index a6237649..90a93cea 100644 --- a/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart +++ b/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart @@ -3,7 +3,9 @@ import 'package:equatable/equatable.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/domain/entities/preparation_entity.dart'; import 'package:on_time_front/domain/use-cases/get_default_preparation_use_case.dart'; -import 'package:on_time_front/domain/use-cases/onboard_use_case.dart'; +import 'package:on_time_front/domain/use-cases/update_default_preparation_use_case.dart'; +import 'package:on_time_front/domain/use-cases/update_spare_time_use_case.dart'; +import 'package:on_time_front/domain/use-cases/load_user_use_case.dart'; part 'default_preparation_spare_time_form_event.dart'; part 'default_preparation_spare_time_form_state.dart'; @@ -14,7 +16,9 @@ class DefaultPreparationSpareTimeFormBloc extends Bloc< DefaultPreparationSpareTimeFormState> { DefaultPreparationSpareTimeFormBloc( this._getDefaultPreparationUseCase, - this._onboardUseCase, + this._updateDefaultPreparationUseCase, + this._updateSpareTimeUseCase, + this._loadUserUseCase, ) : super(DefaultPreparationSpareTimeFormState()) { on(_onFormEditRequested); on(_onSpareTimeIncreased); @@ -23,7 +27,9 @@ class DefaultPreparationSpareTimeFormBloc extends Bloc< } final GetDefaultPreparationUseCase _getDefaultPreparationUseCase; - final OnboardUseCase _onboardUseCase; + final UpdateDefaultPreparationUseCase _updateDefaultPreparationUseCase; + final UpdateSpareTimeUseCase _updateSpareTimeUseCase; + final LoadUserUseCase _loadUserUseCase; final Duration lowerBound = Duration(minutes: 10); final Duration stepSize = Duration(minutes: 5); @@ -78,11 +84,9 @@ class DefaultPreparationSpareTimeFormBloc extends Bloc< )); try { - await _onboardUseCase( - preparationEntity: event.preparation, - spareTime: state.spareTime!, - note: event.note, - ); + await _updateDefaultPreparationUseCase(event.preparation); + await _updateSpareTimeUseCase(state.spareTime!); + await _loadUserUseCase(); emit(state.copyWith( status: DefaultPreparationSpareTimeStatus.success, From f5d4e827353605610bd197160a1768b3a557118d Mon Sep 17 00:00:00 2001 From: ya_yo0 Date: Thu, 25 Sep 2025 17:55:26 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20API=20=EC=9A=94=EC=B2=AD=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EB=B0=B1=EC=97=94=EB=93=9C=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=B0=20=EB=B3=80=EA=B2=BD,=20=EC=A4=80=EB=B9=84=EA=B3=BC?= =?UTF-8?q?=EC=A0=95=20=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=B4=9D=20=EC=8B=9C=EA=B0=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../preparation_remote_data_source.dart | 2 +- .../preparation_spare_time_edit_screen.dart | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/data/data_sources/preparation_remote_data_source.dart b/lib/data/data_sources/preparation_remote_data_source.dart index f6b46dfe..33108cfc 100644 --- a/lib/data/data_sources/preparation_remote_data_source.dart +++ b/lib/data/data_sources/preparation_remote_data_source.dart @@ -124,7 +124,7 @@ class PreparationRemoteDataSourceImpl implements PreparationRemoteDataSource { PreparationUserModifyRequestModelListExtension.fromEntityList( preparationEntity.preparationStepList); - final result = await dio.post( + final result = await dio.put( Endpoint.updateDefaultPreparation, data: updateModel.map((model) => model.toJson()).toList(), ); diff --git a/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart b/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart index 5ab142bf..cb5ffcb7 100644 --- a/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart +++ b/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart @@ -181,9 +181,16 @@ class _PreparationSection extends StatelessWidget { width: double.infinity, child: Padding( padding: const EdgeInsets.only(bottom: 15.0), - child: Text( - AppLocalizations.of(context)!.totalTime, - textAlign: TextAlign.end, + child: Builder( + builder: (context) { + final totalDuration = preparationNameState.preparationStepList + .fold(Duration.zero, + (prev, step) => prev + step.preparationTime.value); + return Text( + '${AppLocalizations.of(context)!.totalTime}${totalDuration.inMinutes}분', + textAlign: TextAlign.end, + ); + }, ), ), ), From b6aad100e4b6a2c037dbf598a0f59e3b139dd106 Mon Sep 17 00:00:00 2001 From: ya_yo0 Date: Wed, 15 Oct 2025 10:47:24 +0900 Subject: [PATCH 3/4] feat: spare time and preparation time edit --- .../preparation_spare_time_edit_screen.dart | 37 +++++--- .../bloc/preparation_form_bloc.dart | 8 ++ .../preparation_form_create_list.dart | 48 +++++++--- ...on_form_reorderable_list_dissmissible.dart | 94 +++++++++++++++++++ 4 files changed, 157 insertions(+), 30 deletions(-) create mode 100644 lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_reorderable_list_dissmissible.dart diff --git a/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart b/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart index cb5ffcb7..ba9a7960 100644 --- a/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart +++ b/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart @@ -47,6 +47,10 @@ class _PreparationSpareTimeEditView extends StatelessWidget { builder: (context, state2) { return Scaffold( appBar: AppBar( + elevation: 0, + shadowColor: Colors.transparent, + scrolledUnderElevation: 0, + backgroundColor: Colors.transparent, leading: IconButton( icon: Icon( Icons.arrow_back_ios_rounded, @@ -194,21 +198,24 @@ class _PreparationSection extends StatelessWidget { ), ), ), - PreparationFormCreateList( - preparationNameState: preparationNameState, - onNameChanged: ({required int index, required String value}) { - context.read().add( - PreparationFormPreparationStepNameChanged( - index: index, - preparationStepName: value, - ), - ); - }, - onCreationRequested: () { - context.read().add( - const PreparationFormPreparationStepCreationRequested(), - ); - }, + Expanded( + child: PreparationFormCreateList( + preparationNameState: preparationNameState, + enableDismissible: true, + onNameChanged: ({required int index, required String value}) { + context.read().add( + PreparationFormPreparationStepNameChanged( + index: index, + preparationStepName: value, + ), + ); + }, + onCreationRequested: () { + context.read().add( + const PreparationFormPreparationStepCreationRequested(), + ); + }, + ), ), ], ); diff --git a/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc.dart b/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc.dart index bc2898aa..8eae2568 100644 --- a/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc.dart +++ b/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc.dart @@ -74,12 +74,18 @@ class PreparationFormBloc PreparationFormPreparationStepRemoved event, Emitter emit, ) { + if (state.preparationStepList.length <= 1) { + return; + } + final removedList = List.from(state.preparationStepList); removedList.removeWhere((element) => element.id == event.preparationStepId); + final isValid = _validate(removedList); emit(state.copyWith( preparationStepList: removedList, + isValid: isValid, )); } @@ -133,8 +139,10 @@ class PreparationFormBloc final item = changedList.removeAt(oldIndex); changedList.insert(newIndex, item); + final isValid = _validate(changedList); emit(state.copyWith( preparationStepList: changedList, + isValid: isValid, )); } diff --git a/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_create_list.dart b/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_create_list.dart index 6a7ca06a..280ac666 100644 --- a/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_create_list.dart +++ b/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_create_list.dart @@ -4,6 +4,7 @@ import 'package:on_time_front/presentation/onboarding/preparation_name_select/co import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc.dart'; import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_list_field.dart'; import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_reorderable_list.dart'; +import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_reorderable_list_dissmissible.dart'; import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/cubit/preparation_step_form_cubit.dart'; class PreparationFormCreateList extends StatelessWidget { @@ -11,32 +12,49 @@ class PreparationFormCreateList extends StatelessWidget { {super.key, required this.preparationNameState, required this.onNameChanged, - required this.onCreationRequested}); + required this.onCreationRequested, + this.enableDismissible = false}); final PreparationFormState preparationNameState; final void Function({required int index, required String value}) onNameChanged; final VoidCallback onCreationRequested; + final bool enableDismissible; @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ - PreparationFormReorderableList( - preparationStepList: preparationNameState.preparationStepList, - onNameChanged: (index, value) { - onNameChanged(index: index, value: value); - }, - onTimeChanged: (index, value) => context - .read() - .add(PreparationFormPreparationStepTimeChanged( - index: index, preparationStepTime: value)), - onReorder: (oldIndex, newIndex) => context - .read() - .add(PreparationFormPreparationStepOrderChanged( - oldIndex: oldIndex, newIndex: newIndex)), - ), + enableDismissible + ? PreparationFormReorderableListDismissible( + preparationStepList: preparationNameState.preparationStepList, + onNameChanged: (index, value) { + onNameChanged(index: index, value: value); + }, + onTimeChanged: (index, value) => context + .read() + .add(PreparationFormPreparationStepTimeChanged( + index: index, preparationStepTime: value)), + onReorder: (oldIndex, newIndex) => context + .read() + .add(PreparationFormPreparationStepOrderChanged( + oldIndex: oldIndex, newIndex: newIndex)), + ) + : PreparationFormReorderableList( + preparationStepList: preparationNameState.preparationStepList, + onNameChanged: (index, value) { + onNameChanged(index: index, value: value); + }, + onTimeChanged: (index, value) => context + .read() + .add(PreparationFormPreparationStepTimeChanged( + index: index, preparationStepTime: value)), + onReorder: (oldIndex, newIndex) => context + .read() + .add(PreparationFormPreparationStepOrderChanged( + oldIndex: oldIndex, newIndex: newIndex)), + ), preparationNameState.status == PreparationFormStatus.adding ? BlocProvider( create: (context) => PreparationStepFormCubit( diff --git a/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_reorderable_list_dissmissible.dart b/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_reorderable_list_dissmissible.dart new file mode 100644 index 00000000..376df148 --- /dev/null +++ b/lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_reorderable_list_dissmissible.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc.dart'; +import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_list_field.dart'; +import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/cubit/preparation_step_form_cubit.dart'; + +class PreparationFormReorderableListDismissible extends StatelessWidget { + const PreparationFormReorderableListDismissible({ + super.key, + required this.preparationStepList, + required this.onNameChanged, + required this.onTimeChanged, + required this.onReorder, + }); + + final List preparationStepList; + final Function(int index, String value) onNameChanged; + final Function(int index, Duration value) onTimeChanged; + final Function(int oldIndex, int newIndex) onReorder; + + @override + Widget build(BuildContext context) { + Widget proxyDecorator( + Widget child, int index, Animation animation) { + return AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + return SizedBox( + child: child, + ); + }, + child: child, + ); + } + + return SingleChildScrollView( + child: ReorderableListView.builder( + buildDefaultDragHandles: false, + proxyDecorator: proxyDecorator, + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + itemCount: preparationStepList.length, + itemBuilder: (context, index) { + return Dismissible( + key: ValueKey( + 'dismissible_${preparationStepList[index].id}'), + direction: DismissDirection.endToStart, + background: Container( + height: double.infinity, + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20.0), + color: Colors.red, + child: const Icon( + Icons.delete, + color: Colors.white, + size: 24, + ), + ), + confirmDismiss: (direction) async { + if (preparationStepList.length <= 1) { + return false; + } + return direction == DismissDirection.endToStart; + }, + onDismissed: (direction) { + if (direction == DismissDirection.endToStart) { + context.read().add( + PreparationFormPreparationStepRemoved( + preparationStepId: preparationStepList[index].id, + ), + ); + } + }, + child: PreparationFormListField( + key: ValueKey(preparationStepList[index].id), + index: index, + preparationStep: preparationStepList[index], + onNameChanged: (value) { + onNameChanged(index, value); + }, + onPreparationTimeChanged: (value) { + onTimeChanged(index, value); + }, + ), + ); + }, + onReorder: (int oldIndex, int newIndex) { + onReorder(oldIndex, newIndex); + }, + ), + ); + } +} From 139d579e33ab4d9736daaf6fc881dd8b2134a0cf Mon Sep 17 00:00:00 2001 From: ya_yo0 Date: Wed, 15 Oct 2025 10:47:48 +0900 Subject: [PATCH 4/4] fix(auth): update Google sign out flow and Apple sign in button UI --- assets/appleid_button.png | Bin 0 -> 9795 bytes .../repositories/user_repository_impl.dart | 10 ++ lib/domain/repositories/user_repository.dart | 2 + .../use-cases/delete_user_use_case.dart | 2 + .../apple_sign_in_button_mobile.dart | 25 +++-- .../google_sign_in_button_mobile.dart | 86 ++++++++++-------- .../login/screens/sign_in_main_screen.dart | 2 +- 7 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 assets/appleid_button.png diff --git a/assets/appleid_button.png b/assets/appleid_button.png new file mode 100644 index 0000000000000000000000000000000000000000..7b80164aed9b373d2615111c7993d0c071ac7a71 GIT binary patch literal 9795 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 literal 0 HcmV?d00001 diff --git a/lib/data/repositories/user_repository_impl.dart b/lib/data/repositories/user_repository_impl.dart index e9be96a5..6e0b7d15 100644 --- a/lib/data/repositories/user_repository_impl.dart +++ b/lib/data/repositories/user_repository_impl.dart @@ -168,6 +168,16 @@ class UserRepositoryImpl implements UserRepository { } } + @override + Future disconnectGoogleSignIn() async { + try { + await _googleSignIn.disconnect(); + debugPrint('Google Sign-In disconnected'); + } catch (e) { + debugPrint('Google Sign-In disconnect failed: $e'); + } + } + @override Stream get userStream => _userStreamController.asBroadcastStream(); diff --git a/lib/domain/repositories/user_repository.dart b/lib/domain/repositories/user_repository.dart index e9a8fbf0..da9f2579 100644 --- a/lib/domain/repositories/user_repository.dart +++ b/lib/domain/repositories/user_repository.dart @@ -33,4 +33,6 @@ abstract interface class UserRepository { Future postFeedback(String message); Future getUserSocialType(); + + Future disconnectGoogleSignIn(); } diff --git a/lib/domain/use-cases/delete_user_use_case.dart b/lib/domain/use-cases/delete_user_use_case.dart index b5c1fb4b..105f9490 100644 --- a/lib/domain/use-cases/delete_user_use_case.dart +++ b/lib/domain/use-cases/delete_user_use_case.dart @@ -15,7 +15,9 @@ class DeleteUserUseCase { final socialTypeString = await _userRepository.getUserSocialType(); final socialType = socialTypeFromString(socialTypeString); + if (socialType == SocialType.google) { + await _userRepository.disconnectGoogleSignIn(); await _userRepository.deleteGoogleUser(); } else if (socialType == SocialType.apple) { await _userRepository.deleteAppleUser(); diff --git a/lib/presentation/login/components/google_sign_in_button/apple_sign_in_button_mobile.dart b/lib/presentation/login/components/google_sign_in_button/apple_sign_in_button_mobile.dart index abf72a54..fdf9f18e 100644 --- a/lib/presentation/login/components/google_sign_in_button/apple_sign_in_button_mobile.dart +++ b/lib/presentation/login/components/google_sign_in_button/apple_sign_in_button_mobile.dart @@ -12,15 +12,11 @@ class AppleSignInButton extends StatelessWidget { return SizedBox( width: 358, - child: DefaultTextStyle.merge( - style: TextStyle( - fontSize: 19, - fontWeight: FontWeight.w600, - height: 24 / 19, - fontFamily: 'SF Pro', - ), - child: SignInWithAppleButton( - onPressed: () async { + height: 54, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () async { try { final credential = await SignInWithApple.getAppleIDCredential( scopes: [ @@ -51,11 +47,14 @@ class AppleSignInButton extends StatelessWidget { debugPrint('Apple Sign In Error: ${e.toString()}'); } }, - style: SignInWithAppleButtonStyle.black, - height: 54, borderRadius: BorderRadius.circular(14), - iconAlignment: IconAlignment.center, - text: 'Sign in with Apple', + child: Image.asset( + 'appleid_button.png', + package: 'assets', + width: 358, + height: 54, + fit: BoxFit.cover, + ), ), ), ); diff --git a/lib/presentation/login/components/google_sign_in_button/google_sign_in_button_mobile.dart b/lib/presentation/login/components/google_sign_in_button/google_sign_in_button_mobile.dart index a0b5d7d5..b83115ec 100644 --- a/lib/presentation/login/components/google_sign_in_button/google_sign_in_button_mobile.dart +++ b/lib/presentation/login/components/google_sign_in_button/google_sign_in_button_mobile.dart @@ -4,50 +4,64 @@ import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/domain/repositories/user_repository.dart'; class GoogleSignInButton extends StatelessWidget { - GoogleSignInButton({super.key}); - - final Widget googleIconSvg = SvgPicture.asset( - 'google_icon.svg', - package: 'assets', - semanticsLabel: 'Google Icon', - fit: BoxFit.contain, - ); + const GoogleSignInButton({super.key}); @override Widget build(BuildContext context) { final UserRepository authenticationRepository = getIt.get(); - return GestureDetector( - onTap: () async { - try { - final googleAccout = - await authenticationRepository.googleSignIn.signIn(); - if (googleAccout == null) { - throw Exception('Google Sign In Failed, Sign In Accout is null'); + + return SizedBox( + width: 358, + height: 54, + child: ElevatedButton( + onPressed: () async { + try { + final googleAccount = + await authenticationRepository.googleSignIn.signIn(); + if (googleAccount == null) { + throw Exception('Google Sign In Failed, Sign In Account is null'); + } + await authenticationRepository.signInWithGoogle(googleAccount); + } catch (e) { + debugPrint(e.toString()); } - await authenticationRepository.signInWithGoogle(googleAccout); - } catch (e) { - debugPrint(e.toString()); - } - }, - child: Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: Color(0xFF747775)), - boxShadow: [ - BoxShadow( - color: Colors.black26, - blurRadius: 2, - offset: Offset(0, 1), + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + elevation: 1, + shadowColor: Colors.black26, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + side: BorderSide(color: Color(0xFFDADCE0), width: 1), + ), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + 'google_icon.svg', + package: 'assets', + semanticsLabel: 'Google Icon', + fit: BoxFit.contain, + width: 20, + height: 20, + ), + SizedBox(width: 10), + Text( + 'Sign in with Google', + style: TextStyle( + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + fontSize: 21, + height: 1.4, + letterSpacing: 0, + color: Colors.black87, + ), ), ], ), - child: Padding( - padding: const EdgeInsets.all(9.0), - child: googleIconSvg, - ), ), ); } diff --git a/lib/presentation/login/screens/sign_in_main_screen.dart b/lib/presentation/login/screens/sign_in_main_screen.dart index 845d89e2..5250e475 100644 --- a/lib/presentation/login/screens/sign_in_main_screen.dart +++ b/lib/presentation/login/screens/sign_in_main_screen.dart @@ -44,7 +44,7 @@ class _SignInMainScreenState extends State { SizedBox(height: 41), if (!kIsWeb && Platform.isIOS) ...[ AppleSignInButton(), - SizedBox(height: 22), + SizedBox(height: 16), ], GoogleSignInButton(), ],