diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 001df22..2ebf3a6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,3 +13,5 @@ close # ## ๐Ÿ’ฌ๋ฆฌ๋ทฐ ์š”๊ตฌ์‚ฌํ•ญ +## ๋‹ค์Œ ์ž‘์—… + diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index a0a7822..f958fb1 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -4,7 +4,6 @@ plugins { id("com.android.application") id("kotlin-android") id("com.google.gms.google-services") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } @@ -53,3 +52,4 @@ android { flutter { source = "../.." } + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0079ea1..70d1b84 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,18 @@ android:label="code_l" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> + + + + + + + + + + ("clean") { delete(rootProject.layout.buildDirectory) } + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.google.gms:google-services:4.3.15") // โœ… ๊ด„ํ˜ธ ์ฃผ์˜ + } +} + diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..4fdea37 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"code-l-b109b","appId":"1:971644319685:android:34198e5a313e5294b53dac","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"code-l-b109b","appId":"1:971644319685:ios:bde2639222525193b53dac","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"code-l-b109b","configurations":{"android":"1:971644319685:android:34198e5a313e5294b53dac","ios":"1:971644319685:ios:bde2639222525193b53dac"}}}}}} \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..dfc16f7 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,136 @@ +PODS: + - Firebase/Auth (11.10.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 11.10.0) + - Firebase/CoreOnly (11.10.0): + - FirebaseCore (~> 11.10.0) + - firebase_auth (5.5.2): + - Firebase/Auth (= 11.10.0) + - firebase_core + - Flutter + - firebase_core (3.13.0): + - Firebase/CoreOnly (= 11.10.0) + - Flutter + - FirebaseAppCheckInterop (11.11.0) + - FirebaseAuth (11.10.0): + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseAuthInterop (~> 11.0) + - FirebaseCore (~> 11.10.0) + - FirebaseCoreExtension (~> 11.10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.0) + - GoogleUtilities/Environment (~> 8.0) + - GTMSessionFetcher/Core (< 5.0, >= 3.4) + - RecaptchaInterop (~> 101.0) + - FirebaseAuthInterop (11.11.0) + - FirebaseCore (11.10.0): + - FirebaseCoreInternal (~> 11.10.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Logger (~> 8.0) + - FirebaseCoreExtension (11.10.0): + - FirebaseCore (~> 11.10.0) + - FirebaseCoreInternal (11.10.0): + - "GoogleUtilities/NSData+zlib (~> 8.0)" + - Flutter (1.0.0) + - fluttertoast (0.0.2): + - Flutter + - GoogleUtilities/AppDelegateSwizzler (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.0.2): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.0.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.0.2)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.0.2) + - GoogleUtilities/Reachability (8.0.2): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GTMSessionFetcher/Core (4.4.0) + - kakao_flutter_sdk_common (1.9.7-3): + - Flutter + - RecaptchaInterop (101.0.0) + - screen_protector (1.2.1): + - Flutter + - ScreenProtectorKit (~> 1.3.1) + - ScreenProtectorKit (1.3.1) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sign_in_with_apple (0.0.1): + - Flutter + +DEPENDENCIES: + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - Flutter (from `Flutter`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - kakao_flutter_sdk_common (from `.symlinks/plugins/kakao_flutter_sdk_common/ios`) + - screen_protector (from `.symlinks/plugins/screen_protector/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) + +SPEC REPOS: + trunk: + - Firebase + - FirebaseAppCheckInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - GoogleUtilities + - GTMSessionFetcher + - RecaptchaInterop + - ScreenProtectorKit + +EXTERNAL SOURCES: + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + Flutter: + :path: Flutter + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" + kakao_flutter_sdk_common: + :path: ".symlinks/plugins/kakao_flutter_sdk_common/ios" + screen_protector: + :path: ".symlinks/plugins/screen_protector/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sign_in_with_apple: + :path: ".symlinks/plugins/sign_in_with_apple/ios" + +SPEC CHECKSUMS: + Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 + firebase_auth: ad485af7f854ba5f86634f81725b22b37f317883 + firebase_core: 2d4534e7b489907dcede540c835b48981d890943 + FirebaseAppCheckInterop: f23709c9ce92d810aa53ff4ce12ad3e666a3c7be + FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32 + FirebaseAuthInterop: ac22ed402c2f4e3a8c63ebd3278af9a06073c1be + FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 + FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac + FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 + GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d + GTMSessionFetcher: 75b671f9e551e4c49153d4c4f8659ef4f559b970 + kakao_flutter_sdk_common: 600d55b532da0bd37268a529e1add49302477710 + RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba + screen_protector: 3d90d44ac886b25335aebd93230b454aef45794a + ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 + +PODFILE CHECKSUM: f8c2dcdfb50bb67645580d28a6bf814fca30bdec + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d7b0228..7ccd31d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -62,6 +62,8 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A0C3D7BA37E297BF670F5764 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + AFD21D852DB512DE00452D52 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; + AFE368902DB7F7D10072E7EF /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 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 = ""; }; @@ -145,6 +147,8 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + AFE368902DB7F7D10072E7EF /* Runner.entitlements */, + AFD21D852DB512DE00452D52 /* RunnerDebug.entitlements */, E1FBF0C02DAE7C460017C51F /* GoogleService-Info.plist */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -473,6 +477,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -660,6 +665,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -687,6 +693,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..f6353c7 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 971644319685-jo8kiqh779jhevlh53d3cbj3lrdavovf.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.971644319685-jo8kiqh779jhevlh53d3cbj3lrdavovf + ANDROID_CLIENT_ID + 971644319685-vnlopiq93h2n0cukes3chrm8n51t2836.apps.googleusercontent.com + API_KEY + AIzaSyA6aPcY0Qv4eCO4Q_KLxF0KMDQ9M9wdZlw + GCM_SENDER_ID + 971644319685 + PLIST_VERSION + 1 + BUNDLE_ID + com.codel.codel + PROJECT_ID + code-l-b109b + STORAGE_BUCKET + code-l-b109b.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:971644319685:ios:bde2639222525193b53dac + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index b2fbcc6..f425cd0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,15 +2,8 @@ -CFBundleURLTypes - - - CFBundleURLSchemes - - kakaoe17acdc68adf7250d61dd15f8ee185c1 - - - + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -29,10 +22,43 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + kakaoe17acdc68adf7250d61dd15f8ee185c1 + app-1-971644319685-ios-bde2639222525193b53dac + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.971644319685-jo8kiqh779jhevlh53d3cbj3lrdavovf + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + app-1-971644319685-ios-bde2639222525193b53dac + + + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + remote-notification + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -50,9 +76,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..a812db5 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements new file mode 100644 index 0000000..80b5221 --- /dev/null +++ b/ios/Runner/RunnerDebug.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.developer.applesignin + + Default + + + diff --git a/lib/auth/data/repositories/auth_repository_impl.dart b/lib/auth/data/repositories/auth_repository_impl.dart new file mode 100644 index 0000000..dda74b5 --- /dev/null +++ b/lib/auth/data/repositories/auth_repository_impl.dart @@ -0,0 +1,54 @@ +import 'dart:developer'; + +import 'package:firebase_auth/firebase_auth.dart'; + +import '../../domain/repositories/auth_repository.dart'; + +class AuthRepositoryImpl implements AuthRepository { + final FirebaseAuth _auth = FirebaseAuth.instance; + + @override + Future verifyPhoneNumber( + String phoneNumber, Function(String) onCodeSent) async { + await _auth.verifyPhoneNumber( + phoneNumber: phoneNumber, + timeout: const Duration(seconds: 60), + verificationCompleted: (PhoneAuthCredential credential) async { + log(name: 'AuthRepositoryImpl : verifyPhoneNumber', 'ํ•ธ๋“œํฐ ์ž๋™ ์ธ์ฆ ์™„๋ฃŒ'); + await _auth.signInWithCredential(credential); + }, + verificationFailed: (FirebaseAuthException e) { + log( + name: 'AuthRepositoryImpl : verifyPhoneNumber', + 'Phone verification failed: ${e.message}'); + }, + codeSent: (String verificationId, int? forceResendingToken) async { + log(name: 'AuthRepositoryImpl : verifyPhoneNumber', '์ธ์ฆ ๋ฒˆํ˜ธ ์ „์†ก'); + onCodeSent(verificationId); + }, + codeAutoRetrievalTimeout: (String verificationId) { + log(name: 'AuthRepositoryImpl : verifyPhoneNumber', "์ธ์ฆ ๋ฒˆํ˜ธ ๋งŒ๋ฃŒ"); + }, + ); + } + + @override + Future signInWithSmsCode(String verificationId, String smsCode) async { + try { + log( + name: 'AuthRepositoryImpl : signInWithSmsCode', + "$verificationId - $smsCode"); + final credential = PhoneAuthProvider.credential( + verificationId: verificationId, + smsCode: smsCode, + ); + await _auth.signInWithCredential(credential); + log(name: 'AuthRepositoryImpl : signInWithSmsCode', 'Sign in successful'); + } on FirebaseAuthException catch (e) { + log( + name: 'AuthRepositoryImpl : signInWithSmsCode', + 'FirebaseAuthException: ${e.code} - ${e.message}'); + rethrow; + } + } +} diff --git a/lib/auth/domain/repositories/.gitkeep b/lib/auth/domain/repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/auth/domain/repositories/auth_repository.dart b/lib/auth/domain/repositories/auth_repository.dart new file mode 100644 index 0000000..3bb06e6 --- /dev/null +++ b/lib/auth/domain/repositories/auth_repository.dart @@ -0,0 +1,5 @@ +abstract class AuthRepository { + Future verifyPhoneNumber( + String phoneNumber, Function(String) onCodeSent); + Future signInWithSmsCode(String verificationId, String smsCode); +} diff --git a/lib/auth/domain/usecases/.gitkeep b/lib/auth/domain/usecases/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/auth/domain/usecases/phone_verificarion_usecase.dart b/lib/auth/domain/usecases/phone_verificarion_usecase.dart new file mode 100644 index 0000000..978ee5f --- /dev/null +++ b/lib/auth/domain/usecases/phone_verificarion_usecase.dart @@ -0,0 +1,20 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../repositories/auth_repository.dart'; + +class VerifyPhoneNumberUseCase { + final AuthRepository _authRepository; + + VerifyPhoneNumberUseCase(this._authRepository); + + Future sendCode( + String phoneNumber, + Function(String) onCodeSent, + ) async { + await _authRepository.verifyPhoneNumber(phoneNumber, onCodeSent); + } + + Future verifyCode(String verificationId, String smsCode) async { + await _authRepository.signInWithSmsCode(verificationId, smsCode); + } +} diff --git a/lib/auth/presentation/pages/congratulation/congratulation_page.dart b/lib/auth/presentation/pages/congratulation/congratulation_page.dart index 86e1ffc..a31ea27 100644 --- a/lib/auth/presentation/pages/congratulation/congratulation_page.dart +++ b/lib/auth/presentation/pages/congratulation/congratulation_page.dart @@ -100,7 +100,6 @@ class CongratulationPage extends StatelessWidget { height: 40, ), ), - Padding( padding: const EdgeInsets.all(AppGaps.gap4), child: CongratulationConfirmButton( diff --git a/lib/auth/presentation/pages/identity/identity_verification_page.dart b/lib/auth/presentation/pages/identity/identity_verification_page.dart new file mode 100644 index 0000000..42c3b7c --- /dev/null +++ b/lib/auth/presentation/pages/identity/identity_verification_page.dart @@ -0,0 +1,136 @@ +import 'package:code_l/auth/presentation/pages/identity/providers.dart'; +import 'package:code_l/auth/presentation/pages/identity/widgets/identity_verification_app_bar.dart'; +import 'package:code_l/auth/presentation/widgets/auth_confirm_button.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 '../login/login_page.dart'; + +class PhoneVerificationPage extends ConsumerWidget { + const PhoneVerificationPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final viewModel = ref.watch(phoneVerificationViewModelProvider.notifier); + final state = ref.watch(phoneVerificationViewModelProvider); + + return Scaffold( + appBar: IdentityVerificationAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(20.0), + child: AuthConfirmButton( + enabled: state.codeSent, + onPressed: () { + if (viewModel.formKey.currentState!.validate()) { + viewModel.verifyCode(() { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const LoginPage()), + ); + }); + } + }, + ), + ), + body: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Form( + key: viewModel.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: AppGaps.gap40), + Text("ํœด๋Œ€์ „ํ™” ๋ฒˆํ˜ธ\n์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค", style: AppTypography.header1), + Text( + "์›ํ™œํ•œ ์„œ๋น„์Šค ์ด์šฉ์„ ์œ„ํ•ด ๋ฒˆํ˜ธ์ธ์ฆ์„ ํ•ด์ฃผ์„ธ์š”", + style: AppTypography.body2.copyWith( + color: AppColors.grey600, + ), + ), + SizedBox(height: AppGaps.gap40), + SizedBox(height: AppGaps.gap40), + TextFormField( + controller: viewModel.phoneController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + border: UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.grey400, + width: 0.6, + ), + ), + hintText: 'ํœด๋Œ€์ „ํ™” ๋ฒˆํ˜ธ', + suffixIcon: ElevatedButton( + onPressed: () { + if (viewModel.formKey.currentState!.validate()) { + viewModel.sendSmsCode(); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: AppColors.white, + textStyle: AppTypography.subtitle2.copyWith( + color: AppColors.white, + ), + shape: RoundedRectangleBorder(), + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap12, + vertical: AppGaps.gap12, + ), + ), + child: const Text("์ธ์ฆ ์š”์ฒญ"), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'ํœด๋Œ€์ „ํ™” ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.'; + } + if (!RegExp( + r'^01[0-9]-?\d{3,4}-?\d{4}$', + ).hasMatch(value)) { + return '์œ ํšจํ•œ ํœด๋Œ€์ „ํ™” ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.'; + } + return null; + }, + ), + const SizedBox(height: 15), + if (state.codeSent) ...[ + const SizedBox(height: 20), + // ์ธ์ฆ ์ฝ”๋“œ ์ž…๋ ฅ ํ•„๋“œ + TextFormField( + controller: viewModel.smsCodeController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.grey400, + width: 0.6, + ), + ), + hintText: '์ธ์ฆ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return '์ธ์ฆ๋ฒˆํ˜ธ ์ž…๋ ฅ'; + } + if (!RegExp(r'^\d{6}$').hasMatch(value)) { + return '6์ž๋ฆฌ ์ธ์ฆ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.'; + } + return null; + }, + ), + ], + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart new file mode 100644 index 0000000..cb42ef0 --- /dev/null +++ b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart @@ -0,0 +1,85 @@ +import 'dart:developer'; + +import 'package:code_l/auth/presentation/pages/identity/providers.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../../domain/usecases/phone_verificarion_usecase.dart'; + +class PhoneVerificationState { + final bool codeSent; + final String verificationId; + + PhoneVerificationState({ + this.codeSent = false, + this.verificationId = '', + }); + + PhoneVerificationState copyWith({ + bool? codeSent, + String? verificationId, + }) { + return PhoneVerificationState( + codeSent: codeSent ?? this.codeSent, + verificationId: verificationId ?? this.verificationId, + ); + } +} + +class PhoneVerificationViewModel extends Notifier { + late final VerifyPhoneNumberUseCase _verifyPhoneNumberUseCase; + final formKey = GlobalKey(); + final phoneController = TextEditingController(); + final smsCodeController = TextEditingController(); + + @override + PhoneVerificationState build() { + _verifyPhoneNumberUseCase = ref.read(verifyPhoneNumberUseCaseProvider); + return PhoneVerificationState(); + } + + Future sendSmsCode() async { + if (formKey.currentState!.validate()) { + await _verifyPhoneNumberUseCase.sendCode( + "+82${phoneController.text.substring(1)}", + (verificationId) { + state = state.copyWith( + verificationId: verificationId, + codeSent: true, + ); + }, + ); + log( + name: 'PhoneVerificationViewModel : sendSmsCode', + "+82${phoneController.text.substring(1)}"); + } + } + + Future verifyCode(Function onSuccess) async { + try { + await _verifyPhoneNumberUseCase.verifyCode( + state.verificationId, + smsCodeController.text, + ); + log( + name: 'PhoneVerificationViewModel : verifyCode', + state.verificationId); + log( + name: 'PhoneVerificationViewModel : verifyCode', + smsCodeController.text); + log(name: 'PhoneVerificationViewModel : verifyCode', 'Code verified'); + Fluttertoast.showToast(msg: '์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); + onSuccess(); + } catch (e) { + log(name: 'PhoneVerificationViewModel : verifyCode', e.toString()); + log( + name: 'PhoneVerificationViewModel : verifyCode', + state.verificationId); + log( + name: 'PhoneVerificationViewModel : verifyCode', + smsCodeController.text); + Fluttertoast.showToast(msg: "์ธ์ฆ ๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค"); + } + } +} diff --git a/lib/auth/presentation/pages/identity/providers.dart b/lib/auth/presentation/pages/identity/providers.dart new file mode 100644 index 0000000..a079449 --- /dev/null +++ b/lib/auth/presentation/pages/identity/providers.dart @@ -0,0 +1,20 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../data/repositories/auth_repository_impl.dart'; +import '../../../domain/repositories/auth_repository.dart'; +import '../../../domain/usecases/phone_verificarion_usecase.dart'; +import 'identity_verification_viewmodel.dart'; + +final verifyPhoneNumberUseCaseProvider = + Provider((ref) { + return VerifyPhoneNumberUseCase(ref.read(authRepositoryProvider)); +}); + +final phoneVerificationViewModelProvider = + NotifierProvider( + PhoneVerificationViewModel.new, +); + +final authRepositoryProvider = Provider((ref) { + return AuthRepositoryImpl(); +}); diff --git a/lib/auth/presentation/pages/identity/widgets/identity_verification_app_bar.dart b/lib/auth/presentation/pages/identity/widgets/identity_verification_app_bar.dart new file mode 100644 index 0000000..07b29ff --- /dev/null +++ b/lib/auth/presentation/pages/identity/widgets/identity_verification_app_bar.dart @@ -0,0 +1,38 @@ +import 'package:code_l/core/utills/design/app_gaps.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; +import '../../../../../core/utills/design/app_typography.dart'; + +class IdentityVerificationAppBar extends StatelessWidget + implements PreferredSizeWidget { + const IdentityVerificationAppBar({super.key}); + + @override + Size get preferredSize => Size.fromHeight(56); + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: AppGaps.gap36), + Text( + "ํœด๋Œ€์ „ํ™” ๋ฒˆํ˜ธ ์ธ์ฆ", + style: AppTypography.subtitle2.copyWith(color: AppColors.grey900), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/login/login_page.dart b/lib/auth/presentation/pages/login/login_page.dart index 7c4a875..986d1ea 100644 --- a/lib/auth/presentation/pages/login/login_page.dart +++ b/lib/auth/presentation/pages/login/login_page.dart @@ -1,7 +1,9 @@ +import 'package:code_l/auth/presentation/pages/identity/identity_verification_page.dart'; import 'package:code_l/auth/presentation/pages/login/providers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import '../../../../core/utills/design/app_colors.dart'; import '../../../../core/utills/design/app_gaps.dart'; @@ -19,7 +21,7 @@ class LoginPage extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Flexible( - flex: 4, + flex: 3, child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -32,11 +34,19 @@ class LoginPage extends ConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("์ฝ”๋“œ๊ฐ€ ๋งž๋Š” ์šฐ๋ฆฌ๋งŒ์˜", style: AppTypography.body1), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("๊ณต๊ฐ„์—์„œ ์‹œ์ž‘๋˜๋Š” ", style: AppTypography.body1), + Text("์ฝ”๋“œ๊ฐ€ ๋งž๋Š” ์šฐ๋ฆฌ๋งŒ์˜ ๊ณต๊ฐ„", + style: AppTypography.subtitle2 + .copyWith(color: AppColors.primary)), + Text("์—์„œ", style: AppTypography.body1), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("์‹œ์ž‘๋˜๋Š” ", style: AppTypography.body1), Text("์ง„์งœ ์ธ์—ฐ", style: AppTypography.subtitle1), ], ), @@ -68,7 +78,7 @@ class LoginPage extends ConsumerWidget { SvgPicture.asset('assets/icons/kakao_logo.svg'), const SizedBox(width: 10), Text( - "์นด์นด์˜ค๋กœ 3์ดˆ๋งŒ์— ๋กœ๊ทธ์ธ", + "์นด์นด์˜ค๋กœ ๋กœ๊ทธ์ธ", style: AppTypography.body1.copyWith( color: AppColors.black, ), @@ -88,10 +98,11 @@ class LoginPage extends ConsumerWidget { side: const BorderSide(color: AppColors.black), ), onPressed: () { + ref.read(loginViewModelProvider).loginWithApple(); Navigator.push( context, MaterialPageRoute( - builder: (context) => const LoginPage(), + builder: (context) => const PhoneVerificationPage(), ), ); }, @@ -103,7 +114,7 @@ class LoginPage extends ConsumerWidget { SvgPicture.asset('assets/icons/apple_logo.svg'), const SizedBox(width: 10), Text( - "Apple๋กœ ๊ณ„์†ํ•˜๊ธฐ", + "Apple๋กœ ๋กœ๊ทธ์ธ", style: AppTypography.body1.copyWith( color: AppColors.black, ), @@ -116,6 +127,16 @@ class LoginPage extends ConsumerWidget { ], ), ), + Flexible( + flex: 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("๋ ˆ์ฆˆ๋น„์–ธ", style: AppTypography.subtitle2), + Text("์„ ์œ„ํ•œ ๋” ๊นŠ์ด์žˆ๋Š” ๋งค์นญ์•ฑ", style: AppTypography.body1), + ], + ), + ), ], ), ), diff --git a/lib/auth/presentation/pages/login/login_viewmodel.dart b/lib/auth/presentation/pages/login/login_viewmodel.dart index 830484f..896e9f5 100644 --- a/lib/auth/presentation/pages/login/login_viewmodel.dart +++ b/lib/auth/presentation/pages/login/login_viewmodel.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:code_l/auth/domain/model/login_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import '../../../domain/usecases/login_usecase.dart'; @@ -15,10 +16,9 @@ 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(); @@ -38,4 +38,38 @@ class LoginViewModel extends ChangeNotifier { log(name: 'LoginViewModel::loginOauth', 'error: $e'); } } + + Future loginWithApple() async { + try { + // ์• ํ”Œ ์ธ์ฆ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ + final AuthorizationCredentialAppleID credential = + await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + webAuthenticationOptions: WebAuthenticationOptions( + clientId: "codel.codel.com", // Apple Developer Console์— ๋“ฑ๋ก๋œ clientId + redirectUri: Uri.parse( + "https://chartreuse-gratis-lungfish.glitch.me/callbacks/sign_in_with_apple", + ), + ), + ); + + // authorizationCode์™€ identityToken ํ™•์ธ + final authorizationCode = credential.authorizationCode; + final identityToken = credential.identityToken; + + if (authorizationCode == null || identityToken == null) { + throw Exception("Authorization Code ๋˜๋Š” Identity Token์„ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + + // ๋ฐฑ์—”๋“œ๋กœ OAuth ์ธ์ฆ ์š”์ฒญ + await loginOauth(LoginType.apple, identityToken); + + log(name: 'LoginViewModel::loginWithApple', 'success: $identityToken'); + } 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 4c8c842..87332b0 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,10 +73,9 @@ 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( @@ -84,10 +83,9 @@ 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, ), ), ], @@ -104,10 +102,9 @@ 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, ), ), ), @@ -116,10 +113,9 @@ 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 9b45753..44cf916 100644 --- a/lib/auth/presentation/pages/woman/woman_verification_Page.dart +++ b/lib/auth/presentation/pages/woman/woman_verification_Page.dart @@ -130,10 +130,9 @@ class WomanVerificationPage extends ConsumerWidget { Text( "์œ„ ์•ˆ๋‚ด์‚ฌํ•ญ์„ ๋ชจ๋‘ ํ™•์ธํ•˜์˜€์Šต๋‹ˆ๋‹ค.", style: AppTypography.subtitle2.copyWith( - color: - viewModel.isChecked - ? AppColors.grey800 - : AppColors.grey400, + color: viewModel.isChecked + ? AppColors.grey800 + : AppColors.grey400, ), ), IconButton( @@ -142,10 +141,9 @@ 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/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..0a2eae0 --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,72 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyAGaxpjP5Jn2P1J-O5W5DmFCQgqcIhuxyw', + appId: '1:971644319685:android:34198e5a313e5294b53dac', + messagingSenderId: '971644319685', + projectId: 'code-l-b109b', + storageBucket: 'code-l-b109b.firebasestorage.app', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyA6aPcY0Qv4eCO4Q_KLxF0KMDQ9M9wdZlw', + appId: '1:971644319685:ios:bde2639222525193b53dac', + messagingSenderId: '971644319685', + projectId: 'code-l-b109b', + storageBucket: 'code-l-b109b.firebasestorage.app', + androidClientId: + '971644319685-vnlopiq93h2n0cukes3chrm8n51t2836.apps.googleusercontent.com', + iosClientId: + '971644319685-jo8kiqh779jhevlh53d3cbj3lrdavovf.apps.googleusercontent.com', + iosBundleId: 'com.codel.codel', + ); +} diff --git a/lib/main.dart b/lib/main.dart index c30236d..409f503 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,5 @@ import 'package:code_l/auth/presentation/pages/login/login_page.dart'; import 'package:code_l/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart'; -import 'package:code_l/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -27,7 +26,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Kakao Login Demo', - home: const ProfileInterestPage(), + home: const LoginPage(), theme: ThemeData( primarySwatch: Colors.blue, scaffoldBackgroundColor: Colors.white, 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 fcfa1b4..7fd1d29 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,47 +63,44 @@ 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), + 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, ), - 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, - ), - ], + 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 76147d2..13b66d8 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 @@ -52,12 +52,11 @@ class _CodeLifestyleDrinkPageState extends State { style: AppTypography.body2.copyWith(color: AppColors.grey600), ), SizedBox(height: AppGaps.gap40), - 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; @@ -75,15 +74,13 @@ 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 2ed14aa..5f9a069 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,15 +82,13 @@ 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 cc4556e..43a082f 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,58 +356,56 @@ 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, - ), + 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, ), - if (isSelected) - const Icon( - Icons.check, - color: AppColors.primary, - size: 20, - ), - ], - ), + ], ), - ); - }, - ), + ), + ); + }, + ), ), ], ), 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 99b0f5f..a7a3d42 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,15 +109,13 @@ 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_lovestyle/profile_lovestyle_page.dart b/lib/sign_up/presentation/pages/profile_lovestyle/profile_lovestyle_page.dart index 0a9a834..0ed62f7 100644 --- a/lib/sign_up/presentation/pages/profile_lovestyle/profile_lovestyle_page.dart +++ b/lib/sign_up/presentation/pages/profile_lovestyle/profile_lovestyle_page.dart @@ -161,7 +161,6 @@ class _ProfileLoveStylePageState extends State { }, ), ), - SizedBox(height: AppGaps.gap32), Text("๊ฐˆ๋“ฑ ํ•ด๊ฒฐ", style: AppTypography.subtitle1), SizedBox(height: AppGaps.gap12), diff --git a/pubspec.lock b/pubspec.lock index 3f6ba92..328c584 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f url: "https://pub.dev" source: hosted - version: "80.0.0" + version: "82.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + sha256: f4c21c94eb4623b183c1014a470196b3910701bea9b926e6c91270d756e6fc60 url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.4.1" args: dependency: transitive description: @@ -266,10 +266,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -280,6 +280,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" glob: dependency: transitive description: @@ -324,10 +332,10 @@ packages: dependency: "direct main" description: name: json_serializable - sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c url: "https://pub.dev" source: hosted - version: "6.9.4" + version: "6.9.5" kakao_flutter_sdk_auth: dependency: transitive description: @@ -540,10 +548,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -584,6 +592,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + sha256: "8bd875c8e8748272749eb6d25b896f768e7e9d60988446d543fe85a37a2392b8" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + sign_in_with_apple_platform_interface: + dependency: transitive + description: + name: sign_in_with_apple_platform_interface + sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + sign_in_with_apple_web: + dependency: transitive + description: + name: sign_in_with_apple_web + sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index d867a8a..8cd355b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,9 +41,11 @@ dependencies: firebase_auth: ^5.5.2 firebase_core: ^3.13.0 http: ^1.3.0 + fluttertoast: ^8.2.12 kakao_flutter_sdk_user: ^1.9.7+3 flutter_dotenv: ^5.2.1 screen_protector: ^1.4.2+1 + sign_in_with_apple: ^7.0.1 dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart index 7f18f08..3855f07 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -11,20 +11,20 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:code_l/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); + // testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // // Build our app and trigger a frame. + // await tester.pumpWidget(const MyApp()); + // + // // Verify that our counter starts at 0. + // expect(find.text('0'), findsOneWidget); + // expect(find.text('1'), findsNothing); + // + // // Tap the '+' icon and trigger a frame. + // await tester.tap(find.byIcon(Icons.add)); + // await tester.pump(); + // + // // Verify that our counter has incremented. + // expect(find.text('0'), findsNothing); + // expect(find.text('1'), findsOneWidget); + // }); }