diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8576892..225c36d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -66,6 +66,7 @@ B955AE8AC425CE69815F5900 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C7F447F3DE684CF75971C579 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; E1FBF0C02DAE7C460017C51F /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + E1FBF0C22DBBDA7A0017C51F /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; E6228E8708160B7145026049 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E771DCC6EFA21C1573E7EC1E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -146,6 +147,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + E1FBF0C22DBBDA7A0017C51F /* Runner.entitlements */, AFE368912DBBC8390072E7EF /* Runner.entitlements */, E1FBF0C02DAE7C460017C51F /* GoogleService-Info.plist */, 97C146FA1CF9000F007C117D /* Main.storyboard */, diff --git a/lib/auth/data/datasources/remote/login_remote_datasource.dart b/lib/auth/data/datasources/remote/login_remote_datasource.dart index c1deaf0..cd210f6 100644 --- a/lib/auth/data/datasources/remote/login_remote_datasource.dart +++ b/lib/auth/data/datasources/remote/login_remote_datasource.dart @@ -15,8 +15,8 @@ class LoginRemoteDataSource { } Future loginWithApple() async { - final AuthorizationCredentialAppleID credential = - await SignInWithApple.getAppleIDCredential( + final AuthorizationCredentialAppleID + credential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, diff --git a/lib/auth/presentation/pages/login/login_viewmodel.dart b/lib/auth/presentation/pages/login/login_viewmodel.dart index 4a33a4a..4ca3fa0 100644 --- a/lib/auth/presentation/pages/login/login_viewmodel.dart +++ b/lib/auth/presentation/pages/login/login_viewmodel.dart @@ -16,9 +16,10 @@ class LoginViewModel extends ChangeNotifier { try { bool installed = await isKakaoTalkInstalled(); - OAuthToken token = installed - ? await UserApi.instance.loginWithKakaoTalk() - : await UserApi.instance.loginWithKakaoAccount(); + OAuthToken token = + installed + ? await UserApi.instance.loginWithKakaoTalk() + : await UserApi.instance.loginWithKakaoAccount(); final user = await UserApi.instance.me(); @@ -44,8 +45,9 @@ class LoginViewModel extends ChangeNotifier { // 애플 인증 정보 가져오기 final credential = useCase.callAppleLogin(LoginType.apple); // authorizationCode와 identityToken 확인 - final authorizationCode = - credential.then((value) => value.authorizationCode); + final authorizationCode = credential.then( + (value) => value.authorizationCode, + ); final identityToken = credential.then((value) => value.identityToken); if (authorizationCode == null || identityToken == null) { @@ -56,8 +58,9 @@ class LoginViewModel extends ChangeNotifier { await loginOauth(LoginType.apple, identityToken.toString()); log( - name: 'LoginViewModel::loginWithApple', - 'success: ${identityToken.toString()}'); + name: 'LoginViewModel::loginWithApple', + 'success: ${identityToken.toString()}', + ); } catch (e) { log(name: 'LoginViewModel::loginWithApple', 'error: $e'); } diff --git a/lib/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart b/lib/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart index 87332b0..4c8c842 100644 --- a/lib/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart +++ b/lib/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart @@ -73,9 +73,10 @@ class TermsAndConditionPage extends ConsumerWidget { Text( "모두 동의합니다.", style: AppTypography.subtitle2.copyWith( - color: viewmodel.checkState[0] - ? AppColors.grey900 - : AppColors.grey500, + color: + viewmodel.checkState[0] + ? AppColors.grey900 + : AppColors.grey500, ), ), IconButton( @@ -83,9 +84,10 @@ class TermsAndConditionPage extends ConsumerWidget { iconSize: 36, icon: Icon( Icons.check_circle_outline, - color: viewmodel.checkState[0] - ? AppColors.grey900 - : AppColors.grey500, + color: + viewmodel.checkState[0] + ? AppColors.grey900 + : AppColors.grey500, ), ), ], @@ -102,9 +104,10 @@ class TermsAndConditionPage extends ConsumerWidget { getLabel(i), style: AppTypography.body2.copyWith( decoration: TextDecoration.underline, - color: viewmodel.checkState[i] - ? AppColors.grey900 - : AppColors.grey500, + color: + viewmodel.checkState[i] + ? AppColors.grey900 + : AppColors.grey500, ), ), ), @@ -113,9 +116,10 @@ class TermsAndConditionPage extends ConsumerWidget { iconSize: 24, icon: Icon( Icons.check, - color: viewmodel.checkState[i] - ? AppColors.grey900 - : AppColors.grey500, + color: + viewmodel.checkState[i] + ? AppColors.grey900 + : AppColors.grey500, ), ), ], diff --git a/lib/auth/presentation/pages/woman/woman_verification_Page.dart b/lib/auth/presentation/pages/woman/woman_verification_Page.dart index 44cf916..9b45753 100644 --- a/lib/auth/presentation/pages/woman/woman_verification_Page.dart +++ b/lib/auth/presentation/pages/woman/woman_verification_Page.dart @@ -130,9 +130,10 @@ class WomanVerificationPage extends ConsumerWidget { Text( "위 안내사항을 모두 확인하였습니다.", style: AppTypography.subtitle2.copyWith( - color: viewModel.isChecked - ? AppColors.grey800 - : AppColors.grey400, + color: + viewModel.isChecked + ? AppColors.grey800 + : AppColors.grey400, ), ), IconButton( @@ -141,9 +142,10 @@ class WomanVerificationPage extends ConsumerWidget { }, icon: Icon( Icons.check_circle_outline, - color: viewModel.isChecked - ? AppColors.grey800 - : AppColors.grey400, + color: + viewModel.isChecked + ? AppColors.grey800 + : AppColors.grey400, ), ), ], diff --git a/lib/sign_up/presentation/pages/code_job/code_job_page.dart b/lib/sign_up/presentation/pages/code_job/code_job_page.dart index 7fd1d29..fcfa1b4 100644 --- a/lib/sign_up/presentation/pages/code_job/code_job_page.dart +++ b/lib/sign_up/presentation/pages/code_job/code_job_page.dart @@ -63,44 +63,47 @@ class _JobSelectionPageState extends ConsumerState { mainAxisSpacing: 12, crossAxisSpacing: 12, childAspectRatio: 1.1, - children: jobList.map((job) { - final isSelected = selectedJob == job['label']; - return GestureDetector( - onTap: () { - setState(() => selectedJob = job['label']); - }, - child: Container( - decoration: BoxDecoration( - color: isSelected - ? AppColors.primaryLight - : AppColors.grey100, - border: Border.all( - color: isSelected - ? AppColors.primary - : AppColors.grey100, - width: 1.5, - ), - borderRadius: BorderRadius.circular(8), - ), - padding: EdgeInsets.symmetric(vertical: 16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - job['image']!, - width: 16, - height: 22, + children: + jobList.map((job) { + final isSelected = selectedJob == job['label']; + return GestureDetector( + onTap: () { + setState(() => selectedJob = job['label']); + }, + child: Container( + decoration: BoxDecoration( + color: + isSelected + ? AppColors.primaryLight + : AppColors.grey100, + border: Border.all( + color: + isSelected + ? AppColors.primary + : AppColors.grey100, + width: 1.5, + ), + borderRadius: BorderRadius.circular(8), ), - SizedBox(height: AppGaps.gap4), - Text( - job['label'] ?? '', - style: AppTypography.body2, + padding: EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + job['image']!, + width: 16, + height: 22, + ), + SizedBox(height: AppGaps.gap4), + Text( + job['label'] ?? '', + style: AppTypography.body2, + ), + ], ), - ], - ), - ), - ); - }).toList(), + ), + ); + }).toList(), ), ), ], diff --git a/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_drink_page.dart b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_drink_page.dart index 13b66d8..29680e6 100644 --- a/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_drink_page.dart +++ b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_drink_page.dart @@ -55,8 +55,8 @@ class _CodeLifestyleDrinkPageState extends State { Expanded( child: ListView.separated( itemCount: drinkOptions.length, - separatorBuilder: (_, __) => - const SizedBox(height: AppGaps.gap16), + separatorBuilder: + (_, __) => const SizedBox(height: AppGaps.gap16), itemBuilder: (context, index) { final item = drinkOptions[index]; final isSelected = selectedIndex == index; @@ -74,13 +74,15 @@ class _CodeLifestyleDrinkPageState extends State { ), decoration: BoxDecoration( border: Border.all( - color: isSelected - ? AppColors.primary - : AppColors.grey400, + color: + isSelected + ? AppColors.primary + : AppColors.grey400, ), - color: isSelected - ? AppColors.primaryLight - : AppColors.white, + color: + isSelected + ? AppColors.primaryLight + : AppColors.white, ), child: Row( children: [ diff --git a/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_smoke_page.dart b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_smoke_page.dart index 5f9a069..2ed14aa 100644 --- a/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_smoke_page.dart +++ b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_smoke_page.dart @@ -63,8 +63,8 @@ class _CodeLifestyleDrinkPageState extends State { Expanded( child: ListView.separated( itemCount: drinkOptions.length, - separatorBuilder: (_, __) => - const SizedBox(height: AppGaps.gap16), + separatorBuilder: + (_, __) => const SizedBox(height: AppGaps.gap16), itemBuilder: (context, index) { final item = drinkOptions[index]; final isSelected = selectedIndex == index; @@ -82,13 +82,15 @@ class _CodeLifestyleDrinkPageState extends State { ), decoration: BoxDecoration( border: Border.all( - color: isSelected - ? AppColors.primary - : AppColors.grey400, + color: + isSelected + ? AppColors.primary + : AppColors.grey400, ), - color: isSelected - ? AppColors.primaryLight - : AppColors.white, + color: + isSelected + ? AppColors.primaryLight + : AppColors.white, ), child: Row( children: [ diff --git a/lib/sign_up/presentation/pages/profile_area/profile_area_page.dart b/lib/sign_up/presentation/pages/profile_area/profile_area_page.dart index 43a082f..cc4556e 100644 --- a/lib/sign_up/presentation/pages/profile_area/profile_area_page.dart +++ b/lib/sign_up/presentation/pages/profile_area/profile_area_page.dart @@ -356,56 +356,58 @@ class _ProfileRegionPageState extends State { ), SizedBox(width: AppGaps.gap20), Expanded( - child: selectedMainRegionIndex == null - ? const SizedBox.shrink() - : ListView.builder( - itemCount: subRegions[ - mainRegions[selectedMainRegionIndex!]]! - .length, - itemBuilder: (context, index) { - final subRegionName = subRegions[mainRegions[ - selectedMainRegionIndex!]]![index]; - final isSelected = - selectedSubRegionIndex == index; - return GestureDetector( - onTap: () { - setState(() { - selectedSubRegionIndex = index; - }); - }, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 16, - horizontal: 8, - ), - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - subRegionName, - style: AppTypography.body1.copyWith( - color: isSelected - ? AppColors.primary - : AppColors.grey900, - ), - ), - if (isSelected) - const Icon( - Icons.check, - color: AppColors.primary, - size: 20, + child: + selectedMainRegionIndex == null + ? const SizedBox.shrink() + : ListView.builder( + itemCount: + subRegions[mainRegions[selectedMainRegionIndex!]]! + .length, + itemBuilder: (context, index) { + final subRegionName = + subRegions[mainRegions[selectedMainRegionIndex!]]![index]; + final isSelected = + selectedSubRegionIndex == index; + return GestureDetector( + onTap: () { + setState(() { + selectedSubRegionIndex = index; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 8, + ), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + subRegionName, + style: AppTypography.body1.copyWith( + color: + isSelected + ? AppColors.primary + : AppColors.grey900, + ), ), - ], + if (isSelected) + const Icon( + Icons.check, + color: AppColors.primary, + size: 20, + ), + ], + ), ), - ), - ); - }, - ), + ); + }, + ), ), ], ), diff --git a/lib/sign_up/presentation/pages/profile_image/profile_face_image_page.dart b/lib/sign_up/presentation/pages/profile_image/profile_face_image_page.dart new file mode 100644 index 0000000..d77cbe3 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/profile_face_image_page.dart @@ -0,0 +1,85 @@ +import 'package:code_l/sign_up/presentation/pages/profile_image/providers.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileFaceImagePage extends ConsumerWidget { + const ProfileFaceImagePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final images = ref.watch(faceImagesProvider); + final viewModel = ref.read(faceImagesProvider.notifier); + final screenHeight = MediaQuery.of(context).size.height; + + return Scaffold( + appBar: const SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: images.length == 3, + onPressed: () { + // TODO: 업로드 후 다음 페이지 이동 + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + + Text("페이스 인증 사진을\n등록해주세요", style: AppTypography.header1), + const SizedBox(height: AppGaps.gap12), + Text( + "신뢰있는 매칭 위한\n얼굴 인증 사진 3장을 올려주세요", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + const SizedBox(height: AppGaps.gap40), + + Text( + "사진 첨부 ${images.length} / 3", + style: AppTypography.subtitle3.copyWith( + color: AppColors.grey900, + ), + ), + const SizedBox(height: 10), + + Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImageAddButton( + onTap: () => viewModel.pickImage(), + ), + ), + ...images.map( + (file) => Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImagePreview( + file: file, + onDelete: () => viewModel.removeImage(file), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/profile_image_page.dart b/lib/sign_up/presentation/pages/profile_image/profile_image_page.dart new file mode 100644 index 0000000..5547c8e --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/profile_image_page.dart @@ -0,0 +1,91 @@ +import 'package:code_l/sign_up/presentation/pages/profile_image/profile_face_image_page.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/providers.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileImagePage extends ConsumerWidget { + const ProfileImagePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final images = ref.watch(profileImagesProvider); + final viewModel = ref.read(profileImagesProvider.notifier); + final screenHeight = MediaQuery.of(context).size.height; + + return Scaffold( + appBar: const SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: images.isNotEmpty, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ProfileFaceImagePage(), + ), + ); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + + Text("프로필 사진을\n등록해주세요", style: AppTypography.header1), + const SizedBox(height: AppGaps.gap12), + Text( + "나를 표현할 수 있는 사진을\n최소 1~최대 3장 등록해주세요", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + const SizedBox(height: AppGaps.gap40), + + Text( + "사진 첨부 ${images.length} / 3", + style: AppTypography.subtitle3.copyWith( + color: AppColors.grey900, + ), + ), + const SizedBox(height: 10), + + Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImageAddButton( + onTap: () => viewModel.pickImage(), + ), + ), + ...images.map( + (file) => Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImagePreview( + file: file, + onDelete: () => viewModel.removeImage(file), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart b/lib/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart new file mode 100644 index 0000000..e980cd3 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart @@ -0,0 +1,21 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; + +class ProfileImageViewModel extends StateNotifier> { + ProfileImageViewModel() : super([]); + + final _picker = ImagePicker(); + + Future pickImage() async { + final pickedList = await _picker.pickMultiImage(); + if (pickedList.isNotEmpty) { + final maxAllowed = 3 - state.length; + final limited = pickedList.take(maxAllowed).toList(); + state = [...state, ...limited]; + } + } + + void removeImage(XFile file) { + state = [...state]..remove(file); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/providers.dart b/lib/sign_up/presentation/pages/profile_image/providers.dart new file mode 100644 index 0000000..18fb03b --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/providers.dart @@ -0,0 +1,13 @@ +import 'package:code_l/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; + +final profileImagesProvider = + StateNotifierProvider>( + (ref) => ProfileImageViewModel(), + ); + +final faceImagesProvider = + StateNotifierProvider>( + (ref) => ProfileImageViewModel(), + ); diff --git a/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart new file mode 100644 index 0000000..0441b1a --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart @@ -0,0 +1,50 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; + +class ProfileImageAddButton extends StatelessWidget { + final VoidCallback onTap; + + const ProfileImageAddButton({super.key, required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColors.grey100, + borderRadius: BorderRadius.circular(24), + ), + child: const Center( + child: Icon(Icons.add, size: 16, color: AppColors.grey300), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Container( + width: 24, + height: 24, + decoration: const BoxDecoration( + color: AppColors.grey900, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.camera_alt, + color: Colors.white, + size: 12, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart new file mode 100644 index 0000000..fede688 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart @@ -0,0 +1,52 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; + +class ProfileImagePreview extends StatelessWidget { + final XFile file; + final VoidCallback onDelete; + + const ProfileImagePreview({ + super.key, + required this.file, + required this.onDelete, + }); + + @override + Widget build(BuildContext context) { + return Stack( + clipBehavior: Clip.none, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Image.file( + File(file.path), + width: 64, + height: 64, + fit: BoxFit.cover, + ), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: onDelete, + child: Container( + width: 24, + height: 24, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: AppColors.grey900, + ), + child: const Icon(Icons.close, size: 12, color: Colors.white), + ), + ), + ), + ], + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart b/lib/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart index a7a3d42..99b0f5f 100644 --- a/lib/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart +++ b/lib/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart @@ -109,13 +109,15 @@ class _ProfileInterestPageState extends State { ), decoration: BoxDecoration( border: Border.all( - color: isSelected - ? AppColors.primary - : AppColors.grey400, + color: + isSelected + ? AppColors.primary + : AppColors.grey400, ), - color: isSelected - ? AppColors.primaryLight - : AppColors.white, + color: + isSelected + ? AppColors.primaryLight + : AppColors.white, borderRadius: BorderRadius.circular(100), ), child: Text( diff --git a/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_page.dart b/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_page.dart index a1342ca..3a4d2b4 100644 --- a/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_page.dart +++ b/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_page.dart @@ -57,50 +57,54 @@ class ProfileMBTIPage extends ConsumerWidget { return Padding( padding: const EdgeInsets.only(bottom: AppGaps.gap20), child: Row( - children: group.map((option) { - final isSelected = viewModel.isSelected( - groupIndex, - option, - ); - return Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 6, - ), - child: GestureDetector( - onTap: () => viewModel.select( - groupIndex, - option, - ), - child: Container( + children: + group.map((option) { + final isSelected = viewModel.isSelected( + groupIndex, + option, + ); + return Expanded( + child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 74, - vertical: 34, - ), - decoration: BoxDecoration( - color: isSelected - ? AppColors.primaryLight - : AppColors.grey100, - border: Border.all( - color: isSelected - ? AppColors.primary - : AppColors.grey100, - ), - borderRadius: BorderRadius.circular(8), + horizontal: 6, ), - child: Center( - child: Text( - option, - style: AppTypography.body1.copyWith( - color: AppColors.grey900, + child: GestureDetector( + onTap: + () => viewModel.select( + groupIndex, + option, + ), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 74, + vertical: 34, + ), + decoration: BoxDecoration( + color: + isSelected + ? AppColors.primaryLight + : AppColors.grey100, + border: Border.all( + color: + isSelected + ? AppColors.primary + : AppColors.grey100, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + option, + style: AppTypography.body1.copyWith( + color: AppColors.grey900, + ), + ), ), ), ), ), - ), - ), - ); - }).toList(), + ); + }).toList(), ), ); }, diff --git a/lib/sign_up/presentation/pages/profile_mbti/providers.dart b/lib/sign_up/presentation/pages/profile_mbti/providers.dart index 9159df2..90f47cd 100644 --- a/lib/sign_up/presentation/pages/profile_mbti/providers.dart +++ b/lib/sign_up/presentation/pages/profile_mbti/providers.dart @@ -5,5 +5,5 @@ import '../../../domain/model/profile_mbti/profile_mbti_state.dart'; final profileMBTIProvider = StateNotifierProvider( - (ref) => ProfileMBTIViewModel(), -); + (ref) => ProfileMBTIViewModel(), + ); diff --git a/pubspec.lock b/pubspec.lock index cffe782..704416c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -185,6 +193,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" firebase_auth: dependency: "direct main" description: @@ -254,6 +294,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" flutter_riverpod: dependency: "direct main" description: @@ -304,6 +352,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.dev" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" js: dependency: transitive description: @@ -416,6 +528,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a052e9a..c932278 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: flutter_dotenv: ^5.2.1 screen_protector: ^1.4.2+1 sign_in_with_apple: ^7.0.1 + image_picker: ^1.1.2 dev_dependencies: flutter_test: