diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 001df22..b7504a3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,3 +13,4 @@ close # ## ๐Ÿ’ฌ๋ฆฌ๋ทฐ ์š”๊ตฌ์‚ฌํ•ญ +## ๋‹ค์Œ ์ž‘์—… ๊ณ„ํš diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 580bd7f..7706582 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,15 +21,6 @@ jobs: - name: Install dependencies run: flutter pub get - - name: Analyze code - run: flutter analyze - - - name: Check code formatting - run: dart format --set-exit-if-changed . - - - name: Run tests - run: flutter test - - name: Cache Flutter dependencies uses: actions/cache@v3 with: diff --git a/.gitignore b/.gitignore index 88dc08f..70c3083 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build/ # Local configuration file (sdk path, etc) local.properties +.env # Log/OS Files *.log @@ -32,6 +33,7 @@ render.experimental.xml # Google Services (e.g. APIs or Firebase) google-services.json +ios/Runner/GoogleService-Info.plist # Android Profiling *.hprof @@ -109,7 +111,7 @@ lib/generated_plugin_registrant.dart # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - +ios/Podfile.lock ## User settings xcuserdata/ diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 705b56b..a0a7822 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,14 +1,22 @@ +import java.util.Properties + plugins { id("com.android.application") id("kotlin-android") + id("com.google.gms.google-services") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } +val properties = + Properties().apply { + load(rootProject.file("local.properties").inputStream()) + } + android { namespace = "com.codel.code_l" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "27.0.12077973" compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -24,10 +32,13 @@ android { applicationId = "com.codel.code_l" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + minSdk = 23 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName + + val kakaoAppKey = properties.getProperty("KAKAO_NATIVE_APP_KEY") as String? ?: "" + manifestPlaceholders["appkey"] = kakaoAppKey } buildTypes { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1ec9902..70d1b84 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,24 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/ic_job_creator.png b/assets/icons/ic_job_creator.png new file mode 100644 index 0000000..d31c9ef Binary files /dev/null and b/assets/icons/ic_job_creator.png differ diff --git a/assets/icons/ic_job_educator.png b/assets/icons/ic_job_educator.png new file mode 100644 index 0000000..17788c7 Binary files /dev/null and b/assets/icons/ic_job_educator.png differ diff --git a/assets/icons/ic_job_entertainer.png b/assets/icons/ic_job_entertainer.png new file mode 100644 index 0000000..18c31c7 Binary files /dev/null and b/assets/icons/ic_job_entertainer.png differ diff --git a/assets/icons/ic_job_etc.png b/assets/icons/ic_job_etc.png new file mode 100644 index 0000000..56b9c86 Binary files /dev/null and b/assets/icons/ic_job_etc.png differ diff --git a/assets/icons/ic_job_freelancer.png b/assets/icons/ic_job_freelancer.png new file mode 100644 index 0000000..3bb97ff Binary files /dev/null and b/assets/icons/ic_job_freelancer.png differ diff --git a/assets/icons/ic_job_government_employee.png b/assets/icons/ic_job_government_employee.png new file mode 100644 index 0000000..4c9c19e Binary files /dev/null and b/assets/icons/ic_job_government_employee.png differ diff --git a/assets/icons/ic_job_healthcare.png b/assets/icons/ic_job_healthcare.png new file mode 100644 index 0000000..2398b22 Binary files /dev/null and b/assets/icons/ic_job_healthcare.png differ diff --git a/assets/icons/ic_job_office_worker.png b/assets/icons/ic_job_office_worker.png new file mode 100644 index 0000000..b235cd8 Binary files /dev/null and b/assets/icons/ic_job_office_worker.png differ diff --git a/assets/icons/ic_job_seeker.png b/assets/icons/ic_job_seeker.png new file mode 100644 index 0000000..49a36ff Binary files /dev/null and b/assets/icons/ic_job_seeker.png differ diff --git a/assets/icons/ic_job_self_employed.png b/assets/icons/ic_job_self_employed.png new file mode 100644 index 0000000..872cea4 Binary files /dev/null and b/assets/icons/ic_job_self_employed.png differ diff --git a/assets/icons/ic_job_servicer.png b/assets/icons/ic_job_servicer.png new file mode 100644 index 0000000..c26c3af Binary files /dev/null and b/assets/icons/ic_job_servicer.png differ diff --git a/assets/icons/ic_job_student.png b/assets/icons/ic_job_student.png new file mode 100644 index 0000000..94ca5f6 Binary files /dev/null and b/assets/icons/ic_job_student.png differ diff --git a/assets/icons/paper_tray.gif b/assets/icons/paper_tray.gif new file mode 100644 index 0000000..3c62e49 Binary files /dev/null and b/assets/icons/paper_tray.gif differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ios/Podfile b/ios/Podfile index e549ee2..7eff7c9 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,4 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ab5cfd0..225c36d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ 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 */; }; @@ -14,6 +15,8 @@ 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 */; }; + A95C5CF9F58E465261D3D523 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6228E8708160B7145026049 /* Pods_Runner.framework */; }; + E1FBF0C12DAE7C460017C51F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E1FBF0C02DAE7C460017C51F /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,11 +43,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 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 = ""; }; 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 = ""; }; + 5378F9951DC6876528AE2A36 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 70F87F2E741779C30B5DB63A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -55,6 +61,14 @@ 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 = ""; }; + 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 = ""; }; + AFE368912DBBC8390072E7EF /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + B955AE8AC425CE69815F5900 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C7F447F3DE684CF75971C579 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + E1FBF0C02DAE7C460017C51F /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + E1FBF0C22DBBDA7A0017C51F /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + E6228E8708160B7145026049 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E771DCC6EFA21C1573E7EC1E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,6 +76,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A95C5CF9F58E465261D3D523 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FC480051F976BE30F4AC21A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 104F1D23AF21CC7B53B7C8A7 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -76,6 +99,19 @@ path = RunnerTests; sourceTree = ""; }; + 85511697A5F0AF89913BD830 /* Pods */ = { + isa = PBXGroup; + children = ( + 11EC9BDD1FDC47F03500C4D3 /* Pods-Runner.debug.xcconfig */, + C7F447F3DE684CF75971C579 /* Pods-Runner.release.xcconfig */, + 5378F9951DC6876528AE2A36 /* Pods-Runner.profile.xcconfig */, + E771DCC6EFA21C1573E7EC1E /* Pods-RunnerTests.debug.xcconfig */, + 70F87F2E741779C30B5DB63A /* Pods-RunnerTests.release.xcconfig */, + A0C3D7BA37E297BF670F5764 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +130,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 85511697A5F0AF89913BD830 /* Pods */, + B92EEAB882964D6C972ADB60 /* Frameworks */, ); sourceTree = ""; }; @@ -109,6 +147,9 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + E1FBF0C22DBBDA7A0017C51F /* Runner.entitlements */, + AFE368912DBBC8390072E7EF /* Runner.entitlements */, + E1FBF0C02DAE7C460017C51F /* GoogleService-Info.plist */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -121,6 +162,15 @@ path = Runner; sourceTree = ""; }; + B92EEAB882964D6C972ADB60 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E6228E8708160B7145026049 /* Pods_Runner.framework */, + B955AE8AC425CE69815F5900 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +178,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 5D2C3991EF6A09801BF051B0 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + FC480051F976BE30F4AC21A4 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +197,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 90FA8763DD4A6906B4030C96 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + F317B067B108CCC9832E933A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -214,6 +268,7 @@ files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + E1FBF0C12DAE7C460017C51F /* GoogleService-Info.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -238,6 +293,50 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 5D2C3991EF6A09801BF051B0 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-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 */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + 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; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +352,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + F317B067B108CCC9832E933A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -361,15 +477,21 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = LRG496B3VQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.codel.codel; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -378,6 +500,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E771DCC6EFA21C1573E7EC1E /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,6 +518,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 70F87F2E741779C30B5DB63A /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -410,6 +534,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A0C3D7BA37E297BF670F5764 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -540,15 +665,21 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = LRG496B3VQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.codel.codel; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -562,15 +693,21 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = LRG496B3VQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.codel.codel; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6266644..f6f1b9b 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ import Flutter import UIKit +import Firebase @main @objc class AppDelegate: FlutterAppDelegate { @@ -7,6 +8,7 @@ import UIKit _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + FirebaseApp.configure() GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d20c4bd..b2fbcc6 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,15 @@ +CFBundleURLTypes + + + CFBundleURLSchemes + + kakaoe17acdc68adf7250d61dd15f8ee185c1 + + + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..a812db5 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/lib/auth/data/datasources/.gitkeep b/lib/auth/data/datasources/local/.gitkeep similarity index 100% rename from lib/auth/data/datasources/.gitkeep rename to lib/auth/data/datasources/local/.gitkeep diff --git a/lib/auth/data/datasources/providers.dart b/lib/auth/data/datasources/providers.dart new file mode 100644 index 0000000..2101a2d --- /dev/null +++ b/lib/auth/data/datasources/providers.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../core/utills/network/auth_api_service.dart'; +import '../../../core/utills/network/dio_provider.dart'; + +final apiServiceProvider = Provider((ref) { + final dio = ref.watch(dioProvider); + return AuthApiService(dio); +}); diff --git a/lib/auth/data/datasources/remote/auth_remote_datasource.dart b/lib/auth/data/datasources/remote/auth_remote_datasource.dart new file mode 100644 index 0000000..8fe9a7a --- /dev/null +++ b/lib/auth/data/datasources/remote/auth_remote_datasource.dart @@ -0,0 +1,20 @@ +import '../../../../core/utills/network/auth_api_service.dart'; +import '../../models/request/login_request.dart'; + +class AuthRemoteDataSource { + final AuthApiService api; + + AuthRemoteDataSource(this.api); + + Future loginWithOauth({ + required String type, + required String id, + }) async { + final response = await api.login( + LoginRequest(oauthType: type, oauthId: id), + ); + final jwt = response.headers.value('Authorization') ?? ''; + + return jwt; + } +} diff --git a/lib/auth/data/datasources/remote/login_remote_datasource.dart b/lib/auth/data/datasources/remote/login_remote_datasource.dart new file mode 100644 index 0000000..cd210f6 --- /dev/null +++ b/lib/auth/data/datasources/remote/login_remote_datasource.dart @@ -0,0 +1,33 @@ +import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +class LoginRemoteDataSource { + Future loginWithKakao() async { + bool installed = await isKakaoTalkInstalled(); + + if (installed) { + await UserApi.instance.loginWithKakaoTalk(); + } else { + await UserApi.instance.loginWithKakaoAccount(); + } + + return await UserApi.instance.me(); + } + + Future loginWithApple() async { + final AuthorizationCredentialAppleID + credential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + webAuthenticationOptions: WebAuthenticationOptions( + clientId: "codel.codel.com", // Apple Developer Console์— ๋“ฑ๋ก๋œ clientId + redirectUri: Uri.parse( + "https://chartreuse-gratis-lungfish.glitch.me/callbacks/sign_in_with_apple", + ), + ), + ); + return credential; + } +} diff --git a/lib/auth/data/models/request/login_request.dart b/lib/auth/data/models/request/login_request.dart new file mode 100644 index 0000000..31d9d88 --- /dev/null +++ b/lib/auth/data/models/request/login_request.dart @@ -0,0 +1,8 @@ +class LoginRequest { + final String oauthType; + final String oauthId; + + LoginRequest({required this.oauthType, required this.oauthId}); + + Map toJson() => {'oauthType': oauthType, 'oauthId': oauthId}; +} diff --git a/lib/auth/data/models/response/login_response.dart b/lib/auth/data/models/response/login_response.dart new file mode 100644 index 0000000..5ce88bf --- /dev/null +++ b/lib/auth/data/models/response/login_response.dart @@ -0,0 +1,13 @@ +class LoginResponse { + final String memberStatus; + final String jwt; + + LoginResponse({required this.memberStatus, required this.jwt}); + + factory LoginResponse.fromJson( + Map json, { + required String jwt, + }) { + return LoginResponse(memberStatus: json['memberStatus'], jwt: jwt); + } +} diff --git a/lib/auth/data/repositories/default_login_repository.dart b/lib/auth/data/repositories/default_login_repository.dart new file mode 100644 index 0000000..b07e1cc --- /dev/null +++ b/lib/auth/data/repositories/default_login_repository.dart @@ -0,0 +1,31 @@ +import 'package:code_l/auth/domain/repositories/login_repository.dart'; +import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +import '../datasources/remote/auth_remote_datasource.dart'; +import '../datasources/remote/login_remote_datasource.dart'; + +class DefaultLoginRepository implements LoginRepository { + final LoginRemoteDataSource loginRemoteDataSource; + final AuthRemoteDataSource authRemoteDataSource; + + DefaultLoginRepository({ + required this.loginRemoteDataSource, + required this.authRemoteDataSource, + }); + + @override + Future loginWithKakao() { + return loginRemoteDataSource.loginWithKakao(); + } + + @override + Future loginWithApple() { + throw loginRemoteDataSource.loginWithApple(); + } + + @override + Future loginOauth(String type, String id) { + return authRemoteDataSource.loginWithOauth(type: type, id: id); + } +} diff --git a/lib/auth/data/models/.gitkeep b/lib/auth/domain/model/.gitkeep similarity index 100% rename from lib/auth/data/models/.gitkeep rename to lib/auth/domain/model/.gitkeep diff --git a/lib/auth/domain/model/login_type.dart b/lib/auth/domain/model/login_type.dart new file mode 100644 index 0000000..d63f308 --- /dev/null +++ b/lib/auth/domain/model/login_type.dart @@ -0,0 +1 @@ +enum LoginType { kakao, apple } diff --git a/lib/auth/domain/repositories/login_repository.dart b/lib/auth/domain/repositories/login_repository.dart new file mode 100644 index 0000000..fa76f39 --- /dev/null +++ b/lib/auth/domain/repositories/login_repository.dart @@ -0,0 +1,8 @@ +import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +abstract class LoginRepository { + Future loginOauth(String type, String id); + Future loginWithKakao(); + Future loginWithApple(); +} diff --git a/lib/auth/domain/usecases/login_usecase.dart b/lib/auth/domain/usecases/login_usecase.dart new file mode 100644 index 0000000..3203eb9 --- /dev/null +++ b/lib/auth/domain/usecases/login_usecase.dart @@ -0,0 +1,26 @@ +import 'package:code_l/auth/domain/model/login_type.dart'; +import 'package:code_l/auth/domain/repositories/login_repository.dart'; +import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +class LoginUseCase { + final LoginRepository repository; + LoginUseCase(this.repository); + + Future callKakaoLogin(LoginType type) async { + return await repository.loginWithKakao(); + } + + Future callAppleLogin(LoginType type) async { + return await repository.loginWithApple(); + } + + Future callOauth(LoginType type, String id) async { + switch (type) { + case LoginType.kakao: + return await repository.loginOauth('KAKAO', id); + case LoginType.apple: + return await repository.loginOauth('APPLE', id); + } + } +} diff --git a/lib/auth/presentation/pages/congratulation/congratulation_page.dart b/lib/auth/presentation/pages/congratulation/congratulation_page.dart new file mode 100644 index 0000000..a31ea27 --- /dev/null +++ b/lib/auth/presentation/pages/congratulation/congratulation_page.dart @@ -0,0 +1,116 @@ +import 'package:code_l/auth/presentation/pages/congratulation/widgets/congratulation_app_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import 'widgets/congratulation_confirm_button.dart'; + +class CongratulationPage extends StatelessWidget { + const CongratulationPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const CongratulationAppBar(), + body: _buildContentField(), + ); + } + + Widget _buildContentField() { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset("assets/icons/logo_with_image.svg"), + SizedBox(height: AppGaps.gap12), + Text( + "๊ฐ€์ž…์„ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค!", + style: AppTypography.header2.copyWith( + color: AppColors.grey900, + ), + ), + ], + ), + ], + ), + SizedBox(height: 54), + Row( + children: [Text("๊ฐœ์„ฑ์žˆ๋Š” ์ต๋ช… ํ™œ๋™ ๐Ÿคซ", style: AppTypography.subtitle3)], + ), + Flexible( + child: Text( + "์ž์‹ ์˜ ์–ผ๊ตด์„ ๋ฐ”๋กœ ๊ณต๊ฐœํ•˜์ง€ ์•Š๊ณ ๋„ ์ž์œ ๋กญ๊ฒŒ ์†Œํ†ตํ•  ์ˆ˜ ์žˆ์–ด์š”.", + style: AppTypography.body2, + ), + ), + SizedBox(height: AppGaps.gap20), + Row( + children: [ + Text("์•ˆ์‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฐœ ์‹œ์Šคํ…œ ๐Ÿ”", style: AppTypography.subtitle3), + ], + ), + Flexible( + child: Text( + "์›์น˜ ์•Š๋Š” ์‚ฌ๋žŒ์—๊ฒŒ ์–ผ๊ตด์ด ๊ณต๊ฐœ๋  ๊ฑฑ์ • ์—†์ด, ๋‚ด๊ฐ€ ์„ ํƒํ•œ ์‚ฌ๋žŒ์—๊ฒŒ๋งŒ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์–ด์š”.", + style: AppTypography.body2, + ), + ), + SizedBox(height: 54), + Container( + height: 114, + width: double.infinity, + alignment: Alignment.center, + color: AppColors.grey100, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("์ฝ”๋“œ ํ”„๋กœํ•„์ด๋ž€?", style: AppTypography.caption1), + SizedBox(height: AppGaps.gap8), + Flexible( + child: Text( + "์ด๋ฆ„ ๋Œ€์‹  ์ฝ”๋“œ ๋„ค์ž„๊ณผ ๊ฐœ์„ฑ์žˆ๋Š” ์ •๋ณด๋ฅผ ํ†ตํ•ด ๋‚˜๋ฅผ ํ‘œํ˜„ํ•˜๋Š” Code : L์˜ ํŠน๋ณ„ํ•œ ํ”„๋กœํ•„ ์ž…๋‹ˆ๋‹ค. ๊ด€์‹ฌ์‚ฌ, ์ทจํ–ฅ, ์Šคํƒ€์ผ ๋“ฑ ๋‚˜๋งŒ์˜ ํ”„๋กœํ•„๋กœ ๋‚˜๋ฅผ ๋ณด์—ฌ์ฃผ์„ธ์š”!", + style: AppTypography.caption1.copyWith( + color: AppColors.grey700, + ), + ), + ), + ], + ), + ), + ), + SizedBox(height: 120), + Padding( + padding: const EdgeInsets.only(left: 18.0), + child: SvgPicture.asset( + "assets/icons/bubble.svg", + width: double.infinity, + height: 40, + ), + ), + Padding( + padding: const EdgeInsets.all(AppGaps.gap4), + child: CongratulationConfirmButton( + enabled: true, + text: "์ฝ”๋“œ ํ”„๋กœํ•„ ์ž‘์„ฑํ•˜๊ธฐ", + onPressed: () {}, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/congratulation/widgets/congratulation_app_bar.dart b/lib/auth/presentation/pages/congratulation/widgets/congratulation_app_bar.dart new file mode 100644 index 0000000..8485d34 --- /dev/null +++ b/lib/auth/presentation/pages/congratulation/widgets/congratulation_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 CongratulationAppBar extends StatelessWidget + implements PreferredSizeWidget { + const CongratulationAppBar({super.key}); + + @override + Size get preferredSize => Size.fromHeight(56); + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: AppGaps.gap36), + Text( + "ํšŒ์›๊ฐ€์ž…", + style: AppTypography.subtitle2.copyWith(color: AppColors.grey900), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/congratulation/widgets/congratulation_confirm_button.dart b/lib/auth/presentation/pages/congratulation/widgets/congratulation_confirm_button.dart new file mode 100644 index 0000000..1e95f38 --- /dev/null +++ b/lib/auth/presentation/pages/congratulation/widgets/congratulation_confirm_button.dart @@ -0,0 +1,40 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; +import '../../../../../core/utills/design/app_typography.dart'; + +class CongratulationConfirmButton extends StatelessWidget { + const CongratulationConfirmButton({ + super.key, + required this.enabled, + this.onPressed, + this.text = "ํ™•์ธ", + }); + + final bool enabled; + final VoidCallback? onPressed; + final String text; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: enabled ? onPressed : null, + child: Container( + height: 54, + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 16), + alignment: Alignment.center, + decoration: BoxDecoration( + color: enabled ? AppColors.primary : AppColors.grey200, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + style: AppTypography.subtitle2.copyWith( + color: enabled ? AppColors.white : AppColors.grey400, + ), + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/login/login_page.dart b/lib/auth/presentation/pages/login/login_page.dart new file mode 100644 index 0000000..bffead7 --- /dev/null +++ b/lib/auth/presentation/pages/login/login_page.dart @@ -0,0 +1,139 @@ +import 'package:code_l/auth/presentation/pages/login/providers.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; + +class LoginPage extends ConsumerWidget { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SvgPicture.asset('assets/icons/logo_with_image.svg'), + ], + ), + ), + Flexible( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "์ฝ”๋“œ๊ฐ€ ๋งž๋Š” ์šฐ๋ฆฌ๋งŒ์˜ ๊ณต๊ฐ„", + style: AppTypography.subtitle2.copyWith( + color: AppColors.primary, + ), + ), + Text(" ์—์„œ", style: AppTypography.body1), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("์‹œ์ž‘๋˜๋Š” ", style: AppTypography.body1), + Text("์ง„์งœ ์ธ์—ฐ", style: AppTypography.subtitle1), + ], + ), + ], + ), + ), + Flexible( + flex: 2, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap16, + vertical: AppGaps.gap8, + ), + child: OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: AppColors.kakao, + side: const BorderSide(color: AppColors.kakao), + ), + onPressed: () { + ref.read(loginViewModelProvider).loginWithKakao(); + }, + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap16), + 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: AppGaps.gap16, + ), + child: OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: AppColors.white, + side: const BorderSide(color: AppColors.black), + ), + onPressed: () { + ref.read(loginViewModelProvider).loginWithApple(); + }, + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap16), + 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, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("๋ ˆ์ฆˆ๋น„์–ธ", style: AppTypography.subtitle2), + Text("์„ ์œ„ํ•œ ๋” ๊นŠ์ด์žˆ๋Š” ๋งค์นญ์•ฑ", style: AppTypography.body1), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/login/login_viewmodel.dart b/lib/auth/presentation/pages/login/login_viewmodel.dart new file mode 100644 index 0000000..4ca3fa0 --- /dev/null +++ b/lib/auth/presentation/pages/login/login_viewmodel.dart @@ -0,0 +1,68 @@ +import 'dart:developer'; + +import 'package:code_l/auth/domain/model/login_type.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +import '../../../domain/usecases/login_usecase.dart'; + +class LoginViewModel extends ChangeNotifier { + final LoginUseCase useCase; + + LoginViewModel({required this.useCase}); + + Future loginWithKakao() async { + try { + bool installed = await isKakaoTalkInstalled(); + + OAuthToken token = + installed + ? await UserApi.instance.loginWithKakaoTalk() + : await UserApi.instance.loginWithKakaoAccount(); + + final user = await UserApi.instance.me(); + + log(name: 'LoginViewModel::login', 'success: $token'); + await loginOauth(LoginType.kakao, user.id.toString()); + } catch (e) { + log(name: 'LoginViewModel::login', 'error: $e'); + } + } + + Future loginOauth(LoginType type, String id) async { + try { + final jwt = await useCase.callOauth(type, id); + + log(name: 'LoginViewModel::loginOauth', 'success: $jwt'); + } catch (e) { + log(name: 'LoginViewModel::loginOauth', 'error: $e'); + } + } + + Future loginWithApple() async { + try { + // ์• ํ”Œ ์ธ์ฆ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ + final credential = useCase.callAppleLogin(LoginType.apple); + // authorizationCode์™€ identityToken ํ™•์ธ + final authorizationCode = credential.then( + (value) => value.authorizationCode, + ); + final identityToken = credential.then((value) => value.identityToken); + + if (authorizationCode == null || identityToken == null) { + throw Exception("Authorization Code ๋˜๋Š” Identity Token์„ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + + // ๋ฐฑ์—”๋“œ๋กœ OAuth ์ธ์ฆ ์š”์ฒญ + await loginOauth(LoginType.apple, identityToken.toString()); + + log( + name: 'LoginViewModel::loginWithApple', + 'success: ${identityToken.toString()}', + ); + } catch (e) { + log(name: 'LoginViewModel::loginWithApple', 'error: $e'); + } + } +} diff --git a/lib/auth/presentation/pages/login/providers.dart b/lib/auth/presentation/pages/login/providers.dart new file mode 100644 index 0000000..f1867d3 --- /dev/null +++ b/lib/auth/presentation/pages/login/providers.dart @@ -0,0 +1,37 @@ +import 'package:code_l/auth/data/repositories/default_login_repository.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../data/datasources/providers.dart'; +import '../../../data/datasources/remote/auth_remote_datasource.dart'; +import '../../../data/datasources/remote/login_remote_datasource.dart'; +import '../../../domain/repositories/login_repository.dart'; +import '../../../domain/usecases/login_usecase.dart'; +import 'login_viewmodel.dart'; + +final loginRepositoryProvider = Provider((ref) { + final dataSource = ref.read(loginRemoteDataSourceProvider); + final remoteDataSource = ref.read(authRemoteDataSourceProvider); + return DefaultLoginRepository( + loginRemoteDataSource: dataSource, + authRemoteDataSource: remoteDataSource, + ); +}); + +final loginRemoteDataSourceProvider = Provider((ref) { + return LoginRemoteDataSource(); +}); + +final authRemoteDataSourceProvider = Provider((ref) { + final dio = ref.read(apiServiceProvider); + return AuthRemoteDataSource(dio); +}); + +final loginUseCaseProvider = Provider((ref) { + final repository = ref.read(loginRepositoryProvider); + return LoginUseCase(repository); +}); + +final loginViewModelProvider = ChangeNotifierProvider((ref) { + final useCase = ref.read(loginUseCaseProvider); + return LoginViewModel(useCase: useCase); +}); diff --git a/lib/auth/presentation/pages/terms_and_conditions/providers.dart b/lib/auth/presentation/pages/terms_and_conditions/providers.dart new file mode 100644 index 0000000..b6a8ff6 --- /dev/null +++ b/lib/auth/presentation/pages/terms_and_conditions/providers.dart @@ -0,0 +1,6 @@ +import 'package:code_l/auth/presentation/pages/terms_and_conditions/terms_and_conditions_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final termsAndConditionsViewModelProvider = ChangeNotifierProvider( + (ref) => TermsAndConditionsViewmodel(), +); diff --git a/lib/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart b/lib/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart new file mode 100644 index 0000000..4c8c842 --- /dev/null +++ b/lib/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart @@ -0,0 +1,135 @@ +import 'package:code_l/auth/presentation/pages/terms_and_conditions/providers.dart'; +import 'package:code_l/auth/presentation/pages/woman/woman_verification_Page.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 '../../../../sign_up/presentation/widgets/sign_up_confirm_button.dart'; +import '../../widgets/auth_confirm_button.dart'; +import 'widgets/terms_and_conditions_app_bar.dart'; +import '../login/login_page.dart'; + +class TermsAndConditionPage extends ConsumerWidget { + const TermsAndConditionPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final viewmodel = ref.watch(termsAndConditionsViewModelProvider); + final notifier = ref.read(termsAndConditionsViewModelProvider.notifier); + String getLabel(int index) { + switch (index) { + case 1: + return "์„œ๋น„์Šค ์ด์šฉ ์•ฝ๊ด€ ๋™์˜ (ํ•„์ˆ˜)"; + case 2: + return "๊ฐœ์ธ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ์ด์šฉ ๋™์˜ (ํ•„์ˆ˜)"; + case 3: + return "๋ฏผ๊ฐ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ์ด์šฉ ๋™์˜ (ํ•„์ˆ˜)"; + case 4: + return "๋งˆ์ผ€ํŒ… ์ •๋ณด ์ˆ˜์‹  ๋™์˜ (์„ ํƒ)"; + default: + return ""; + } + } + + return Scaffold( + appBar: const AgreeToTermsAndConditionsAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: AuthConfirmButton( + enabled: viewmodel.isValid, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const WomanVerificationPage(), + ), + ); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + Text( + "์„œ๋น„์Šค ์ด์šฉ์— ํ•„์š”ํ•œ ์•ฝ๊ด€ ๋ฐ ์ •์ฑ…์—\n๋™์˜ํ•ด์ฃผ์„ธ์š”", + style: AppTypography.subtitle1.copyWith( + color: AppColors.grey900, + ), + ), + const SizedBox(height: 60), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // "๋ชจ๋‘ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค." Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "๋ชจ๋‘ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค.", + style: AppTypography.subtitle2.copyWith( + color: + viewmodel.checkState[0] + ? AppColors.grey900 + : AppColors.grey500, + ), + ), + IconButton( + onPressed: () => notifier.toggleAll(), + iconSize: 36, + icon: Icon( + Icons.check_circle_outline, + color: + viewmodel.checkState[0] + ? AppColors.grey900 + : AppColors.grey500, + ), + ), + ], + ), + // Individual agreement rows + for (int i = 1; i < viewmodel.checkState.length; i++) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), + onPressed: () {}, + child: Text( + getLabel(i), + style: AppTypography.body2.copyWith( + decoration: TextDecoration.underline, + color: + viewmodel.checkState[i] + ? AppColors.grey900 + : AppColors.grey500, + ), + ), + ), + IconButton( + onPressed: () => notifier.toggle(i), + iconSize: 24, + icon: Icon( + Icons.check, + color: + viewmodel.checkState[i] + ? AppColors.grey900 + : AppColors.grey500, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/terms_and_conditions/terms_and_conditions_viewmodel.dart b/lib/auth/presentation/pages/terms_and_conditions/terms_and_conditions_viewmodel.dart new file mode 100644 index 0000000..3ecfbe6 --- /dev/null +++ b/lib/auth/presentation/pages/terms_and_conditions/terms_and_conditions_viewmodel.dart @@ -0,0 +1,35 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class TermsAndConditionsViewmodel extends ChangeNotifier { + List checkState = [false, false, false, false, false]; + bool isValid = false; + void toggle(int index) { + checkState[index] = !checkState[index]; + + if (index > 0) { + final allChecked = checkState.sublist(1).every((e) => e); + checkState[0] = allChecked; + } + if (checkState.sublist(1, 4).every((checked) => checked)) { + isValid = true; + } else { + isValid = false; + } + notifyListeners(); + } + + void toggleAll() { + final allAgree = !checkState[0]; + checkState = [ + allAgree, + for (int i = 1; i < checkState.length; i++) allAgree, + ]; + if (checkState.sublist(1, 4).every((checked) => checked)) { + isValid = true; + } else { + isValid = false; + } + notifyListeners(); + } +} diff --git a/lib/auth/presentation/pages/terms_and_conditions/widgets/terms_and_conditions_app_bar.dart b/lib/auth/presentation/pages/terms_and_conditions/widgets/terms_and_conditions_app_bar.dart new file mode 100644 index 0000000..b16db4e --- /dev/null +++ b/lib/auth/presentation/pages/terms_and_conditions/widgets/terms_and_conditions_app_bar.dart @@ -0,0 +1,41 @@ +import 'package:code_l/core/utills/design/app_typography.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; + +class AgreeToTermsAndConditionsAppBar extends StatelessWidget + implements PreferredSizeWidget { + const AgreeToTermsAndConditionsAppBar({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: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios_new_rounded), + onPressed: () => Navigator.pop(context), + ), + Text( + "์„œ๋น„์Šค ์ด์šฉ ์•ฝ๊ด€ ๋ฐ ์ •์ฑ…", + style: AppTypography.subtitle2.copyWith(color: AppColors.grey800), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/woman/providers.dart b/lib/auth/presentation/pages/woman/providers.dart new file mode 100644 index 0000000..fa488fa --- /dev/null +++ b/lib/auth/presentation/pages/woman/providers.dart @@ -0,0 +1,6 @@ +import 'package:code_l/auth/presentation/pages/woman/woman_verification_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final womanViewmodelProvider = ChangeNotifierProvider( + (ref) => WomanVerificationViewmodel(), +); diff --git a/lib/auth/presentation/pages/woman/widgets/woman_verification_app_bar.dart b/lib/auth/presentation/pages/woman/widgets/woman_verification_app_bar.dart new file mode 100644 index 0000000..568a74e --- /dev/null +++ b/lib/auth/presentation/pages/woman/widgets/woman_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 WomanVerificationAppBar extends StatelessWidget + implements PreferredSizeWidget { + const WomanVerificationAppBar({super.key}); + + @override + Size get preferredSize => Size.fromHeight(56); + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: AppGaps.gap36), + Text( + "์—ฌ์„ฑํ™•์ธ ์•ˆ๋‚ด", + style: AppTypography.subtitle2.copyWith(color: AppColors.grey900), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/woman/woman_verification_Page.dart b/lib/auth/presentation/pages/woman/woman_verification_Page.dart new file mode 100644 index 0000000..9b45753 --- /dev/null +++ b/lib/auth/presentation/pages/woman/woman_verification_Page.dart @@ -0,0 +1,273 @@ +import 'dart:developer'; + +import 'package:code_l/auth/presentation/pages/woman/providers.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:http/http.dart' as ref; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../../../sign_up/presentation/widgets/sign_up_confirm_button.dart'; +import '../congratulation/congratulation_page.dart'; +import 'widgets/woman_verification_app_bar.dart'; +import '../login/login_page.dart'; + +class WomanVerificationPage extends ConsumerWidget { + const WomanVerificationPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final viewModel = ref.watch(womanViewmodelProvider); + final notifier = ref.read(womanViewmodelProvider.notifier); + return Scaffold( + appBar: WomanVerificationAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: ConfirmButton( + enabled: true, + onPressed: () { + _buildBottomSheetField(context, viewModel, notifier); + }, + ), + ), + body: _buildInformationField(), + ); + } + + Future _buildBottomSheetField(context, viewModel, notifier) async { + return showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Consumer( + builder: (context, ref, _) { + final viewModel = ref.watch(womanViewmodelProvider); + final notifier = ref.read(womanViewmodelProvider.notifier); + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(AppGaps.gap20), + topRight: Radius.circular(AppGaps.gap20), + ), + color: AppColors.white, + ), + height: MediaQuery.sizeOf(context).height * 0.5, + child: Center( + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "๊ผญ ํ™•์ธํ•ด ์ฃผ์„ธ์š”", + style: AppTypography.subtitle2.copyWith( + color: AppColors.grey900, + ), + ), + IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon(Icons.close), + ), + ], + ), + SizedBox(height: AppGaps.gap20), + Flexible( + child: Text( + "code:L์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ์•ˆ์ „์„ ์†Œ์ค‘ํžˆ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.\nํ•ด๋‹น ํ™•์ธ์„œ๋Š” ์—ฌ์„ฑ๋“ค๋งŒ์˜ ์•ˆ์ „ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ํ•จ๊ป˜ ์ง€ํ‚ค๊ธฐ ์œ„ํ•œ ์•ฝ์†์ž…๋‹ˆ๋‹ค.", + style: AppTypography.subtitle2.copyWith( + color: AppColors.grey900, + ), + ), + ), + SizedBox(height: AppGaps.gap20), + Row( + children: [ + Text( + "1", + style: AppTypography.subtitle3.copyWith( + color: AppColors.grey800, + ), + ), + SizedBox(width: AppGaps.gap16), + Flexible( + child: Text( + "๋ณธ ์„œ๋น„์Šค๊ฐ€ ์—ฌ์„ฑ๋“ค๋ผ๋ฆฌ์˜ ์†Œ์ค‘ํ•œ ์ธ์—ฐ์„ ์œ„ํ•œ ํ”Œ๋žซํผ์ž„์„ ์ดํ•ดํ–ˆ์œผ๋ฉฐ, ์—ฌ์„ฑ ์‚ฌ์šฉ์ž์ž„์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.", + style: AppTypography.body2.copyWith( + color: AppColors.grey800, + ), + ), + ), + ], + ), + SizedBox(height: AppGaps.gap20), + Row( + children: [ + Text( + "2", + style: AppTypography.subtitle3.copyWith( + color: AppColors.grey800, + ), + ), + SizedBox(width: AppGaps.gap16), + Flexible( + child: Text( + "์—ฌ์„ฑ์ด ์•„๋‹˜์—๋„ ๊ฐ€์ž…ํ•  ๊ฒฝ์šฐ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  ๋ฒ•์  ์ฑ…์ž„์€ ์‚ฌ์šฉ์ž ๋ณธ์ธ์—๊ฒŒ ์žˆ์Šต๋‹ˆ๋‹ค.", + style: AppTypography.body2.copyWith( + color: AppColors.grey800, + ), + ), + ), + ], + ), + SizedBox(height: AppGaps.gap20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "์œ„ ์•ˆ๋‚ด์‚ฌํ•ญ์„ ๋ชจ๋‘ ํ™•์ธํ•˜์˜€์Šต๋‹ˆ๋‹ค.", + style: AppTypography.subtitle2.copyWith( + color: + viewModel.isChecked + ? AppColors.grey800 + : AppColors.grey400, + ), + ), + IconButton( + onPressed: () { + notifier.toggle(); + }, + icon: Icon( + Icons.check_circle_outline, + color: + viewModel.isChecked + ? AppColors.grey800 + : AppColors.grey400, + ), + ), + ], + ), + SizedBox(height: AppGaps.gap20), + ConfirmButton( + enabled: viewModel.isChecked, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CongratulationPage(), + ), + ); + }, + ), + ], + ), + ), + ), + ); + }, + ); + }, + ); + } + + Widget _buildInformationField() { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 30), + SvgPicture.asset("assets/icons/logo.svg"), + SizedBox(height: AppGaps.gap12), + Row( + children: [ + Text( + "๋ ˆ์ฆˆ๋น„์–ธ", + style: AppTypography.header2.copyWith( + color: AppColors.primary, + ), + ), + Text("์„ ์œ„ํ•œ", style: AppTypography.header2), + ], + ), + Text( + "๋” ๊นŠ์ด์žˆ๋Š” ๋งค์นญ ๊ณต๊ฐ„", + style: AppTypography.header2.copyWith(color: AppColors.grey900), + ), + SizedBox(height: AppGaps.gap36), + Text( + "CODE:L์€ ์—ฌ์„ฑ๋“ค์ด ์„œ๋กœ์˜ ๋งˆ์Œ์„ ๋‚˜๋ˆ„๊ณ ,\n์ธ์—ฐ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋Š” ์•ˆ์ „ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ์ž…๋‹ˆ๋‹ค.\n", + style: AppTypography.body1, + ), + Text( + "๊ฐ™์€ ๋งˆ์Œ์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ๋“ค๊ณผ ๋” ์•ˆ์ „ํ•˜๊ณ  ํŽธ์•ˆํ•˜๊ฒŒ\n์†Œํ†ตํ•  ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฐ„์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ๊ทœ์น™์„\n๊ผญ ์ง€์ผœ์ฃผ์„ธ์š”.", + style: AppTypography.body1, + ), + SizedBox(height: AppGaps.gap36), + Row( + children: [ + Icon(Icons.favorite, color: AppColors.error), + SizedBox(width: AppGaps.gap8), + Text("์—ฌ์„ฑ ์‚ฌ์šฉ์ž๋งŒ ๊ฐ€์ž… ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", style: AppTypography.subtitle3), + ], + ), + Row( + children: [ + Icon(Icons.favorite, color: AppColors.error), + SizedBox(width: AppGaps.gap8), + Text("๋ชจ๋“  ํ”„๋กœํ•„์€ ์‹ฌ์‚ฌ ํ›„ ์Šน์ธ๋ฉ๋‹ˆ๋‹ค.", style: AppTypography.subtitle3), + ], + ), + Row( + children: [ + Icon(Icons.favorite, color: AppColors.error), + SizedBox(width: AppGaps.gap8), + Text("์„œ๋กœ๋ฅผ ์กด์ค‘ํ•˜๋Š” ๋Œ€ํ™”๋ฅผ ์ง€ํ–ฅํ•ฉ๋‹ˆ๋‹ค.", style: AppTypography.subtitle3), + ], + ), + Row( + children: [ + Icon(Icons.favorite, color: AppColors.error), + SizedBox(width: AppGaps.gap8), + Text( + "์ฝ”๋“œ๊ฐ€ ๋งž๋Š” ์‚ฌ๋žŒ๊ณผ ๊นŠ์ด ์žˆ๋Š” ์—ฐ๊ฒฐ์„ ๋งŒ๋“ค์–ด ๊ฐ‘๋‹ˆ๋‹ค.", + style: AppTypography.subtitle3, + ), + ], + ), + SizedBox(height: AppGaps.gap36), + Container( + height: 114, + width: double.infinity, + alignment: Alignment.center, + color: AppColors.grey100, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("๐Ÿ’Œ์šด์˜์ง„์˜ ๋…ธ๋ ฅ", style: AppTypography.caption1), + SizedBox(height: AppGaps.gap8), + Flexible( + child: Text( + "CODE ๊ฐ€ ๋งž๋Š” ์‚ฌ๋žŒ๋“ค์˜ ์ง„์‹ฌ ์–ด๋ฆฐ ์—ฐ๊ฒฐ์„ ์œ„ํ•ด ์šด์˜์ง„์€\n์–ธ์ œ๋‚˜ ๋ณด์ด์ง€ ์•Š๋Š” ๊ณณ์—์„œ ์‹ ์ค‘ํžˆ ๊ณ ๋ฏผํ•˜๊ณ  ์ •์„ฑ์Šค๋Ÿฝ๊ฒŒ ๊ณต๊ฐ„์„\n์ง€์ผœ๊ฐ€๊ณ  ์žˆ์–ด์š”.", + style: AppTypography.caption1.copyWith( + color: AppColors.grey700, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/auth/presentation/pages/woman/woman_verification_viewmodel.dart b/lib/auth/presentation/pages/woman/woman_verification_viewmodel.dart new file mode 100644 index 0000000..3a26a5e --- /dev/null +++ b/lib/auth/presentation/pages/woman/woman_verification_viewmodel.dart @@ -0,0 +1,13 @@ +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class WomanVerificationViewmodel extends ChangeNotifier { + bool isChecked = false; + + void toggle() { + isChecked = !isChecked; + notifyListeners(); + } +} diff --git a/lib/auth/presentation/widgets/auth_confirm_button.dart b/lib/auth/presentation/widgets/auth_confirm_button.dart new file mode 100644 index 0000000..4e41969 --- /dev/null +++ b/lib/auth/presentation/widgets/auth_confirm_button.dart @@ -0,0 +1,40 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../core/utills/design/app_colors.dart'; +import '../../../core/utills/design/app_typography.dart'; + +class AuthConfirmButton extends StatelessWidget { + const AuthConfirmButton({ + super.key, + required this.enabled, + this.onPressed, + this.text = "ํ™•์ธ", + }); + + final bool enabled; + final VoidCallback? onPressed; + final String text; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: enabled ? onPressed : null, + child: Container( + height: 54, + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 16), + alignment: Alignment.center, + decoration: BoxDecoration( + color: enabled ? AppColors.primary : AppColors.grey200, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + style: AppTypography.subtitle2.copyWith( + color: enabled ? AppColors.white : AppColors.grey400, + ), + ), + ), + ); + } +} diff --git a/lib/auth/data/repositories/.gitkeep b/lib/chat/domain/model/.gitkeep similarity index 100% rename from lib/auth/data/repositories/.gitkeep rename to lib/chat/domain/model/.gitkeep diff --git a/lib/core/utills/design/app_colors.dart b/lib/core/utills/design/app_colors.dart new file mode 100644 index 0000000..0ae9df5 --- /dev/null +++ b/lib/core/utills/design/app_colors.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class AppColors { + static const primary = Color(0xFFFF77AD); + static const secondary = Color(0xFFFFA04C); + static const primaryLight = Color(0xFFFFF2FA); + static const secondaryLight = Color(0xFFFFE1C8); + static const white = Color(0xFFFFFFFF); + static const black = Color(0xFF000000); + static const grey100 = Color(0xFFF8F8F8); + static const grey200 = Color(0xFFF1F1F2); + static const grey300 = Color(0xFFE0E0E0); + static const grey400 = Color(0xFFCCCCCC); + static const grey500 = Color(0xFFB0B0B0); + static const grey600 = Color(0xFF999999); + static const grey700 = Color(0xFF666666); + static const grey800 = Color(0xFF333333); + static const grey900 = Color(0xFF222222); + static const error = Color(0xFFEF2B2A); + static const success = Color(0xFF27C937); + static const warning = Color(0xFFFFAA00); + static const kakao = Color(0xFFFEE500); +} diff --git a/lib/core/utills/design/app_gaps.dart b/lib/core/utills/design/app_gaps.dart new file mode 100644 index 0000000..0776454 --- /dev/null +++ b/lib/core/utills/design/app_gaps.dart @@ -0,0 +1,12 @@ +class AppGaps { + static const gap4 = 4.0; + static const gap8 = 8.0; + static const gap12 = 12.0; + static const gap16 = 16.0; + static const gap20 = 20.0; + static const gap24 = 24.0; + static const gap28 = 28.0; + static const gap32 = 32.0; + static const gap36 = 36.0; + static const gap40 = 40.0; +} diff --git a/lib/core/utills/design/app_theme.dart b/lib/core/utills/design/app_theme.dart new file mode 100644 index 0000000..17c5c76 --- /dev/null +++ b/lib/core/utills/design/app_theme.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +import 'app_colors.dart'; + +final lightTheme = ThemeData( + colorScheme: const ColorScheme.light( + primary: AppColors.primary, + secondary: AppColors.secondary, + ), +); diff --git a/lib/core/utills/design/app_typography.dart b/lib/core/utills/design/app_typography.dart new file mode 100644 index 0000000..339321f --- /dev/null +++ b/lib/core/utills/design/app_typography.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; + +class AppTypography { + static const header1 = TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + height: 10 / 7, + ); + static const header2 = TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + height: 17 / 12, + ); + static const header3 = TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + height: 3 / 2, + ); + static const subtitle1 = TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + height: 13 / 9, + ); + static const subtitle2 = TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + height: 11 / 8, + ); + static const subtitle3 = TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + height: 10 / 7, + ); + static const body1 = TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + height: 11 / 8, + ); + static const body2 = TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + height: 10 / 7, + ); + static const caption1 = TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + height: 3 / 2, + ); + static const caption2 = TextStyle( + fontSize: 12, + fontWeight: FontWeight.normal, + height: 3 / 2, + ); + static const caption3 = TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + height: 8 / 5, + ); + static const caption4 = TextStyle( + fontSize: 10, + fontWeight: FontWeight.normal, + height: 8 / 5, + ); +} diff --git a/lib/core/utills/network/auth_api_service.dart b/lib/core/utills/network/auth_api_service.dart new file mode 100644 index 0000000..b3fc21c --- /dev/null +++ b/lib/core/utills/network/auth_api_service.dart @@ -0,0 +1,18 @@ +import 'package:dio/dio.dart'; + +import '../../../auth/data/models/request/login_request.dart'; + +class AuthApiService { + final Dio _dio; + + AuthApiService(this._dio); + + Future login(LoginRequest request) async { + final response = await _dio.post( + '/v1/member/login', + data: request.toJson(), + ); + + return response; + } +} diff --git a/lib/core/utills/network/dio_provider.dart b/lib/core/utills/network/dio_provider.dart new file mode 100644 index 0000000..5c9a8b3 --- /dev/null +++ b/lib/core/utills/network/dio_provider.dart @@ -0,0 +1,14 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final dioProvider = Provider((ref) { + return Dio( + BaseOptions( + baseUrl: dotenv.env['BASE_URL'] ?? '', + connectTimeout: const Duration(seconds: 5), + receiveTimeout: const Duration(seconds: 5), + headers: {'Content-Type': 'application/json'}, + ), + ); +}); diff --git a/lib/auth/domain/entities/.gitkeep b/lib/core/utills/string/.gitkeep similarity index 100% rename from lib/auth/domain/entities/.gitkeep rename to lib/core/utills/string/.gitkeep diff --git a/lib/auth/presentation/pages/.gitkeep b/lib/home/domain/model/.gitkeep similarity index 100% rename from lib/auth/presentation/pages/.gitkeep rename to lib/home/domain/model/.gitkeep diff --git a/lib/main.dart b/lib/main.dart index 7b7f5b6..fec5c17 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,38 @@ +import 'package:code_l/auth/presentation/pages/login/login_page.dart'; +import 'package:code_l/auth/presentation/pages/terms_and_conditions/terms_and_condition_page.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_summary/profile_summary_page.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart'; +import 'package:screen_protector/screen_protector.dart'; -void main() { - runApp(const MyApp()); +main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + + await dotenv.load(fileName: ".env"); + + KakaoSdk.init(nativeAppKey: dotenv.env['KAKAO_NATIVE_APP_KEY']); + await ScreenProtector.preventScreenshotOn(); + + 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', + title: 'Kakao Login Demo', + home: LoginPage(), 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), - ), - 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, - ), - ], - ), + primarySwatch: Colors.blue, + scaffoldBackgroundColor: Colors.white, ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } diff --git a/lib/profie_registration/domain/repositories/.gitkeep b/lib/profie_registration/domain/repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/profie_registration/domain/usecases/.gitkeep b/lib/profie_registration/domain/usecases/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/profie_registration/presentation/pages/.gitkeep b/lib/profie_registration/presentation/pages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/profie_registration/presentation/widgets/.gitkeep b/lib/profie_registration/presentation/widgets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/auth/presentation/widgets/.gitkeep b/lib/sign_up/data/datasources/.gitkeep similarity index 100% rename from lib/auth/presentation/widgets/.gitkeep rename to lib/sign_up/data/datasources/.gitkeep diff --git a/lib/chat/domain/entities/.gitkeep b/lib/sign_up/data/models/.gitkeep similarity index 100% rename from lib/chat/domain/entities/.gitkeep rename to lib/sign_up/data/models/.gitkeep diff --git a/lib/core/utills/.gitkeep b/lib/sign_up/data/repositories/.gitkeep similarity index 100% rename from lib/core/utills/.gitkeep rename to lib/sign_up/data/repositories/.gitkeep diff --git a/lib/home/domain/entities/.gitkeep b/lib/sign_up/domain/model/.gitkeep similarity index 100% rename from lib/home/domain/entities/.gitkeep rename to lib/sign_up/domain/model/.gitkeep diff --git a/lib/sign_up/domain/model/profile_mbti/profile_mbti_state.dart b/lib/sign_up/domain/model/profile_mbti/profile_mbti_state.dart new file mode 100644 index 0000000..7ae2731 --- /dev/null +++ b/lib/sign_up/domain/model/profile_mbti/profile_mbti_state.dart @@ -0,0 +1,16 @@ +class MBTIState { + final String? ei, ns, tf, pj; + + const MBTIState({this.ei, this.ns, this.tf, this.pj}); + + MBTIState copyWith({String? ei, String? ns, String? tf, String? pj}) { + return MBTIState( + ei: ei ?? this.ei, + ns: ns ?? this.ns, + tf: tf ?? this.tf, + pj: pj ?? this.pj, + ); + } + + bool get isValid => ei != null && ns != null && tf != null && pj != null; +} diff --git a/lib/profie_registration/data/datasources/.gitkeep b/lib/sign_up/domain/repositories/.gitkeep similarity index 100% rename from lib/profie_registration/data/datasources/.gitkeep rename to lib/sign_up/domain/repositories/.gitkeep diff --git a/lib/profie_registration/data/models/.gitkeep b/lib/sign_up/domain/usecases/.gitkeep similarity index 100% rename from lib/profie_registration/data/models/.gitkeep rename to lib/sign_up/domain/usecases/.gitkeep diff --git a/lib/profie_registration/data/repositories/.gitkeep b/lib/sign_up/presentation/pages/.gitkeep similarity index 100% rename from lib/profie_registration/data/repositories/.gitkeep rename to lib/sign_up/presentation/pages/.gitkeep diff --git a/lib/sign_up/presentation/pages/code_age/code_age_page.dart b/lib/sign_up/presentation/pages/code_age/code_age_page.dart new file mode 100644 index 0000000..e31e794 --- /dev/null +++ b/lib/sign_up/presentation/pages/code_age/code_age_page.dart @@ -0,0 +1,92 @@ +import 'package:code_l/sign_up/presentation/pages/code_job/code_job_page.dart'; +import 'package:code_l/sign_up/presentation/widgets/sign_up_app_bar.dart'; +import 'package:code_l/sign_up/presentation/widgets/sign_up_confirm_button.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; + +class CodeAgePage extends StatelessWidget { + const CodeAgePage({super.key}); + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final isValid = true; // todo ์ƒํƒœ๊ด€๋ฆฌ + + return Scaffold( + appBar: SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: ConfirmButton( + enabled: isValid, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => CodeJobPage()), + ); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap24, + vertical: AppGaps.gap40, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("๋‚˜์ด๋ฅผ", style: AppTypography.header1), + Text("์ž…๋ ฅํ•ด์ฃผ์„ธ์š”", style: AppTypography.header1), + SizedBox(height: AppGaps.gap12), + SizedBox(height: AppGaps.gap40 + AppGaps.gap40 + AppGaps.gap40), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + decoration: InputDecoration( + hintText: '๋‚˜์ด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”', + hintStyle: AppTypography.header2.copyWith( + color: AppColors.grey400, + ), + contentPadding: EdgeInsets.symmetric( + vertical: AppGaps.gap8, + ), + border: UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.grey400, + width: 0.6, + ), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.grey400, + width: 0.6, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.grey400, + width: 0.6, + ), + ), + suffixIcon: IconButton( + icon: Icon(Icons.clear, size: 16), + onPressed: () => {/*todo ์‚ญ์ œ๋ฒ„ํŠผ*/}, + ), + ), + ), + SizedBox(height: AppGaps.gap4), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/code_job/code_job_page.dart b/lib/sign_up/presentation/pages/code_job/code_job_page.dart new file mode 100644 index 0000000..fcfa1b4 --- /dev/null +++ b/lib/sign_up/presentation/pages/code_job/code_job_page.dart @@ -0,0 +1,115 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class CodeJobPage extends ConsumerStatefulWidget { + const CodeJobPage({super.key}); + + @override + ConsumerState createState() => _JobSelectionPageState(); +} + +class _JobSelectionPageState extends ConsumerState { + String? selectedJob; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final isValid = true; // todo ์ƒํƒœ๊ด€๋ฆฌ + + final List> jobList = [ + {'label': '์ง์žฅ์ธ', 'image': 'assets/icons/ic_job_office_worker.png'}, + {'label': 'ํ•™์ƒ', 'image': 'assets/icons/ic_job_student.png'}, + {'label': '์ทจ์ค€์ƒ', 'image': 'assets/icons/ic_job_seeker.png'}, + {'label': 'ํ”„๋ฆฌ๋žœ์„œ', 'image': 'assets/icons/ic_job_freelancer.png'}, + {'label': '์ž์˜์—…์ž', 'image': 'assets/icons/ic_job_self_employed.png'}, + {'label': '๊ณต๋ฌด์›', 'image': 'assets/icons/ic_job_government_employee.png'}, + {'label': '์„œ๋น„์Šค์ง', 'image': 'assets/icons/ic_job_servicer.png'}, + {'label': '์˜๋ฃŒ์ธ', 'image': 'assets/icons/ic_job_healthcare.png'}, + {'label': '๊ต์œก์ž', 'image': 'assets/icons/ic_job_educator.png'}, + {'label': '์—”ํ„ฐํ…Œ์ด๋„ˆ', 'image': 'assets/icons/ic_job_entertainer.png'}, + {'label': 'ํฌ๋ฆฌ์—์ดํ„ฐ', 'image': 'assets/icons/ic_job_creator.png'}, + {'label': '๊ธฐํƒ€', 'image': 'assets/icons/ic_job_etc.png'}, + ]; + + return Scaffold( + appBar: SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: ConfirmButton(enabled: isValid), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap24, + vertical: AppGaps.gap40, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("์ง์—…์„", style: AppTypography.header1), + Text("์„ ํƒํ•ด์ฃผ์„ธ์š”", style: AppTypography.header1), + SizedBox(height: AppGaps.gap40), + Expanded( + child: GridView.count( + crossAxisCount: 3, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + childAspectRatio: 1.1, + children: + jobList.map((job) { + final isSelected = selectedJob == job['label']; + return GestureDetector( + onTap: () { + setState(() => selectedJob = job['label']); + }, + child: Container( + decoration: BoxDecoration( + color: + isSelected + ? AppColors.primaryLight + : AppColors.grey100, + border: Border.all( + color: + isSelected + ? AppColors.primary + : AppColors.grey100, + width: 1.5, + ), + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + job['image']!, + width: 16, + height: 22, + ), + SizedBox(height: AppGaps.gap4), + Text( + job['label'] ?? '', + style: AppTypography.body2, + ), + ], + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_drink_page.dart b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_drink_page.dart new file mode 100644 index 0000000..29680e6 --- /dev/null +++ b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_drink_page.dart @@ -0,0 +1,110 @@ +import 'package:code_l/sign_up/presentation/pages/code_lifestyle/widgets/code_lifestyle_check_icon.dart'; +import 'package:code_l/sign_up/presentation/widgets/sign_up_app_bar.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_confirm_button.dart'; +import 'code_lifestyle_smoke_page.dart'; + +class CodeLifestyleDrinkPage extends StatefulWidget { + const CodeLifestyleDrinkPage({super.key}); + + @override + State createState() => _CodeLifestyleDrinkPageState(); +} + +class _CodeLifestyleDrinkPageState extends State { + int? selectedIndex; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final isValid = true; + final drinkOptions = ['์Œ์ฃผ๋Š” ์•ˆํ•ด์š”', '์•ฝ์† ์žˆ์„ ๋•Œ๋งŒ ๋จน์–ด์š”', '์Œ์ฃผ๋ฅผ ์ฆ๊ธฐ๋Š” ๋งค๋‹ˆ์•„', '๊ธˆ์ฃผ์ค‘']; + + return Scaffold( + appBar: SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: ConfirmButton( + enabled: selectedIndex != null, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => CodeLifestyleSmokePage()), + ); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("์Œ์ฃผ ํ•˜์‹œ๋‚˜์š”?", style: AppTypography.header1), + SizedBox(height: AppGaps.gap12), + Text( + "์ผ์ฃผ์ผ์— ์–ผ๋งˆ๋‚˜ ์Œ์ฃผ๋ฅผ ์ฆ๊ธฐ์‹œ๋‚˜์š”?", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + SizedBox(height: AppGaps.gap40), + Expanded( + child: ListView.separated( + itemCount: drinkOptions.length, + separatorBuilder: + (_, __) => const SizedBox(height: AppGaps.gap16), + itemBuilder: (context, index) { + final item = drinkOptions[index]; + final isSelected = selectedIndex == index; + + return GestureDetector( + onTap: () { + setState(() { + selectedIndex = index; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 18, + horizontal: 20, + ), + decoration: BoxDecoration( + border: Border.all( + color: + isSelected + ? AppColors.primary + : AppColors.grey400, + ), + color: + isSelected + ? AppColors.primaryLight + : AppColors.white, + ), + child: Row( + children: [ + buildCheckIcon(isSelected), + const SizedBox(width: 10), + Text( + drinkOptions[index], + style: AppTypography.body2.copyWith( + color: AppColors.grey800, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_smoke_page.dart b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_smoke_page.dart new file mode 100644 index 0000000..2ed14aa --- /dev/null +++ b/lib/sign_up/presentation/pages/code_lifestyle/code_lifestyle_smoke_page.dart @@ -0,0 +1,118 @@ +import 'package:code_l/sign_up/presentation/pages/code_lifestyle/widgets/code_lifestyle_check_icon.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; +import '../code_name/code_name_page.dart'; + +class CodeLifestyleSmokePage extends StatefulWidget { + const CodeLifestyleSmokePage({super.key}); + + @override + State createState() => _CodeLifestyleDrinkPageState(); +} + +class _CodeLifestyleDrinkPageState extends State { + int? selectedIndex; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final isValid = true; + final drinkOptions = [ + 'ํก์—ฐ์ค‘์ด์—์š”', + '๊ฐ€๋” ํก์—ฐํ•ด์š”', + '๋น„ํก์—ฐ์ž์ง€๋งŒ ํก์—ฐ์— ๊ฑฐ๋ถ€๊ฐ์€ ์—†์–ด์š”', + '๋น„ํก์—ฐ์ž์ด๋ฉฐ, ํก์—ฐ์ž๋Š” ์›์น˜ ์•Š์•„์š”', + ]; + + return Scaffold( + appBar: SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: ConfirmButton( + enabled: selectedIndex != null, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => CodeNamePage()), + ); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("ํก์—ฐ์„ ํ•˜์‹œ๋‚˜์š”?", style: AppTypography.header1), + SizedBox(height: AppGaps.gap12), + Text( + "ํก์—ฐ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”!", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + // todo gap์ˆ˜์ • + SizedBox(height: AppGaps.gap40), + + Expanded( + child: ListView.separated( + itemCount: drinkOptions.length, + separatorBuilder: + (_, __) => const SizedBox(height: AppGaps.gap16), + itemBuilder: (context, index) { + final item = drinkOptions[index]; + final isSelected = selectedIndex == index; + + return GestureDetector( + onTap: () { + setState(() { + selectedIndex = index; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 18, + horizontal: 20, + ), + decoration: BoxDecoration( + border: Border.all( + color: + isSelected + ? AppColors.primary + : AppColors.grey400, + ), + color: + isSelected + ? AppColors.primaryLight + : AppColors.white, + ), + child: Row( + children: [ + buildCheckIcon(isSelected), + const SizedBox(width: 10), + Text( + drinkOptions[index], + style: AppTypography.body2.copyWith( + color: AppColors.grey800, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/code_lifestyle/widgets/code_lifestyle_check_icon.dart b/lib/sign_up/presentation/pages/code_lifestyle/widgets/code_lifestyle_check_icon.dart new file mode 100644 index 0000000..49495bc --- /dev/null +++ b/lib/sign_up/presentation/pages/code_lifestyle/widgets/code_lifestyle_check_icon.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; +import '../../../../../core/utills/design/app_gaps.dart'; + +Widget buildCheckIcon(bool isSelected) { + return Container( + width: AppGaps.gap24, + height: AppGaps.gap24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? AppColors.grey800 : AppColors.grey400, + width: 1.6, + ), + ), + child: Center( + child: Icon( + Icons.check, + size: AppGaps.gap16, + color: isSelected ? AppColors.grey800 : AppColors.grey400, + ), + ), + ); +} diff --git a/lib/sign_up/presentation/pages/code_name/code_name_page.dart b/lib/sign_up/presentation/pages/code_name/code_name_page.dart new file mode 100644 index 0000000..1900372 --- /dev/null +++ b/lib/sign_up/presentation/pages/code_name/code_name_page.dart @@ -0,0 +1,91 @@ +import 'package:code_l/core/utills/design/app_colors.dart'; +import 'package:code_l/core/utills/design/app_gaps.dart'; +import 'package:code_l/core/utills/design/app_typography.dart'; +import 'package:flutter/material.dart'; + +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class CodeNamePage extends StatelessWidget { + const CodeNamePage({super.key}); + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final isValid = true; // todo ์ƒํƒœ๊ด€๋ฆฌ + + return Scaffold( + appBar: SignUpAppBar(), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + _buildHeaderText(), + SizedBox(height: AppGaps.gap40 + AppGaps.gap40), + _buildCodeNameInputField(isValid, TextEditingController()), + Spacer(), + ConfirmButton(enabled: isValid, onPressed: () {}), + ], + ), + ), + ), + ); + } +} + +Widget _buildHeaderText() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("CODE:L์—์„œ ์‚ฌ์šฉํ• ", style: AppTypography.header1), + Text("์ฝ”๋“œ๋„ค์ž„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”", style: AppTypography.header1), + SizedBox(height: AppGaps.gap12), + Text( + "ํ•œ๊ธ€, ์ˆซ์ž ์ตœ๋Œ€ 10์ž / ๊ณต๋ฐฑ, ์˜๋ฌธ, ํŠน์ˆ˜๊ธฐํ˜ธ ๋ถˆ๊ฐ€", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + ], + ); +} + +Widget _buildCodeNameInputField( + bool isValid, + TextEditingController controller, +) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: controller, + decoration: InputDecoration( + hintText: '์ฝ”๋“œ๋„ค์ž„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”', + hintStyle: AppTypography.header2.copyWith(color: AppColors.grey400), + contentPadding: EdgeInsets.symmetric(vertical: AppGaps.gap8), + border: UnderlineInputBorder( + borderSide: BorderSide(color: AppColors.grey400, width: 0.6), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: isValid ? AppColors.success : AppColors.grey400, + width: 0.6, + ), + ), + suffixIcon: IconButton( + icon: Icon(Icons.clear, size: 16), + onPressed: () => controller.clear(), + ), + ), + ), + SizedBox(height: AppGaps.gap4), + Text( + isValid ? '๋ฉ‹์ง„ ์ฝ”๋“œ๋„ค์ž„์ด๋„ค์š”!' : 'ํ•œ๊ธ€, ์ˆซ์ž ์ตœ๋Œ€ 10๊ธ€์ž๊นŒ์ง€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค', + style: AppTypography.caption2.copyWith( + color: isValid ? AppColors.success : AppColors.error, + ), + ), + ], + ); +} diff --git a/lib/sign_up/presentation/pages/pending_approval/pending_approval_page.dart b/lib/sign_up/presentation/pages/pending_approval/pending_approval_page.dart new file mode 100644 index 0000000..57a8ed7 --- /dev/null +++ b/lib/sign_up/presentation/pages/pending_approval/pending_approval_page.dart @@ -0,0 +1,51 @@ +import 'package:code_l/auth/presentation/pages/login/login_page.dart'; +import 'package:code_l/sign_up/presentation/pages/pending_approval/widgets/pending_app_bar.dart'; +import 'package:code_l/sign_up/presentation/pages/pending_approval/widgets/pending_profile_button.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:gif/gif.dart'; + +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; + +class PendingApprovalPage extends StatelessWidget { + const PendingApprovalPage({super.key}); + + @override + Widget build(BuildContext context) { + var screenWidth = MediaQuery.of(context).size.width; + return Scaffold( + appBar: PendingAppBar(), + bottomNavigationBar: PendingProfileButton( + enabled: true, + onPressed: (){Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoginPage(), + ), + );} + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 130,width: screenWidth,), + Gif( + image: AssetImage("assets/icons/paper_tray.gif"), + autostart: Autostart.loop, + placeholder: (context) => + const Center(child: CircularProgressIndicator()), + ), + Text("์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค ์ฃผ์„ธ์š”",style: AppTypography.header1,), + SizedBox(height: 24,), + Text("์šด์˜์ง„๋“ค์ด ์‚ฌ์šฉ์ž๋‹˜์˜ ์ •๋ณด๋ฅผ",style: AppTypography.subtitle3,), + Text("ํ™•์ธ ์ค‘ ์ž…๋‹ˆ๋‹ค.",style: AppTypography.subtitle3,), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/pending_approval/widgets/pending_app_bar.dart b/lib/sign_up/presentation/pages/pending_approval/widgets/pending_app_bar.dart new file mode 100644 index 0000000..fb00ce1 --- /dev/null +++ b/lib/sign_up/presentation/pages/pending_approval/widgets/pending_app_bar.dart @@ -0,0 +1,33 @@ +import 'package:code_l/core/utills/design/app_colors.dart'; +import 'package:code_l/core/utills/design/app_typography.dart'; +import 'package:flutter/material.dart'; + +class PendingAppBar extends StatelessWidget implements PreferredSizeWidget { + const PendingAppBar({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, + children: [ + SizedBox(width: 40,), + Text("์Šน์ธ ๋Œ€๊ธฐ์ค‘",style: AppTypography.subtitle2,), + IconButton( + icon: Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/pending_approval/widgets/pending_profile_button.dart b/lib/sign_up/presentation/pages/pending_approval/widgets/pending_profile_button.dart new file mode 100644 index 0000000..e15e511 --- /dev/null +++ b/lib/sign_up/presentation/pages/pending_approval/widgets/pending_profile_button.dart @@ -0,0 +1,45 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; +import '../../../../../core/utills/design/app_gaps.dart'; +import '../../../../../core/utills/design/app_typography.dart'; + +class PendingProfileButton extends StatelessWidget { + final bool enabled; + final VoidCallback? onPressed; + final String text; + + const PendingProfileButton({ + super.key, + required this.enabled, + this.onPressed, + this.text = "์ž‘์„ฑํ•œ ์ฝ”๋“œํ”„๋กœํ•„ ๋ณด๊ธฐ", + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(AppGaps.gap24), + child: GestureDetector( + onTap: enabled ? onPressed : null, + child: Container( + height: 54, + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 16), + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColors.white, + border: Border.all( + color: enabled ? AppColors.primary : AppColors.grey400, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + style: AppTypography.subtitle2, + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_area/profile_area_page.dart b/lib/sign_up/presentation/pages/profile_area/profile_area_page.dart new file mode 100644 index 0000000..cc4556e --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_area/profile_area_page.dart @@ -0,0 +1,421 @@ +import 'package:code_l/sign_up/presentation/widgets/sign_up_app_bar.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileRegionPage extends StatefulWidget { + const ProfileRegionPage({super.key}); + + @override + State createState() => _ProfileRegionPageState(); +} + +class _ProfileRegionPageState extends State { + int? selectedMainRegionIndex; + int? selectedSubRegionIndex; + + final List mainRegions = [ + '์„œ์šธ', + '๊ฒฝ๊ธฐ', + '๋ถ€์‚ฐ', + '๋Œ€๊ตฌ', + '์ธ์ฒœ', + '๊ด‘์ฃผ', + '๋Œ€์ „', + '์šธ์‚ฐ', + '์„ธ์ข…', + '์ถฉ๋ถ', + '์ถฉ๋‚จ', + '์ „๋ถ', + '์ „๋‚จ', + '๊ฒฝ๋ถ', + '๊ฒฝ๋‚จ', + '์ œ์ฃผ', + ]; + + final Map> subRegions = { + '์„œ์šธ': [ + '๊ฐ•๋‚จ', + '๊ฐ•๋™', + '๊ฐ•๋ถ', + '๊ฐ•์„œ', + '๊ด€์•…', + '๊ด‘์ง„', + '๊ตฌ๋กœ', + '๊ธˆ์ฒœ', + '๋…ธ์›', + '๋„๋ด‰', + '๋™๋Œ€๋ฌธ', + '๋™์ž‘', + '๋งˆํฌ', + '์„œ๋Œ€๋ฌธ', + '์„œ์ดˆ', + '์„ฑ๋™', + '์„ฑ๋ถ', + '์†กํŒŒ', + '์–‘์ฒœ', + '์˜๋“ฑํฌ', + '์šฉ์‚ฐ', + '์€ํ‰', + '์ข…๋กœ', + '์ค‘๊ตฌ', + '์ค‘๋ž‘', + ], + '๊ฒฝ๊ธฐ': [ + '๊ฐ€ํ‰๊ตฐ', + '๊ณ ์–‘์‹œ', + '๋•์–‘๊ตฌ', + '์ผ์‚ฐ๋™๊ตฌ', + '์ผ์‚ฐ์„œ๊ตฌ', + '๊ณผ์ฒœ์‹œ', + '๊ด‘๋ช…์‹œ', + '๊ด‘์ฃผ์‹œ', + '๊ตฌ๋ฆฌ์‹œ', + '๊ตฐํฌ์‹œ', + '๊น€ํฌ์‹œ', + '๋‚จ์–‘์ฃผ์‹œ', + '๋™๋‘์ฒœ์‹œ', + '๋ถ€์ฒœ์‹œ', + '์†Œ์‚ฌ๊ตฌ', + '์˜ค์ •๊ตฌ', + '์›๋ฏธ๊ตฌ', + '์„ฑ๋‚จ์‹œ', + '๋ถ„๋‹น๊ตฌ', + '์ˆ˜์ •๊ตฌ', + '์ค‘์›๊ตฌ', + '์ˆ˜์›์‹œ', + '๊ถŒ์„ ๊ตฌ', + '์˜ํ†ต๊ตฌ', + '์žฅ์•ˆ๊ตฌ', + 'ํŒ”๋‹ฌ๊ตฌ', + '์‹œํฅ์‹œ', + '์•ˆ์‚ฐ์‹œ', + '๋‹จ์›๊ตฌ', + '์ƒ๋ก๊ตฌ', + '์•ˆ์„ฑ์‹œ', + '์•ˆ์–‘์‹œ', + '๋™์•ˆ๊ตฌ', + '๋งŒ์•ˆ๊ตฌ', + '์–‘์ฃผ์‹œ', + '์–‘ํ‰๊ตฐ', + '์—ฌ์ฃผ์‹œ', + '์—ฐ์ฒœ๊ตฐ', + '์˜ค์‚ฐ์‹œ', + '์šฉ์ธ์‹œ', + '๊ธฐํฅ๊ตฌ', + '์ˆ˜์ง€๊ตฌ', + '์ฒ˜์ธ๊ตฌ', + '์˜์™•์‹œ', + '์˜์ •๋ถ€์‹œ', + '์ด์ฒœ์‹œ', + 'ํŒŒ์ฃผ์‹œ', + 'ํ‰ํƒ์‹œ', + 'ํฌ์ฒœ์‹œ', + 'ํ•˜๋‚จ์‹œ', + 'ํ™”์„ฑ์‹œ', + ], + '๋ถ€์‚ฐ': [ + '๊ฐ•์„œ๊ตฌ', + '๊ธˆ์ •๊ตฌ', + '๊ธฐ์žฅ๊ตฐ', + '๋‚จ๊ตฌ', + '๋™๊ตฌ', + '๋™๋ž˜๊ตฌ', + '๋ถ€์‚ฐ์ง„๊ตฌ', + '๋ถ๊ตฌ', + '์‚ฌ์ƒ๊ตฌ', + '์‚ฌํ•˜๊ตฌ', + '์„œ๊ตฌ', + '์ˆ˜์˜๊ตฌ', + '์—ฐ์ œ๊ตฌ', + '์˜๋„๊ตฌ', + '์ค‘๊ตฌ', + 'ํ•ด์šด๋Œ€๊ตฌ', + 'ํ•ด์šด๋Œ€', + '์„œ๋ฉด', + '๋‚จํฌ๋™', + '๊ด‘์•ˆ๋ฆฌ', + '๊ธฐ์žฅ', + ], + '๋Œ€๊ตฌ': ['๊ตฐ์œ„๊ตฐ', '๋‚จ๊ตฌ', '๋‹ฌ์„œ๊ตฌ', '๋‹ฌ์„ฑ๊ตฐ', '๋™๊ตฌ', '๋ถ๊ตฌ', '์„œ๊ตฌ', '์ˆ˜์„ฑ๊ตฌ', '์ค‘๊ตฌ', '๋™์„ฑ๋กœ'], + '์ธ์ฒœ': [ + '๊ฐ•ํ™”๊ตฐ', + '๊ณ„์–‘๊ตฌ', + '๋‚จ๋™๊ตฌ', + '๋™๊ตฌ', + '๋ฏธ์ถ”ํ™€๊ตฌ', + '๋ถ€ํ‰๊ตฌ', + '์„œ๊ตฌ', + '์—ฐ์ˆ˜๊ตฌ', + '์˜น์ง„๊ตฐ', + '์ค‘๊ตฌ', + '์†ก๋„', + '๋ถ€ํ‰', + '์ฒญ๋ผ', + ], + '๊ด‘์ฃผ': ['์ƒ๋ฌด์ง€๊ตฌ', '์ถฉ์žฅ๋กœ', '๊ด‘์‚ฐ๊ตฌ', '๋‚จ๊ตฌ', '๋™๊ตฌ', '๋ถ๊ตฌ', '์„œ๊ตฌ'], + '๋Œ€์ „': ['๋Œ€๋•๊ตฌ', '๋™๊ตฌ', '์„œ๊ตฌ', '์œ ์„ฑ๊ตฌ', '์ค‘๊ตฌ'], + '์šธ์‚ฐ': ['๋‚จ๊ตฌ', '๋™๊ตฌ', '๋ถ๊ตฌ', '์šธ์ฃผ๊ตฐ', '์ค‘๊ตฌ'], + '์„ธ์ข…': ['์„ธ์ข… ์ „์ฒด'], + '์ถฉ๋ถ': [ + '๊ดด์‚ฐ๊ตฐ', + '๋‹จ์–‘๊ตฐ', + '๋ณด์€๊ตฐ', + '์˜๋™๊ตฐ', + '์˜ฅ์ฒœ๊ตฐ', + '์Œ์„ฑ๊ตฐ', + '์ œ์ฒœ์‹œ', + '์ฆํ‰๊ตฐ', + '์ง„์ฒœ๊ตฐ', + '์ฒญ์ฃผ์‹œ', + '์ƒ๋‹น๊ตฌ', + '์„œ์›๊ตฌ', + '์ฒญ์›๊ตฌ', + 'ํฅ๋•๊ตฌ', + '์ถฉ์ฃผ์‹œ', + ], + '์ถฉ๋‚จ': [ + '๊ณ„๋ฃก์‹œ', + '๊ณต์ฃผ์‹œ', + '๊ธˆ์‚ฐ๊ตฐ', + '๋…ผ์‚ฐ์‹œ', + '๋‹น์ง„์‹œ', + '๋ณด๋ น์‹œ', + '๋ถ€์—ฌ๊ตฐ', + '์„œ์‚ฐ์‹œ', + '์„œ์ฒœ๊ตฐ', + '์•„์‚ฐ์‹œ', + '์˜ˆ์‚ฐ๊ตฐ', + '์ฒœ์•ˆ์‹œ', + '๋™๋‚จ๊ตฌ', + '์„œ๋ถ๊ตฌ', + '์ฒญ์–‘๊ตฐ', + 'ํƒœ์•ˆ๊ตฐ', + 'ํ™์„ฑ๊ตฐ', + ], + '์ „๋ถ': [ + '๊ณ ์ฐฝ๊ตฐ', + '๊ตฐ์‚ฐ์‹œ', + '๊น€์ œ์‹œ', + '๋‚จ์›์‹œ', + '๋ฌด์ฃผ๊ตฐ', + '๋ถ€์•ˆ๊ตฐ', + '์ˆœ์ฐฝ๊ตฐ', + '์™„์ฃผ๊ตฐ', + '์ต์‚ฐ์‹œ', + '์ž„์‹ค๊ตฐ', + '์žฅ์ˆ˜๊ตฐ', + '์ „์ฃผ์‹œ', + '์ „์ฃผ์‹œ ๋•์ง„๊ตฌ', + '์ „์ฃผ์‹œ ์™„์‚ฐ๊ตฌ', + '์ •์์‹œ', + '์ง„์•ˆ๊ตฐ', + ], + '์ „๋‚จ': [ + '๊ฐ•์ง„๊ตฐ', + '๊ณ ํฅ๊ตฐ', + '๊ณก์„ฑ๊ตฐ', + '๊ด‘์–‘์‹œ', + '๊ตฌ๋ก€๊ตฐ', + '๋‚˜์ฃผ์‹œ', + '๋‹ด์–‘๊ตฐ', + '๋ชฉํฌ์‹œ', + '๋ฌด์•ˆ๊ตฐ', + '๋ณด์„ฑ๊ตฐ', + '์ˆœ์ฒœ์‹œ', + '์‹ ์•ˆ๊ตฐ', + '์—ฌ์ˆ˜์‹œ', + '์˜๊ด‘๊ตฐ', + '์˜์•”๊ตฐ', + '์™„๋„๊ตฐ', + '์žฅ์„ฑ๊ตฐ', + '์žฅํฅ๊ตฐ', + '์ง„๋„๊ตฐ', + 'ํ•จํ‰๊ตฐ', + 'ํ•ด๋‚จ๊ตฐ', + 'ํ™”์ˆœ๊ตฐ', + ], + '๊ฒฝ๋ถ': [ + '๊ฒฝ์‚ฐ์‹œ', + '๊ฒฝ์ฃผ์‹œ', + '๊ณ ๋ น๊ตฐ', + '๊ตฌ๋ฏธ์‹œ', + '๊ตฐ์œ„๊ตฐ', + '๊น€์ฒœ์‹œ', + '๋ฌธ๊ฒฝ์‹œ', + '๋ด‰ํ™”๊ตฐ', + '์ƒ์ฃผ์‹œ', + '์„ฑ์ฃผ๊ตฐ', + '์•ˆ๋™์‹œ', + '์˜๋•๊ตฐ', + '์˜์–‘๊ตฐ', + '์˜์ฃผ์‹œ', + '์˜์ฒœ์‹œ', + '์˜ˆ์ฒœ๊ตฐ', + '์šธ๋ฆ‰๊ตฐ', + '์šธ์ง„๊ตฐ', + '์˜์„ฑ๊ตฐ', + '์ฒญ๋„๊ตฐ', + '์ฒญ์†ก๊ตฐ', + '์น ๊ณก๊ตฐ', + 'ํฌํ•ญ์‹œ', + ], + '๊ฒฝ๋‚จ': [ + '๊ฑฐ์ œ์‹œ', + '๊ฑฐ์ฐฝ๊ตฐ', + '๊ณ ์„ฑ๊ตฐ', + '๊น€ํ•ด์‹œ', + '๋‚จํ•ด๊ตฐ', + '๋ฐ€์–‘์‹œ', + '์‚ฌ์ฒœ์‹œ', + '์‚ฐ์ฒญ๊ตฐ', + '์–‘์‚ฐ์‹œ', + '์˜๋ น๊ตฐ', + '์ง„์ฃผ์‹œ', + '์ฐฝ๋…•๊ตฐ', + '์ฐฝ์›์‹œ', + '์ฐฝ์›์‹œ ๋งˆ์‚ฐํ•ฉํฌ๊ตฌ', + '์ฐฝ์›์‹œ ๋งˆ์‚ฐํšŒ์›๊ตฌ', + '์ฐฝ์›์‹œ ์„ฑ์‚ฐ๊ตฌ', + '์ฐฝ์›์‹œ ์˜์ฐฝ๊ตฌ', + '์ฐฝ์›์‹œ ์ง„ํ•ด๊ตฌ', + 'ํ†ต์˜์‹œ', + 'ํ•˜๋™๊ตฐ', + 'ํ•จ์•ˆ๊ตฐ', + 'ํ•จ์–‘๊ตฐ', + 'ํ•ฉ์ฒœ๊ตฐ', + ], + '์ œ์ฃผ': ['์„œ๊ท€ํฌ์‹œ', '์ œ์ฃผ์‹œ'], + }; + + bool get isValid => + selectedMainRegionIndex != null && selectedSubRegionIndex != null; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + + return Scaffold( + appBar: const SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: isValid, + onPressed: () { + // TODO: ๋‹ค์Œ ํŽ˜์ด์ง€ ์ด๋™ + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("์ฃผ๋กœ ์–ด๋””์„œ\nํ™œ๋™ํ•˜์‹œ๋‚˜์š”?", style: AppTypography.header1), + SizedBox(height: AppGaps.gap40), + Expanded( + child: Row( + children: [ + Expanded( + child: ListView.builder( + itemCount: mainRegions.length, + itemBuilder: (context, index) { + final isSelected = selectedMainRegionIndex == index; + return GestureDetector( + onTap: () { + setState(() { + selectedMainRegionIndex = index; + selectedSubRegionIndex = null; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: isSelected ? null : AppColors.grey100, + ), + child: Center( + child: Text( + mainRegions[index], + style: AppTypography.body1.copyWith( + color: AppColors.grey900, + ), + ), + ), + ), + ); + }, + ), + ), + SizedBox(width: AppGaps.gap20), + Expanded( + child: + selectedMainRegionIndex == null + ? const SizedBox.shrink() + : ListView.builder( + itemCount: + subRegions[mainRegions[selectedMainRegionIndex!]]! + .length, + itemBuilder: (context, index) { + final subRegionName = + subRegions[mainRegions[selectedMainRegionIndex!]]![index]; + final isSelected = + selectedSubRegionIndex == index; + return GestureDetector( + onTap: () { + setState(() { + selectedSubRegionIndex = index; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 8, + ), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + subRegionName, + style: AppTypography.body1.copyWith( + color: + isSelected + ? AppColors.primary + : AppColors.grey900, + ), + ), + if (isSelected) + const Icon( + Icons.check, + color: AppColors.primary, + size: 20, + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/profile_face_image_page.dart b/lib/sign_up/presentation/pages/profile_image/profile_face_image_page.dart new file mode 100644 index 0000000..d77cbe3 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/profile_face_image_page.dart @@ -0,0 +1,85 @@ +import 'package:code_l/sign_up/presentation/pages/profile_image/providers.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileFaceImagePage extends ConsumerWidget { + const ProfileFaceImagePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final images = ref.watch(faceImagesProvider); + final viewModel = ref.read(faceImagesProvider.notifier); + final screenHeight = MediaQuery.of(context).size.height; + + return Scaffold( + appBar: const SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: images.length == 3, + onPressed: () { + // TODO: ์—…๋กœ๋“œ ํ›„ ๋‹ค์Œ ํŽ˜์ด์ง€ ์ด๋™ + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + + Text("ํŽ˜์ด์Šค ์ธ์ฆ ์‚ฌ์ง„์„\n๋“ฑ๋กํ•ด์ฃผ์„ธ์š”", style: AppTypography.header1), + const SizedBox(height: AppGaps.gap12), + Text( + "์‹ ๋ขฐ์žˆ๋Š” ๋งค์นญ ์œ„ํ•œ\n์–ผ๊ตด ์ธ์ฆ ์‚ฌ์ง„ 3์žฅ์„ ์˜ฌ๋ ค์ฃผ์„ธ์š”", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + const SizedBox(height: AppGaps.gap40), + + Text( + "์‚ฌ์ง„ ์ฒจ๋ถ€ ${images.length} / 3", + style: AppTypography.subtitle3.copyWith( + color: AppColors.grey900, + ), + ), + const SizedBox(height: 10), + + Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImageAddButton( + onTap: () => viewModel.pickImage(), + ), + ), + ...images.map( + (file) => Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImagePreview( + file: file, + onDelete: () => viewModel.removeImage(file), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/profile_image_page.dart b/lib/sign_up/presentation/pages/profile_image/profile_image_page.dart new file mode 100644 index 0000000..5547c8e --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/profile_image_page.dart @@ -0,0 +1,91 @@ +import 'package:code_l/sign_up/presentation/pages/profile_image/profile_face_image_page.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/providers.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileImagePage extends ConsumerWidget { + const ProfileImagePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final images = ref.watch(profileImagesProvider); + final viewModel = ref.read(profileImagesProvider.notifier); + final screenHeight = MediaQuery.of(context).size.height; + + return Scaffold( + appBar: const SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: images.isNotEmpty, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ProfileFaceImagePage(), + ), + ); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + + Text("ํ”„๋กœํ•„ ์‚ฌ์ง„์„\n๋“ฑ๋กํ•ด์ฃผ์„ธ์š”", style: AppTypography.header1), + const SizedBox(height: AppGaps.gap12), + Text( + "๋‚˜๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์ง„์„\n์ตœ์†Œ 1~์ตœ๋Œ€ 3์žฅ ๋“ฑ๋กํ•ด์ฃผ์„ธ์š”", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + const SizedBox(height: AppGaps.gap40), + + Text( + "์‚ฌ์ง„ ์ฒจ๋ถ€ ${images.length} / 3", + style: AppTypography.subtitle3.copyWith( + color: AppColors.grey900, + ), + ), + const SizedBox(height: 10), + + Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImageAddButton( + onTap: () => viewModel.pickImage(), + ), + ), + ...images.map( + (file) => Padding( + padding: const EdgeInsets.only(right: 12), + child: ProfileImagePreview( + file: file, + onDelete: () => viewModel.removeImage(file), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart b/lib/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart new file mode 100644 index 0000000..e980cd3 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart @@ -0,0 +1,21 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; + +class ProfileImageViewModel extends StateNotifier> { + ProfileImageViewModel() : super([]); + + final _picker = ImagePicker(); + + Future pickImage() async { + final pickedList = await _picker.pickMultiImage(); + if (pickedList.isNotEmpty) { + final maxAllowed = 3 - state.length; + final limited = pickedList.take(maxAllowed).toList(); + state = [...state, ...limited]; + } + } + + void removeImage(XFile file) { + state = [...state]..remove(file); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/providers.dart b/lib/sign_up/presentation/pages/profile_image/providers.dart new file mode 100644 index 0000000..18fb03b --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/providers.dart @@ -0,0 +1,13 @@ +import 'package:code_l/sign_up/presentation/pages/profile_image/profile_image_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; + +final profileImagesProvider = + StateNotifierProvider>( + (ref) => ProfileImageViewModel(), + ); + +final faceImagesProvider = + StateNotifierProvider>( + (ref) => ProfileImageViewModel(), + ); diff --git a/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart new file mode 100644 index 0000000..0441b1a --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_add_button.dart @@ -0,0 +1,50 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; + +class ProfileImageAddButton extends StatelessWidget { + final VoidCallback onTap; + + const ProfileImageAddButton({super.key, required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColors.grey100, + borderRadius: BorderRadius.circular(24), + ), + child: const Center( + child: Icon(Icons.add, size: 16, color: AppColors.grey300), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Container( + width: 24, + height: 24, + decoration: const BoxDecoration( + color: AppColors.grey900, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.camera_alt, + color: Colors.white, + size: 12, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart new file mode 100644 index 0000000..fede688 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_image/widgets/profile_image_preview.dart @@ -0,0 +1,52 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../../../../../core/utills/design/app_colors.dart'; + +class ProfileImagePreview extends StatelessWidget { + final XFile file; + final VoidCallback onDelete; + + const ProfileImagePreview({ + super.key, + required this.file, + required this.onDelete, + }); + + @override + Widget build(BuildContext context) { + return Stack( + clipBehavior: Clip.none, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Image.file( + File(file.path), + width: 64, + height: 64, + fit: BoxFit.cover, + ), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: onDelete, + child: Container( + width: 24, + height: 24, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: AppColors.grey900, + ), + child: const Icon(Icons.close, size: 12, color: Colors.white), + ), + ), + ), + ], + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart b/lib/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart new file mode 100644 index 0000000..99b0f5f --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_interest/profile_intereset_page.dart @@ -0,0 +1,141 @@ +import 'package:code_l/sign_up/presentation/widgets/sign_up_app_bar.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileInterestPage extends StatefulWidget { + const ProfileInterestPage({super.key}); + + @override + State createState() => _ProfileInterestPageState(); +} + +class _ProfileInterestPageState extends State { + // todo ์ƒํƒœ๊ด€๋ฆฌ + final List interestOptions = [ + '๊ฒŒ์ž„ ๐ŸŽฎ', + '๋ฐฉํƒˆ์ถœ/๋ณด๋“œ๊ฒŒ์ž„ ๐ŸŽฒ', + '์•ผ์™ธํ™œ๋™ ๐Ÿšด', + '์šด๋™ ๐Ÿ‹๏ธ', + '๋…์„œ & ๊ธ€์“ฐ๊ธฐ ๐Ÿ“šโœ๏ธ', + '์Œ์•… & ์•…๊ธฐ ๐ŸŽต๐ŸŽธ', + '์˜ํ™” ๐ŸŽฌ', + '์บ ํ•‘ ๐Ÿ•๏ธ', + 'OTT/๋“œ๋ผ๋งˆ ๐Ÿ“บ', + '์—ฌํ–‰ โœˆ', + '์š”๋ฆฌ ๐Ÿณ', + '๋ง›์ง‘ ํƒ๋ฐฉ ๐Ÿฝ', + 'ํŒจ์…˜ & ๋ทฐํ‹ฐ ๐Ÿ‘—๐Ÿ’„', + '์‡ผํ•‘ ๐Ÿ›', + '๋™๋ฌผ ๐Ÿถ๐Ÿฑ', + 'IT๊ธฐ์ˆ  ๐Ÿ’ป', + '๋Œ„์Šค & ํผํฌ๋จผ์Šค ๐Ÿ’ƒ๐Ÿ•บ', + '์ธํ…Œ๋ฆฌ์–ด & DIY ๐Ÿ ๐Ÿ”จ', + '์‚ฌ์ง€ & ์˜์ƒ ์ œ์ž‘ ๐ŸŽฅ', + '์ „์‹œ๊ด€๋žŒ ๐Ÿ–ผ', + ]; + + final List selectedIndexes = []; + final int maxSelection = 5; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final isValid = selectedIndexes.isNotEmpty; + + return Scaffold( + appBar: SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: isValid, + onPressed: () { + // TODO: ๋‹ค์Œ ํŽ˜์ด์ง€ ์ด๋™ ๋กœ์ง + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("์–ด๋–ค ์ฃผ์ œ๋ฅผ ์ข‹์•„ํ•˜์„ธ์š”?", style: AppTypography.header1), + SizedBox(height: AppGaps.gap12), + Text( + "๊ด€์‹ฌ ์žˆ๋Š” ์ฃผ์ œ๋ฅผ ์ตœ๋Œ€ 5๊ฐœ๊นŒ์ง€ ์„ ํƒํ•ด์ฃผ์„ธ์š”!\n๋‚˜์—๊ฒŒ ๋”ฑ ๋งž๋Š” CODE ์‚ฌ์šฉ์ž๋ฅผ ์ถ”์ฒœํ•ด๋“œ๋ ค์š”", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + SizedBox(height: AppGaps.gap20), + Align( + alignment: Alignment.centerRight, + child: Text( + "${selectedIndexes.length} / $maxSelection", + style: AppTypography.body2.copyWith(color: AppColors.grey500), + ), + ), + SizedBox(height: AppGaps.gap20), + Expanded( + child: SingleChildScrollView( + child: Wrap( + spacing: 12, + runSpacing: 12, + children: List.generate(interestOptions.length, (index) { + final isSelected = selectedIndexes.contains(index); + + return GestureDetector( + onTap: () { + setState(() { + if (isSelected) { + selectedIndexes.remove(index); + } else { + if (selectedIndexes.length < maxSelection) { + selectedIndexes.add(index); + } + } + }); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ), + decoration: BoxDecoration( + border: Border.all( + color: + isSelected + ? AppColors.primary + : AppColors.grey400, + ), + color: + isSelected + ? AppColors.primaryLight + : AppColors.white, + borderRadius: BorderRadius.circular(100), + ), + child: Text( + interestOptions[index], + style: AppTypography.body2.copyWith( + color: AppColors.grey800, + ), + ), + ), + ); + }), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_introduce/profile_introduce_page.dart b/lib/sign_up/presentation/pages/profile_introduce/profile_introduce_page.dart new file mode 100644 index 0000000..6dbfc58 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_introduce/profile_introduce_page.dart @@ -0,0 +1,107 @@ +import 'package:code_l/core/utills/design/app_colors.dart'; +import 'package:code_l/core/utills/design/app_gaps.dart'; +import 'package:code_l/core/utills/design/app_typography.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_introduce/providers.dart'; +import 'package:flutter/material.dart'; + +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../profile_image/profile_image_page.dart'; +import 'profile_introduce_viewmodel.dart'; + +class ProfileIntroducePage extends ConsumerWidget { + const ProfileIntroducePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final screenHeight = MediaQuery.of(context).size.height; + final state = ref.watch(profileIntroduceProvider); + final notifier = ref.read(profileIntroduceProvider.notifier); + + return Scaffold( + appBar: SignUpAppBar(), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(AppGaps.gap24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + _buildHeaderText(), + SizedBox(height: AppGaps.gap40 + AppGaps.gap40), + _buildCodeNameInputField(state.introduction, notifier), + Spacer(), + ConfirmButton( + enabled: state.isValid, + onPressed: + state.isValid + ? () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProfileImagePage(), + ), + ), + } + : null, + ), + ], + ), + ), + ), + ); + } + + Widget _buildHeaderText() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("๊ฐ„๋‹จํ•˜๊ฒŒ", style: AppTypography.header1), + Text("๋‚˜๋ฅผ ์†Œ๊ฐœํ•ด์ฃผ์„ธ์š”", style: AppTypography.header1), + SizedBox(height: AppGaps.gap12), + Text( + "์ž๊ธฐ์†Œ๊ฐœ๋กœ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์—๊ฒŒ ๋‚˜๋ฅผ ์–ดํ•„ํ•ด๋ณด์„ธ์š”!", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + ], + ); + } + + Widget _buildCodeNameInputField( + String introduction, + ProfileIntroduceViewmodel notifier, + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + onChanged: notifier.updateIntroduction, + decoration: InputDecoration( + hintText: '์ž๊ธฐ์†Œ๊ฐœ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”', + hintStyle: AppTypography.header2.copyWith(color: AppColors.grey400), + contentPadding: EdgeInsets.symmetric(vertical: AppGaps.gap8), + border: UnderlineInputBorder( + borderSide: BorderSide(color: AppColors.grey400, width: 0.6), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: AppColors.grey400, width: 0.6), + ), + ), + ), + SizedBox(height: AppGaps.gap4), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(introduction.length.toString(), style: AppTypography.caption3), + Text( + " / 15", + style: AppTypography.caption3.copyWith(color: AppColors.grey500), + ), + ], + ), + ], + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_introduce/profile_introduce_viewmodel.dart b/lib/sign_up/presentation/pages/profile_introduce/profile_introduce_viewmodel.dart new file mode 100644 index 0000000..e23e02f --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_introduce/profile_introduce_viewmodel.dart @@ -0,0 +1,32 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class ProfileIntroduceState { + final String introduction; + final bool isValid; + + ProfileIntroduceState({ + this.introduction = '', + this.isValid = false, + }); + + ProfileIntroduceState copyWith({ + String? introduction, + bool? isValid, + }) { + return ProfileIntroduceState( + introduction: introduction ?? this.introduction, + isValid: isValid ?? this.isValid, + ); + } +} + +class ProfileIntroduceViewmodel extends StateNotifier { + ProfileIntroduceViewmodel() : super(ProfileIntroduceState()); + + void updateIntroduction(String introduction) { + state = state.copyWith( + introduction: introduction, + isValid: introduction.isNotEmpty && introduction.length <= 15, + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_introduce/providers.dart b/lib/sign_up/presentation/pages/profile_introduce/providers.dart new file mode 100644 index 0000000..1be260c --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_introduce/providers.dart @@ -0,0 +1,6 @@ +import 'package:code_l/sign_up/presentation/pages/profile_introduce/profile_introduce_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final profileIntroduceProvider = + StateNotifierProvider( + (ref) => ProfileIntroduceViewmodel()); diff --git a/lib/sign_up/presentation/pages/profile_lovestyle/profile_lovestyle_page.dart b/lib/sign_up/presentation/pages/profile_lovestyle/profile_lovestyle_page.dart new file mode 100644 index 0000000..0ed62f7 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_lovestyle/profile_lovestyle_page.dart @@ -0,0 +1,213 @@ +import 'package:code_l/sign_up/presentation/widgets/sign_up_app_bar.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileLoveStylePage extends StatefulWidget { + const ProfileLoveStylePage({super.key}); + + @override + State createState() => _ProfileLoveStylePageState(); +} + +class _ProfileLoveStylePageState extends State { + int? selectedRelationshipIndex; + int? selectedAffectionIndex; + int? selectedCommunicationIndex; + int? selectedDateIndex; + int? selectedConflictIndex; + + final List relationshipStyles = [ + '์ฒœ์ฒœํžˆ ์•Œ์•„๊ฐ€๋Š” ์Šคํƒ€์ผ โณ', + '๋งŒ๋‚˜๋ฉด ์šด๋ช…์ด๋ผ๊ณ  ๋ฏฟ๋Š” ์ง์ง„ํ˜• ๐Ÿ’ซ', + '์•ˆ์ •์ ์ด๊ณ  ์‹ ๋ขฐ๊ฐ ์žˆ๋Š” ์—ฐ์•  ์„ ํ˜ธ ๐Ÿก', + '์„œ๋กœ์˜ ์„ฑ์žฅ๊ณผ ์ž๋ฆฝ์„ ์ค‘์š”์‹œํ•˜๋Š” ์Šคํƒ€์ผ ๐ŸŒฑ', + 'ํ•ญ์ƒ ์ƒˆ๋กญ๊ณ  ์„ค๋ ˆ๋Š” ์—ฐ์• ๋ฅผ ์ถ”๊ตฌํ•˜๋Š” ์Šคํƒ€์ผ ๐Ÿ’ƒ', + ]; + + final List communicationStyles = [ + 'ํ•˜๋ฃจ ์ข…์ผ ์—ฐ๋ฝํ•˜๋Š” ์Šคํƒ€์ผ ๐Ÿ“ฒ', + '์ค‘์š”ํ•œ ์ˆœ๊ฐ„์—๋งŒ ์—ฐ๋ฝํ•˜๋Š” ์Šคํƒ€์ผ ๐Ÿ•ฐ', + '์ „ํ™”๋ณด๋‹ค ํ†ก์„ ์„ ํ˜ธํ•˜๋Š” ์Šคํƒ€์ผ ๐Ÿ’ฌ', + 'ํ•˜๋ฃจ ํ•œ ํ†ต์ด๋ผ๋„ ์ง„์‹ฌ์„ ๋‹ด๋Š” ์Šคํƒ€์ผ ๐Ÿ’Œ', + '์ƒ๋Œ€๊ฐ€ ํ•„์š”ํ•  ๋•Œ ํ•ญ์ƒ ๋จผ์ € ์—ฐ๋ฝํ•˜๋Š” ์Šคํƒ€์ผ ๐Ÿ™‹โ€โ™‚๏ธ', + ]; + + final List dateStyles = [ + '์ง‘์—์„œ ํ•จ๊ป˜ ์˜ํ™” ๋ณด๋Š” ํ™ˆ ๋ฐ์ดํŠธ ๐Ÿ ', + '๋ง›์ง‘ ํƒ๋ฐฉํ•˜๊ณ  ์ƒˆ๋กœ์šด ๊ณณ ์ฐพ๊ธฐ ๐Ÿ”', + '์•ผ์™ธํ™œ๋™์ด๋‚˜ ์Šคํฌ์ธ  ์ฆ๊ธฐ๊ธฐ ๐Ÿšดโ€โ™‚๏ธ', + '๋ฌธํ™”์ƒํ™œ (์ „์‹œํšŒ, ๊ณต์—ฐ ๋“ฑ) ์ฆ๊ธฐ๊ธฐ ๐ŸŽญ', + '๊ณ„ํš์ ์ด๊ณ  ์ฒ ์ €ํ•œ ๋ฐ์ดํŠธ ์Šค์ผ€์ค„๋Ÿฌ ๐Ÿ“…', + '์ฆ‰ํฅ์ ์œผ๋กœ ๋†€๋Ÿฌ ๊ฐ€๋Š” ์Šคํƒ€์ผ ๐ŸŒ', + ]; + + final List conflictResolutionStyles = [ + '๋ฐ”๋กœ ๋Œ€ํ™”ํ•ด์„œ ํ‘ธ๋Š” ์ง์„คํ˜•๐Ÿ’ฌ', + '์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„ ์ฐจ๋ถ„ํ•˜๊ฒŒ ์ด์•ผ๊ธฐํ•˜๋Š” ์Šคํƒ€์ผ๐Ÿคซ', + '์ƒ๋Œ€๋ฐฉ์˜ ์ž…์žฅ์„ ๋จผ์ € ์ดํ•ดํ•˜๋Š” ๋ฐฐ๋ คํ˜•๐Ÿค', + '์ž˜๋ชป์„ ๋น ๋ฅด๊ฒŒ ์ธ์ •ํ•˜๊ณ  ์‚ฌ๊ณผํ•˜๋Š” ์†”์งํ˜•๐Ÿ™‡โ€โ™‚๏ธ', + 'ํ™”๊ฐ€ ๋‚˜๋„ ์กฐ์šฉํžˆ ๋งˆ์Œ์„ ๊ฐ€๋ผ์•‰ํžˆ๋Š” ์นจ์ฐฉํ˜•๐Ÿง˜โ€โ™€๏ธ', + ]; + + final List affectionExpressions = [ + 'ํ‘œํ˜„์„ ์ž˜ํ•˜๋Š” ์ง์ง„ํ˜• ๐Ÿ’ฌ', + '์€๊ทผํ•˜๊ฒŒ ๋งˆ์Œ์„ ์ „ํ•˜๋Š” ๋ฌด๋š๋šํ˜• ๐Ÿ˜Ž', + '๋ง๋ณด๋‹ค๋Š” ํ–‰๋™์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ์‹ค์ฒœํŒŒ ๐Ÿšถโ€โ™‚', + '์• ๊ต ๋งŽ๊ณ  ๊ท€์—ฌ์šด ์Šคํƒ€์ผ ๐Ÿฅฐ', + '์ง„์ง€ํ•˜๊ณ  ๊นŠ์ด ์žˆ๋Š” ์Šคํƒ€์ผ ๐Ÿค”', + '๋‹ค์ •๋‹ค๊ฐํ•œ ๋ฐฐ๋ คํ˜• ๐Ÿ’•', + ]; + + bool get isValid => + selectedRelationshipIndex != null && + selectedAffectionIndex != null && + selectedCommunicationIndex != null && + selectedDateIndex != null && + selectedConflictIndex != null; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + + return Scaffold( + appBar: const SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: isValid, + onPressed: () { + // TODO: ๋‹ค์Œ ํŽ˜์ด์ง€ ์ด๋™ ๋กœ์ง + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("์–ด๋–ค ์—ฐ์• ์Šคํƒ€์ผ์„\n์„ ํ˜ธํ•˜์‹œ๋‚˜์š”?", style: AppTypography.header1), + SizedBox(height: AppGaps.gap12), + Text( + "๋‚ด๊ฐ€ ์„ ํ˜ธํ•˜๋Š” ์—ฐ์• ์Šคํƒ€์ผ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”", + style: AppTypography.body2.copyWith(color: AppColors.grey600), + ), + SizedBox(height: AppGaps.gap40), + Text("์—ฐ์•  ๊ฐ€์น˜๊ด€", style: AppTypography.subtitle1), + SizedBox(height: AppGaps.gap12), + ...List.generate( + relationshipStyles.length, + (index) => _buildSelectableItem( + text: relationshipStyles[index], + isSelected: selectedRelationshipIndex == index, + onTap: () { + setState(() { + selectedRelationshipIndex = index; + }); + }, + ), + ), + SizedBox(height: AppGaps.gap32), + Text("์• ์ • ํ‘œํ˜„", style: AppTypography.subtitle1), + SizedBox(height: AppGaps.gap12), + ...List.generate( + affectionExpressions.length, + (index) => _buildSelectableItem( + text: affectionExpressions[index], + isSelected: selectedAffectionIndex == index, + onTap: () { + setState(() { + selectedAffectionIndex = index; + }); + }, + ), + ), + SizedBox(height: AppGaps.gap32), + Text("์—ฐ๋ฝ", style: AppTypography.subtitle1), + SizedBox(height: AppGaps.gap12), + ...List.generate( + communicationStyles.length, + (index) => _buildSelectableItem( + text: communicationStyles[index], + isSelected: selectedCommunicationIndex == index, + onTap: () { + setState(() { + selectedCommunicationIndex = index; + }); + }, + ), + ), + SizedBox(height: AppGaps.gap32), + Text("๋ฐ์ดํŠธ", style: AppTypography.subtitle1), + SizedBox(height: AppGaps.gap12), + ...List.generate( + dateStyles.length, + (index) => _buildSelectableItem( + text: dateStyles[index], + isSelected: selectedDateIndex == index, + onTap: () { + setState(() { + selectedDateIndex = index; + }); + }, + ), + ), + SizedBox(height: AppGaps.gap32), + Text("๊ฐˆ๋“ฑ ํ•ด๊ฒฐ", style: AppTypography.subtitle1), + SizedBox(height: AppGaps.gap12), + ...List.generate( + conflictResolutionStyles.length, + (index) => _buildSelectableItem( + text: conflictResolutionStyles[index], + isSelected: selectedConflictIndex == index, + onTap: () { + setState(() { + selectedConflictIndex = index; + }); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildSelectableItem({ + required String text, + required bool isSelected, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryLight : AppColors.grey100, + border: Border.all( + color: isSelected ? AppColors.primary : AppColors.grey300, + width: isSelected ? 1.0 : 0.0, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + style: AppTypography.body2.copyWith(color: AppColors.grey800), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_page.dart b/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_page.dart new file mode 100644 index 0000000..2e3d9ab --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_page.dart @@ -0,0 +1,124 @@ +import 'package:code_l/sign_up/presentation/pages/profile_introduce/profile_introduce_page.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_mbti/providers.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../core/utills/design/app_colors.dart'; +import '../../../../core/utills/design/app_gaps.dart'; +import '../../../../core/utills/design/app_typography.dart'; +import '../../widgets/sign_up_app_bar.dart'; +import '../../widgets/sign_up_confirm_button.dart'; + +class ProfileMBTIPage extends ConsumerWidget { + const ProfileMBTIPage({super.key}); + + final mbtiOptions = const [ + ['E', 'I'], + ['N', 'S'], + ['T', 'F'], + ['P', 'J'], + ]; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final screenHeight = MediaQuery.of(context).size.height; + final mbtiState = ref.watch(profileMBTIProvider); + final viewModel = ref.read(profileMBTIProvider.notifier); + + return Scaffold( + appBar: const SignUpAppBar(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppGaps.gap20, + vertical: 34, + ), + child: ConfirmButton( + enabled: mbtiState.isValid, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: + (context) => ProfileIntroducePage()), + ); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: AppGaps.gap20), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.05), + Text("MBTI๋ฅผ\n์„ ํƒํ•ด์ฃผ์„ธ์š”", style: AppTypography.header1), + const SizedBox(height: AppGaps.gap40), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: mbtiOptions.length, + itemBuilder: (context, groupIndex) { + final group = mbtiOptions[groupIndex]; + return Padding( + padding: const EdgeInsets.only(bottom: AppGaps.gap20), + child: Row( + children: + group.map((option) { + final isSelected = viewModel.isSelected( + groupIndex, + option, + ); + return Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6, + ), + child: GestureDetector( + onTap: + () => viewModel.select( + groupIndex, + option, + ), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 74, + vertical: 34, + ), + decoration: BoxDecoration( + color: + isSelected + ? AppColors.primaryLight + : AppColors.grey100, + border: Border.all( + color: + isSelected + ? AppColors.primary + : AppColors.grey100, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + option, + style: AppTypography.body1.copyWith( + color: AppColors.grey900, + ), + ), + ), + ), + ), + ), + ); + }).toList(), + ), + ); + }, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_viewmodel.dart b/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_viewmodel.dart new file mode 100644 index 0000000..ccbc8b0 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_mbti/profile_mbti_viewmodel.dart @@ -0,0 +1,27 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../domain/model/profile_mbti/profile_mbti_state.dart'; + +class ProfileMBTIViewModel extends StateNotifier { + ProfileMBTIViewModel() : super(const MBTIState()); + + void select(int groupIndex, String value) { + state = switch (groupIndex) { + 0 => state.copyWith(ei: value), + 1 => state.copyWith(ns: value), + 2 => state.copyWith(tf: value), + 3 => state.copyWith(pj: value), + _ => state, + }; + } + + bool isSelected(int groupIndex, String value) { + return switch (groupIndex) { + 0 => state.ei == value, + 1 => state.ns == value, + 2 => state.tf == value, + 3 => state.pj == value, + _ => false, + }; + } +} diff --git a/lib/sign_up/presentation/pages/profile_mbti/providers.dart b/lib/sign_up/presentation/pages/profile_mbti/providers.dart new file mode 100644 index 0000000..90f47cd --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_mbti/providers.dart @@ -0,0 +1,9 @@ +import 'package:code_l/sign_up/presentation/pages/profile_mbti/profile_mbti_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../domain/model/profile_mbti/profile_mbti_state.dart'; + +final profileMBTIProvider = + StateNotifierProvider( + (ref) => ProfileMBTIViewModel(), + ); diff --git a/lib/sign_up/presentation/pages/profile_summary/profile_summary_page.dart b/lib/sign_up/presentation/pages/profile_summary/profile_summary_page.dart new file mode 100644 index 0000000..e8ac819 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_summary/profile_summary_page.dart @@ -0,0 +1,270 @@ +import 'package:code_l/core/utills/design/app_gaps.dart'; +import 'package:code_l/core/utills/design/app_typography.dart'; +import 'package:code_l/sign_up/presentation/pages/profile_summary/widgets/profile_summary_app_bar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/utills/design/app_colors.dart'; + +class ProfileSummaryPage extends StatelessWidget { + ProfileSummaryPage({Key? key}) : super(key: key); + final PageController pageController = PageController(); + final ValueNotifier currentPageNotifier = ValueNotifier(0); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ProfileSummaryAppBar(), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildCodeProfileImage([ + 'https://i.namu.wiki/i/26zrz9vXNoJuC8XAawLsvE54JAHRKgQZcpx0SQ1g60r3Nn80nqpXFK0VBulBYNuuayrcCpli4u8jUMUm_1n5CIo84vxWbQcN46w0Ah_VS2hJZ0m88_jTWwKWtij1_m38H1G-Py_8fdiHrSWKyWAWCQ.webp', + 'https://i.namu.wiki/i/26zrz9vXNoJuC8XAawLsvE54JAHRKgQZcpx0SQ1g60r3Nn80nqpXFK0VBulBYNuuayrcCpli4u8jUMUm_1n5CIo84vxWbQcN46w0Ah_VS2hJZ0m88_jTWwKWtij1_m38H1G-Py_8fdiHrSWKyWAWCQ.webp', + 'https://i.namu.wiki/i/26zrz9vXNoJuC8XAawLsvE54JAHRKgQZcpx0SQ1g60r3Nn80nqpXFK0VBulBYNuuayrcCpli4u8jUMUm_1n5CIo84vxWbQcN46w0Ah_VS2hJZ0m88_jTWwKWtij1_m38H1G-Py_8fdiHrSWKyWAWCQ.webp', + ]), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildNameAge(), + Container(height: AppGaps.gap8, color: AppColors.grey100), + buildIntroduce(), + Container(height: AppGaps.gap8, color: AppColors.grey100), + buildProfileFaceImage(), + Container(height: AppGaps.gap8, color: AppColors.grey100), + buildProfileSection('์‚ฌ์šฉ๊ฐ€๋Šฅ์ฝ”๋“œ๋„ค์ž„์˜ LIFE', [ + buildListItem('์ง์—…', 'ํ•™์ƒ'), + buildListItem('์Œ์ฃผ์—ฌ๋ถ€', '์œ ์‚ฐ์†Œ ์ฆ๊ธฐ๋Š” ๋งค๋‹ˆ์•„'), + buildListItem('ํก์—ฐ์—ฌ๋ถ€', '์œ ์‚ฐ์†Œ ์ฆ๊ธฐ๋Š” ๋งค๋‹ˆ์•„'), + buildListItem('MBTI', 'ENTJ'), + ]), + Container(height: AppGaps.gap8, color: AppColors.grey100), + buildProfileSection('์‚ฌ์šฉ๊ฐ€๋Šฅ์ฝ”๋“œ๋„ค์ž„์˜ LIKE', [ + buildListItem('์ทจํ–ฅ', '์šด๋™, ๋…์„œ & ๊ธ€์“ฐ๊ธฐ, IT๊ธฐ์ˆ '), + buildListItem('ํ™œ๋™์ง€์—ญ', '์„œ์šธํŠน๋ณ„์‹œ ๊ฐ•๋™๊ตฌ'), + ]), + Container(height: AppGaps.gap8, color: AppColors.grey100), + buildProfileSection('์‚ฌ์šฉ๊ฐ€๋Šฅ์ฝ”๋“œ๋„ค์ž„์˜ LIFESTYLE', [ + buildListItem('์—ฐ์• ๊ฐ€์น˜๊ด€', '์„œ๋กœ์˜ ์„ฑ์žฅ์„ ์ง€์›ํ•˜๋Š” ์Šคํƒ€์ผ'), + buildListItem('์• ์ •ํ‘œํ˜„', '๋‚˜๋งŒ ๋”ฐ๋ผ์™€ ๋ฐ์ดํŠธ ์Šคํƒ€์ผ'), + buildListItem('์—ฐ๋ฝ', '๋‚˜๋งŒ ๋”ฐ๋ผ์™€ ๋ฐ์ดํŠธ ์Šคํƒ€์ผ'), + buildListItem('๋ฐ์ดํŠธ', '๋‚˜๋งŒ ๋”ฐ๋ผ์™€ ๋ฐ์ดํŠธ ์Šคํƒ€์ผ'), + buildListItem('๊ฐˆ๋“ฑํ•ด๊ฒฐ', '๋‚˜๋งŒ ๋”ฐ๋ผ์™€ ๋ฐ์ดํŠธ ์Šคํƒ€์ผ'), + buildListItem('์‚ฌ๋ž‘์˜ ์–ธ์–ด', '๋‚˜๋งŒ ๋”ฐ๋ผ์™€ ๋ฐ์ดํŠธ ์Šคํƒ€์ผ'), + ]), + ], + ), + ], + ), + ), + ); + } + + Widget buildNameAge() { + return Padding( + padding: const EdgeInsets.all(AppGaps.gap16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text('์‚ฌ์šฉ๊ฐ€๋Šฅ์ฝ”๋“œ๋„ค์ž„', style: AppTypography.header3), + SizedBox(width: AppGaps.gap8), + Text("26์„ธ", style: AppTypography.body2), + ], + ), + IconButton(icon: Icon(Icons.more_vert), onPressed: () {}), + ], + ), + ); + } + + Widget buildIntroduce() { + return Padding( + padding: const EdgeInsets.all(AppGaps.gap16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildSectionTitle('์‚ฌ์šฉ๊ฐ€๋Šฅ์ฝ”๋“œ๋„ค์ž„์˜ ์†Œ๊ฐœ'), + SizedBox(height: AppGaps.gap8), + Text('๋ฐฉ๊ฐ€๋ฐฉ๊ฐ€', style: AppTypography.subtitle3), + ], + ), + ); + } + + Widget buildProfileFaceImage() { + return Padding( + padding: const EdgeInsets.all(AppGaps.gap16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildSectionTitle('์‚ฌ์šฉ๊ฐ€๋Šฅ์ฝ”๋“œ๋„ค์ž„์˜ ํŽ˜์ด์Šค ์ธ์ฆ ์ด๋ฏธ์ง€'), + SizedBox(height: AppGaps.gap12), + buildFaceProfileImages([ + 'https://i.namu.wiki/i/26zrz9vXNoJuC8XAawLsvE54JAHRKgQZcpx0SQ1g60r3Nn80nqpXFK0VBulBYNuuayrcCpli4u8jUMUm_1n5CIo84vxWbQcN46w0Ah_VS2hJZ0m88_jTWwKWtij1_m38H1G-Py_8fdiHrSWKyWAWCQ.webp', + 'https://i.namu.wiki/i/26zrz9vXNoJuC8XAawLsvE54JAHRKgQZcpx0SQ1g60r3Nn80nqpXFK0VBulBYNuuayrcCpli4u8jUMUm_1n5CIo84vxWbQcN46w0Ah_VS2hJZ0m88_jTWwKWtij1_m38H1G-Py_8fdiHrSWKyWAWCQ.webp', + 'https://i.namu.wiki/i/26zrz9vXNoJuC8XAawLsvE54JAHRKgQZcpx0SQ1g60r3Nn80nqpXFK0VBulBYNuuayrcCpli4u8jUMUm_1n5CIo84vxWbQcN46w0Ah_VS2hJZ0m88_jTWwKWtij1_m38H1G-Py_8fdiHrSWKyWAWCQ.webp', + ]), + SizedBox(height: AppGaps.gap12), + Container( + color: AppColors.grey100, + height: 24, + width: double.infinity, + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(left: AppGaps.gap8), + child: Text( + "ํŽ˜์ด์Šค ์ธ์ฆ ์ด๋ฏธ์ง€๋Š” ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ๋ณด์ด์ง€ ์•Š์•„์š”!", + style: AppTypography.caption3.copyWith( + color: AppColors.grey500, + ), + ), + ), + ), + ], + ), + ); + } + Widget buildFaceProfileImages(List imagePath) { + return Row( + children: List.generate( + imagePath.length, + (index) => buildFaceProfileImage(imagePath[index]), + ), + ); + } + + Widget buildCodeProfileImage(List imagePaths) { + return AspectRatio( + aspectRatio: 7 / 6, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: imagePaths.length, + onPageChanged: (index) { + currentPageNotifier.value = index; // ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ ์—…๋ฐ์ดํŠธ + }, + itemBuilder: (context, index) { + return Image.network( + imagePaths[index], + fit: BoxFit.cover, + ); + }, + ), + Align( + alignment: Alignment.topLeft, + child: ValueListenableBuilder( + valueListenable: currentPageNotifier, + builder: (context, currentPage, _) { + return currentPageNotifier.value == 0 + ? Container( + margin: const EdgeInsets.all(8.0), + padding: const EdgeInsets.symmetric( + horizontal: 12.0, vertical: 6.0), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + ), + child: Text( + '๋Œ€ํ‘œ', + style: + AppTypography.body2.copyWith(color: Colors.white), + ), + ) + : Container(); + }, + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: ValueListenableBuilder( + valueListenable: currentPageNotifier, + builder: (context, currentPage, _) { + return Container( + margin: const EdgeInsets.all(8.0), + padding: const EdgeInsets.symmetric( + horizontal: 12.0, vertical: 6.0), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + borderRadius: BorderRadius.circular(12.0), + ), + child: Text( + '${currentPage + 1} / ${imagePaths.length}', + style: AppTypography.body2.copyWith(color: Colors.white), + ), + ); + }, + ), + ), + ], + ), + ); + } + + Widget buildSectionTitle(String title) { + return Text( + title, + style: AppTypography.subtitle3.copyWith(color: AppColors.primary), + ); + } + + Widget buildFaceProfileImage(String imagePath) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Container( + width: 64, + height: 64, + decoration: BoxDecoration( + image: DecorationImage( + image: NetworkImage(imagePath), + fit: BoxFit.cover, + ), + ), + ), + ), + ); + } + + Widget buildProfileSection(String title, List items) { + return Padding( + padding: const EdgeInsets.all(AppGaps.gap16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildSectionTitle(title), + SizedBox(height: AppGaps.gap8), + ...items, + SizedBox(height: AppGaps.gap16), + ], + ), + ); + } + + Widget buildListItem(String key, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + children: [ + SizedBox( + width: 80, // key์˜ ๊ณ ์ • ๋„ˆ๋น„ ์„ค์ • + child: Text( + key, + style: AppTypography.subtitle3.copyWith(color: AppColors.grey500), + textAlign: TextAlign.left, + ), + ), + Expanded( + child: Text( + value, + style: AppTypography.subtitle3, + textAlign: TextAlign.left, + ), + ), + ], + ), + ); + } +} diff --git a/lib/sign_up/presentation/pages/profile_summary/widgets/profile_summary_app_bar.dart b/lib/sign_up/presentation/pages/profile_summary/widgets/profile_summary_app_bar.dart new file mode 100644 index 0000000..94022d4 --- /dev/null +++ b/lib/sign_up/presentation/pages/profile_summary/widgets/profile_summary_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 ProfileSummaryAppBar extends StatelessWidget + implements PreferredSizeWidget { + const ProfileSummaryAppBar({super.key}); + + @override + Size get preferredSize => Size.fromHeight(56); + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: AppGaps.gap36), + Text( + "๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ์ฝ”๋“œํ”„๋กœํ•„", + style: AppTypography.subtitle2.copyWith(color: AppColors.grey900), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/widgets/sign_up_app_bar.dart b/lib/sign_up/presentation/widgets/sign_up_app_bar.dart new file mode 100644 index 0000000..cad93d3 --- /dev/null +++ b/lib/sign_up/presentation/widgets/sign_up_app_bar.dart @@ -0,0 +1,59 @@ +import 'package:code_l/core/utills/design/app_colors.dart'; +import 'package:flutter/material.dart'; + +class SignUpAppBar extends StatelessWidget implements PreferredSizeWidget { + const SignUpAppBar({super.key}); + + @override + Size get preferredSize => Size.fromHeight(56); + + @override + Widget build(BuildContext context) { + double progress = 0.20; // todo ์ง„ํ–‰๋ฅ  ๊ด€๋ฆฌ + + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + // ๋’ค๋กœ๊ฐ€๊ธฐ + IconButton( + icon: Icon(Icons.arrow_back_ios_new_rounded), + onPressed: () => Navigator.pop(context), + ), + + // ์ง„ํ–‰๋ฅ  + Expanded( + child: Container( + height: 6, + margin: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: AppColors.primaryLight, // ๋ฐฐ๊ฒฝ (์ „์ฒด ๋ฐ”) + borderRadius: BorderRadius.circular(2), + ), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: progress, // ์ง„ํ–‰๋ฅ  + child: Container( + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + ), + ), + + // ๋‹ซ๊ธฐ ๋ฒ„ํŠผ + IconButton( + icon: Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/sign_up/presentation/widgets/sign_up_confirm_button.dart b/lib/sign_up/presentation/widgets/sign_up_confirm_button.dart new file mode 100644 index 0000000..22bcd34 --- /dev/null +++ b/lib/sign_up/presentation/widgets/sign_up_confirm_button.dart @@ -0,0 +1,40 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../core/utills/design/app_colors.dart'; +import '../../../core/utills/design/app_typography.dart'; + +class ConfirmButton extends StatelessWidget { + final bool enabled; + final VoidCallback? onPressed; + final String text; + + const ConfirmButton({ + super.key, + required this.enabled, + this.onPressed, + this.text = "ํ™•์ธ", + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: enabled ? onPressed : null, + child: Container( + height: 54, + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 16), + alignment: Alignment.center, + decoration: BoxDecoration( + color: enabled ? AppColors.primary : AppColors.grey200, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + style: AppTypography.subtitle2.copyWith( + color: enabled ? AppColors.white : AppColors.grey400, + ), + ), + ), + ); + } +} diff --git a/lib/signal/domain/entities/.gitkeep b/lib/signal/domain/entities/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/profie_registration/domain/entities/.gitkeep b/lib/signal/domain/model/.gitkeep similarity index 100% rename from lib/profie_registration/domain/entities/.gitkeep rename to lib/signal/domain/model/.gitkeep diff --git a/pubspec.lock b/pubspec.lock index c216c09..732a124 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: e02d018628c870ef2d7f03e33f9ad179d89ff6ec52ca6c56bcb80bcef979867f + url: "https://pub.dev" + source: hosted + version: "1.6.2" async: dependency: transitive description: @@ -105,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -145,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" fake_async: dependency: transitive description: @@ -153,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" file: dependency: transitive description: @@ -161,6 +193,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" firebase_auth: dependency: "direct main" description: @@ -214,6 +278,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_lints: dependency: "direct dev" description: @@ -222,6 +294,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" flutter_svg: dependency: "direct main" description: @@ -240,6 +328,14 @@ packages: description: flutter source: sdk version: "0.0.0" + gif: + dependency: "direct main" + description: + name: gif + sha256: ade95694f1471da737922806818ffade2814d1d7f8d10af38ebcf36ace012bc0 + url: "https://pub.dev" + source: hosted + version: "2.3.0" glob: dependency: transitive description: @@ -264,6 +360,78 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.dev" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" json_annotation: dependency: transitive description: @@ -280,6 +448,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.9.4" + kakao_flutter_sdk_auth: + dependency: transitive + description: + name: kakao_flutter_sdk_auth + sha256: "028d8803b7545cc5f41d20cda43b813683700e45c6c13aa4ca56f4b9c673305c" + url: "https://pub.dev" + source: hosted + version: "1.9.7+3" + kakao_flutter_sdk_common: + dependency: transitive + description: + name: kakao_flutter_sdk_common + sha256: "1c4944cc50c363d4626e9006ab39ee496c8f5ca602b96154272b90d40544aaca" + url: "https://pub.dev" + source: hosted + version: "1.9.7+3" + kakao_flutter_sdk_user: + dependency: "direct main" + description: + name: kakao_flutter_sdk_user + sha256: "5157feeafe58d677d314baa5ccdcb435fd9680c485c3b23e9ad7d97a0c93694f" + url: "https://pub.dev" + source: hosted + version: "1.9.7+3" leak_tracker: dependency: transitive description: @@ -344,6 +536,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" package_config: dependency: transitive description: @@ -368,6 +568,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -376,6 +600,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -384,6 +616,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" pub_semver: dependency: transitive description: @@ -401,13 +641,101 @@ packages: source: hosted version: "1.5.0" riverpod: - dependency: "direct main" + dependency: transitive description: name: riverpod sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" url: "https://pub.dev" source: hosted version: "2.6.1" + screen_protector: + dependency: "direct main" + description: + name: screen_protector + sha256: "305fd157f6f0b210afe216e790022bfe469c3b1d1f2e0d3dcc5906cc9c49c3e9" + url: "https://pub.dev" + source: hosted + version: "1.4.2+1" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + sha256: "8bd875c8e8748272749eb6d25b896f768e7e9d60988446d543fe85a37a2392b8" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + sign_in_with_apple_platform_interface: + dependency: transitive + description: + name: sign_in_with_apple_platform_interface + sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + sign_in_with_apple_web: + dependency: transitive + description: + name: sign_in_with_apple_web + sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -549,6 +877,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xml: dependency: transitive description: @@ -567,4 +903,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 91bf824..5fa8cb0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,12 +35,18 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 flutter_svg: ^2.0.17 - riverpod: ^2.6.1 + flutter_riverpod: ^2.6.1 dio: ^5.8.0+1 json_serializable: ^6.9.4 firebase_auth: ^5.5.2 firebase_core: ^3.13.0 http: ^1.3.0 + kakao_flutter_sdk_user: ^1.9.7+3 + flutter_dotenv: ^5.2.1 + screen_protector: ^1.4.2+1 + sign_in_with_apple: ^7.0.1 + image_picker: ^1.1.2 + gif: ^2.3.0 dev_dependencies: flutter_test: @@ -94,3 +100,6 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/to/font-from-package + assets: + - .env + - assets/icons/