From 4f02b03764726f570a5b42d5d825c83f1749579e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Mon, 7 Apr 2025 19:40:06 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20firebase=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - firebase.json, lib/firebase_options.dart, ios/Runner/GoogleService-Info.plist 추가 - firebase android, ios 설정 추가 --- firebase.json | 1 + ios/Runner/GoogleService-Info.plist | 30 +++++++++++++ lib/firebase_options.dart | 68 +++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 firebase.json create mode 100644 ios/Runner/GoogleService-Info.plist create mode 100644 lib/firebase_options.dart diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..b278400 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"code-l-22481","appId":"1:404116276033:android:ad49a4f5c03d3820b617c0","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"code-l-22481","appId":"1:404116276033:ios:7abc2a9288fcd67eb617c0","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"code-l-22481","configurations":{"android":"1:404116276033:android:ad49a4f5c03d3820b617c0","ios":"1:404116276033:ios:7abc2a9288fcd67eb617c0"}}}}}} \ No newline at end of file diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..c633ffc --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyA33-9IbpgZW-NvXi-38-M2Bn717ZpaKow + GCM_SENDER_ID + 404116276033 + PLIST_VERSION + 1 + BUNDLE_ID + com.codel.codel + PROJECT_ID + code-l-22481 + STORAGE_BUCKET + code-l-22481.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:404116276033:ios:7abc2a9288fcd67eb617c0 + + \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..35b5c1b --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,68 @@ +// 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: 'AIzaSyD87R2tz_q6RqcOXVwyLXnpF_4dLr7kpqQ', + appId: '1:404116276033:android:ad49a4f5c03d3820b617c0', + messagingSenderId: '404116276033', + projectId: 'code-l-22481', + storageBucket: 'code-l-22481.firebasestorage.app', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyA33-9IbpgZW-NvXi-38-M2Bn717ZpaKow', + appId: '1:404116276033:ios:7abc2a9288fcd67eb617c0', + messagingSenderId: '404116276033', + projectId: 'code-l-22481', + storageBucket: 'code-l-22481.firebasestorage.app', + iosBundleId: 'com.codel.codel', + ); +} From 6679f45159adf7ce6d29a561ca8f84280dbb2106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Mon, 7 Apr 2025 19:40:31 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9D=B8=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement phone number verification functionality, including sending and verifying SMS codes. Create related repositories, use cases, view models, and UI pages. --- .../repositories/auth_repository_impl.dart | 44 +++++++++++ .../domain/repositories/auth_repository.dart | 5 ++ .../usecases/phone_verificarion_usecase.dart | 20 +++++ .../identity/identity_verification_page.dart | 78 +++++++++++++++++++ .../identity_verification_viewmodel.dart | 77 ++++++++++++++++++ .../pages/identity/providers.dart | 19 +++++ 6 files changed, 243 insertions(+) create mode 100644 lib/auth/data/repositories/auth_repository_impl.dart create mode 100644 lib/auth/domain/repositories/auth_repository.dart create mode 100644 lib/auth/domain/usecases/phone_verificarion_usecase.dart create mode 100644 lib/auth/presentation/pages/identity/identity_verification_page.dart create mode 100644 lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart create mode 100644 lib/auth/presentation/pages/identity/providers.dart 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..4e2e1c9 --- /dev/null +++ b/lib/auth/data/repositories/auth_repository_impl.dart @@ -0,0 +1,44 @@ +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, + verificationCompleted: (PhoneAuthCredential credential) async { + await _auth.signInWithCredential(credential); + }, + verificationFailed: (FirebaseAuthException e) { + log(name: 'AuthRepositoryImpl : verifyPhoneNumber', 'Phone verification failed: ${e.message}'); + }, + codeSent: (String verificationId, int? forceResendingToken) async { + onCodeSent(verificationId); + }, + codeAutoRetrievalTimeout: (String verificationId) { + log(name: 'AuthRepositoryImpl : verifyPhoneNumber', "Code auto retrieval timeout"); + }, + ); + } + + @override + Future signInWithSmsCode(String verificationId, String smsCode) async { + try { + 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}'); + throw Exception('인증번호가 올바르지 않거나 만료되었습니다.'); + } + } +} \ No newline at end of file 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/phone_verificarion_usecase.dart b/lib/auth/domain/usecases/phone_verificarion_usecase.dart new file mode 100644 index 0000000..3ebec8f --- /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/identity/identity_verification_page.dart b/lib/auth/presentation/pages/identity/identity_verification_page.dart new file mode 100644 index 0000000..1752e45 --- /dev/null +++ b/lib/auth/presentation/pages/identity/identity_verification_page.dart @@ -0,0 +1,78 @@ +import 'dart:async'; + +import 'package:code_l/auth/presentation/pages/identity/providers.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../login_page.dart'; + +class PhoneVerificationPage extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final viewModel = ref.watch(phoneVerificationViewModelProvider.notifier); + final state = ref.watch(phoneVerificationViewModelProvider); + + return Scaffold( + appBar: AppBar(title: const Text("전화번호 인증")), + body: Padding( + padding: const EdgeInsets.all(15), + child: Center( + child: Form( + key: viewModel.formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + controller: viewModel.phoneController, + keyboardType: TextInputType.phone, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Input your identity number.', + labelText: 'Phone Number', + ), + ), + const SizedBox(height: 15), + if (!state.codeSent) + ElevatedButton( + onPressed: () => viewModel.sendSmsCode(), + child: const Text("Send SMS Code"), + ), + if (state.codeSent) ...[ + const SizedBox(height: 15), + TextFormField( + controller: viewModel.smsCodeController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Input your SMS code.', + labelText: 'SMS Code', + ), + ), + const SizedBox(height: 15), + ElevatedButton( + onPressed: + () async => { + viewModel.verifyCode(() { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const LoginPage(), + ), + ); + }), + }, + child: const Text("Verify"), + ), + ], + ], + ), + ), + ), + ), + ); + } + + const PhoneVerificationPage({super.key}); +} 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..acf231c --- /dev/null +++ b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart @@ -0,0 +1,77 @@ +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', 'Code sent'); + } + } + + 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..d965858 --- /dev/null +++ b/lib/auth/presentation/pages/identity/providers.dart @@ -0,0 +1,19 @@ +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(); +}); From 656611873cc073d63f2daaa9c727f4ea98e83b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Mon, 7 Apr 2025 19:40:49 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20firebase=20=EC=97=B0=EB=8F=99=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20UI=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pubspec.lock : flutter_riverpod, fluttertoast 패키지 추가 - pubspec.yaml: firebase 관련 패키지 및 flutter_riverpod, fluttertoast, svg 에셋 추가 - main.dart : firebase 초기화 및 riverpod 설정, 로그인 페이지 진입 - android, ios : firebase 연동 - test/widget_test.dart : Counter 테스트 제거 - lib/auth : auth 관련 폴더 추가 - lib/auth/presentation/pages/login_page.dart : 로그인 페이지 UI 추가 - lib/auth/presentation/pages/identity/identity_verification_page.dart : 전화번호 인증 페이지 UI 추가 --- android/app/build.gradle.kts | 4 + android/build.gradle.kts | 11 ++ android/settings.gradle.kts | 3 + ios/Runner.xcodeproj/project.pbxproj | 4 + lib/auth/data/repositories/.gitkeep | 0 lib/auth/domain/repositories/.gitkeep | 0 lib/auth/domain/usecases/.gitkeep | 0 lib/auth/presentation/pages/login_page.dart | 9 +- lib/main.dart | 125 +++----------------- pubspec.lock | 16 +++ pubspec.yaml | 12 +- test/widget_test.dart | 32 ++--- 12 files changed, 89 insertions(+), 127 deletions(-) delete mode 100644 lib/auth/data/repositories/.gitkeep delete mode 100644 lib/auth/domain/repositories/.gitkeep delete mode 100644 lib/auth/domain/usecases/.gitkeep diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index dbb089e..178c533 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,5 +1,8 @@ plugins { id("com.android.application") + // START: FlutterFire Configuration + id("com.google.gms.google-services") + // END: FlutterFire Configuration id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") @@ -42,3 +45,4 @@ android { flutter { source = "../.." } + diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 89176ef..6d09106 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -19,3 +19,14 @@ subprojects { tasks.register("clean") { delete(rootProject.layout.buildDirectory) } + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.google.gms:google-services:4.3.15") // ✅ 괄호 주의 + } +} + diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index a439442..9e2d35c 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,6 +19,9 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false + // START: FlutterFire Configuration + id("com.google.gms.google-services") version("4.3.15") apply false + // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "1.8.22" apply false } diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ab5cfd0..b959d56 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + A0A458BCBF938DC81FE39AFC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DC3BCFB780DA748BC4DB8CCA /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -55,6 +56,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 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 = ""; }; + DC3BCFB780DA748BC4DB8CCA /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,6 +96,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + DC3BCFB780DA748BC4DB8CCA /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -216,6 +219,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + A0A458BCBF938DC81FE39AFC /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/lib/auth/data/repositories/.gitkeep b/lib/auth/data/repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 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/usecases/.gitkeep b/lib/auth/domain/usecases/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/auth/presentation/pages/login_page.dart b/lib/auth/presentation/pages/login_page.dart index de74bc1..b14e4bb 100644 --- a/lib/auth/presentation/pages/login_page.dart +++ b/lib/auth/presentation/pages/login_page.dart @@ -1,3 +1,4 @@ +import 'package:code_l/auth/presentation/pages/identity/identity_verification_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -83,7 +84,13 @@ class LoginPage extends StatelessWidget { backgroundColor: AppColors.white, side: const BorderSide(color: AppColors.black), ), - onPressed: () {}, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const PhoneVerificationPage()), + ); + }, child: Padding( padding: const EdgeInsets.all(AppPaddings.padding16), child: Row( diff --git a/lib/main.dart b/lib/main.dart index 7b7f5b6..0a9cce7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,35 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'auth/data/repositories/auth_repository_impl.dart'; +import 'auth/presentation/pages/login_page.dart'; +import 'firebase_options.dart'; import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + runApp( + const ProviderScope( + child: MyApp(), + ), + ); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', + debugShowCheckedModeBanner: false, theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + home: const LoginPage(), ); } } diff --git a/pubspec.lock b/pubspec.lock index c216c09..ca8f440 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -222,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" flutter_svg: dependency: "direct main" description: @@ -240,6 +248,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: diff --git a/pubspec.yaml b/pubspec.yaml index 91bf824..e3c9167 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,8 @@ dependencies: firebase_auth: ^5.5.2 firebase_core: ^3.13.0 http: ^1.3.0 + flutter_riverpod: ^2.6.1 + fluttertoast: ^8.2.12 dev_dependencies: flutter_test: @@ -64,10 +66,12 @@ flutter: # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/icons/apple_logo.svg + - assets/icons/kakao_logo.svg + - assets/icons/logo_with_image.svg + - assets/icons/logo.svg + - assets/icons/logo_image.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images 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); + // }); } From bbfb2f4880315f0c89036bd20b6ff3018469319d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Mon, 7 Apr 2025 20:05:54 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20firebase=20=EC=97=B0=EB=8F=99=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20UI=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pubspec.lock : flutter_riverpod, fluttertoast 패키지 추가 - pubspec.yaml: firebase 관련 패키지 및 flutter_riverpod, fluttertoast, svg 에셋 추가 - main.dart : firebase 초기화 및 riverpod 설정, 로그인 페이지 진입 - android, ios : firebase 연동 - test/widget_test.dart : Counter 테스트 제거 - lib/auth : auth 관련 폴더 추가 - lib/auth/presentation/pages/login_page.dart : 로그인 페이지 UI 추가 - lib/auth/presentation/pages/identity/identity_verification_page.dart : 전화번호 인증 페이지 UI 추가 --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 093193b..9ffe742 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,7 @@ flutter: - assets/icons/logo_with_image.svg - assets/icons/logo.svg - assets/icons/logo_image.svg + - .env # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images @@ -100,5 +101,4 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/to/font-from-package - assets: - - .env + From 4d4c35f67379730b285510df1b6a2e390912cbb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Sat, 12 Apr 2025 00:47:03 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=EC=A0=84=ED=99=94=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ndk 버전 수정 --- android/app/build.gradle.kts | 2 +- firebase.json | 2 +- ios/Podfile.lock | 12 +- ios/Runner.xcodeproj/project.pbxproj | 78 ++++++++---- ios/Runner/GoogleService-Info.plist | 16 ++- ios/Runner/Info.plist | 1 + ios/Runner/RunnerDebug.entitlements | 8 ++ .../repositories/auth_repository_impl.dart | 8 +- .../identity/identity_verification_page.dart | 9 +- .../identity_verification_viewmodel.dart | 2 +- .../presentation/pages/login/login_page.dart | 3 +- lib/auth/presentation/pages/login_page.dart | 114 ------------------ lib/firebase_options.dart | 25 ++-- lib/main.dart | 6 +- pubspec.lock | 6 +- 15 files changed, 114 insertions(+), 178 deletions(-) create mode 100644 ios/Runner/RunnerDebug.entitlements delete mode 100644 lib/auth/presentation/pages/login_page.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 871a3a4..7ec17aa 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -18,7 +18,7 @@ val properties = android { namespace = "com.codel.code_l" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "27.0.12077973" compileOptions { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/firebase.json b/firebase.json index b278400..4fdea37 100644 --- a/firebase.json +++ b/firebase.json @@ -1 +1 @@ -{"flutter":{"platforms":{"android":{"default":{"projectId":"code-l-22481","appId":"1:404116276033:android:ad49a4f5c03d3820b617c0","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"code-l-22481","appId":"1:404116276033:ios:7abc2a9288fcd67eb617c0","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"code-l-22481","configurations":{"android":"1:404116276033:android:ad49a4f5c03d3820b617c0","ios":"1:404116276033:ios:7abc2a9288fcd67eb617c0"}}}}}} \ No newline at end of file +{"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 index f3a2b75..718b7a2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -31,6 +31,8 @@ PODS: - 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 @@ -64,6 +66,7 @@ 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`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -87,6 +90,8 @@ EXTERNAL SOURCES: :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" shared_preferences_foundation: @@ -94,8 +99,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_auth: e37065f3f80ff90580c13ad0e5a48e3bb8d2ad77 - firebase_core: 432718558359a8c08762151b5f49bb0f093eb6e0 + firebase_auth: ad485af7f854ba5f86634f81725b22b37f317883 + firebase_core: 2d4534e7b489907dcede540c835b48981d890943 FirebaseAppCheckInterop: f23709c9ce92d810aa53ff4ce12ad3e666a3c7be FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32 FirebaseAuthInterop: ac22ed402c2f4e3a8c63ebd3278af9a06073c1be @@ -103,11 +108,12 @@ SPEC CHECKSUMS: FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GTMSessionFetcher: 75b671f9e551e4c49153d4c4f8659ef4f559b970 kakao_flutter_sdk_common: 3dc8492c202af7853585d151490b1c5c6b7576cb RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 PODFILE CHECKSUM: f8c2dcdfb50bb67645580d28a6bf814fca30bdec diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index b4db6e9..43a5f10 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -7,11 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - 104F1D23AF21CC7B53B7C8A7 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B955AE8AC425CE69815F5900 /* Pods_RunnerTests.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 4800CB4AEF36C4DE31C99FCB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05ACCC454FC4BA8FB35863DE /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7A413FD1528188F5DA06B149 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DCB1977CE42170B2C2EE457 /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -43,9 +44,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 05ACCC454FC4BA8FB35863DE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 11EC9BDD1FDC47F03500C4D3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1DCB1977CE42170B2C2EE457 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -61,34 +64,44 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 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 = ""; }; - DC3BCFB780DA748BC4DB8CCA /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-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 = ""; }; - B955AE8AC425CE69815F5900 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AF9C92D62DA94A7E00F5E688 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 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 = ""; }; + DC3BCFB780DA748BC4DB8CCA /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; 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 */ /* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { + 35BF1A915912C167036F507A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A95C5CF9F58E465261D3D523 /* Pods_Runner.framework in Frameworks */, + 4800CB4AEF36C4DE31C99FCB /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - FC480051F976BE30F4AC21A4 /* Frameworks */ = { + 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 104F1D23AF21CC7B53B7C8A7 /* Pods_RunnerTests.framework in Frameworks */, + A95C5CF9F58E465261D3D523 /* Pods_Runner.framework in Frameworks */, + 7A413FD1528188F5DA06B149 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0E18AD61C614C88D143D5890 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DCB1977CE42170B2C2EE457 /* Pods_Runner.framework */, + 05ACCC454FC4BA8FB35863DE /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -107,7 +120,6 @@ 70F87F2E741779C30B5DB63A /* Pods-RunnerTests.release.xcconfig */, A0C3D7BA37E297BF670F5764 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -131,7 +143,8 @@ 331C8082294A63A400263BE5 /* RunnerTests */, DC3BCFB780DA748BC4DB8CCA /* GoogleService-Info.plist */, 85511697A5F0AF89913BD830 /* Pods */, - B92EEAB882964D6C972ADB60 /* Frameworks */, + 0E18AD61C614C88D143D5890 /* Frameworks */, + AF9C92D52DA9486100F5E688 /* Recovered References */, ); sourceTree = ""; }; @@ -147,6 +160,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + AF9C92D62DA94A7E00F5E688 /* RunnerDebug.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -159,6 +173,14 @@ path = Runner; sourceTree = ""; }; + AF9C92D52DA9486100F5E688 /* Recovered References */ = { + isa = PBXGroup; + children = ( + E6228E8708160B7145026049 /* Pods_Runner.framework */, + ); + name = "Recovered References"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -166,8 +188,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + B6FF98C983BB444DC766B8BE /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 35BF1A915912C167036F507A /* Frameworks */, ); buildRules = ( ); @@ -279,7 +303,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 5D2C3991EF6A09801BF051B0 /* [CP] Check Pods Manifest.lock */ = { + 90FA8763DD4A6906B4030C96 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -294,49 +318,49 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 90FA8763DD4A6906B4030C96 /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( ); + name = "Run Script"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + B6FF98C983BB444DC766B8BE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Run Script"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; F317B067B108CCC9832E933A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -645,7 +669,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = LRG496B3VQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist index c633ffc..f6353c7 100644 --- a/ios/Runner/GoogleService-Info.plist +++ b/ios/Runner/GoogleService-Info.plist @@ -2,18 +2,24 @@ + 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 - AIzaSyA33-9IbpgZW-NvXi-38-M2Bn717ZpaKow + AIzaSyA6aPcY0Qv4eCO4Q_KLxF0KMDQ9M9wdZlw GCM_SENDER_ID - 404116276033 + 971644319685 PLIST_VERSION 1 BUNDLE_ID com.codel.codel PROJECT_ID - code-l-22481 + code-l-b109b STORAGE_BUCKET - code-l-22481.firebasestorage.app + code-l-b109b.firebasestorage.app IS_ADS_ENABLED IS_ANALYTICS_ENABLED @@ -25,6 +31,6 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:404116276033:ios:7abc2a9288fcd67eb617c0 + 1:971644319685:ios:bde2639222525193b53dac \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index b2fbcc6..5921c81 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -8,6 +8,7 @@ CFBundleURLSchemes kakaoe17acdc68adf7250d61dd15f8ee185c1 + app-1-971644319685-ios-bde2639222525193b53dac diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/ios/Runner/RunnerDebug.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/lib/auth/data/repositories/auth_repository_impl.dart b/lib/auth/data/repositories/auth_repository_impl.dart index 4e2e1c9..9a06beb 100644 --- a/lib/auth/data/repositories/auth_repository_impl.dart +++ b/lib/auth/data/repositories/auth_repository_impl.dart @@ -12,17 +12,20 @@ class AuthRepositoryImpl implements AuthRepository { 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', "Code auto retrieval timeout"); + log(name: 'AuthRepositoryImpl : verifyPhoneNumber', "인증 번호 만료"); }, ); } @@ -30,6 +33,7 @@ class AuthRepositoryImpl implements AuthRepository { @override Future signInWithSmsCode(String verificationId, String smsCode) async { try { + log(name: 'AuthRepositoryImpl : signInWithSmsCode', "$verificationId - $smsCode"); final credential = PhoneAuthProvider.credential( verificationId: verificationId, smsCode: smsCode, @@ -38,7 +42,7 @@ class AuthRepositoryImpl implements AuthRepository { log(name: 'AuthRepositoryImpl : signInWithSmsCode', 'Sign in successful'); } on FirebaseAuthException catch (e) { log(name: 'AuthRepositoryImpl : signInWithSmsCode', 'FirebaseAuthException: ${e.code} - ${e.message}'); - throw Exception('인증번호가 올바르지 않거나 만료되었습니다.'); + rethrow; } } } \ No newline at end of file diff --git a/lib/auth/presentation/pages/identity/identity_verification_page.dart b/lib/auth/presentation/pages/identity/identity_verification_page.dart index 1752e45..11a8082 100644 --- a/lib/auth/presentation/pages/identity/identity_verification_page.dart +++ b/lib/auth/presentation/pages/identity/identity_verification_page.dart @@ -1,12 +1,9 @@ import 'dart:async'; import 'package:code_l/auth/presentation/pages/identity/providers.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:fluttertoast/fluttertoast.dart'; - -import '../login_page.dart'; +import '../login/login_page.dart'; class PhoneVerificationPage extends ConsumerWidget { @override @@ -14,6 +11,7 @@ class PhoneVerificationPage extends ConsumerWidget { final viewModel = ref.watch(phoneVerificationViewModelProvider.notifier); final state = ref.watch(phoneVerificationViewModelProvider); + return Scaffold( appBar: AppBar(title: const Text("전화번호 인증")), body: Padding( @@ -29,8 +27,7 @@ class PhoneVerificationPage extends ConsumerWidget { keyboardType: TextInputType.phone, decoration: const InputDecoration( border: OutlineInputBorder(), - hintText: 'Input your identity number.', - labelText: 'Phone Number', + hintText: 'Input your phone number.', ), ), const SizedBox(height: 15), diff --git a/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart index acf231c..2f2e05b 100644 --- a/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart +++ b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart @@ -50,7 +50,7 @@ class PhoneVerificationViewModel extends Notifier { ); }, ); - log(name: 'PhoneVerificationViewModel : sendSmsCode', 'Code sent'); + log(name: 'PhoneVerificationViewModel : sendSmsCode', "+82${phoneController.text.substring(1)}"); } } diff --git a/lib/auth/presentation/pages/login/login_page.dart b/lib/auth/presentation/pages/login/login_page.dart index 61b500a..b864a3a 100644 --- a/lib/auth/presentation/pages/login/login_page.dart +++ b/lib/auth/presentation/pages/login/login_page.dart @@ -1,3 +1,4 @@ +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'; @@ -91,7 +92,7 @@ class LoginPage extends ConsumerWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => const LoginPage(), + builder: (context) => const PhoneVerificationPage(), ), ); }, diff --git a/lib/auth/presentation/pages/login_page.dart b/lib/auth/presentation/pages/login_page.dart deleted file mode 100644 index b14e4bb..0000000 --- a/lib/auth/presentation/pages/login_page.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:code_l/auth/presentation/pages/identity/identity_verification_page.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -import '../../../core/utills/app_colors.dart'; -import '../../../core/utills/app_paddings.dart'; -import '../../../core/utills/app_typography.dart'; - -class LoginPage extends StatelessWidget { - const LoginPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Flexible( - flex: 4, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SvgPicture.asset('assets/icons/logo_with_image.svg'), - ], - ), - ), - Flexible( - flex: 2, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "코드가 맞는 우리만의", - style: AppTypography.body1, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "공간에서 시작되는 ", - style: AppTypography.body1, - ), - Text("진짜 인연", style: AppTypography.subtitle1) - ], - ), - ], - ), - ), - Flexible( - flex: 2, - child: Column(children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: AppPaddings.padding16, - vertical: AppPaddings.padding8), - child: OutlinedButton( - style: OutlinedButton.styleFrom( - backgroundColor: AppColors.kakao, - side: const BorderSide(color: AppColors.kakao), - ), - onPressed: () {}, - child: Padding( - padding: const EdgeInsets.all(AppPaddings.padding16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset('assets/icons/kakao_logo.svg'), - const SizedBox(width: 10), - Text( - "카카오로 3초만에 로그인", - style: AppTypography.body1 - .copyWith(color: AppColors.black), - ) - ]), - )), - ), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: AppPaddings.padding16), - child: OutlinedButton( - style: OutlinedButton.styleFrom( - backgroundColor: AppColors.white, - side: const BorderSide(color: AppColors.black), - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const PhoneVerificationPage()), - ); - }, - child: Padding( - padding: const EdgeInsets.all(AppPaddings.padding16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset('assets/icons/apple_logo.svg'), - const SizedBox(width: 10), - Text( - "Apple로 계속하기", - style: AppTypography.body1 - .copyWith(color: AppColors.black), - ) - ]), - )), - ) - ]), - ) - ], - ))); - } -} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index 35b5c1b..e783b66 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -50,19 +50,22 @@ class DefaultFirebaseOptions { } static const FirebaseOptions android = FirebaseOptions( - apiKey: 'AIzaSyD87R2tz_q6RqcOXVwyLXnpF_4dLr7kpqQ', - appId: '1:404116276033:android:ad49a4f5c03d3820b617c0', - messagingSenderId: '404116276033', - projectId: 'code-l-22481', - storageBucket: 'code-l-22481.firebasestorage.app', + 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: 'AIzaSyA33-9IbpgZW-NvXi-38-M2Bn717ZpaKow', - appId: '1:404116276033:ios:7abc2a9288fcd67eb617c0', - messagingSenderId: '404116276033', - projectId: 'code-l-22481', - storageBucket: 'code-l-22481.firebasestorage.app', + 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', ); -} + +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 4a4bddc..5446b67 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ +import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'auth/data/repositories/auth_repository_impl.dart'; -import 'auth/presentation/pages/login_page.dart'; import 'firebase_options.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,14 +9,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; import 'package:code_l/auth/presentation/pages/login/login_page.dart'; -main() async { - WidgetsFlutterBinding.ensureInitialized(); - void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + runApp( const ProviderScope( child: MyApp(), diff --git a/pubspec.lock b/pubspec.lock index db46483..32017b4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -316,10 +316,10 @@ packages: dependency: transitive description: name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.6.7" json_annotation: dependency: transitive description: @@ -521,7 +521,7 @@ packages: source: hosted version: "1.5.0" riverpod: - dependency: transitive + dependency: "direct main" description: name: riverpod sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" From 59ab3c0167b3f20934d7ff72fa1215fd963d0325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Sat, 12 Apr 2025 00:49:06 +0900 Subject: [PATCH 06/12] =?UTF-8?q?chore:=20pr=20=ED=85=9C=ED=94=8C=EB=A6=BF?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/PULL_REQUEST_TEMPLATE.md | 2 ++ 1 file changed, 2 insertions(+) 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 # ## 💬리뷰 요구사항 +## 다음 작업 + From 63e0faf8210a21defe721ce1702881c0ff0550b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Sat, 12 Apr 2025 19:01:44 +0900 Subject: [PATCH 07/12] =?UTF-8?q?chore:=20pr=20=ED=85=9C=ED=94=8C=EB=A6=BF?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/auth/data/repositories/auth_repository_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/auth/data/repositories/auth_repository_impl.dart b/lib/auth/data/repositories/auth_repository_impl.dart index 9a06beb..75d09bf 100644 --- a/lib/auth/data/repositories/auth_repository_impl.dart +++ b/lib/auth/data/repositories/auth_repository_impl.dart @@ -14,7 +14,7 @@ class AuthRepositoryImpl implements AuthRepository { phoneNumber: phoneNumber, timeout: const Duration(seconds: 60), verificationCompleted: (PhoneAuthCredential credential) async { - log(name: 'AuthRepositoryImpl : verifyPhoneNumber', '핸드폰 번호 인증 완료'); + log(name: 'AuthRepositoryImpl : verifyPhoneNumber', '핸드폰 자동 인증 완료'); await _auth.signInWithCredential(credential); }, verificationFailed: (FirebaseAuthException e) { From 2e7a8b5d2416633a248ca9523c6b024eb7b0c173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Mon, 21 Apr 2025 20:23:15 +0900 Subject: [PATCH 08/12] =?UTF-8?q?refactor:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle.kts | 4 --- android/settings.gradle.kts | 3 -- ios/Podfile.lock | 12 ++++++- ios/Runner.xcodeproj/project.pbxproj | 3 ++ ios/Runner/Info.plist | 49 ++++++++++++++++++++-------- lib/main.dart | 2 +- pubspec.lock | 26 +++++++-------- 7 files changed, 63 insertions(+), 36 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e7789c8..f958fb1 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -2,12 +2,8 @@ import java.util.Properties plugins { id("com.android.application") - // START: FlutterFire Configuration - id("com.google.gms.google-services") - // END: FlutterFire Configuration 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") } diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 19fa2f1..6d4ae12 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,9 +19,6 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false - // START: FlutterFire Configuration - id("com.google.gms.google-services") version("4.3.15") apply false - // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "1.8.22" apply false id("com.google.gms.google-services") version "4.4.2" apply false } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 718b7a2..12bf7dc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -58,6 +58,10 @@ PODS: - 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 @@ -68,6 +72,7 @@ DEPENDENCIES: - 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`) SPEC REPOS: @@ -82,6 +87,7 @@ SPEC REPOS: - GoogleUtilities - GTMSessionFetcher - RecaptchaInterop + - ScreenProtectorKit EXTERNAL SOURCES: firebase_auth: @@ -94,6 +100,8 @@ EXTERNAL SOURCES: :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" @@ -111,8 +119,10 @@ SPEC CHECKSUMS: fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GTMSessionFetcher: 75b671f9e551e4c49153d4c4f8659ef4f559b970 - kakao_flutter_sdk_common: 3dc8492c202af7853585d151490b1c5c6b7576cb + kakao_flutter_sdk_common: 600d55b532da0bd37268a529e1add49302477710 RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba + screen_protector: 3d90d44ac886b25335aebd93230b454aef45794a + ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 PODFILE CHECKSUM: f8c2dcdfb50bb67645580d28a6bf814fca30bdec diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d7b0228..1362d24 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ 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 = ""; }; 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 +146,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + AFD21D852DB512DE00452D52 /* RunnerDebug.entitlements */, E1FBF0C02DAE7C460017C51F /* GoogleService-Info.plist */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -660,6 +662,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)"; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 5921c81..f425cd0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,16 +2,8 @@ -CFBundleURLTypes - - - CFBundleURLSchemes - - kakaoe17acdc68adf7250d61dd15f8ee185c1 - app-1-971644319685-ios-bde2639222525193b53dac - - - + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -30,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 @@ -51,9 +76,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/lib/main.dart b/lib/main.dart index 335e24e..a541911 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,7 +26,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Kakao Login Demo', - home: const TermsAndConditionPage(), + home: const LoginPage(), theme: ThemeData(primarySwatch: Colors.blue, scaffoldBackgroundColor: Colors.white, ), diff --git a/pubspec.lock b/pubspec.lock index 71e0e59..03d7c2e 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 @@ -316,10 +316,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.2" json_annotation: dependency: transitive description: @@ -332,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: @@ -521,7 +521,7 @@ packages: source: hosted version: "1.5.0" riverpod: - dependency: "direct main" + dependency: transitive description: name: riverpod sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" @@ -548,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: From f13d936ece39025b7dadb9ed56d431114baeeb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=ED=98=95=EC=84=9D?= Date: Tue, 22 Apr 2025 23:21:46 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=EC=A0=84=ED=99=94=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../identity/identity_verification_page.dart | 163 ++++++++++++------ .../identity_verification_app_bar.dart | 38 ++++ 2 files changed, 151 insertions(+), 50 deletions(-) create mode 100644 lib/auth/presentation/pages/identity/widgets/identity_verification_app_bar.dart diff --git a/lib/auth/presentation/pages/identity/identity_verification_page.dart b/lib/auth/presentation/pages/identity/identity_verification_page.dart index 11a8082..7e83b13 100644 --- a/lib/auth/presentation/pages/identity/identity_verification_page.dart +++ b/lib/auth/presentation/pages/identity/identity_verification_page.dart @@ -1,75 +1,138 @@ -import 'dart:async'; - 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: AppBar(title: const Text("전화번호 인증")), + 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(15), - child: Center( - child: Form( - key: viewModel.formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - controller: viewModel.phoneController, - keyboardType: TextInputType.phone, - decoration: const InputDecoration( - border: OutlineInputBorder(), - hintText: 'Input your phone number.', - ), - ), - const SizedBox(height: 15), - if (!state.codeSent) - ElevatedButton( - onPressed: () => viewModel.sendSmsCode(), - child: const Text("Send SMS Code"), + 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, + ), ), - if (state.codeSent) ...[ - const SizedBox(height: 15), + SizedBox(height: AppGaps.gap40), + SizedBox(height: AppGaps.gap40), TextFormField( - controller: viewModel.smsCodeController, - keyboardType: TextInputType.number, - decoration: const InputDecoration( - border: OutlineInputBorder(), - hintText: 'Input your SMS code.', - labelText: 'SMS Code', + 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), - ElevatedButton( - onPressed: - () async => { - viewModel.verifyCode(() { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const LoginPage(), - ), - ); - }), - }, - child: const Text("Verify"), - ), + + 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; + }, + ), + ], ], - ], + ), ), - ), + ], ), ), ); } - - const PhoneVerificationPage({super.key}); } 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); + }, + ), + ], + ), + ), + ); + } +} From 80d4e4fb5521d5198a9b374eaad9ac6d6df915e8 Mon Sep 17 00:00:00 2001 From: kkosang Date: Sat, 26 Apr 2025 15:47:55 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 12 +++++------ ...t => default_phone_number_repository.dart} | 21 +++++++++---------- ...tory.dart => phone_number_repository.dart} | 2 +- ...=> phone_number_verification_usecase.dart} | 8 +++---- .../identity_verification_viewmodel.dart | 4 ++-- .../pages/identity/providers.dart | 14 ++++++------- 6 files changed, 30 insertions(+), 31 deletions(-) rename lib/auth/data/repositories/{auth_repository_impl.dart => default_phone_number_repository.dart} (56%) rename lib/auth/domain/repositories/{auth_repository.dart => phone_number_repository.dart} (80%) rename lib/auth/domain/usecases/{phone_verificarion_usecase.dart => phone_number_verification_usecase.dart} (66%) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 12bf7dc..294b470 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -107,8 +107,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_auth: ad485af7f854ba5f86634f81725b22b37f317883 - firebase_core: 2d4534e7b489907dcede540c835b48981d890943 + firebase_auth: e37065f3f80ff90580c13ad0e5a48e3bb8d2ad77 + firebase_core: 432718558359a8c08762151b5f49bb0f093eb6e0 FirebaseAppCheckInterop: f23709c9ce92d810aa53ff4ce12ad3e666a3c7be FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32 FirebaseAuthInterop: ac22ed402c2f4e3a8c63ebd3278af9a06073c1be @@ -116,14 +116,14 @@ SPEC CHECKSUMS: FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GTMSessionFetcher: 75b671f9e551e4c49153d4c4f8659ef4f559b970 - kakao_flutter_sdk_common: 600d55b532da0bd37268a529e1add49302477710 + kakao_flutter_sdk_common: 3dc8492c202af7853585d151490b1c5c6b7576cb RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba - screen_protector: 3d90d44ac886b25335aebd93230b454aef45794a + screen_protector: 6f92086bd2f2f4b54f54913289b9d1310610140b ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4 - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 PODFILE CHECKSUM: f8c2dcdfb50bb67645580d28a6bf814fca30bdec diff --git a/lib/auth/data/repositories/auth_repository_impl.dart b/lib/auth/data/repositories/default_phone_number_repository.dart similarity index 56% rename from lib/auth/data/repositories/auth_repository_impl.dart rename to lib/auth/data/repositories/default_phone_number_repository.dart index 75d09bf..b3c6dd5 100644 --- a/lib/auth/data/repositories/auth_repository_impl.dart +++ b/lib/auth/data/repositories/default_phone_number_repository.dart @@ -2,9 +2,9 @@ import 'dart:developer'; import 'package:firebase_auth/firebase_auth.dart'; -import '../../domain/repositories/auth_repository.dart'; +import '../../domain/repositories/phone_number_repository.dart'; -class AuthRepositoryImpl implements AuthRepository { +class DefaultPhoneNumberRepository implements PhoneNumberRepository { final FirebaseAuth _auth = FirebaseAuth.instance; @override @@ -12,20 +12,20 @@ class AuthRepositoryImpl implements AuthRepository { String phoneNumber, Function(String) onCodeSent) async { await _auth.verifyPhoneNumber( phoneNumber: phoneNumber, - timeout: const Duration(seconds: 60), + timeout: const Duration(seconds: 120), verificationCompleted: (PhoneAuthCredential credential) async { - log(name: 'AuthRepositoryImpl : verifyPhoneNumber', '핸드폰 자동 인증 완료'); + log(name: 'DefaultPhoneNumberRepository : verifyPhoneNumber', '핸드폰 자동 인증 완료'); await _auth.signInWithCredential(credential); }, verificationFailed: (FirebaseAuthException e) { - log(name: 'AuthRepositoryImpl : verifyPhoneNumber', 'Phone verification failed: ${e.message}'); + log(name: 'DefaultPhoneNumberRepository : verifyPhoneNumber', '인증 번호 실패: ${e.message}'); }, codeSent: (String verificationId, int? forceResendingToken) async { - log(name: 'AuthRepositoryImpl : verifyPhoneNumber', '인증 번호 전송'); + log(name: 'DefaultPhoneNumberRepository : verifyPhoneNumber', '인증 번호 전송'); onCodeSent(verificationId); }, codeAutoRetrievalTimeout: (String verificationId) { - log(name: 'AuthRepositoryImpl : verifyPhoneNumber', "인증 번호 만료"); + log(name: 'DefaultPhoneNumberRepository : verifyPhoneNumber', "인증 번호 시간 초과"); }, ); } @@ -33,16 +33,15 @@ class AuthRepositoryImpl implements AuthRepository { @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'); + log(name: 'DefaultPhoneNumberRepository : signInWithSmsCode', '로그인 성공'); } on FirebaseAuthException catch (e) { - log(name: 'AuthRepositoryImpl : signInWithSmsCode', 'FirebaseAuthException: ${e.code} - ${e.message}'); + log(name: 'DefaultPhoneNumberRepository : signInWithSmsCode', '로그인 실패: ${e.code} - ${e.message}'); rethrow; } } -} \ No newline at end of file +} diff --git a/lib/auth/domain/repositories/auth_repository.dart b/lib/auth/domain/repositories/phone_number_repository.dart similarity index 80% rename from lib/auth/domain/repositories/auth_repository.dart rename to lib/auth/domain/repositories/phone_number_repository.dart index 3bb06e6..52f9eac 100644 --- a/lib/auth/domain/repositories/auth_repository.dart +++ b/lib/auth/domain/repositories/phone_number_repository.dart @@ -1,4 +1,4 @@ -abstract class AuthRepository { +abstract class PhoneNumberRepository { Future verifyPhoneNumber( String phoneNumber, Function(String) onCodeSent); Future signInWithSmsCode(String verificationId, String smsCode); diff --git a/lib/auth/domain/usecases/phone_verificarion_usecase.dart b/lib/auth/domain/usecases/phone_number_verification_usecase.dart similarity index 66% rename from lib/auth/domain/usecases/phone_verificarion_usecase.dart rename to lib/auth/domain/usecases/phone_number_verification_usecase.dart index 3ebec8f..399adaf 100644 --- a/lib/auth/domain/usecases/phone_verificarion_usecase.dart +++ b/lib/auth/domain/usecases/phone_number_verification_usecase.dart @@ -1,11 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../repositories/auth_repository.dart'; +import '../repositories/phone_number_repository.dart'; -class VerifyPhoneNumberUseCase { - final AuthRepository _authRepository; +class PhoneNumberVerificationUseCase { + final PhoneNumberRepository _authRepository; - VerifyPhoneNumberUseCase(this._authRepository); + PhoneNumberVerificationUseCase(this._authRepository); Future sendCode( String phoneNumber, diff --git a/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart index 2f2e05b..5e54780 100644 --- a/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart +++ b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import '../../../domain/usecases/phone_verificarion_usecase.dart'; +import '../../../domain/usecases/phone_number_verification_usecase.dart'; class PhoneVerificationState { final bool codeSent; @@ -28,7 +28,7 @@ class PhoneVerificationState { } class PhoneVerificationViewModel extends Notifier { - late final VerifyPhoneNumberUseCase _verifyPhoneNumberUseCase; + late final PhoneNumberVerificationUseCase _verifyPhoneNumberUseCase; final formKey = GlobalKey(); final phoneController = TextEditingController(); final smsCodeController = TextEditingController(); diff --git a/lib/auth/presentation/pages/identity/providers.dart b/lib/auth/presentation/pages/identity/providers.dart index d965858..db95967 100644 --- a/lib/auth/presentation/pages/identity/providers.dart +++ b/lib/auth/presentation/pages/identity/providers.dart @@ -1,12 +1,12 @@ 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 '../../../data/repositories/default_phone_number_repository.dart'; +import '../../../domain/repositories/phone_number_repository.dart'; +import '../../../domain/usecases/phone_number_verification_usecase.dart'; import 'identity_verification_viewmodel.dart'; -final verifyPhoneNumberUseCaseProvider = Provider((ref) { - return VerifyPhoneNumberUseCase(ref.read(authRepositoryProvider)); +final verifyPhoneNumberUseCaseProvider = Provider((ref) { + return PhoneNumberVerificationUseCase(ref.read(authRepositoryProvider)); }); final phoneVerificationViewModelProvider = @@ -14,6 +14,6 @@ NotifierProvider( PhoneVerificationViewModel.new, ); -final authRepositoryProvider = Provider((ref) { - return AuthRepositoryImpl(); +final authRepositoryProvider = Provider((ref) { + return DefaultPhoneNumberRepository(); }); From 6644b899dee0c90c343ff5c02cc4fa5a2841f67e Mon Sep 17 00:00:00 2001 From: kkosang Date: Sat, 26 Apr 2025 17:50:33 +0900 Subject: [PATCH 11/12] chore: pub add firestore --- pubspec.lock | 24 ++++++++++++++++++++++++ pubspec.yaml | 1 + 2 files changed, 25 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index 03d7c2e..7a08264 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "89a5e32716794b6a8d0ec1b5dfda988194e92daedaa3f3bed66fa0d0a595252e" + url: "https://pub.dev" + source: hosted + version: "5.6.6" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "9f012844eb59be6827ed97415875c5a29ccacd28bc79bf85b4680738251a33df" + url: "https://pub.dev" + source: hosted + version: "6.6.6" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: b8b754269be0e907acd9ff63ad60f66b84c78d330ca1d7e474f86c9527ddc803 + url: "https://pub.dev" + source: hosted + version: "4.4.6" collection: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index acc051d..bc6f2f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: kakao_flutter_sdk_user: ^1.9.7+3 flutter_dotenv: ^5.2.1 screen_protector: ^1.4.2+1 + cloud_firestore: ^5.6.6 dev_dependencies: flutter_test: From f77965ab5187f67b60116e04569cc50cc760da59 Mon Sep 17 00:00:00 2001 From: kkosang Date: Sat, 26 Apr 2025 17:51:27 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=A4=91=EB=B3=B5=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/identity/phone_number.dart | 24 +++++++++ .../phone_number_verification_usecase.dart | 41 ++++++++++++++-- .../identity_verification_viewmodel.dart | 49 ++++++------------- .../identity/phone_verification_state.dart | 20 ++++++++ .../pages/identity/providers.dart | 1 + 5 files changed, 99 insertions(+), 36 deletions(-) create mode 100644 lib/auth/domain/model/identity/phone_number.dart create mode 100644 lib/auth/presentation/pages/identity/phone_verification_state.dart diff --git a/lib/auth/domain/model/identity/phone_number.dart b/lib/auth/domain/model/identity/phone_number.dart new file mode 100644 index 0000000..f4a3117 --- /dev/null +++ b/lib/auth/domain/model/identity/phone_number.dart @@ -0,0 +1,24 @@ + +class PhoneNumber { + final String value; + + PhoneNumber._(this.value); + + factory PhoneNumber.fromLocal(String local) { + if (!local.startsWith('0')) { + throw FormatException("로컬 번호는 0으로 시작해야 합니다"); + } + final formatted = "+82${local.substring(1)}"; + return PhoneNumber._(formatted); + } + + factory PhoneNumber.fromInternational(String international) { + if (!international.startsWith('+82')) { + throw FormatException("국제번호 형식이 아닙니다"); + } + return PhoneNumber._(international); + } + + @override + String toString() => value; +} diff --git a/lib/auth/domain/usecases/phone_number_verification_usecase.dart b/lib/auth/domain/usecases/phone_number_verification_usecase.dart index 399adaf..c8c6c47 100644 --- a/lib/auth/domain/usecases/phone_number_verification_usecase.dart +++ b/lib/auth/domain/usecases/phone_number_verification_usecase.dart @@ -1,5 +1,9 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'dart:developer'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +import '../model/identity/phone_number.dart'; import '../repositories/phone_number_repository.dart'; class PhoneNumberVerificationUseCase { @@ -8,13 +12,44 @@ class PhoneNumberVerificationUseCase { PhoneNumberVerificationUseCase(this._authRepository); Future sendCode( - String phoneNumber, + PhoneNumber phoneNumber, Function(String) onCodeSent, ) async { - await _authRepository.verifyPhoneNumber(phoneNumber, onCodeSent); + await _authRepository.verifyPhoneNumber(phoneNumber.value, onCodeSent); } Future verifyCode(String verificationId, String smsCode) async { await _authRepository.signInWithSmsCode(verificationId, smsCode); } + + Future isRegistered(PhoneNumber phoneNumber) async { + final firestore = FirebaseFirestore.instance; + + final querySnapshot = await firestore + .collection('users') + .where('phoneNumber', isEqualTo: phoneNumber.value) + .get(); + + return querySnapshot.docs.isNotEmpty; + } + + Future saveUser() async { + final user = FirebaseAuth.instance.currentUser; + + if (user != null) { + final uid = user.uid; + final phone = PhoneNumber.fromInternational(user.phoneNumber!); + + await FirebaseFirestore.instance + .collection('users') + .doc(uid) + .set({ + 'phoneNumber': phone, + 'createdAt': FieldValue.serverTimestamp(), + }); + log(name: 'PhoneNumberVerificationUseCase : saveUser', 'firestore에 사용자 정보 저장 완료'); + } else { + log(name: 'PhoneNumberVerificationUseCase : saveUser', 'firestore에 사용자 정보 저장 실패'); + } + } } diff --git a/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart index 5e54780..4252b72 100644 --- a/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart +++ b/lib/auth/presentation/pages/identity/identity_verification_viewmodel.dart @@ -1,32 +1,12 @@ -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/model/identity/phone_number.dart'; +import 'phone_verification_state.dart'; import '../../../domain/usecases/phone_number_verification_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 PhoneNumberVerificationUseCase _verifyPhoneNumberUseCase; final formKey = GlobalKey(); @@ -41,8 +21,16 @@ class PhoneVerificationViewModel extends Notifier { Future sendSmsCode() async { if (formKey.currentState!.validate()) { + final number = PhoneNumber.fromLocal(phoneController.text); + + final isRegistered = await _verifyPhoneNumberUseCase.isRegistered(number); + if (isRegistered) { + Fluttertoast.showToast(msg: "이미 등록된 번호입니다"); + return; + } + await _verifyPhoneNumberUseCase.sendCode( - "+82${phoneController.text.substring(1)}", + number, (verificationId) { state = state.copyWith( verificationId: verificationId, @@ -50,7 +38,8 @@ class PhoneVerificationViewModel extends Notifier { ); }, ); - log(name: 'PhoneVerificationViewModel : sendSmsCode', "+82${phoneController.text.substring(1)}"); + + Fluttertoast.showToast(msg: "인증번호가 전송되었습니다"); } } @@ -60,18 +49,12 @@ class PhoneVerificationViewModel extends Notifier { 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: '인증이 완료되었습니다'); + await _verifyPhoneNumberUseCase.saveUser(); onSuccess(); } catch (e) { - log(name: 'PhoneVerificationViewModel : verifyCode', e.toString()); - log(name: 'PhoneVerificationViewModel : verifyCode', state.verificationId); - log(name: 'PhoneVerificationViewModel : verifyCode', smsCodeController.text); - Fluttertoast.showToast(msg: "인증 번호가 올바르지 않습니다"); + Fluttertoast.showToast(msg: "인증번호가 올바르지 않습니다"); } - - } } diff --git a/lib/auth/presentation/pages/identity/phone_verification_state.dart b/lib/auth/presentation/pages/identity/phone_verification_state.dart new file mode 100644 index 0000000..048a2b8 --- /dev/null +++ b/lib/auth/presentation/pages/identity/phone_verification_state.dart @@ -0,0 +1,20 @@ + +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, + ); + } +} diff --git a/lib/auth/presentation/pages/identity/providers.dart b/lib/auth/presentation/pages/identity/providers.dart index db95967..b6c2919 100644 --- a/lib/auth/presentation/pages/identity/providers.dart +++ b/lib/auth/presentation/pages/identity/providers.dart @@ -1,6 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../data/repositories/default_phone_number_repository.dart'; +import 'phone_verification_state.dart'; import '../../../domain/repositories/phone_number_repository.dart'; import '../../../domain/usecases/phone_number_verification_usecase.dart'; import 'identity_verification_viewmodel.dart';