From 7009ab6cdeba472204b9cf0aeed4bd3c4ab018f4 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 11 Feb 2025 03:04:55 +0900 Subject: [PATCH 01/18] feat: add formz package as a dependency --- pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + 2 files changed, 9 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index 338cbff..84f928b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -468,6 +468,14 @@ packages: description: flutter source: sdk version: "0.0.0" + formz: + dependency: "direct main" + description: + name: formz + sha256: "382c7be452ff76833f9efa0b2333fec3a576393f6d2c7801725bed502f3d40c3" + url: "https://pub.dev" + source: hosted + version: "0.8.0" freezed: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e9095f3..0c386ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,7 @@ dependencies: flutter_secure_storage: ^4.2.1 freezed: ^2.5.7 freezed_annotation: ^2.4.4 + formz: ^0.8.0 From 2399a65b9f4867eb725f848d187c039a8c9d6021 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 11 Feb 2025 13:17:16 +0900 Subject: [PATCH 02/18] refactor: move onboarding cubit files to onboarding folder --- .../onboarding/cubit/{ => onboarding}/onboarding_cubit.dart | 0 .../onboarding/cubit/{ => onboarding}/onboarding_state.dart | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename lib/presentation/onboarding/cubit/{ => onboarding}/onboarding_cubit.dart (100%) rename lib/presentation/onboarding/cubit/{ => onboarding}/onboarding_state.dart (100%) diff --git a/lib/presentation/onboarding/cubit/onboarding_cubit.dart b/lib/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart similarity index 100% rename from lib/presentation/onboarding/cubit/onboarding_cubit.dart rename to lib/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart diff --git a/lib/presentation/onboarding/cubit/onboarding_state.dart b/lib/presentation/onboarding/cubit/onboarding/onboarding_state.dart similarity index 100% rename from lib/presentation/onboarding/cubit/onboarding_state.dart rename to lib/presentation/onboarding/cubit/onboarding/onboarding_state.dart From 7f6fbc0e5a66eb5962bdcb54137783ea327fd037 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 13 Feb 2025 04:11:30 +0900 Subject: [PATCH 03/18] fix: update import paths for onboarding cubit files --- .../onboarding/components/preparation_select_list.dart | 1 + lib/presentation/onboarding/screens/onboarding_screen.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/onboarding/components/preparation_select_list.dart b/lib/presentation/onboarding/components/preparation_select_list.dart index 9c1e2fc..182d643 100644 --- a/lib/presentation/onboarding/components/preparation_select_list.dart +++ b/lib/presentation/onboarding/components/preparation_select_list.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:on_time_front/presentation/onboarding/components/onboarding_page_view_layout.dart'; +import 'package:on_time_front/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/screens/onboarding_screen.dart'; import 'package:on_time_front/presentation/shared/components/check_button.dart'; import 'package:on_time_front/presentation/shared/components/tile.dart'; diff --git a/lib/presentation/onboarding/screens/onboarding_screen.dart b/lib/presentation/onboarding/screens/onboarding_screen.dart index c812773..05beae0 100644 --- a/lib/presentation/onboarding/screens/onboarding_screen.dart +++ b/lib/presentation/onboarding/screens/onboarding_screen.dart @@ -7,7 +7,7 @@ import 'package:on_time_front/presentation/onboarding/components/preparation_reo import 'package:on_time_front/presentation/onboarding/components/preparation_select_list.dart'; import 'package:on_time_front/presentation/onboarding/components/preparation_time_input_list.dart'; import 'package:on_time_front/presentation/onboarding/components/schedule_spare_time_input.dart'; -import 'package:on_time_front/presentation/onboarding/cubit/onboarding_cubit.dart'; +import 'package:on_time_front/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart'; class OnboardingScreen extends StatelessWidget { const OnboardingScreen({super.key}); From e024e391b858120cd38730b7ff2f50c52b10f8e5 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 13 Feb 2025 04:11:40 +0900 Subject: [PATCH 04/18] feat: add PreparationStepNameCubit and state for onboarding process --- .../preparation_step_name_cubit.dart | 32 +++++++++++++++ .../preparation_step_name_state.dart | 40 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart create mode 100644 lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_state.dart diff --git a/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart b/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart new file mode 100644 index 0000000..c564dc0 --- /dev/null +++ b/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:uuid/uuid.dart'; + +part 'preparation_step_name_state.dart'; + +class PreparationStepNameCubit extends Cubit { + PreparationStepNameCubit() : super(PreparationStepNameState()); + + void nameChanged(String preparationName) { + emit(state.copyWith(preparationName: preparationName)); + } + + void selectionToggled() { + emit(state.copyWith( + status: state.status == PreparationStepNameStatus.selected + ? PreparationStepNameStatus.unselected + : PreparationStepNameStatus.selected, + )); + } + + void preparationStepSaved() { + emit(state.copyWith(status: PreparationStepNameStatus.selected)); + } + + @override + Future close() { + state.focusNode.dispose(); + return super.close(); + } +} diff --git a/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_state.dart b/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_state.dart new file mode 100644 index 0000000..b949689 --- /dev/null +++ b/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_state.dart @@ -0,0 +1,40 @@ +part of 'preparation_step_name_cubit.dart'; + +enum PreparationStepNameStatus { selected, unselected } + +class PreparationStepNameState extends Equatable { + PreparationStepNameState({ + String? preparationId, + this.preparationName = '', + FocusNode? focusNode, + this.isValid = false, + this.status = PreparationStepNameStatus.unselected, + }) : preparationId = preparationId ?? Uuid().v7(), + focusNode = focusNode ?? FocusNode(); + + final String preparationId; + final String preparationName; + final FocusNode focusNode; + final bool isValid; + final PreparationStepNameStatus status; + + PreparationStepNameState copyWith({ + String? preparationId, + String? preparationName, + FocusNode? focusNode, + bool? isValid, + PreparationStepNameStatus? status, + }) { + return PreparationStepNameState( + preparationId: preparationId ?? this.preparationId, + preparationName: preparationName ?? this.preparationName, + focusNode: focusNode ?? this.focusNode, + isValid: isValid ?? this.isValid, + status: status ?? this.status, + ); + } + + @override + List get props => + [preparationId, preparationName, focusNode, isValid, status]; +} From 8f14c480154d4e0e4eb1b8a77493bcfd0a261990 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 13 Feb 2025 04:11:46 +0900 Subject: [PATCH 05/18] feat: add PreparationNameCubit and state for onboarding process --- .../preparation_name_cubit.dart | 9 ++++++ .../preparation_name_state.dart | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 lib/presentation/onboarding/cubit/preparation_name/preparation_name_cubit.dart create mode 100644 lib/presentation/onboarding/cubit/preparation_name/preparation_name_state.dart diff --git a/lib/presentation/onboarding/cubit/preparation_name/preparation_name_cubit.dart b/lib/presentation/onboarding/cubit/preparation_name/preparation_name_cubit.dart new file mode 100644 index 0000000..bee5ec4 --- /dev/null +++ b/lib/presentation/onboarding/cubit/preparation_name/preparation_name_cubit.dart @@ -0,0 +1,9 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:on_time_front/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart'; + +part 'preparation_name_state.dart'; + +class PreparationNameCubit extends Cubit { + PreparationNameCubit() : super(PreparationNameState()); +} diff --git a/lib/presentation/onboarding/cubit/preparation_name/preparation_name_state.dart b/lib/presentation/onboarding/cubit/preparation_name/preparation_name_state.dart new file mode 100644 index 0000000..84978dc --- /dev/null +++ b/lib/presentation/onboarding/cubit/preparation_name/preparation_name_state.dart @@ -0,0 +1,31 @@ +part of 'preparation_name_cubit.dart'; + +enum PreparationNameStatus { initial, adding } + +final class PreparationNameState extends Equatable { + const PreparationNameState({ + this.preparationStepList = const [], + this.status = PreparationNameStatus.initial, + }); + + final List preparationStepList; + final PreparationNameStatus status; + + @override + List get props => [status]; +} + +final onBoardingPreparationSuggestion = [ + PreparationStepNameState( + preparationName: '화장실 가기', + ), + PreparationStepNameState( + preparationName: '메이크업', + ), + PreparationStepNameState( + preparationName: '머리 세팅하기', + ), + PreparationStepNameState( + preparationName: '짐 챙기기', + ), +]; From a3cb6bbb0e7657fc9ec385eff503cd3a9a8b9a4d Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 13 Feb 2025 04:14:36 +0900 Subject: [PATCH 06/18] feat: add PreparationNameSelectField widget for onboarding process --- .../components/preparation_select_list.dart | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/presentation/onboarding/components/preparation_select_list.dart b/lib/presentation/onboarding/components/preparation_select_list.dart index 182d643..ce016b1 100644 --- a/lib/presentation/onboarding/components/preparation_select_list.dart +++ b/lib/presentation/onboarding/components/preparation_select_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:on_time_front/presentation/onboarding/components/onboarding_page_view_layout.dart'; import 'package:on_time_front/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/screens/onboarding_screen.dart'; @@ -171,6 +172,51 @@ class _PreparationSelectListState extends State { } } +class PreparationNameSelectField extends StatelessWidget { + const PreparationNameSelectField({super.key}); + + @override + Widget build(BuildContext context) { + final preparationStepState = + context.select((PreparationStepNameCubit cubit) => cubit.state); + + return Tile( + key: ValueKey(preparationStepState.preparationId), + style: TileStyle(padding: EdgeInsets.all(16.0)), + leading: SizedBox( + width: 30, + height: 30, + child: CheckButton( + isChecked: false, + onPressed: () {}, + )), + child: Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 19.0), + child: Container( + constraints: BoxConstraints(minHeight: 30), + child: TextField( + controller: TextEditingController( + text: preparationStepState.preparationName), + onChanged: context.read().nameChanged, + focusNode: preparationStepState.focusNode, + onSubmitted: (_) {}, + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + contentPadding: EdgeInsets.all(3.0), + ), + ), + ), + ), + ), + ); + } +} + class PreparationNameTextField extends StatefulWidget { const PreparationNameTextField( {super.key, From 2d8640629cab6e32515a74a18397114a772e99d4 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 13 Feb 2025 04:17:53 +0900 Subject: [PATCH 07/18] refactor: change folder structure in onboarding folder --- .../cubit/preparation_name/preparation_name_cubit.dart | 2 +- .../cubit/preparation_name/preparation_name_state.dart | 0 .../preparation_step_name}/preparation_step_name_cubit.dart | 0 .../preparation_step_name}/preparation_step_name_state.dart | 0 .../screens}/preparation_select_list.dart | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename lib/presentation/onboarding/{ => preparation_name_select}/cubit/preparation_name/preparation_name_cubit.dart (64%) rename lib/presentation/onboarding/{ => preparation_name_select}/cubit/preparation_name/preparation_name_state.dart (100%) rename lib/presentation/onboarding/{cubit/preparateion_step_name => preparation_name_select/cubit/preparation_step_name}/preparation_step_name_cubit.dart (100%) rename lib/presentation/onboarding/{cubit/preparateion_step_name => preparation_name_select/cubit/preparation_step_name}/preparation_step_name_state.dart (100%) rename lib/presentation/onboarding/{components => preparation_name_select/screens}/preparation_select_list.dart (98%) diff --git a/lib/presentation/onboarding/cubit/preparation_name/preparation_name_cubit.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart similarity index 64% rename from lib/presentation/onboarding/cubit/preparation_name/preparation_name_cubit.dart rename to lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart index bee5ec4..b147ff4 100644 --- a/lib/presentation/onboarding/cubit/preparation_name/preparation_name_cubit.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:on_time_front/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart'; part 'preparation_name_state.dart'; diff --git a/lib/presentation/onboarding/cubit/preparation_name/preparation_name_state.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart similarity index 100% rename from lib/presentation/onboarding/cubit/preparation_name/preparation_name_state.dart rename to lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart diff --git a/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart similarity index 100% rename from lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart rename to lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart diff --git a/lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_state.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_state.dart similarity index 100% rename from lib/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_state.dart rename to lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_state.dart diff --git a/lib/presentation/onboarding/components/preparation_select_list.dart b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart similarity index 98% rename from lib/presentation/onboarding/components/preparation_select_list.dart rename to lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart index ce016b1..4f57ec2 100644 --- a/lib/presentation/onboarding/components/preparation_select_list.dart +++ b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:on_time_front/presentation/onboarding/components/onboarding_page_view_layout.dart'; -import 'package:on_time_front/presentation/onboarding/cubit/preparateion_step_name/preparation_step_name_cubit.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/screens/onboarding_screen.dart'; import 'package:on_time_front/presentation/shared/components/check_button.dart'; import 'package:on_time_front/presentation/shared/components/tile.dart'; From a62e6d791ce903486bb940047999f0d074ab54aa Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Mon, 17 Feb 2025 19:08:08 +0900 Subject: [PATCH 08/18] feat: enhance PreparationStepNameCubit to integrate PreparationNameCubit and update state management --- .../preparation_step_name_cubit.dart | 28 +++++++++---------- .../preparation_step_name_state.dart | 25 ++++++----------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart index c564dc0..ed0d7c7 100644 --- a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart @@ -1,32 +1,32 @@ import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart'; import 'package:uuid/uuid.dart'; part 'preparation_step_name_state.dart'; class PreparationStepNameCubit extends Cubit { - PreparationStepNameCubit() : super(PreparationStepNameState()); + PreparationStepNameCubit( + super.initialState, { + required this.preparationNameCubit, + }); - void nameChanged(String preparationName) { - emit(state.copyWith(preparationName: preparationName)); + final PreparationNameCubit preparationNameCubit; + + void nameChanged(String value) { + final preparationName = PreparationNameInputModel.dirty(value); + emit(state.copyWith( + preparationName: preparationName, isValid: preparationName.isValid)); } void selectionToggled() { emit(state.copyWith( - status: state.status == PreparationStepNameStatus.selected - ? PreparationStepNameStatus.unselected - : PreparationStepNameStatus.selected, + isSelected: !state.isSelected, )); } void preparationStepSaved() { - emit(state.copyWith(status: PreparationStepNameStatus.selected)); - } - - @override - Future close() { - state.focusNode.dispose(); - return super.close(); + preparationNameCubit.preparationStepSaved(state); } } diff --git a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_state.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_state.dart index b949689..8949b93 100644 --- a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_state.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_state.dart @@ -1,40 +1,33 @@ part of 'preparation_step_name_cubit.dart'; -enum PreparationStepNameStatus { selected, unselected } - class PreparationStepNameState extends Equatable { PreparationStepNameState({ String? preparationId, - this.preparationName = '', - FocusNode? focusNode, + this.preparationName = const PreparationNameInputModel.pure(), this.isValid = false, - this.status = PreparationStepNameStatus.unselected, - }) : preparationId = preparationId ?? Uuid().v7(), - focusNode = focusNode ?? FocusNode(); + this.isSelected = true, + }) : preparationId = preparationId ?? Uuid().v7(); final String preparationId; - final String preparationName; - final FocusNode focusNode; + final PreparationNameInputModel preparationName; final bool isValid; - final PreparationStepNameStatus status; + final bool isSelected; PreparationStepNameState copyWith({ String? preparationId, - String? preparationName, - FocusNode? focusNode, + PreparationNameInputModel? preparationName, bool? isValid, - PreparationStepNameStatus? status, + bool? isSelected, }) { return PreparationStepNameState( preparationId: preparationId ?? this.preparationId, preparationName: preparationName ?? this.preparationName, - focusNode: focusNode ?? this.focusNode, isValid: isValid ?? this.isValid, - status: status ?? this.status, + isSelected: isSelected ?? this.isSelected, ); } @override List get props => - [preparationId, preparationName, focusNode, isValid, status]; + [preparationId, preparationName, isValid, isSelected]; } From 8ad24e3a76e7946bedb5caf24ba97498d6ebd6c2 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Mon, 17 Feb 2025 19:08:20 +0900 Subject: [PATCH 09/18] feat: implement preparationStepSaved method and enhance PreparationNameState with copyWith functionality --- .../preparation_name_cubit.dart | 30 ++++++++++++++++++- .../preparation_name_state.dart | 18 ++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart index b147ff4..d135f35 100644 --- a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart @@ -1,9 +1,37 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart'; part 'preparation_name_state.dart'; class PreparationNameCubit extends Cubit { - PreparationNameCubit() : super(PreparationNameState()); + PreparationNameCubit() + : super(PreparationNameState( + preparationStepList: onBoardingPreparationSuggestion)); + + void preparationStepSaved(PreparationStepNameState state) { + if (this.state.status == PreparationNameStatus.adding) { + final preparationStepList = [ + ...this.state.preparationStepList, + state, + ]; + emit(this.state.copyWith( + preparationStepList: preparationStepList, + status: PreparationNameStatus.initial, + )); + } else { + final preparationStepList = this + .state + .preparationStepList + .map((preparationStep) => + preparationStep.preparationId == state.preparationId + ? state + : preparationStep) + .toList(); + emit(this.state.copyWith( + preparationStepList: preparationStepList, + )); + } + } } diff --git a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart index 84978dc..a3e6cb6 100644 --- a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart @@ -11,21 +11,31 @@ final class PreparationNameState extends Equatable { final List preparationStepList; final PreparationNameStatus status; + PreparationNameState copyWith({ + List? preparationStepList, + PreparationNameStatus? status, + }) { + return PreparationNameState( + preparationStepList: preparationStepList ?? this.preparationStepList, + status: status ?? this.status, + ); + } + @override List get props => [status]; } final onBoardingPreparationSuggestion = [ PreparationStepNameState( - preparationName: '화장실 가기', + preparationName: PreparationNameInputModel.dirty('화장실 가기'), ), PreparationStepNameState( - preparationName: '메이크업', + preparationName: PreparationNameInputModel.dirty('메이크업'), ), PreparationStepNameState( - preparationName: '머리 세팅하기', + preparationName: PreparationNameInputModel.dirty('머리 세팅하기'), ), PreparationStepNameState( - preparationName: '짐 챙기기', + preparationName: PreparationNameInputModel.dirty('짐 챙기기'), ), ]; From 28aac8f8350dc1dbfca5d3597340f1ca61f4809c Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Mon, 17 Feb 2025 19:08:25 +0900 Subject: [PATCH 10/18] feat: add PreparationNameInputModel for form validation in onboarding process --- .../preparation_name_input_model.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart diff --git a/lib/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart b/lib/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart new file mode 100644 index 0000000..207066e --- /dev/null +++ b/lib/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart @@ -0,0 +1,17 @@ +import 'package:formz/formz.dart'; + +/// Validation errors for the [PreparationNameInputModel] [FormzInput]. +enum PreparationNameValidationError { + empty, +} + +class PreparationNameInputModel + extends FormzInput { + const PreparationNameInputModel.pure() : super.pure(''); + const PreparationNameInputModel.dirty([super.value = '']) : super.dirty(); + + @override + PreparationNameValidationError? validator(String value) { + return value.isEmpty ? PreparationNameValidationError.empty : null; + } +} From 11a0a8adc1a0f6160faf54447cea4143bf260c66 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Mon, 17 Feb 2025 19:08:33 +0900 Subject: [PATCH 11/18] refactor: update import path for PreparationSelectList in onboarding_screen.dart --- .../screens/preparation_select_list.dart | 366 ++++++++---------- .../onboarding/screens/onboarding_screen.dart | 2 +- 2 files changed, 166 insertions(+), 202 deletions(-) diff --git a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart index 4f57ec2..f6d0eff 100644 --- a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart +++ b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:on_time_front/presentation/onboarding/components/onboarding_page_view_layout.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/screens/onboarding_screen.dart'; import 'package:on_time_front/presentation/shared/components/check_button.dart'; import 'package:on_time_front/presentation/shared/components/tile.dart'; import 'package:on_time_front/presentation/shared/theme/tile_style.dart'; -import 'package:uuid/uuid.dart'; typedef OnSelectedStepChangedCallBackFunction = void Function(List); @@ -25,23 +25,15 @@ class PreparationSelectList extends StatefulWidget { } class _PreparationSelectListState extends State { - final List focusNodes = []; - final FocusNode tmpAddFocusNode = FocusNode(); bool isAdding = false; @override void initState() { super.initState(); - for (var i = 0; i < widget.preparationList.length; i++) { - focusNodes.add(FocusNode()); - } } @override void dispose() { - for (var i = 0; i < widget.preparationList.length; i++) { - focusNodes[i].dispose(); - } super.dispose(); } @@ -51,124 +43,110 @@ class _PreparationSelectListState extends State { widget.onSelectedStepChanged(widget.preparationList); } - List _listViewChildren(BuildContext context) { + // List _listViewChildren(BuildContext context) { + // List children = []; + // for (var i = 0; i < widget.preparationList.length; i++) { + // PreparationStepWithSelection step = widget.preparationList[i]; + // children.add( + // Padding( + // padding: const EdgeInsets.only(bottom: 8.0), + // child: GestureDetector( + // onTap: () => FocusScope.of(context).requestFocus(focusNodes[i]), + // child: Tile( + // style: TileStyle( + // padding: EdgeInsets.all(16.0), + // ), + // key: ValueKey(step.id), + // leading: Padding( + // padding: const EdgeInsets.symmetric(vertical: 0.0), + // child: SizedBox( + // width: 30, + // height: 30, + // child: CheckButton( + // isChecked: step.isSelected, + // onPressed: () => onStepSelected(i), + // )), + // ), + // child: Expanded( + // child: Padding( + // padding: const EdgeInsets.symmetric(horizontal: 19.0), + // child: PreparationNameTextField( + // preparationName: step.preparationName, + // focusNode: focusNodes[i], + // onChanged: (value) {}, + // onsubmitted: (value) { + // widget.preparationList[i].preparationName = value; + // widget.onSelectedStepChanged(widget.preparationList); + // }, + // ), + // ), + // )), + // ), + // ), + // ); + // } + // return children; + // } + + @override + Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - List children = []; - for (var i = 0; i < widget.preparationList.length; i++) { - PreparationStepWithSelection step = widget.preparationList[i]; - children.add( - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: GestureDetector( - onTap: () => FocusScope.of(context).requestFocus(focusNodes[i]), - child: Tile( - style: TileStyle( - padding: EdgeInsets.all(16.0), - ), - key: ValueKey(step.id), - leading: Padding( - padding: const EdgeInsets.symmetric(vertical: 0.0), - child: SizedBox( - width: 30, - height: 30, - child: CheckButton( - isChecked: step.isSelected, - onPressed: () => onStepSelected(i), - )), + + return BlocBuilder( + builder: (context, state) { + return ListView( + children: [ + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: state.preparationStepList.length, + itemBuilder: (context, index) { + return BlocProvider( + create: (context) => PreparationStepNameCubit( + state.preparationStepList[index], + preparationNameCubit: context.read(), ), - child: Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 19.0), - child: PreparationNameTextField( - preparationName: step.preparationName, - focusNode: focusNodes[i], - onChanged: (value) {}, - onsubmitted: (value) { - widget.preparationList[i].preparationName = value; - widget.onSelectedStepChanged(widget.preparationList); - }, - ), - ), - )), + child: PreparationNameSelectField(), + ); + }, ), - ), - ); - } - children.addAll([ - isAdding - ? Tile( - key: ValueKey('adding'), - style: TileStyle(padding: EdgeInsets.all(16.0)), - leading: SizedBox( - width: 30, - height: 30, - child: CheckButton( - isChecked: false, - onPressed: () {}, - )), - child: Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 19.0), - child: PreparationNameTextField( - preparationName: '', - focusNode: tmpAddFocusNode, - onChanged: (value) {}, - onsubmitted: (value) { - setState(() { - isAdding = false; - }); - widget.preparationList.add(PreparationStepWithSelection( - id: Uuid().v7(), - preparationName: value, - isSelected: true)); - focusNodes.add(FocusNode()); - widget.onSelectedStepChanged(widget.preparationList); - }, - onFocusChange: (value) { - if (!value) { - setState(() { - isAdding = false; - }); - } - }, - ), + isAdding + ? BlocProvider( + create: (context) => PreparationStepNameCubit( + PreparationStepNameState(), + preparationNameCubit: + context.read()), + child: PreparationNameSelectField(), + ) + : SizedBox.shrink(), + SizedBox( + height: 32.0, + ), + Center( + child: SizedBox( + height: 30, + width: 30, + child: IconButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + colorScheme.primaryContainer), ), - )) - : SizedBox.shrink(), - SizedBox( - height: 32.0, - ), - Center( - child: SizedBox( - height: 30, - width: 30, - child: IconButton( - style: ButtonStyle( - backgroundColor: - WidgetStateProperty.all(colorScheme.primaryContainer), + onPressed: () { + //tmpAddFocusNode.requestFocus(); + setState(() { + isAdding = true; + }); + }, + color: colorScheme.onPrimary, + icon: Icon(Icons.add), + padding: EdgeInsets.zero, + iconSize: 30.0, + ), ), - onPressed: () { - tmpAddFocusNode.requestFocus(); - setState(() { - isAdding = true; - }); - }, - color: colorScheme.onPrimary, - icon: Icon(Icons.add), - padding: EdgeInsets.zero, - iconSize: 30.0, ), - ), - ), - ]); - return children; - } - - @override - Widget build(BuildContext context) { - return ListView( - children: _listViewChildren(context), - ); + ], + ); + }); } } @@ -177,42 +155,46 @@ class PreparationNameSelectField extends StatelessWidget { @override Widget build(BuildContext context) { - final preparationStepState = - context.select((PreparationStepNameCubit cubit) => cubit.state); - - return Tile( - key: ValueKey(preparationStepState.preparationId), - style: TileStyle(padding: EdgeInsets.all(16.0)), - leading: SizedBox( - width: 30, - height: 30, - child: CheckButton( - isChecked: false, - onPressed: () {}, - )), - child: Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 19.0), - child: Container( - constraints: BoxConstraints(minHeight: 30), - child: TextField( - controller: TextEditingController( - text: preparationStepState.preparationName), - onChanged: context.read().nameChanged, - focusNode: preparationStepState.focusNode, - onSubmitted: (_) {}, - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); - }, - decoration: InputDecoration( - isDense: true, - border: InputBorder.none, - contentPadding: EdgeInsets.all(3.0), + return BlocBuilder( + builder: (context, state) { + return Tile( + key: ValueKey(state.preparationId), + style: TileStyle(padding: EdgeInsets.all(16.0)), + leading: SizedBox( + width: 30, + height: 30, + child: CheckButton( + isChecked: state.isSelected, + onPressed: () { + context.read().selectionToggled(); + }, + )), + child: Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 19.0), + child: Container( + constraints: BoxConstraints(minHeight: 30), + child: TextFormField( + initialValue: state.preparationName.value, + onChanged: + context.read().nameChanged, + onFieldSubmitted: (value) => context + .read() + .preparationStepSaved(), + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + contentPadding: EdgeInsets.all(3.0), + ), + ), ), ), ), - ), - ), + ); + }, ); } } @@ -318,28 +300,7 @@ class _PreparationSelectFieldState extends State { void initState() { super.initState(); if (widget.initailValue.isEmpty) { - preparationStepSelectingList = [ - PreparationStepWithSelection( - id: Uuid().v7(), - preparationName: '샤워하기', - isSelected: false, - ), - PreparationStepWithSelection( - id: Uuid().v7(), - preparationName: '메이크업', - isSelected: false, - ), - PreparationStepWithSelection( - id: Uuid().v7(), - preparationName: '머리 세팅하기', - isSelected: false, - ), - PreparationStepWithSelection( - id: Uuid().v7(), - preparationName: '짐 챙기기', - isSelected: false, - ), - ]; + preparationStepSelectingList = []; } else { preparationStepSelectingList.addAll( widget.initailValue.map((e) { @@ -356,32 +317,35 @@ class _PreparationSelectFieldState extends State { @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; - return OnboardingPageViewLayout( - title: Text( - '주로 하는 준비 과정을\n선택해주세요', - style: textTheme.titleLarge, - ), - form: Form( - key: widget.formKey, - child: FormField>( - initialValue: preparationStepSelectingList, - onSaved: (value) { - widget.onSaved - ?.call(value!.where((element) => element.isSelected).map((e) { - return PreparationStepWithNameAndId( - id: e.id, - preparationName: e.preparationName, - ); - }).toList()); - }, - builder: (field) => PreparationSelectList( - preparationList: preparationStepSelectingList, - onSelectedStepChanged: (value) { - field.didChange(value); - setState(() { - preparationStepSelectingList = value; - }); + return BlocProvider( + create: (context) => PreparationNameCubit(), + child: OnboardingPageViewLayout( + title: Text( + '주로 하는 준비 과정을\n선택해주세요', + style: textTheme.titleLarge, + ), + form: Form( + key: widget.formKey, + child: FormField>( + initialValue: preparationStepSelectingList, + onSaved: (value) { + widget.onSaved + ?.call(value!.where((element) => element.isSelected).map((e) { + return PreparationStepWithNameAndId( + id: e.id, + preparationName: e.preparationName, + ); + }).toList()); }, + builder: (field) => PreparationSelectList( + preparationList: preparationStepSelectingList, + onSelectedStepChanged: (value) { + field.didChange(value); + setState(() { + preparationStepSelectingList = value; + }); + }, + ), ), ), ), diff --git a/lib/presentation/onboarding/screens/onboarding_screen.dart b/lib/presentation/onboarding/screens/onboarding_screen.dart index 05beae0..de2d91f 100644 --- a/lib/presentation/onboarding/screens/onboarding_screen.dart +++ b/lib/presentation/onboarding/screens/onboarding_screen.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/presentation/onboarding/components/preparation_reordarable_list.dart'; -import 'package:on_time_front/presentation/onboarding/components/preparation_select_list.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart'; import 'package:on_time_front/presentation/onboarding/components/preparation_time_input_list.dart'; import 'package:on_time_front/presentation/onboarding/components/schedule_spare_time_input.dart'; import 'package:on_time_front/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart'; From c8e7483428f94670e881dddd962bd10c3e752167 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 18 Feb 2025 21:02:47 +0900 Subject: [PATCH 12/18] feat: enhance onboarding state management with form validation and preparation step handling --- .../cubit/onboarding/onboarding_cubit.dart | 11 ++ .../cubit/onboarding/onboarding_state.dart | 19 ++- .../preparation_name_cubit.dart | 160 +++++++++++++++--- .../preparation_name_state.dart | 10 +- 4 files changed, 167 insertions(+), 33 deletions(-) diff --git a/lib/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart b/lib/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart index bc064ed..8d731a6 100644 --- a/lib/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart +++ b/lib/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart @@ -22,4 +22,15 @@ class OnboardingCubit extends Cubit { note: state.note ?? '', ); } + + void onboardingFormChanged( + List preparationStepList) { + emit(state.copyWith(preparationStepList: preparationStepList)); + } + + void onboardingFormValidated({ + required bool isValid, + }) { + emit(state.copyWith(isValid: isValid)); + } } diff --git a/lib/presentation/onboarding/cubit/onboarding/onboarding_state.dart b/lib/presentation/onboarding/cubit/onboarding/onboarding_state.dart index 334357a..02d0ddc 100644 --- a/lib/presentation/onboarding/cubit/onboarding/onboarding_state.dart +++ b/lib/presentation/onboarding/cubit/onboarding/onboarding_state.dart @@ -2,19 +2,26 @@ part of 'onboarding_cubit.dart'; class OnboardingState extends Equatable { const OnboardingState( - {this.preparationStepList = const [], this.spareTime, this.note}); + {this.preparationStepList = const [], + this.spareTime, + this.note, + this.isValid = false}); final List preparationStepList; final Duration? spareTime; final String? note; + final bool isValid; - OnboardingState copyWith( - {List? preparationStepList, - Duration? spareTime, - String? note}) { + OnboardingState copyWith({ + List? preparationStepList, + Duration? spareTime, + String? note, + bool? isValid, + }) { return OnboardingState( preparationStepList: preparationStepList ?? this.preparationStepList, spareTime: spareTime ?? this.spareTime, note: note ?? this.note, + isValid: isValid ?? this.isValid, ); } @@ -32,7 +39,7 @@ class OnboardingState extends Equatable { } @override - List get props => [preparationStepList, spareTime, note]; + List get props => [preparationStepList, spareTime, note, isValid]; } class OnboardingPreparationStepState extends Equatable { diff --git a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart index d135f35..512d0bf 100644 --- a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart @@ -1,37 +1,145 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:formz/formz.dart'; +import 'package:on_time_front/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart'; part 'preparation_name_state.dart'; class PreparationNameCubit extends Cubit { - PreparationNameCubit() - : super(PreparationNameState( - preparationStepList: onBoardingPreparationSuggestion)); - - void preparationStepSaved(PreparationStepNameState state) { - if (this.state.status == PreparationNameStatus.adding) { - final preparationStepList = [ - ...this.state.preparationStepList, - state, - ]; - emit(this.state.copyWith( - preparationStepList: preparationStepList, - status: PreparationNameStatus.initial, - )); - } else { - final preparationStepList = this - .state - .preparationStepList - .map((preparationStep) => - preparationStep.preparationId == state.preparationId - ? state - : preparationStep) - .toList(); - emit(this.state.copyWith( - preparationStepList: preparationStepList, - )); + PreparationNameCubit({ + required this.onboardingCubit, + }) : super(PreparationNameState()) { + initialize(); + } + + final OnboardingCubit onboardingCubit; + + void initialize() { + List preparationStepList = + onboardingCubit.state.preparationStepList.map( + (e) { + return PreparationStepNameState( + preparationId: e.id, + preparationName: PreparationNameInputModel.dirty(e.preparationName), + isSelected: true, + ); + }, + ).toList(); + if (preparationStepList.isEmpty) { + preparationStepList = onBoardingPreparationSuggestion; + } + emit(state.copyWith( + status: PreparationNameStatus.initial, + isValid: false, + preparationStepList: preparationStepList, + )); + } + + void preparationStepSaved(PreparationStepNameState stepState) { + if (state.status == PreparationNameStatus.adding) { + final List preparationStepList; + if (stepState.preparationName.isValid) { + preparationStepList = [ + ...state.preparationStepList, + stepState, + ]; + } else { + preparationStepList = state.preparationStepList; + } + final isValid = _validate(preparationStepList); + emit(state.copyWith( + preparationStepList: preparationStepList, + status: PreparationNameStatus.initial, + isValid: isValid, + )); + onboardingCubit.onboardingFormValidated(isValid: isValid); + } + } + + void preparationStepNameChanged({ + required int index, + required String value, + }) { + final preparationStepList = + List.from(state.preparationStepList); + final preparationStep = preparationStepList[index]; + final updatedPreparationStep = preparationStep.copyWith( + preparationName: PreparationNameInputModel.dirty(value), + ); + preparationStepList[index] = updatedPreparationStep; + + final isValid = _validate(preparationStepList); + emit( + state.copyWith( + preparationStepList: preparationStepList, isValid: isValid), + ); + onboardingCubit.onboardingFormValidated(isValid: isValid); + } + + void preparationStepSelectionChanged({ + required int index, + }) { + final preparationStepList = + List.from(state.preparationStepList); + final preparationStep = preparationStepList[index]; + final updatedPreparationStep = preparationStep.copyWith( + isSelected: !preparationStep.isSelected, + ); + preparationStepList[index] = updatedPreparationStep; + + final isValid = _validate(preparationStepList); + emit(state.copyWith( + preparationStepList: preparationStepList, + isValid: isValid, + )); + onboardingCubit.onboardingFormValidated(isValid: isValid); + } + + void preparationStepCreateRequested() { + emit(state.copyWith(status: PreparationNameStatus.adding)); + } + + void preparationSaved() { + final List + onboardingPreparationStepStateList = []; + final selectedList = state.preparationStepList + .where((element) => element.isSelected) + .toList(); + final onboardingState = onboardingCubit.state; + int j = 0; + for (int i = 0; i < selectedList.length; i++) { + while (j < onboardingState.preparationStepList.length && + onboardingState.preparationStepList[j].id != + selectedList[i].preparationId) { + j++; + } + if (j == onboardingState.preparationStepList.length) { + onboardingPreparationStepStateList.add(OnboardingPreparationStepState( + id: selectedList[i].preparationId, + preparationName: selectedList[i].preparationName.value, + )); + continue; + } + onboardingPreparationStepStateList + .add(onboardingState.preparationStepList[j].copyWith( + preparationName: selectedList[i].preparationName.value, + )); } + onboardingCubit.onboardingFormChanged(onboardingPreparationStepStateList); + } + + bool _validate(List preparationStepList) { + final selectedPreparationStepList = + preparationStepList.where((element) => element.isSelected).toList(); + final isValid = selectedPreparationStepList.isNotEmpty && + Formz.validate( + selectedPreparationStepList.map((e) => e.preparationName).toList()); + return isValid; } } diff --git a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart index a3e6cb6..bb94552 100644 --- a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart @@ -5,37 +5,45 @@ enum PreparationNameStatus { initial, adding } final class PreparationNameState extends Equatable { const PreparationNameState({ this.preparationStepList = const [], + this.isValid = false, this.status = PreparationNameStatus.initial, }); final List preparationStepList; + final bool isValid; final PreparationNameStatus status; PreparationNameState copyWith({ List? preparationStepList, + bool? isValid, PreparationNameStatus? status, }) { return PreparationNameState( preparationStepList: preparationStepList ?? this.preparationStepList, + isValid: isValid ?? this.isValid, status: status ?? this.status, ); } @override - List get props => [status]; + List get props => [preparationStepList, isValid, status]; } final onBoardingPreparationSuggestion = [ PreparationStepNameState( preparationName: PreparationNameInputModel.dirty('화장실 가기'), + isSelected: false, ), PreparationStepNameState( preparationName: PreparationNameInputModel.dirty('메이크업'), + isSelected: false, ), PreparationStepNameState( preparationName: PreparationNameInputModel.dirty('머리 세팅하기'), + isSelected: false, ), PreparationStepNameState( preparationName: PreparationNameInputModel.dirty('짐 챙기기'), + isSelected: false, ), ]; From 85405891c26bd3b4e64101a866581497c58d6016 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 18 Feb 2025 21:02:56 +0900 Subject: [PATCH 13/18] feat: integrate PreparationNameCubit into onboarding screen and enhance page navigation --- .../screens/preparation_select_list.dart | 364 ++++++------------ .../onboarding/screens/onboarding_screen.dart | 182 +++++---- 2 files changed, 203 insertions(+), 343 deletions(-) diff --git a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart index f6d0eff..90335e0 100644 --- a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart +++ b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart @@ -3,90 +3,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:on_time_front/presentation/onboarding/components/onboarding_page_view_layout.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart'; -import 'package:on_time_front/presentation/onboarding/screens/onboarding_screen.dart'; import 'package:on_time_front/presentation/shared/components/check_button.dart'; import 'package:on_time_front/presentation/shared/components/tile.dart'; import 'package:on_time_front/presentation/shared/theme/tile_style.dart'; typedef OnSelectedStepChangedCallBackFunction = void Function(List); -class PreparationSelectList extends StatefulWidget { - final List preparationList; - final OnSelectedStepChangedCallBackFunction - onSelectedStepChanged; - - const PreparationSelectList( - {super.key, - required this.preparationList, - required this.onSelectedStepChanged}); - - @override - State createState() => _PreparationSelectListState(); -} - -class _PreparationSelectListState extends State { - bool isAdding = false; - - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - void onStepSelected(int index) { - widget.preparationList[index].isSelected = - !widget.preparationList[index].isSelected; - widget.onSelectedStepChanged(widget.preparationList); - } - - // List _listViewChildren(BuildContext context) { - // List children = []; - // for (var i = 0; i < widget.preparationList.length; i++) { - // PreparationStepWithSelection step = widget.preparationList[i]; - // children.add( - // Padding( - // padding: const EdgeInsets.only(bottom: 8.0), - // child: GestureDetector( - // onTap: () => FocusScope.of(context).requestFocus(focusNodes[i]), - // child: Tile( - // style: TileStyle( - // padding: EdgeInsets.all(16.0), - // ), - // key: ValueKey(step.id), - // leading: Padding( - // padding: const EdgeInsets.symmetric(vertical: 0.0), - // child: SizedBox( - // width: 30, - // height: 30, - // child: CheckButton( - // isChecked: step.isSelected, - // onPressed: () => onStepSelected(i), - // )), - // ), - // child: Expanded( - // child: Padding( - // padding: const EdgeInsets.symmetric(horizontal: 19.0), - // child: PreparationNameTextField( - // preparationName: step.preparationName, - // focusNode: focusNodes[i], - // onChanged: (value) {}, - // onsubmitted: (value) { - // widget.preparationList[i].preparationName = value; - // widget.onSelectedStepChanged(widget.preparationList); - // }, - // ), - // ), - // )), - // ), - // ), - // ); - // } - // return children; - // } +class PreparationSelectList extends StatelessWidget { + const PreparationSelectList({super.key}); @override Widget build(BuildContext context) { @@ -101,22 +25,49 @@ class _PreparationSelectListState extends State { physics: NeverScrollableScrollPhysics(), itemCount: state.preparationStepList.length, itemBuilder: (context, index) { - return BlocProvider( - create: (context) => PreparationStepNameCubit( - state.preparationStepList[index], - preparationNameCubit: context.read(), - ), - child: PreparationNameSelectField(), + return PreparationNameSelectField( + preparationStep: state.preparationStepList[index], + onNameChanged: (value) { + context + .read() + .preparationStepNameChanged(index: index, value: value); + }, + onSelectionChanged: () { + context + .read() + .preparationStepSelectionChanged(index: index); + }, ); }, ), - isAdding + state.status == PreparationNameStatus.adding ? BlocProvider( create: (context) => PreparationStepNameCubit( PreparationStepNameState(), preparationNameCubit: context.read()), - child: PreparationNameSelectField(), + child: BlocBuilder(builder: (context, state) { + return PreparationNameSelectField( + isAdding: true, + preparationStep: state, + onNameChanged: (value) { + context + .read() + .nameChanged(value); + }, + onSelectionChanged: () { + context + .read() + .selectionToggled(); + }, + onNameSaved: () { + context + .read() + .preparationStepSaved(); + }, + ); + }), ) : SizedBox.shrink(), SizedBox( @@ -132,10 +83,9 @@ class _PreparationSelectListState extends State { colorScheme.primaryContainer), ), onPressed: () { - //tmpAddFocusNode.requestFocus(); - setState(() { - isAdding = true; - }); + context + .read() + .preparationStepCreateRequested(); }, color: colorScheme.onPrimary, icon: Icon(Icons.add), @@ -150,204 +100,118 @@ class _PreparationSelectListState extends State { } } -class PreparationNameSelectField extends StatelessWidget { - const PreparationNameSelectField({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Tile( - key: ValueKey(state.preparationId), - style: TileStyle(padding: EdgeInsets.all(16.0)), - leading: SizedBox( - width: 30, - height: 30, - child: CheckButton( - isChecked: state.isSelected, - onPressed: () { - context.read().selectionToggled(); - }, - )), - child: Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 19.0), - child: Container( - constraints: BoxConstraints(minHeight: 30), - child: TextFormField( - initialValue: state.preparationName.value, - onChanged: - context.read().nameChanged, - onFieldSubmitted: (value) => context - .read() - .preparationStepSaved(), - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); - }, - decoration: InputDecoration( - isDense: true, - border: InputBorder.none, - contentPadding: EdgeInsets.all(3.0), - ), - ), - ), - ), - ), - ); - }, - ); - } -} - -class PreparationNameTextField extends StatefulWidget { - const PreparationNameTextField( - {super.key, - required this.preparationName, - required this.focusNode, - required this.onChanged, - this.onFocusChange, - this.onsubmitted}); +class PreparationNameSelectField extends StatefulWidget { + const PreparationNameSelectField({ + super.key, + required this.preparationStep, + this.onNameChanged, + this.onNameSaved, + required this.onSelectionChanged, + this.isAdding = false, + }); - final String preparationName; - final FocusNode focusNode; - final ValueChanged onChanged; - final ValueChanged? onFocusChange; - final ValueChanged? onsubmitted; + final PreparationStepNameState preparationStep; + final ValueChanged? onNameChanged; + final VoidCallback? onNameSaved; + final VoidCallback onSelectionChanged; + final bool isAdding; @override - State createState() => - _PreparationNameTextFieldState(); + State createState() => + _PreparationNameSelectFieldState(); } -class _PreparationNameTextFieldState extends State { - bool isEditing = false; +class _PreparationNameSelectFieldState + extends State { + final FocusNode focusNode = FocusNode(); - void onFocusChange() { - widget.onFocusChange?.call(widget.focusNode.hasFocus); - setState(() { - isEditing = widget.focusNode.hasFocus; - }); - debugPrint('Focus changed'); + @override + void dispose() { + focusNode.dispose(); + super.dispose(); } @override void initState() { super.initState(); - widget.focusNode.addListener(onFocusChange); - } - - @override - void dispose() { - widget.focusNode.removeListener(onFocusChange); - super.dispose(); + focusNode.addListener(() { + if (!focusNode.hasFocus) { + widget.onNameSaved?.call(); + } + }); } @override Widget build(BuildContext context) { - return Container( - constraints: BoxConstraints(minHeight: 30), - child: TextField( - controller: TextEditingController(text: widget.preparationName), - onChanged: widget.onChanged, - focusNode: widget.focusNode, - onSubmitted: widget.onsubmitted, - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); - }, - decoration: InputDecoration( - isDense: true, - border: InputBorder.none, - contentPadding: EdgeInsets.all(3.0), + if (widget.isAdding) { + focusNode.requestFocus(); + } + return Tile( + key: ValueKey(widget.preparationStep.preparationId), + style: TileStyle(padding: EdgeInsets.all(16.0)), + leading: SizedBox( + width: 30, + height: 30, + child: CheckButton( + isChecked: widget.preparationStep.isSelected, + onPressed: widget.onSelectionChanged, + )), + child: Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 19.0), + child: Container( + constraints: BoxConstraints(minHeight: 30), + child: TextFormField( + initialValue: widget.preparationStep.preparationName.value, + onChanged: widget.onNameChanged, + onFieldSubmitted: (value) => widget.onNameSaved?.call(), + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + contentPadding: EdgeInsets.all(3.0), + ), + focusNode: focusNode, + ), + ), ), ), ); } } -class PreparationStepWithSelection { - PreparationStepWithSelection({ - required this.id, - required this.preparationName, - required this.isSelected, - }); - - final String id; - String preparationName; - bool isSelected; -} - class PreparationSelectField extends StatefulWidget { - const PreparationSelectField( - {super.key, - required this.formKey, - required this.initailValue, - this.onSaved}); + const PreparationSelectField({ + super.key, + required this.formKey, + }); final GlobalKey formKey; - final List initailValue; - - final Function(List)? onSaved; - @override State createState() => _PreparationSelectFieldState(); } class _PreparationSelectFieldState extends State { - List preparationStepSelectingList = []; - @override void initState() { super.initState(); - if (widget.initailValue.isEmpty) { - preparationStepSelectingList = []; - } else { - preparationStepSelectingList.addAll( - widget.initailValue.map((e) { - return PreparationStepWithSelection( - id: e.id, - preparationName: e.preparationName, - isSelected: true, - ); - }), - ); - } + context.read().initialize(); } @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; - return BlocProvider( - create: (context) => PreparationNameCubit(), - child: OnboardingPageViewLayout( - title: Text( - '주로 하는 준비 과정을\n선택해주세요', - style: textTheme.titleLarge, - ), - form: Form( - key: widget.formKey, - child: FormField>( - initialValue: preparationStepSelectingList, - onSaved: (value) { - widget.onSaved - ?.call(value!.where((element) => element.isSelected).map((e) { - return PreparationStepWithNameAndId( - id: e.id, - preparationName: e.preparationName, - ); - }).toList()); - }, - builder: (field) => PreparationSelectList( - preparationList: preparationStepSelectingList, - onSelectedStepChanged: (value) { - field.didChange(value); - setState(() { - preparationStepSelectingList = value; - }); - }, - ), - ), - ), + return OnboardingPageViewLayout( + title: Text( + '주로 하는 준비 과정을\n선택해주세요', + style: textTheme.titleLarge, + ), + form: Form( + key: widget.formKey, + child: PreparationSelectList(), ), ); } diff --git a/lib/presentation/onboarding/screens/onboarding_screen.dart b/lib/presentation/onboarding/screens/onboarding_screen.dart index de2d91f..ffdf7dd 100644 --- a/lib/presentation/onboarding/screens/onboarding_screen.dart +++ b/lib/presentation/onboarding/screens/onboarding_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/presentation/onboarding/components/preparation_reordarable_list.dart'; +import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart'; import 'package:on_time_front/presentation/onboarding/components/preparation_time_input_list.dart'; import 'package:on_time_front/presentation/onboarding/components/schedule_spare_time_input.dart'; @@ -39,6 +40,9 @@ class _OnboardingFormState extends State PreparationFormData preparationFormData = PreparationFormData(); Duration spareTime = const Duration(minutes: 0); final int _numberOfPages = 4; + final List _pageCubitTypes = [ + PreparationNameCubit, + ]; @override void initState() { @@ -60,113 +64,105 @@ class _OnboardingFormState extends State return SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - PageIndicator( - tabController: _tabController, - onUpdateCurrentPageIndex: _updateCurrentPageIndex, + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => PreparationNameCubit( + onboardingCubit: context.read(), + ), ), - Expanded( - child: PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _pageViewController, - onPageChanged: _handlePageViewChanged, - children: [ - PreparationSelectField( - formKey: formKeys[0], - initailValue: preparationFormData - .toPreparationStepWithNameAndIdList(), - onSaved: (value) { - final PreparationFormData tmp = - PreparationFormData(preparationStepList: []); - int j = 0; - for (int i = 0; i < value.length; i++) { - while (j < - preparationFormData - .preparationStepList.length && - preparationFormData.preparationStepList[j].id != - value[i].id) { - j++; - } - if (j == - preparationFormData.preparationStepList.length) { - tmp.preparationStepList.add(PreparationStepFormData( - id: value[i].id, - preparationName: value[i].preparationName)); - continue; - } - tmp.preparationStepList.add( - preparationFormData.preparationStepList[j].copyWith( - preparationName: value[i].preparationName)); - } - setState(() { - preparationFormData = tmp; - }); - }, - ), - PreparationReorderField( - formKey: formKeys[1], - initalValue: preparationFormData - .toPreparationStepWithOriginalIndexList(), - onSaved: (value) { - setState( - () { - for (int i = 0; i < value.length; i++) { - preparationFormData.preparationStepList[ - value[i].originalIndex] = - preparationFormData + ], + child: Builder(builder: (context) { + return Column( + children: [ + PageIndicator( + tabController: _tabController, + onUpdateCurrentPageIndex: _updateCurrentPageIndex, + ), + Expanded( + child: PageView( + physics: const NeverScrollableScrollPhysics(), + controller: _pageViewController, + onPageChanged: _handlePageViewChanged, + children: [ + PreparationSelectField( + formKey: formKeys[0], + ), + PreparationReorderField( + formKey: formKeys[1], + initalValue: preparationFormData + .toPreparationStepWithOriginalIndexList(), + onSaved: (value) { + setState( + () { + for (int i = 0; i < value.length; i++) { + preparationFormData.preparationStepList[ + value[i] + .originalIndex] = preparationFormData .preparationStepList[value[i].originalIndex] .copyWith(order: i); - } + } + }, + ); }, - ); - }, - ), - PreparationTimeInputFieldList( - formKey: formKeys[2], - initalValue: preparationFormData.sortByOrder(), - onSaved: (value) { - setState( - () { - preparationFormData = PreparationFormData( - preparationStepList: preparationFormData - .preparationStepList - .mapWithIndex((e, index) => - e.copyWith(preparationTime: value[index])) - .toList()); + ), + PreparationTimeInputFieldList( + formKey: formKeys[2], + initalValue: preparationFormData.sortByOrder(), + onSaved: (value) { + setState( + () { + preparationFormData = PreparationFormData( + preparationStepList: preparationFormData + .preparationStepList + .mapWithIndex((e, index) => e.copyWith( + preparationTime: value[index])) + .toList()); + }, + ); }, - ); - }, - ), - ScheduleSpareTimeField( - formKey: formKeys[3], - initialValue: spareTime, - onSaved: (value) { - setState( - () { - spareTime = value; + ), + ScheduleSpareTimeField( + formKey: formKeys[3], + initialValue: spareTime, + onSaved: (value) { + setState( + () { + spareTime = value; + }, + ); }, - ); - }, + ), + ], ), - ], - ), - ), - SizedBox( - height: 58, - width: double.infinity, - child: ElevatedButton( - onPressed: _onNextPageButtonClicked, - child: const Text('다음'))), - ], + ), + SizedBox( + height: 58, + width: double.infinity, + child: ElevatedButton( + onPressed: context.select( + (OnboardingCubit cubit) => cubit.state.isValid) + ? () => _onNextPageButtonClicked(context) + : null, + child: const Text('다음'), + )), + ], + ); + }), ), ), ); } - Future _onNextPageButtonClicked() async { + Future _onNextPageButtonClicked(BuildContext context) async { formKeys[_tabController.index].currentState!.save(); if (_tabController.index < _numberOfPages - 1) { + switch (_pageCubitTypes[_tabController.index]) { + case const (PreparationNameCubit): + context.read().preparationSaved(); + break; + // Add other cases if there are more cubit types + } _updateCurrentPageIndex(_tabController.index + 1); } else { return await context.read().onboardingFormSubmitted( From 0c2def11df2d41429715eb1c12e520b46d9d1918 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 18 Feb 2025 21:03:16 +0900 Subject: [PATCH 14/18] refactor: remove unused import for flutter_svg in onboarding_start_screen.dart --- lib/presentation/onboarding/screens/onboarding_start_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/onboarding/screens/onboarding_start_screen.dart b/lib/presentation/onboarding/screens/onboarding_start_screen.dart index 72e4d07..3dbce7d 100644 --- a/lib/presentation/onboarding/screens/onboarding_start_screen.dart +++ b/lib/presentation/onboarding/screens/onboarding_start_screen.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; class OnboardingStartScreen extends StatelessWidget { From b0825026048da7c35a35e6eea49bc6a5ba802765 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 18 Feb 2025 21:03:20 +0900 Subject: [PATCH 15/18] feat: update elevated button theme to handle disabled state with appropriate background color --- lib/presentation/shared/theme/theme.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/presentation/shared/theme/theme.dart b/lib/presentation/shared/theme/theme.dart index 8e47f82..deb2ec9 100644 --- a/lib/presentation/shared/theme/theme.dart +++ b/lib/presentation/shared/theme/theme.dart @@ -73,7 +73,15 @@ ThemeData themeData = ThemeData( )), elevatedButtonTheme: ElevatedButtonThemeData( style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll(colorScheme.primary), + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.disabled)) { + return colorScheme.surfaceDim; + } else { + return colorScheme.primary; + } + }, + ), foregroundColor: WidgetStatePropertyAll(colorScheme.onPrimary), textStyle: WidgetStatePropertyAll(textTheme.titleSmall), maximumSize: WidgetStatePropertyAll(const Size(double.infinity, 50)), From 0bcbbad25f14b376e57d01f20ed8a117fd0d39c7 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 18 Feb 2025 21:10:23 +0900 Subject: [PATCH 16/18] refactor: remove unused imports in app_bloc.dart and preparation_name_cubit.dart --- lib/presentation/app/bloc/app_bloc.dart | 1 - .../cubit/preparation_name/preparation_name_cubit.dart | 4 ---- 2 files changed, 5 deletions(-) diff --git a/lib/presentation/app/bloc/app_bloc.dart b/lib/presentation/app/bloc/app_bloc.dart index 351d875..f2e9829 100644 --- a/lib/presentation/app/bloc/app_bloc.dart +++ b/lib/presentation/app/bloc/app_bloc.dart @@ -2,7 +2,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/domain/entities/user_entity.dart'; -import 'package:on_time_front/domain/repositories/authentication_repository.dart'; import 'package:on_time_front/domain/use-cases/load_user_use_case.dart'; import 'package:on_time_front/domain/use-cases/sign_out_use_case.dart'; import 'package:on_time_front/domain/use-cases/stream_user_use_case.dart'; diff --git a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart index 512d0bf..3ff6683 100644 --- a/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart +++ b/lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart @@ -1,8 +1,4 @@ -import 'dart:math'; - -import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; import 'package:on_time_front/presentation/onboarding/cubit/onboarding/onboarding_cubit.dart'; From 3345a0dac8a515c291798fe563ab43f9bed2f608 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 18 Feb 2025 21:28:59 +0900 Subject: [PATCH 17/18] feat: refactor PreparationSelectList to use SingleChildScrollView for better layout handling --- .../screens/preparation_select_list.dart | 208 +++++++++--------- 1 file changed, 107 insertions(+), 101 deletions(-) diff --git a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart index 90335e0..eb1843d 100644 --- a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart +++ b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:keyboard_avoider/keyboard_avoider.dart'; import 'package:on_time_front/presentation/onboarding/components/onboarding_page_view_layout.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart'; @@ -18,83 +19,85 @@ class PreparationSelectList extends StatelessWidget { return BlocBuilder( builder: (context, state) { - return ListView( - children: [ - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: state.preparationStepList.length, - itemBuilder: (context, index) { - return PreparationNameSelectField( - preparationStep: state.preparationStepList[index], - onNameChanged: (value) { - context - .read() - .preparationStepNameChanged(index: index, value: value); - }, - onSelectionChanged: () { - context - .read() - .preparationStepSelectionChanged(index: index); - }, - ); - }, - ), - state.status == PreparationNameStatus.adding - ? BlocProvider( - create: (context) => PreparationStepNameCubit( - PreparationStepNameState(), - preparationNameCubit: - context.read()), - child: BlocBuilder(builder: (context, state) { - return PreparationNameSelectField( - isAdding: true, - preparationStep: state, - onNameChanged: (value) { - context - .read() - .nameChanged(value); - }, - onSelectionChanged: () { - context - .read() - .selectionToggled(); - }, - onNameSaved: () { - context - .read() - .preparationStepSaved(); - }, - ); - }), - ) - : SizedBox.shrink(), - SizedBox( - height: 32.0, - ), - Center( - child: SizedBox( - height: 30, - width: 30, - child: IconButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - colorScheme.primaryContainer), + return SingleChildScrollView( + child: Column( + children: [ + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: state.preparationStepList.length, + itemBuilder: (context, index) { + return PreparationNameSelectField( + preparationStep: state.preparationStepList[index], + onNameChanged: (value) { + context + .read() + .preparationStepNameChanged(index: index, value: value); + }, + onSelectionChanged: () { + context + .read() + .preparationStepSelectionChanged(index: index); + }, + ); + }, + ), + state.status == PreparationNameStatus.adding + ? BlocProvider( + create: (context) => PreparationStepNameCubit( + PreparationStepNameState(), + preparationNameCubit: + context.read()), + child: BlocBuilder(builder: (context, state) { + return PreparationNameSelectField( + isAdding: true, + preparationStep: state, + onNameChanged: (value) { + context + .read() + .nameChanged(value); + }, + onSelectionChanged: () { + context + .read() + .selectionToggled(); + }, + onNameSaved: () { + context + .read() + .preparationStepSaved(); + }, + ); + }), + ) + : SizedBox.shrink(), + SizedBox( + height: 28.0, + ), + Center( + child: SizedBox( + height: 30, + width: 30, + child: IconButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + colorScheme.primaryContainer), + ), + onPressed: () { + context + .read() + .preparationStepCreateRequested(); + }, + color: colorScheme.onPrimary, + icon: Icon(Icons.add), + padding: EdgeInsets.zero, + iconSize: 30.0, ), - onPressed: () { - context - .read() - .preparationStepCreateRequested(); - }, - color: colorScheme.onPrimary, - icon: Icon(Icons.add), - padding: EdgeInsets.zero, - iconSize: 30.0, ), ), - ), - ], + ], + ), ); }); } @@ -146,34 +149,37 @@ class _PreparationNameSelectFieldState if (widget.isAdding) { focusNode.requestFocus(); } - return Tile( - key: ValueKey(widget.preparationStep.preparationId), - style: TileStyle(padding: EdgeInsets.all(16.0)), - leading: SizedBox( - width: 30, - height: 30, - child: CheckButton( - isChecked: widget.preparationStep.isSelected, - onPressed: widget.onSelectionChanged, - )), - child: Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 19.0), - child: Container( - constraints: BoxConstraints(minHeight: 30), - child: TextFormField( - initialValue: widget.preparationStep.preparationName.value, - onChanged: widget.onNameChanged, - onFieldSubmitted: (value) => widget.onNameSaved?.call(), - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); - }, - decoration: InputDecoration( - isDense: true, - border: InputBorder.none, - contentPadding: EdgeInsets.all(3.0), + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Tile( + key: ValueKey(widget.preparationStep.preparationId), + style: TileStyle(padding: EdgeInsets.all(16.0)), + leading: SizedBox( + width: 30, + height: 30, + child: CheckButton( + isChecked: widget.preparationStep.isSelected, + onPressed: widget.onSelectionChanged, + )), + child: Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 19.0), + child: Container( + constraints: BoxConstraints(minHeight: 30), + child: TextFormField( + initialValue: widget.preparationStep.preparationName.value, + onChanged: widget.onNameChanged, + onFieldSubmitted: (value) => widget.onNameSaved?.call(), + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + contentPadding: EdgeInsets.all(3.0), + ), + focusNode: focusNode, ), - focusNode: focusNode, ), ), ), From bf84660c4c00b1116d0f4a8959a5bee7c7bba531 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Tue, 18 Feb 2025 21:33:29 +0900 Subject: [PATCH 18/18] refactor: remove unused import for keyboard_avoider in preparation_select_list.dart --- .../preparation_name_select/screens/preparation_select_list.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart index eb1843d..5e7dc2f 100644 --- a/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart +++ b/lib/presentation/onboarding/preparation_name_select/screens/preparation_select_list.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:keyboard_avoider/keyboard_avoider.dart'; import 'package:on_time_front/presentation/onboarding/components/onboarding_page_view_layout.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart'; import 'package:on_time_front/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart';