From 2a576a92cb56e17bfa6781236b9c6bb880e487fe Mon Sep 17 00:00:00 2001 From: siiion Date: Fri, 31 Jan 2025 20:33:57 +0900 Subject: [PATCH 1/2] =?UTF-8?q?FEAT:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20api=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- economic_fe/lib/data/models/user_profile.dart | 46 +++-- .../lib/data/services/remote_data_source.dart | 23 ++- economic_fe/lib/main.dart | 2 +- .../profile_setting/basic_info_page.dart | 117 ++++++------- .../profile_setting/job_select_page.dart | 5 +- .../profile_setting/part_select_page.dart | 5 +- .../profile_setting/profile_setting_page.dart | 26 +-- .../profile_setting/basic_controller.dart | 109 ++++++------ .../job_select_controller.dart | 36 ++-- .../part_select_controller.dart | 36 ++-- .../profile_setting_controller.dart | 163 +++++++++--------- 11 files changed, 299 insertions(+), 269 deletions(-) diff --git a/economic_fe/lib/data/models/user_profile.dart b/economic_fe/lib/data/models/user_profile.dart index 11837f0..a588a10 100644 --- a/economic_fe/lib/data/models/user_profile.dart +++ b/economic_fe/lib/data/models/user_profile.dart @@ -1,34 +1,48 @@ class UserProfile { String nickname; + String birthDate; + String gender; + String profileIntro; String businessType; String job; - String ageRange; - String gender; - String? profileIntro; bool isLearningAlarmAllowed; bool isCommunityAlarmAllowed; UserProfile({ required this.nickname, + required this.birthDate, + required this.gender, + required this.profileIntro, required this.businessType, required this.job, - required this.ageRange, - required this.gender, - this.profileIntro, - this.isLearningAlarmAllowed = false, - this.isCommunityAlarmAllowed = false, + this.isLearningAlarmAllowed = true, // 기본값 + this.isCommunityAlarmAllowed = true, // 기본값 }); + // JSON 변환 + factory UserProfile.fromJson(Map json) { + return UserProfile( + nickname: json['nickname'] ?? '', + birthDate: json['birthDate'] ?? '', + gender: json['gender'] ?? '', + profileIntro: json['profileIntro'] ?? '', + businessType: json['businessType'] ?? '', + job: json['job'] ?? '', + isLearningAlarmAllowed: json['isLearningAlarmAllowed'] ?? true, + isCommunityAlarmAllowed: json['isCommunityAlarmAllowed'] ?? true, + ); + } + Map toJson() { return { - 'nickname': nickname, - 'businessType': businessType, - 'job': job, - 'ageRange': ageRange, - 'gender': gender, - 'profileIntro': profileIntro, - 'isLearningAlarmAllowed': isLearningAlarmAllowed, - 'isCommunityAlarmAllowed': isCommunityAlarmAllowed, + "nickname": nickname, + "birthDate": birthDate, + "gender": gender, + "profileIntro": profileIntro, + "businessType": businessType, + "job": job, + "isLearningAlarmAllowed": isLearningAlarmAllowed, + "isCommunityAlarmAllowed": isCommunityAlarmAllowed, }; } } diff --git a/economic_fe/lib/data/services/remote_data_source.dart b/economic_fe/lib/data/services/remote_data_source.dart index 15e0a40..2dc7a4c 100644 --- a/economic_fe/lib/data/services/remote_data_source.dart +++ b/economic_fe/lib/data/services/remote_data_source.dart @@ -19,7 +19,6 @@ class RemoteDataSource { Map jsonData, ) async { String apiUrl = '$baseUrl/$endPoint'; - // String authToken = dotenv.env['AUTHORIZATION_KEY']!; // 환경 변수에서 가져오기 Map headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer $accessToken', @@ -408,4 +407,26 @@ class RemoteDataSource { return null; } } + + /// 사용자 프로필 등록 API + /// API: api/v1/user/profile + static Future registerUserProfile( + Map userProfile) async { + String endpoint = 'api/v1/user/profile'; + + try { + final response = await postApi(endpoint, userProfile); + + if (response == 200) { + debugPrint('사용자 프로필 등록 성공'); + return true; + } else { + debugPrint('사용자 프로필 등록 실패: $response'); + return false; + } + } catch (e) { + debugPrint('registerUserProfile Error: $e'); + return false; + } + } } diff --git a/economic_fe/lib/main.dart b/economic_fe/lib/main.dart index 5484eeb..c0e3b33 100644 --- a/economic_fe/lib/main.dart +++ b/economic_fe/lib/main.dart @@ -21,7 +21,7 @@ class RippleApp extends StatelessWidget { Widget build(BuildContext context) { return GetMaterialApp( title: 'Ripple', - initialRoute: '/mypage', + initialRoute: '/profile_setting', getPages: UserRouter.getPages(), // 라우트 설정 ); } diff --git a/economic_fe/lib/view/screens/profile_setting/basic_info_page.dart b/economic_fe/lib/view/screens/profile_setting/basic_info_page.dart index 5de15b2..37c730a 100644 --- a/economic_fe/lib/view/screens/profile_setting/basic_info_page.dart +++ b/economic_fe/lib/view/screens/profile_setting/basic_info_page.dart @@ -25,7 +25,7 @@ class BasicInfoPage extends StatelessWidget { appBar: CustomAppBar( title: '기본 정보', onPress: () { - controller.navigateToProfileSetting(context); + Get.back(); }, icon: Icons.close, ), @@ -104,7 +104,7 @@ class BasicInfoPage extends StatelessWidget { height: ScreenUtils.getHeight(context, 18.8), ), const BasicLabel( - label: '닉네임(필수)', + label: '닉네임', ), SizedBox( height: ScreenUtils.getHeight(context, 4), @@ -114,9 +114,11 @@ class BasicInfoPage extends StatelessWidget { height: ScreenUtils.getHeight(context, 44), child: Obx(() { return TextField( - onChanged: controller.validateNickname, // 입력 값에 따라 유효성 검사 - controller: - TextEditingController(text: controller.nickname.value), + onChanged: (value) { + controller.nickname.value = value; // 닉네임 업데이트 + controller.validateNickname(value); + }, + controller: controller.nicknameController, decoration: InputDecoration( prefixIcon: Padding( padding: const EdgeInsets.all(12.0), @@ -224,32 +226,28 @@ class BasicInfoPage extends StatelessWidget { child: Row( children: [ GestureDetector( - onTap: () => controller.selectGender('남'), - child: controller.selectedGender.value == '남' - ? const BasicGenderButton( - isSelected: true, - text: '남', - textColor: Colors.black, - ) - : const BasicGenderButton( - isSelected: false, - text: '남', - textColor: Color(0xFFA2A2A2), - ), + onTap: () => controller.selectGender('MALE'), + child: BasicGenderButton( + text: '남', + textColor: + controller.selectedGender.value == 'MALE' + ? Colors.black + : const Color(0xffa2a2a2), + isSelected: + controller.selectedGender.value == 'MALE', + ), ), GestureDetector( - onTap: () => controller.selectGender('여'), - child: controller.selectedGender.value == '여' - ? const BasicGenderButton( - isSelected: true, - text: '여', - textColor: Colors.black, - ) - : const BasicGenderButton( - isSelected: false, - text: '여', - textColor: Color(0xFFA2A2A2), - ), + onTap: () => controller.selectGender('FEMALE'), + child: BasicGenderButton( + text: '여', + textColor: + controller.selectedGender.value == 'FEMALE' + ? Colors.black + : const Color(0xffa2a2a2), + isSelected: + controller.selectedGender.value == 'FEMALE', + ), ), ], ), @@ -330,31 +328,28 @@ class BasicInfoPage extends StatelessWidget { ), SizedBox( width: ScreenUtils.getWidth(context, 216), - child: Obx(() { - return TextField( - controller: TextEditingController( - text: controller.userInput.value), - onChanged: controller.onTextChanged, - maxLines: 5, - inputFormatters: [ - // 글자 수가 maxLength를 초과하지 않도록 제한 - LengthLimitingTextInputFormatter( - controller.maxLength), - ], - decoration: InputDecoration( - border: InputBorder.none, - hintText: '한 줄 소개를 입력하세요.', - hintStyle: Palette.pretendard( - context, - const Color(0xFFA2A2A2), - 16, - FontWeight.w400, - 1.5, - -0.4, - ), + child: TextField( + controller: controller.userInputController, + onChanged: controller.onTextChanged, + maxLines: 5, + inputFormatters: [ + // 글자 수가 maxLength를 초과하지 않도록 제한 + LengthLimitingTextInputFormatter( + controller.maxLength), + ], + decoration: InputDecoration( + border: InputBorder.none, + hintText: '한 줄 소개를 입력하세요.', + hintStyle: Palette.pretendard( + context, + const Color(0xFFA2A2A2), + 16, + FontWeight.w400, + 1.5, + -0.4, ), - ); - }), + ), + ), ), ], ), @@ -404,22 +399,16 @@ class BasicInfoPage extends StatelessWidget { ), // 저장하기 버튼 활성화 Obx(() { - bool isButtonEnabled = controller.isValid.value && - controller.selectedBirthday.value != null; - return isButtonEnabled + return controller.isValid.value && + controller.selectedBirthday.value != null ? CustomButton( text: '저장하기', - onPress: () { - controller.onSaveButtonClicked(); - controller.navigateToProfileSetting(context); - }, - bgColor: Palette.buttonColorBlue, + onPress: () => controller.onSaveButtonClicked(), + bgColor: Palette.buttonColorGreen, ) : CustomButtonUnfilled( text: '저장하기', - onPress: () { - // 저장할 수 없으므로 아무 동작도 하지 않음 - }, + onPress: () {}, ); }), ], diff --git a/economic_fe/lib/view/screens/profile_setting/job_select_page.dart b/economic_fe/lib/view/screens/profile_setting/job_select_page.dart index b69dd65..3f9aca6 100644 --- a/economic_fe/lib/view/screens/profile_setting/job_select_page.dart +++ b/economic_fe/lib/view/screens/profile_setting/job_select_page.dart @@ -21,7 +21,7 @@ class JobSelectPage extends StatelessWidget { appBar: CustomAppBar( title: '업종/직무', onPress: () { - controller.navigateToProfileSetting(context); + controller.navigateToProfileSetting(); }, icon: Icons.close, ), @@ -105,8 +105,7 @@ class JobSelectPage extends StatelessWidget { text: '저장하기', onPress: () { controller.onSaveButtonClicked(); - controller.navigateToProfileSetting( - context); // 프로필 설정 화면으로 이동 + controller.navigateToProfileSetting(); // 프로필 설정 화면으로 이동 }, bgColor: Palette.buttonColorBlue, ), diff --git a/economic_fe/lib/view/screens/profile_setting/part_select_page.dart b/economic_fe/lib/view/screens/profile_setting/part_select_page.dart index 122573e..db9035a 100644 --- a/economic_fe/lib/view/screens/profile_setting/part_select_page.dart +++ b/economic_fe/lib/view/screens/profile_setting/part_select_page.dart @@ -21,7 +21,7 @@ class PartSelectPage extends StatelessWidget { appBar: CustomAppBar( title: '업종/직무', onPress: () { - controller.navigateToProfileSetting(context); + controller.navigateToProfileSetting(); }, icon: Icons.close, ), @@ -299,8 +299,7 @@ class PartSelectPage extends StatelessWidget { text: '저장하기', onPress: () { controller.onSaveButtonClicked(); - controller.navigateToProfileSetting( - context); // 프로필 설정 화면으로 이동 + controller.navigateToProfileSetting(); // 프로필 설정 화면으로 이동 }, bgColor: Palette.buttonColorBlue, ), diff --git a/economic_fe/lib/view/screens/profile_setting/profile_setting_page.dart b/economic_fe/lib/view/screens/profile_setting/profile_setting_page.dart index 6dc54c5..438dd14 100644 --- a/economic_fe/lib/view/screens/profile_setting/profile_setting_page.dart +++ b/economic_fe/lib/view/screens/profile_setting/profile_setting_page.dart @@ -37,11 +37,10 @@ class ProfileSettingPage extends StatelessWidget { return ProfileSettingButton( title: '기본 정보', onPress: () { - // 기본 정보 입력 페이지로 이동 - controller.navigateToBasic(context); + Get.toNamed('/profile_setting/basic'); }, isSelected: controller.basicSaveButtonClicked.value, - icon: const Icon(Icons.person), // 클릭 여부에 따라 버튼 스타일 변경 + icon: const Icon(Icons.person), ); }), SizedBox( @@ -52,11 +51,10 @@ class ProfileSettingPage extends StatelessWidget { return ProfileSettingButton( title: '업종', onPress: () { - // 업종 선택 페이지로 이동 - controller.navigateToJob(context); + Get.toNamed('/profile_setting/job'); }, isSelected: controller.jobSaveButtonClicked.value, - icon: const Icon(Icons.business_center), // 클릭 여부에 따라 버튼 스타일 변경 + icon: const Icon(Icons.business_center), ); }), SizedBox( @@ -67,11 +65,10 @@ class ProfileSettingPage extends StatelessWidget { return ProfileSettingButton( title: '직무', onPress: () { - // 직무 선택 페이지로 이동 - controller.navigateToPart(context); + Get.toNamed('/profile_setting/part'); }, isSelected: controller.partSaveButtonClicked.value, - icon: const Icon(Icons.badge), // 클릭 여부에 따라 버튼 스타일 변경 + icon: const Icon(Icons.badge), ); }), SizedBox( @@ -79,15 +76,12 @@ class ProfileSettingPage extends StatelessWidget { ), // 저장하기 버튼 활성화 Obx(() { - bool isButtonEnabled = controller.basicSaveButtonClicked.value; - return isButtonEnabled + return controller.isProfileReady ? Center( child: CustomButton( text: '저장하기', onPress: () async { - const authToken = - "Bearer eyJhbGciOiJIUz..."; // 실제 Authorization 키 - await controller.saveUserProfile(authToken); + await controller.saveUserProfile(); }, bgColor: Palette.buttonColorBlue, ), @@ -95,9 +89,7 @@ class ProfileSettingPage extends StatelessWidget { : Center( child: CustomButtonUnfilled( text: '저장하기', - onPress: () { - // 저장할 수 없으므로 아무 동작도 하지 않음 - }, + onPress: () {}, ), ); }), diff --git a/economic_fe/lib/view_model/profile_setting/basic_controller.dart b/economic_fe/lib/view_model/profile_setting/basic_controller.dart index 47b4d01..9a1c0e2 100644 --- a/economic_fe/lib/view_model/profile_setting/basic_controller.dart +++ b/economic_fe/lib/view_model/profile_setting/basic_controller.dart @@ -1,56 +1,54 @@ -import 'dart:developer'; - import 'package:economic_fe/data/services/date_picker_service.dart'; import 'package:economic_fe/data/services/image_picker_service.dart'; import 'package:economic_fe/view_model/profile_setting/profile_setting_controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:go_router/go_router.dart'; // GoRouter import class BasicController extends GetxController { - late BuildContext context; - // ImagePickerService 인스턴스 final ImagePickerService _imagePickerService = ImagePickerService(); - var selectedProfileImage = Rx(null); // 선택된 프로필 이미지 경로를 저장 + var selectedProfileImage = Rx(null); - // 닉네임 입력값과 그 유효성 상태 관리 + // 닉네임 입력값과 유효성 상태 관리 var nickname = ''.obs; var isValid = false.obs; var errorMessage = ''.obs; - // 성별 선택 상태 - var selectedGender = Rx(null); + // 닉네임 입력 컨트롤러 (키보드 입력 문제 해결) + final TextEditingController nicknameController = TextEditingController(); + + // 성별 선택 상태 (서버 전송 값 `MALE` / `FEMALE` 로 저장) + var selectedGender = Rxn(); - // 날짜 선택 서비스 인스턴스 생성 + // 생년월일 선택 상태 final DatePickerService _datePickerService = DatePickerService(); - // 생년월일 var selectedBirthday = Rx(null); // 한 줄 소개 - var userInput = ''.obs; // 사용자 입력 값 - var currentLength = 0.obs; // 현재 글자 수 - final int maxLength = 70; // 최대 글자 수 + var userInput = ''.obs; + var currentLength = 0.obs; + final int maxLength = 70; - // 저장하기 버튼 클릭 여부 추적 - var saveButtonClicked = false.obs; // 이 값을 통해 저장 버튼 클릭 여부를 추적 + // 한 줄 소개 입력 컨트롤러 (키보드 입력 문제 해결) + final TextEditingController userInputController = TextEditingController(); - // 프로필 설정 화면으로 전환 - void navigateToProfileSetting(BuildContext context) { - // context.go('/profile_setting'); + // 저장 버튼 클릭 여부 + var saveButtonClicked = false.obs; + + /// 프로필 설정 페이지로 이동 + void navigateToProfileSetting() { Get.toNamed('/profile_setting'); } - // 프로필 사진 선택 함수 + /// 프로필 사진 선택 Future selectProfileImage(BuildContext context) async { final image = await _imagePickerService.showImagePickerDialog(context); if (image != null) { - selectedProfileImage.value = image.path; // 이미지 경로 저장 - print('Selected image path: ${image.path}'); + selectedProfileImage.value = image.path; } } - // 닉네임 유효성 검사 + /// 닉네임 유효성 검사 void validateNickname(String value) { nickname.value = value; @@ -65,57 +63,64 @@ class BasicController extends GetxController { errorMessage.value = ''; } - // 실시간으로 saveButton 상태 업데이트 + _updateProfileField('nickname', value); + + // ✅ 저장 버튼 상태 업데이트 _updateSaveButtonState(); } - // 성별 선택 상태 관리 + /// 성별 선택 (서버 전송 값 `MALE` / `FEMALE`) void selectGender(String gender) { - // 성별을 선택하면 해당 성별로 상태 업데이트 - if (selectedGender.value == gender) { - selectedGender.value = null; // 이미 선택된 성별을 다시 클릭하면 선택 해제 - } else { - selectedGender.value = gender; - } + selectedGender.value = gender; + + // ProfileSettingController에 업데이트 반영 + Get.find() + .updateProfileField('gender', selectedGender.value); + + // UI 강제 업데이트 + selectedGender.refresh(); + + // ✅ 저장 버튼 상태 업데이트 + _updateSaveButtonState(); } - // 생년월일 선택 함수 + /// 생년월일 선택 Future selectBirthday(BuildContext context) async { DateTime? pickedDate = await _datePickerService.pickDate(context); if (pickedDate != null) { selectedBirthday.value = '${pickedDate.year}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.day.toString().padLeft(2, '0')}'; - } + _updateProfileField('birthDate', selectedBirthday.value); - // 실시간으로 saveButton 상태 업데이트 - _updateSaveButtonState(); + // ✅ 저장 버튼 상태 업데이트 + _updateSaveButtonState(); + } } - // 텍스트 길이와 상태 업데이트 + /// 한 줄 소개 입력 처리 void onTextChanged(String value) { userInput.value = value; currentLength.value = value.length; + _updateProfileField('profileIntro', value); } - // 저장하기 버튼 활성화 여부를 실시간으로 업데이트 + /// 프로필 필드 업데이트 → ProfileSettingController에 반영 + void _updateProfileField(String key, dynamic value) { + Get.find().updateProfileField(key, value); + _updateSaveButtonState(); + } + + /// 저장 버튼 상태 업데이트 void _updateSaveButtonState() { - // 닉네임이 유효하고, 연령대가 선택되었을 때만 버튼을 활성화 - if (isValid.value && selectedBirthday.value != null) { - saveButtonClicked.value = true; - } else { - saveButtonClicked.value = false; - } + saveButtonClicked.value = isValid.value && + selectedGender.value != null && + selectedBirthday.value != null; } - // 저장하기 버튼 클릭 상태 업데이트 - void onSaveButtonClicked() async { - Get.find().updateBasicInfo( - ageRange: selectedBirthday.value!, - gender: selectedGender.value!, - profileIntro: userInput.value, - ); - Get.find().updateNickname(nickname.value); - saveButtonClicked.value = true; // 상태 변경 + /// 저장 버튼 클릭 → ProfileSettingController에 상태 반영 + void onSaveButtonClicked() { + saveButtonClicked.value = true; Get.find().updateBasicSaveButtonClicked(); + navigateToProfileSetting(); } } diff --git a/economic_fe/lib/view_model/profile_setting/job_select_controller.dart b/economic_fe/lib/view_model/profile_setting/job_select_controller.dart index 92a7dd1..b0d8fe6 100644 --- a/economic_fe/lib/view_model/profile_setting/job_select_controller.dart +++ b/economic_fe/lib/view_model/profile_setting/job_select_controller.dart @@ -1,7 +1,6 @@ import 'package:economic_fe/view_model/profile_setting/profile_setting_controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:go_router/go_router.dart'; // GoRouter import class JobSelectController extends GetxController { late BuildContext context; @@ -9,28 +8,35 @@ class JobSelectController extends GetxController { // 업종 선택 상태 var selectedJob = Rx(null); - // 저장하기 버튼 클릭 여부 추적 - var saveButtonClicked = false.obs; // 이 값을 통해 저장 버튼 클릭 여부를 추적 + // 저장하기 버튼 클릭 여부 + var saveButtonClicked = false.obs; - // 프로필 설정 화면으로 전환 - void navigateToProfileSetting(BuildContext context) { - // context.go('/profile_setting'); + /// 프로필 설정 페이지로 이동 + void navigateToProfileSetting() { Get.toNamed('/profile_setting'); } - // 업종 선택 + /// 업종 선택 (선택/해제 가능) void selectJob(String job) { - if (selectedJob.value == job) { - selectedJob.value = null; // 이미 선택된 업종 클릭 시 해제 - } else { - selectedJob.value = job; - } + selectedJob.value = (selectedJob.value == job) ? null : job; + _updateProfileField('businessType', selectedJob.value); } - // 저장하기 버튼 클릭 상태 업데이트 + /// `ProfileSettingController`의 `updateProfileField()`를 활용하여 업종 자동 저장 + void _updateProfileField(String key, dynamic value) { + Get.find().updateProfileField(key, value); + _updateSaveButtonState(); + } + + /// 저장 버튼 상태 업데이트 + void _updateSaveButtonState() { + saveButtonClicked.value = selectedJob.value != null; + } + + /// 저장 버튼 클릭 → ProfileSettingController에 상태 반영 void onSaveButtonClicked() { - Get.find().updateBusinessType(selectedJob.value!); - saveButtonClicked.value = true; // 상태 변경 + saveButtonClicked.value = true; Get.find().updateJobSaveButtonClicked(); + navigateToProfileSetting(); } } diff --git a/economic_fe/lib/view_model/profile_setting/part_select_controller.dart b/economic_fe/lib/view_model/profile_setting/part_select_controller.dart index 55b1f4e..865c53e 100644 --- a/economic_fe/lib/view_model/profile_setting/part_select_controller.dart +++ b/economic_fe/lib/view_model/profile_setting/part_select_controller.dart @@ -1,7 +1,6 @@ import 'package:economic_fe/view_model/profile_setting/profile_setting_controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:go_router/go_router.dart'; // GoRouter import class PartSelectController extends GetxController { late BuildContext context; @@ -9,28 +8,35 @@ class PartSelectController extends GetxController { // 직무 선택 상태 var selectedPart = Rx(null); - // 저장하기 버튼 클릭 여부 추적 - var saveButtonClicked = false.obs; // 이 값을 통해 저장 버튼 클릭 여부를 추적 + // 저장하기 버튼 클릭 여부 + var saveButtonClicked = false.obs; - // 프로필 설정 화면으로 전환 - void navigateToProfileSetting(BuildContext context) { - // context.go('/profile_setting'); + /// 프로필 설정 페이지로 이동 + void navigateToProfileSetting() { Get.toNamed('/profile_setting'); } - // 직무 선택 + /// 직무 선택 (선택/해제 가능) void selectPart(String part) { - if (selectedPart.value == part) { - selectedPart.value = null; // 이미 선택된 직무 클릭 시 해제 - } else { - selectedPart.value = part; - } + selectedPart.value = (selectedPart.value == part) ? null : part; + _updateProfileField('job', selectedPart.value); } - // 저장하기 버튼 클릭 상태 업데이트 + /// `ProfileSettingController`의 `updateProfileField()`를 활용하여 업종 자동 저장 + void _updateProfileField(String key, dynamic value) { + Get.find().updateProfileField(key, value); + _updateSaveButtonState(); + } + + /// 저장 버튼 상태 업데이트 + void _updateSaveButtonState() { + saveButtonClicked.value = selectedPart.value != null; + } + + /// 저장 버튼 클릭 → ProfileSettingController에 상태 반영 void onSaveButtonClicked() { - Get.find().updateJob(selectedPart.value!); - saveButtonClicked.value = true; // 상태 변경 + saveButtonClicked.value = true; Get.find().updatePartSaveButtonClicked(); + navigateToProfileSetting(); } } diff --git a/economic_fe/lib/view_model/profile_setting/profile_setting_controller.dart b/economic_fe/lib/view_model/profile_setting/profile_setting_controller.dart index 21a47aa..38c0c06 100644 --- a/economic_fe/lib/view_model/profile_setting/profile_setting_controller.dart +++ b/economic_fe/lib/view_model/profile_setting/profile_setting_controller.dart @@ -1,126 +1,125 @@ -import 'dart:developer'; - import 'package:economic_fe/data/models/user_profile.dart'; import 'package:economic_fe/data/services/remote_data_source.dart'; import 'package:economic_fe/view_model/profile_setting/basic_controller.dart'; import 'package:economic_fe/view_model/profile_setting/job_select_controller.dart'; import 'package:economic_fe/view_model/profile_setting/part_select_controller.dart'; -import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:go_router/go_router.dart'; // GoRouter import class ProfileSettingController extends GetxController { - late BuildContext context; - - // 저장하기 버튼이 클릭되었는지 확인하는 값 + // 저장 버튼 상태 var basicSaveButtonClicked = false.obs; var jobSaveButtonClicked = false.obs; var partSaveButtonClicked = false.obs; - // 이전 화면으로 돌아가는 메서드 + // 사용자 프로필 데이터 + var userProfile = UserProfile( + nickname: '', + birthDate: '', + gender: '', + profileIntro: '', + businessType: '', + job: '', + ).obs; + + // 입력 완료 여부 + var isInfoCompleted = false.obs; + var isBusinessCompleted = false.obs; + var isJobCompleted = false.obs; + + /// 모든 필수 데이터가 입력되었는지 확인 + bool get isProfileReady { + bool ready = isInfoCompleted.value && + isBusinessCompleted.value && + isJobCompleted.value; + return ready; + } + + /// 이전 화면으로 이동 void goBack() { Get.back(); } - // 기본 정보 설정 화면으로 전환 - void navigateToBasic(BuildContext context) { - // context.go('/profile_setting/basic'); - Get.toNamed('/profile_setting/basic'); - } + /// 개별 입력 필드 업데이트 + void updateProfileField(String key, dynamic value) { + userProfile.update((profile) { + if (profile != null) { + switch (key) { + case 'nickname': + profile.nickname = value; + break; + case 'birthDate': + profile.birthDate = value; + break; + case 'gender': + profile.gender = value; + break; + case 'profileIntro': + profile.profileIntro = value; + break; + case 'businessType': + profile.businessType = value; + isBusinessCompleted.value = value.isNotEmpty; + break; + case 'job': + profile.job = value; + isJobCompleted.value = value.isNotEmpty; + break; + } + } + }); - // 업종 선택 화면으로 전환 - void navigateToJob(BuildContext context) { - // context.go('/profile_setting/job'); - Get.toNamed('/profile_setting/job'); + // 프로필 입력 상태 업데이트 + updateProfileCompletionStatus(); } - // 직무 선택 화면으로 전환 - void navigateToPart(BuildContext context) { - // context.go('/profile_setting/part'); - Get.toNamed('/profile_setting/part'); + /// 프로필 입력 완료 여부 업데이트 + void updateProfileCompletionStatus() { + bool infoCompleted = userProfile.value.nickname.isNotEmpty && + userProfile.value.birthDate.isNotEmpty && + userProfile.value.gender.isNotEmpty && + userProfile.value.profileIntro.isNotEmpty; + + isInfoCompleted.value = infoCompleted; } - // 저장하기 버튼 클릭 여부를 업데이트하는 메서드 - 기본 정보 + /// 저장 버튼 상태 업데이트 - 기본 정보 void updateBasicSaveButtonClicked() { basicSaveButtonClicked.value = Get.find().saveButtonClicked.value; + updateProfileCompletionStatus(); // 입력 상태 즉시 반영 } - // 저장하기 버튼 클릭 여부를 업데이트하는 메서드 - 업종 + /// 저장 버튼 상태 업데이트 - 업종 void updateJobSaveButtonClicked() { jobSaveButtonClicked.value = Get.find().saveButtonClicked.value; + updateProfileCompletionStatus(); } - // 저장하기 버튼 클릭 여부를 업데이트하는 메서드 - 직무 + /// 저장 버튼 상태 업데이트 - 직무 void updatePartSaveButtonClicked() { partSaveButtonClicked.value = Get.find().saveButtonClicked.value; + updateProfileCompletionStatus(); } - // 홈 화면으로 연결 - void toHomePage() { - Get.toNamed('/home'); - } - - // 사용자 프로필 정보 통합 관리 - final Rx userProfile = UserProfile( - nickname: '', - businessType: '', - job: '', - ageRange: '', - gender: '', - ).obs; - - void updateNickname(String nickname) { - userProfile.update((profile) { - profile?.nickname = nickname; - }); - } - - void updateBusinessType(String businessType) { - userProfile.update((profile) { - profile?.businessType = businessType; - }); - } - - void updateJob(String job) { - userProfile.update((profile) { - profile?.job = job; - }); - } - - void updateBasicInfo({ - required String ageRange, - required String gender, - String? profileIntro, - }) { - userProfile.update((profile) { - profile?.ageRange = ageRange; - profile?.gender = gender; - profile?.profileIntro = profileIntro; - }); - } - - Future saveUserProfile(String authToken) async { - // 모든 저장 버튼이 클릭되었는지 확인 - if (!basicSaveButtonClicked.value || - !jobSaveButtonClicked.value || - !partSaveButtonClicked.value) { - Get.snackbar('오류', '모든 항목을 완료해주세요.'); + /// 사용자 프로필 저장 API 호출 + Future saveUserProfile() async { + if (!isProfileReady) { + Get.snackbar('알림', '모든 정보를 입력해주세요.'); return; } - final response = await RemoteDataSource.postApi( - 'api/v1/user/profile', - userProfile.value.toJson(), // Token 전달 - ); + print("🚀 전송 데이터: ${userProfile.value.toJson()}"); + + bool success = + await RemoteDataSource.registerUserProfile(userProfile.value.toJson()); - if (response == 200) { - Get.snackbar('성공', '프로필이 저장되었습니다.'); - toHomePage(); // 홈 화면으로 이동 + if (success) { + Get.snackbar('성공', '프로필 등록이 완료되었습니다.'); + Get.offAllNamed('/home'); // 홈 화면으로 이동 } else { - Get.snackbar('오류', '프로필 저장 중 문제가 발생했습니다.'); + Get.snackbar('오류', '프로필 등록에 실패했습니다.'); } } } From 61db82416f2bc50376d242153448336fd538cb2e Mon Sep 17 00:00:00 2001 From: siiion Date: Fri, 31 Jan 2025 20:43:05 +0900 Subject: [PATCH 2/2] =?UTF-8?q?MOD:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20api=20=EC=97=B0=EA=B2=B0=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/data/services/remote_data_source.dart | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/economic_fe/lib/data/services/remote_data_source.dart b/economic_fe/lib/data/services/remote_data_source.dart index 11f6756..7b0b369 100644 --- a/economic_fe/lib/data/services/remote_data_source.dart +++ b/economic_fe/lib/data/services/remote_data_source.dart @@ -492,4 +492,26 @@ class RemoteDataSource { return null; } } + + /// 사용자 프로필 등록 API + /// API: api/v1/user/profile + static Future registerUserProfile( + Map userProfile) async { + String endpoint = 'api/v1/user/profile'; + + try { + final response = await postApiWithJson(endpoint, userProfile); + + if (response == 200) { + debugPrint('사용자 프로필 등록 성공'); + return true; + } else { + debugPrint('사용자 프로필 등록 실패: $response'); + return false; + } + } catch (e) { + debugPrint('registerUserProfile Error: $e'); + return false; + } + } }