From 226d8f8e92ee56090c2ec21bcc9a13b664030962 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Mon, 3 Aug 2026 12:07:49 +0900 Subject: [PATCH 01/18] =?UTF-8?q?fix:=20ios=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 36 +++---- ios/Runner.xcodeproj/project.pbxproj | 146 ++++++++++++++------------- pubspec.lock | 92 ++++++++--------- 3 files changed, 138 insertions(+), 136 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9269530..fd2bfa6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -25,10 +25,10 @@ PODS: - Firebase/Messaging (12.15.0): - Firebase/CoreOnly - FirebaseMessaging (~> 12.15.0) - - firebase_core (4.11.0): + - firebase_core (4.12.1): - Firebase/CoreOnly (= 12.15.0) - Flutter - - firebase_messaging (16.4.1): + - firebase_messaging (16.4.3): - Firebase/Messaging (= 12.15.0) - firebase_core - Flutter @@ -58,7 +58,6 @@ PODS: - Flutter - flutter_image_compress_common (1.0.0): - Flutter - - Mantle - SDWebImage - SDWebImageWebPCoder - flutter_native_splash (2.4.3): @@ -66,6 +65,9 @@ PODS: - flutter_secure_storage_darwin (10.0.0): - Flutter - FlutterMacOS + - gal (1.0.0): + - Flutter + - FlutterMacOS - google_sign_in_ios (0.0.1): - Flutter - FlutterMacOS @@ -129,9 +131,6 @@ PODS: - libwebp/sharpyuv (1.5.0) - libwebp/webp (1.5.0): - libwebp/sharpyuv - - Mantle (2.2.0): - - Mantle/extobjc (= 2.2.0) - - Mantle/extobjc (2.2.0) - nanopb (3.30910.0): - nanopb/decode (= 3.30910.0) - nanopb/encode (= 3.30910.0) @@ -139,10 +138,7 @@ PODS: - nanopb/encode (3.30910.0) - native_camera_sound (0.0.1): - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - photo_manager (3.10.0): + - photo_manager (3.11.0): - Flutter - FlutterMacOS - PromisesObjC (2.4.1) @@ -178,12 +174,12 @@ DEPENDENCIES: - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) + - gal (from `.symlinks/plugins/gal/darwin`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - image_gallery_saver_plus (from `.symlinks/plugins/image_gallery_saver_plus/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - kakao_flutter_sdk_common (from `.symlinks/plugins/kakao_flutter_sdk_common/ios`) - native_camera_sound (from `.symlinks/plugins/native_camera_sound/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - photo_manager (from `.symlinks/plugins/photo_manager/darwin`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -205,7 +201,6 @@ SPEC REPOS: - GTMAppAuth - GTMSessionFetcher - libwebp - - Mantle - nanopb - PromisesObjC - PromisesSwift @@ -236,6 +231,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_secure_storage_darwin: :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" + gal: + :path: ".symlinks/plugins/gal/darwin" google_sign_in_ios: :path: ".symlinks/plugins/google_sign_in_ios/darwin" image_gallery_saver_plus: @@ -246,8 +243,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/kakao_flutter_sdk_common/ios" native_camera_sound: :path: ".symlinks/plugins/native_camera_sound/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" photo_manager: :path: ".symlinks/plugins/photo_manager/darwin" share_plus: @@ -265,19 +260,20 @@ SPEC CHECKSUMS: AppCheckCore: e215d35177a9cf469927863e69c13e220df32a8b audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5 camera_avfoundation: 5675ca25298b6f81fa0a325188e7df62cc217741 - device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896 + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Firebase: a8539b633d474fbeb654c7043f9c1649e274045b - firebase_core: fc23178af8ea070194d09031ae4198a9608a3d22 - firebase_messaging: 870b2939a7f7cc52bc38ca0250127c45b42f4bad + firebase_core: 1c1e250a77e2df9e80bb8bf0875c8b427044a697 + firebase_messaging: 90cf2b62f5058120ab19f29ceff1503ba0f51ba7 FirebaseCore: 2e86a4ea1684d4381707069e4a6d89ac808e901e FirebaseCoreInternal: 6ab6a02c94446c026d2cf35cf5383842ebaa4992 FirebaseInstallations: eb29ccbf64eaedf86fd5b2ccc7fabde567660b52 FirebaseMessaging: 40017d7bc8457ee295b0f41d480a80fdabc9994e Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_appauth: d4abcf54856e5d8ba82ed7646ffc83245d4aa448 - flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 + flutter_image_compress_common: 11d5dcfb36f4ded92320bfa896261813a073240e flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 + gal: baecd024ebfd13c441269ca7404792a7152fde89 google_sign_in_ios: 000870aa06da9b28d1d0bf7ef70ff0213059dd28 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleSignIn: fcee2257188d5eda57a5e2b6a715550ffff9206d @@ -288,11 +284,9 @@ SPEC CHECKSUMS: image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 kakao_flutter_sdk_common: 682b3606698f87467788598dc2dc09d4e6867fbd libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 native_camera_sound: 6304ac5ca3b45d88b1fba6f1bd9c6da9dbf947f1 - path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - photo_manager: 93cc83778e33d5fd49dfd7e34b73f8f5a4f10aaf + photo_manager: 6ab48c2ce7ec21aa06d59e6cc049f0b6d9ba7f94 PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273 PromisesSwift: 217dea0fd5d2ad65222a109c48698add13cc1c5b RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 47ae92a..f21b99e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,15 +8,15 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1E1273485725C2A64084771D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2854B46F269B9E374FB19610 /* Pods_RunnerTests.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 426B7EB1D329128F59D73DBA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B3F97F5F02273ADB06E13B6 /* Pods_Runner.framework */; }; 645EEB062FBCBFE300FC5FD1 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 645EEB052FBCBFE300FC5FD1 /* GoogleService-Info.plist */; }; - 6D6C320DFB58EC02A307DA65 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B7938CD66B50F327A0E94C8 /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 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 */; }; - B4C01431C955B9FADF842796 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D41E1C008ED228BEB320CCC0 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -43,20 +43,21 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 06623951D74E8295B77CDFA6 /* 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 = ""; }; - 222AF339DB600A2B5E01EE0F /* 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 = ""; }; + 2854B46F269B9E374FB19610 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2E68AA69DC79680F084578A6 /* 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 = ""; }; 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; }; + 38DD50535F97A74EA0DE4185 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 5B0ED7BBC442AE978AE8D417 /* 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 = ""; }; 645EEB052FBCBFE300FC5FD1 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; - 66D0A08EDFDC6A4F81AD876C /* 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 = ""; }; - 696A10F514DF2CD03037CA9C /* 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 = ""; }; - 6B7938CD66B50F327A0E94C8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + 8B3F97F5F02273ADB06E13B6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F8E2745537E5F76E469599B /* 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -64,9 +65,8 @@ 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 = ""; }; - 9CB79A0D157D1F0B25AC4A41 /* 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 = ""; }; - A037740DFB6F8686FADEEDEA /* 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 = ""; }; - D41E1C008ED228BEB320CCC0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + ABFEB1C4FB8F956B28267714 /* 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 = ""; }; + F2A895843BD3292018B207E3 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,7 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B4C01431C955B9FADF842796 /* Pods_Runner.framework in Frameworks */, + 426B7EB1D329128F59D73DBA /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -82,7 +82,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6D6C320DFB58EC02A307DA65 /* Pods_RunnerTests.framework in Frameworks */, + 1E1273485725C2A64084771D /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -97,15 +97,6 @@ path = RunnerTests; sourceTree = ""; }; - 95F3614CB6DD26EA78562BF5 /* Frameworks */ = { - isa = PBXGroup; - children = ( - D41E1C008ED228BEB320CCC0 /* Pods_Runner.framework */, - 6B7938CD66B50F327A0E94C8 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -125,7 +116,7 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, F53435CEBB51953C132E7EEE /* Pods */, - 95F3614CB6DD26EA78562BF5 /* Frameworks */, + C800AE767162B9A72D222CA0 /* Frameworks */, ); sourceTree = ""; }; @@ -154,15 +145,24 @@ path = Runner; sourceTree = ""; }; + C800AE767162B9A72D222CA0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8B3F97F5F02273ADB06E13B6 /* Pods_Runner.framework */, + 2854B46F269B9E374FB19610 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; F53435CEBB51953C132E7EEE /* Pods */ = { isa = PBXGroup; children = ( - 5B0ED7BBC442AE978AE8D417 /* Pods-Runner.debug.xcconfig */, - A037740DFB6F8686FADEEDEA /* Pods-Runner.release.xcconfig */, - 696A10F514DF2CD03037CA9C /* Pods-Runner.profile.xcconfig */, - 66D0A08EDFDC6A4F81AD876C /* Pods-RunnerTests.debug.xcconfig */, - 222AF339DB600A2B5E01EE0F /* Pods-RunnerTests.release.xcconfig */, - 9CB79A0D157D1F0B25AC4A41 /* Pods-RunnerTests.profile.xcconfig */, + 06623951D74E8295B77CDFA6 /* Pods-Runner.debug.xcconfig */, + 2E68AA69DC79680F084578A6 /* Pods-Runner.release.xcconfig */, + 8F8E2745537E5F76E469599B /* Pods-Runner.profile.xcconfig */, + ABFEB1C4FB8F956B28267714 /* Pods-RunnerTests.debug.xcconfig */, + F2A895843BD3292018B207E3 /* Pods-RunnerTests.release.xcconfig */, + 38DD50535F97A74EA0DE4185 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -174,7 +174,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 91F3200276A4E86A18DFE0E4 /* [CP] Check Pods Manifest.lock */, + F04B7F7A5F9BFA0D3EFFE1DA /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, EB3CC2CAF7B78D46445B9C5A /* Frameworks */, @@ -193,15 +193,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - BABF811E74C9070047A08B2E /* [CP] Check Pods Manifest.lock */, + 7CB04BF6E2E10F1ECF3D924D /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 0B268321AD0C0BA397BF1DEB /* [CP] Embed Pods Frameworks */, - 2AA5B2287AC283419474F813 /* [CP] Copy Pods Resources */, + 3594C6D699A409224AA82FE3 /* [CP] Embed Pods Frameworks */, + EDD5B35332DD8BDF7C9C3C60 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -274,7 +274,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0B268321AD0C0BA397BF1DEB /* [CP] Embed Pods Frameworks */ = { + 3594C6D699A409224AA82FE3 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -282,30 +282,17 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); 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; - }; - 2AA5B2287AC283419474F813 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { @@ -324,7 +311,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 91F3200276A4E86A18DFE0E4 /* [CP] Check Pods Manifest.lock */ = { + 7CB04BF6E2E10F1ECF3D924D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -339,7 +326,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -361,7 +348,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - BABF811E74C9070047A08B2E /* [CP] Check Pods Manifest.lock */ = { + EDD5B35332DD8BDF7C9C3C60 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + F04B7F7A5F9BFA0D3EFFE1DA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -376,7 +384,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -435,7 +443,7 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 696A10F514DF2CD03037CA9C /* Pods-Runner.profile.xcconfig */; + baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -488,12 +496,12 @@ }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 696A10F514DF2CD03037CA9C /* Pods-Runner.profile.xcconfig */; + baseConfigurationReference = 8F8E2745537E5F76E469599B /* Pods-Runner.profile.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 25VAHVQXN9; + DEVELOPMENT_TEAM = 382P985AXP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.6; @@ -501,7 +509,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.nuk.haenaem; + PRODUCT_BUNDLE_IDENTIFIER = com.example.haenaem; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -511,7 +519,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 66D0A08EDFDC6A4F81AD876C /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = ABFEB1C4FB8F956B28267714 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -529,7 +537,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 222AF339DB600A2B5E01EE0F /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = F2A895843BD3292018B207E3 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -545,7 +553,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9CB79A0D157D1F0B25AC4A41 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 38DD50535F97A74EA0DE4185 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -561,7 +569,7 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5B0ED7BBC442AE978AE8D417 /* Pods-Runner.debug.xcconfig */; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; @@ -619,7 +627,7 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A037740DFB6F8686FADEEDEA /* Pods-Runner.release.xcconfig */; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; @@ -674,12 +682,12 @@ }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5B0ED7BBC442AE978AE8D417 /* Pods-Runner.debug.xcconfig */; + baseConfigurationReference = 06623951D74E8295B77CDFA6 /* Pods-Runner.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 25VAHVQXN9; + DEVELOPMENT_TEAM = 382P985AXP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.6; @@ -687,7 +695,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.nuk.haenaem; + PRODUCT_BUNDLE_IDENTIFIER = com.example.haenaem; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -698,12 +706,12 @@ }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A037740DFB6F8686FADEEDEA /* Pods-Runner.release.xcconfig */; + baseConfigurationReference = 2E68AA69DC79680F084578A6 /* Pods-Runner.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 25VAHVQXN9; + DEVELOPMENT_TEAM = 382P985AXP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.6; @@ -711,7 +719,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.nuk.haenaem; + PRODUCT_BUNDLE_IDENTIFIER = com.example.haenaem; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/pubspec.lock b/pubspec.lock index 2c27c28..de7b697 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: app_links - sha256: f8db46d2ea9ff6f3a37191a7fd5b7813da1253ace8c32c1b9eadace41d1188ea + sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc" url: "https://pub.dev" source: hosted - version: "7.2.1" + version: "7.0.0" app_links_linux: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: app_links_platform_interface - sha256: "7546f09a6e93f4a2df2fe2bd40a5c6c64310ac461b036d82b43033be7a59f809" + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.2" app_links_web: dependency: transitive description: @@ -109,58 +109,58 @@ packages: dependency: "direct main" description: name: audioplayers - sha256: "2ba4bb2944baacbdd5372ff8254a8e7feb8c10d7739545e392f5605a8f618745" + sha256: f16640453cc47487b7de72a2b28d37c7df1ac97999849f4a46d92b1d2b0f093d url: "https://pub.dev" source: hosted - version: "6.8.1" + version: "6.7.1" audioplayers_android: dependency: transitive description: name: audioplayers_android - sha256: f5ff5b15620fbab8cb0849e9636c48e2b96c3f0f71723bbbe2ad3c761b205f05 + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.2.1" audioplayers_darwin: dependency: transitive description: name: audioplayers_darwin - sha256: "1ca553add991384ecf421b9569da850f3ab2472ffb83f6970b0416365abc51be" + sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91 url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.4.0" audioplayers_linux: dependency: transitive description: name: audioplayers_linux - sha256: "15178b726b7cdee5364d0463c8d445630c4e0fb7d26612b73c767e7d25de9417" + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.2.1" audioplayers_platform_interface: dependency: transitive description: name: audioplayers_platform_interface - sha256: "765f6f0e6dca55cb471c9483fc77700564b3484d19198aca4ebb5147c6c85acb" + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.1.1" audioplayers_web: dependency: transitive description: name: audioplayers_web - sha256: ae1e0103c865a03e273f6d13d97b93f5595eac09915729cd5e37ef96e2857319 + sha256: "24a6f258062bd7da8cb2157e83fccb9816a08dd306cbaaa24f9813d071470545" url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.2.1" audioplayers_windows: dependency: transitive description: name: audioplayers_windows - sha256: a70ae82bba2dfcb6eb03dd4815d737a2d46d33ea5a96a03f535cfcaac490e413 + sha256: "95f875a96c88c3dbbcb608d4f8288e300b0113d256a81d0b3197fcc18f0dc91a" url: "https://pub.dev" source: hosted - version: "4.4.1" + version: "4.3.1" boolean_selector: dependency: transitive description: @@ -679,10 +679,10 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff" + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.7" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -817,10 +817,10 @@ packages: dependency: transitive description: name: google_sign_in_android - sha256: a01d4fd5a0e4eb0c1961434a0289a436abf9225b0933963c8e4e5ed88c26e40b + sha256: df5c15533814ed20b7d9e1c5a40a73f174e5d5017bd2669b1c72fb6596fde812 url: "https://pub.dev" source: hosted - version: "7.2.15" + version: "7.2.11" google_sign_in_ios: dependency: transitive description: @@ -913,10 +913,10 @@ packages: dependency: "direct main" description: name: image - sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce url: "https://pub.dev" source: hosted - version: "4.9.1" + version: "4.8.0" image_gallery_saver_plus: dependency: "direct main" description: @@ -937,10 +937,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.13+19" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: @@ -1161,10 +1161,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.18" material_color_utilities: dependency: transitive description: @@ -1177,10 +1177,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1425,10 +1425,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 url: "https://pub.dev" source: hosted - version: "2.4.27" + version: "2.4.23" shared_preferences_foundation: dependency: transitive description: @@ -1558,10 +1558,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "61894a1956de6b4fc1aefd0892e109514a1a706cbece3ac59decd90ff5a7a423" + sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" url: "https://pub.dev" source: hosted - version: "3.4.1+1" + version: "3.4.0+1" table_calendar: dependency: "direct main" description: @@ -1582,10 +1582,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.9" timing: dependency: transitive description: @@ -1622,10 +1622,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" url: "https://pub.dev" source: hosted - version: "6.3.32" + version: "6.3.30" url_launcher_ios: dependency: transitive description: @@ -1766,10 +1766,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: a97db7a44f8e71af2f3971c45550a08cce1fb60059c1b8e534251e6cfb753490 + sha256: ad5182eff9a550925330cb9f0cb038eddfdd5712aba8b77aa0f0400e50f6e688 url: "https://pub.dev" source: hosted - version: "4.13.0" + version: "4.12.0" webview_flutter_platform_interface: dependency: transitive description: @@ -1782,10 +1782,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: c879dd64b87c452aa84381b244d5469da57ba7e8cca6884c7b1e0d406372c12d + sha256: "82648217f537573e1ca9ae9952d3eacedca6ab5aee69dc84445fc763766dcea2" url: "https://pub.dev" source: hosted - version: "3.26.0" + version: "3.25.1" win32: dependency: transitive description: @@ -1814,10 +1814,10 @@ packages: dependency: transitive description: name: xml - sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.6.1" yaml: dependency: transitive description: @@ -1827,5 +1827,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.12.0 <4.0.0" - flutter: ">=3.44.0" + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.38.4" From af36d543adb063ce9f4075afebbd7da4e78c4a6c Mon Sep 17 00:00:00 2001 From: Seungvin Date: Mon, 3 Aug 2026 15:58:59 +0900 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20=EB=8B=A4=ED=81=AC=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=BB=AC=EB=9F=AC=EB=B3=80=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EB=A7=88=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/theme/app_colors.dart | 130 ++++++++++++++++++++++++++++++++- lib/core/theme/app_theme.dart | 37 ++++++---- 2 files changed, 150 insertions(+), 17 deletions(-) diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index a776dc8..f33a4d8 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -3,8 +3,10 @@ import 'package:flutter/material.dart'; class AppColors { - // Grayscale + static const Color white = Color(0xFFFAFAFA); static const Color black = Color(0xFF1b1d1b); + + // Grayscale static const Color gray1 = Color(0xFF444444); static const Color gray2 = Color(0xFF616161); static const Color gray3 = Color(0xFF8c8c8c); @@ -24,3 +26,129 @@ class AppColors { static const Color notification = Color(0xffD11E1B); static const Color blue = Color(0xff4589FF); } + +class AppColorsExtension extends ThemeExtension { + final Color blackToWhite; + final Color whiteToBlack; + final Color gray1; + final Color gray2; + final Color gray3; + final Color gray4; + final Color gray5; + final Color primaryAble; + final Color selected; + final Color disable; + final Color success; + final Color warning; + final Color notification; + + const AppColorsExtension({ + required this.blackToWhite, + required this.whiteToBlack, + required this.gray1, + required this.gray2, + required this.gray3, + required this.gray4, + required this.gray5, + required this.primaryAble, + required this.selected, + required this.disable, + required this.success, + required this.warning, + required this.notification, + }); + + // 라이트 모드 매핑 + factory AppColorsExtension.light() { + return const AppColorsExtension( + blackToWhite: AppColors.black, + whiteToBlack: AppColors.white, + gray1: AppColors.gray1, + gray2: AppColors.gray2, + gray3: AppColors.gray3, + gray4: AppColors.gray4, + gray5: AppColors.gray5, + primaryAble: AppColors.primaryAble, + selected: AppColors.selected, + disable: AppColors.disable, + success: AppColors.success, + warning: AppColors.warning, + notification: AppColors.notification, + ); + } + + // 다크 모드 매핑 (요청하신 계획안 적용) + factory AppColorsExtension.dark() { + return const AppColorsExtension( + blackToWhite: AppColors.white, // black -> white + whiteToBlack: AppColors.black, // white -> black + gray1: AppColors.gray4, // gray1 -> gray4 + gray2: AppColors.gray4, // gray2 -> gray4 + gray3: AppColors.gray3, // gray3 변경없음 + gray4: AppColors.gray2, // gray4 -> gray2 + gray5: Color(0xFF2A2A2A), // gray5 -> d-gray5 + primaryAble: Color(0xFF0EBC6A), // primarygreen -> d-primarygreen + selected: Color(0xFF264F36), // selected -> d-selected + disable: Color(0xFF697C73), // disable -> d-disable + success: Color(0xFF1A3A1E), // success -> d-success + warning: Color(0xFF3A2522), // warning -> d-warning + notification: Color(0xFFEE4750), // notification -> d-notification + ); + } + + @override + ThemeExtension copyWith({ + Color? blackToWhite, + Color? whiteToBlack, + Color? gray1, + Color? gray2, + Color? gray3, + Color? gray4, + Color? gray5, + Color? primaryAble, + Color? selected, + Color? disable, + Color? success, + Color? warning, + Color? notification, + }) { + return AppColorsExtension( + blackToWhite: blackToWhite ?? this.blackToWhite, + whiteToBlack: whiteToBlack ?? this.whiteToBlack, + gray1: gray1 ?? this.gray1, + gray2: gray2 ?? this.gray2, + gray3: gray3 ?? this.gray3, + gray4: gray4 ?? this.gray4, + gray5: gray5 ?? this.gray5, + primaryAble: primaryAble ?? this.primaryAble, + selected: selected ?? this.selected, + disable: disable ?? this.disable, + success: success ?? this.success, + warning: warning ?? this.warning, + notification: notification ?? this.notification, + ); + } + + @override + ThemeExtension lerp( + covariant ThemeExtension? other, + double t, + ) { + if (other is! AppColorsExtension) return this; + return AppColorsExtension( + blackToWhite: Color.lerp(blackToWhite, other.blackToWhite, t)!, + whiteToBlack: Color.lerp(whiteToBlack, other.whiteToBlack, t)!, + gray1: Color.lerp(gray1, other.gray1, t)!, + gray2: Color.lerp(gray2, other.gray2, t)!, + gray3: Color.lerp(gray3, other.gray3, t)!, + gray4: Color.lerp(gray4, other.gray4, t)!, + gray5: Color.lerp(gray5, other.gray5, t)!, + primaryAble: Color.lerp(primaryAble, other.primaryAble, t)!, + selected: Color.lerp(selected, other.selected, t)!, + disable: Color.lerp(disable, other.disable, t)!, + success: Color.lerp(success, other.success, t)!, + warning: Color.lerp(warning, other.warning, t)!, + notification: Color.lerp(notification, other.notification, t)!, + ); + } +} diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 9cb8630..248a39b 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -7,9 +7,12 @@ class AppTheme { static ThemeData lightTheme = ThemeData( useMaterial3: true, fontFamily: 'Pretendard', - scaffoldBackgroundColor: Colors.white, + scaffoldBackgroundColor: AppColors.white, brightness: Brightness.light, // 다크모드 판별을 위해 필요 - // 스피너랑 커서 우리 앱 초록색으로 변경 + // ThemeExtension 등록 + extensions: [AppColorsExtension.light()], + + // 스피너랑 커서 앱 초록색으로 변경 progressIndicatorTheme: const ProgressIndicatorThemeData( color: AppColors.primaryAble, ), @@ -21,8 +24,8 @@ class AppTheme { // 상단 AppBar 테마 설정 appBarTheme: const AppBarTheme( - backgroundColor: Colors.white, - surfaceTintColor: Colors.white, // 스크롤 시 색상 변함 방지 + backgroundColor: AppColors.white, + surfaceTintColor: AppColors.white, // 스크롤 시 색상 변함 방지 elevation: 0, //scrolledUnderElevation: 0, // 스크롤 시 그림자 생김 방지 centerTitle: true, @@ -30,36 +33,38 @@ class AppTheme { // 하단 NavigationBar 테마 설정 navigationBarTheme: const NavigationBarThemeData( - backgroundColor: Colors.white, - surfaceTintColor: Colors.white, + backgroundColor: AppColors.white, + surfaceTintColor: AppColors.white, elevation: 0, ), // 하단 BottomNavigationBar 테마 설정 (Legacy 방식) // NavigationBar가 아닌 BottomNavigationBar를 쓸 경우를 대비 bottomNavigationBarTheme: const BottomNavigationBarThemeData( - backgroundColor: Colors.white, + backgroundColor: AppColors.white, elevation: 0, - selectedItemColor: Colors.white, - unselectedItemColor: Colors.grey, + selectedItemColor: AppColors.white, + unselectedItemColor: AppColors.gray3, ), ); - // 🖤 다크모드 기본 골격 - // TODO: 화면 점검하면서 색상값 다듬기 + // 다크모드 static ThemeData darkTheme = ThemeData( useMaterial3: true, fontFamily: 'Pretendard', scaffoldBackgroundColor: const Color(0xFF121212), brightness: Brightness.dark, + // ThemeExtension 등록 + extensions: [AppColorsExtension.dark()], + progressIndicatorTheme: const ProgressIndicatorThemeData( - color: AppColors.primaryAble, + color: Color(0xFF0EBC6A), // 다크모드 primarygreen ), textSelectionTheme: TextSelectionThemeData( - cursorColor: AppColors.primaryAble, - selectionHandleColor: AppColors.primaryAble, - selectionColor: AppColors.primaryAble.withValues(alpha: 0.3), + cursorColor: const Color(0xFF0EBC6A), + selectionHandleColor: const Color(0xFF0EBC6A), + selectionColor: const Color(0xFF0EBC6A).withValues(alpha: 0.3), ), appBarTheme: const AppBarTheme( @@ -78,7 +83,7 @@ class AppTheme { bottomNavigationBarTheme: const BottomNavigationBarThemeData( backgroundColor: Color(0xFF1E1E1E), elevation: 0, - selectedItemColor: Colors.white, + selectedItemColor: AppColors.white, unselectedItemColor: Colors.grey, ), ); From a32e492516f48295be12496099ed387d6f846814 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Mon, 3 Aug 2026 15:59:16 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20=ED=99=88=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/home/screens/home_screen.dart | 34 +++++++++++++------ lib/features/home/widgets/challenge_card.dart | 34 +++++++++++-------- lib/features/home/widgets/day_chip.dart | 24 +++++++------ 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/lib/features/home/screens/home_screen.dart b/lib/features/home/screens/home_screen.dart index e274f1f..b437291 100644 --- a/lib/features/home/screens/home_screen.dart +++ b/lib/features/home/screens/home_screen.dart @@ -19,6 +19,8 @@ class HomeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + final homeDataAsync = ref.watch(homeNotifierProvider); DateTime now = DateTime.now(); @@ -29,7 +31,7 @@ class HomeScreen extends ConsumerWidget { ); return Scaffold( - backgroundColor: Colors.white, + // backgroundColor: Colors.white, body: SafeArea( child: homeDataAsync.when( loading: () => const Center(child: CircularProgressIndicator()), @@ -130,6 +132,8 @@ class HomeScreen extends ConsumerWidget { WidgetRef ref, int count, ) { + final appColors = Theme.of(context).extension()!; + return Stack( alignment: Alignment.center, children: [ @@ -162,7 +166,13 @@ class HomeScreen extends ConsumerWidget { } } }, - icon: SvgPicture.asset('assets/images/icons/home_notice_icon.svg'), + icon: SvgPicture.asset( + 'assets/images/icons/home_notice_icon.svg', + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), + ), ), if (count > 0) Positioned( @@ -170,13 +180,13 @@ class HomeScreen extends ConsumerWidget { top: 3, child: Container( padding: const EdgeInsets.all(4), - decoration: const BoxDecoration( - color: AppColors.notification, + decoration: BoxDecoration( + color: appColors.notification, shape: BoxShape.circle, ), child: Text( '$count', - style: AppTypography.c1.copyWith(color: Colors.white), + style: AppTypography.c1.copyWith(color: AppColors.white), ), ), ), @@ -186,6 +196,8 @@ class HomeScreen extends ConsumerWidget { // FAB 빌더 Widget _buildFAB(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Positioned( right: 20, bottom: 30, @@ -196,9 +208,9 @@ class HomeScreen extends ConsumerWidget { builder: (context) => const ChallengeCreateScreen(), ), ), - backgroundColor: Colors.white, + backgroundColor: appColors.blackToWhite, shape: const CircleBorder(), - child: const Icon(Icons.add, size: 32, color: Colors.green), + child: Icon(Icons.add, size: 32, color: appColors.primaryAble), ), ); } @@ -217,7 +229,9 @@ class ChallengeListView extends StatelessWidget { @override Widget build(BuildContext context) { - if (challenges.isEmpty) return _buildEmptyState(); + final appColors = Theme.of(context).extension()!; + + if (challenges.isEmpty) return _buildEmptyState(appColors); return ListView.builder( padding: const EdgeInsets.only(bottom: 100), @@ -228,14 +242,14 @@ class ChallengeListView extends StatelessWidget { ); } - Widget _buildEmptyState() { + Widget _buildEmptyState(AppColorsExtension appColors) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(12), ), child: const Text( diff --git a/lib/features/home/widgets/challenge_card.dart b/lib/features/home/widgets/challenge_card.dart index 1f36216..82efb62 100644 --- a/lib/features/home/widgets/challenge_card.dart +++ b/lib/features/home/widgets/challenge_card.dart @@ -12,14 +12,16 @@ class ChallengeCard extends StatelessWidget { const ChallengeCard({super.key, required this.challenge}); - Color get _cardColor { - if (challenge.isDone) return AppColors.success; - if (challenge.warning) return AppColors.warning; - return AppColors.gray5; + Color _getCardColor(AppColorsExtension appColors) { + if (challenge.isDone) return appColors.success; + if (challenge.warning) return appColors.warning; + return appColors.gray5; } @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return GestureDetector( onTap: () { Navigator.push( @@ -38,7 +40,7 @@ class ChallengeCard extends StatelessWidget { child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: _cardColor, + color: _getCardColor(appColors), borderRadius: BorderRadius.circular(12), ), child: IntrinsicHeight( @@ -62,12 +64,12 @@ class ChallengeCard extends StatelessWidget { if (challenge.warning) ...[ const SizedBox(height: 4), - _buildWarningText(), + _buildWarningText(appColors), ], ], ), ), - _buildDivider(), + _buildDivider(appColors), SizedBox(width: 44, child: Center(child: _buildStatusIcon())), ], ), @@ -121,20 +123,20 @@ class ChallengeCard extends StatelessWidget { ); } - Widget _buildWarningText() { - return const Text( + Widget _buildWarningText(AppColorsExtension appColors) { + return Text( '오늘 챌린지를 하지 않으면 실패해요!', - style: TextStyle(color: AppColors.notification, fontSize: 12), + style: TextStyle(color: appColors.notification, fontSize: 12), ); } - Widget _buildDivider() { + Widget _buildDivider(AppColorsExtension appColors) { return SizedBox( width: 40, child: Center( child: CustomPaint( size: const Size(1, double.infinity), - painter: VerticalDashPainter(), + painter: VerticalDashPainter(color: appColors.gray3), ), ), ); @@ -150,14 +152,18 @@ class ChallengeCard extends StatelessWidget { } class VerticalDashPainter extends CustomPainter { + final Color color; + + VerticalDashPainter({required this.color}); + @override void paint(Canvas canvas, Size size) { double dashHeight = 5, dashSpace = 3, startY = 0; // 캔버스의 중앙 X축 계산 final double centerX = size.width / 2; final paint = Paint() - ..color = AppColors - .gray3 // 점선 색상 농도 조절 + ..color = + color // 점선 색상 농도 조절 ..strokeWidth = 1; while (startY < size.height) { diff --git a/lib/features/home/widgets/day_chip.dart b/lib/features/home/widgets/day_chip.dart index 120e1d4..f5de400 100644 --- a/lib/features/home/widgets/day_chip.dart +++ b/lib/features/home/widgets/day_chip.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:haenaem/core/theme/app_colors.dart'; import 'package:haenaem/core/theme/app_typography.dart'; -// import 'package:haenaem/features/challenge/models/challenge_model.dart'; class DayChip extends StatelessWidget { final DateTime date; @@ -17,7 +16,9 @@ class DayChip extends StatelessWidget { @override Widget build(BuildContext context) { - const Color gray2 = AppColors.gray2; + final appColors = Theme.of(context).extension()!; + + Color gray2 = appColors.gray2; // 요일 레이블 매핑 const weekdayLabels = ['일', '월', '화', '수', '목', '금', '토']; @@ -32,18 +33,21 @@ class DayChip extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(label, style: AppTypography.b1.copyWith(color: AppColors.black)), + Text( + label, + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), + ), const SizedBox(height: 8), Container( width: 40, height: 40, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 9), decoration: ShapeDecoration( - color: getBackgroundColor(), + color: getBackgroundColor(appColors), shape: RoundedRectangleBorder( // 오늘 날짜이면서 색상이 안 들어간 상태일 때만 테두리 적용 side: showBorder - ? const BorderSide( + ? BorderSide( width: 1, strokeAlign: BorderSide.strokeAlignOutside, color: gray2, @@ -56,7 +60,7 @@ class DayChip extends StatelessWidget { child: Text( day, style: TextStyle( - color: isColoredStatus ? Colors.white : gray2, + color: isColoredStatus ? appColors.whiteToBlack : gray2, fontSize: 14, fontFamily: 'Pretendard', fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400, @@ -69,16 +73,16 @@ class DayChip extends StatelessWidget { ); } - Color getBackgroundColor() { + Color getBackgroundColor(AppColorsExtension appColors) { switch (status) { case 'RED': - return AppColors.notification; + return appColors.notification; case 'GREEN': - return AppColors.primaryAble; + return appColors.primaryAble; case 'GRAY': case 'NONE': default: - return AppColors.gray5; + return appColors.gray5; } } } From 0dc45bc5301081771969fa5e1bb2d425b8d0b447 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Mon, 3 Aug 2026 16:36:20 +0900 Subject: [PATCH 04/18] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=94=8C=EB=A1=9C=EC=9A=B0,=20=ED=95=98=EB=8B=A8?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC,=20=ED=83=9C=EA=B7=B8=20=EB=8B=A4?= =?UTF-8?q?=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/auth/login/login_screen.dart | 14 ++++++------- .../auth/signup/screens/bio_setup_screen.dart | 13 ++++++------ .../signup/screens/nickname_setup_screen.dart | 20 ++++++++++--------- .../signup/screens/signup_main_screen.dart | 17 ++++++++++------ .../signup/screens/signup_success_screen.dart | 15 ++++++++------ .../auth/signup/screens/tag_screen.dart | 4 +++- .../auth/signup/widgets/next_button.dart | 6 ++++-- .../signup/widgets/signup_page_layout.dart | 10 ++++++++-- .../signup/widgets/signup_progress.bar.dart | 6 ++++-- lib/shared/widgets/app_tag_chip.dart | 8 ++++++-- lib/shared/widgets/bottom_action_button.dart | 6 ++++-- lib/shared/widgets/tag_badge.dart | 6 ++++-- 12 files changed, 77 insertions(+), 48 deletions(-) diff --git a/lib/features/auth/login/login_screen.dart b/lib/features/auth/login/login_screen.dart index 414cf58..dfc24e5 100644 --- a/lib/features/auth/login/login_screen.dart +++ b/lib/features/auth/login/login_screen.dart @@ -29,6 +29,7 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; // 공통으로 사용할 네비게이션 함수 // void navigateToSignup() { // Navigator.push( @@ -45,7 +46,6 @@ class _LoginScreenState extends State { } return Scaffold( - backgroundColor: Colors.white, body: SafeArea( // 스크롤 가능 child: SingleChildScrollView( @@ -74,7 +74,9 @@ class _LoginScreenState extends State { Text( "오늘의 '해냄'을 위해\n지금 시작해보세요", textAlign: TextAlign.center, - style: AppTypography.h2.copyWith(color: Colors.black), + style: AppTypography.h2.copyWith( + color: appColors.blackToWhite, + ), ), const SizedBox(height: 120), // 중간 여백 @@ -82,9 +84,7 @@ class _LoginScreenState extends State { Text( '간편하게 시작하기', textAlign: TextAlign.center, - style: AppTypography.b2.copyWith( - color: const Color(0xFf6A7282), - ), + style: AppTypography.b2.copyWith(color: appColors.gray3), ), const SizedBox(height: 10), @@ -206,9 +206,7 @@ class _LoginScreenState extends State { Text( '계속 진행하면 서비스 이용약관 및\n개인정보 처리방침에 동의하는 것으로 간주됩니다', textAlign: TextAlign.center, - style: AppTypography.b2.copyWith( - color: const Color(0xFF6A7282), - ), + style: AppTypography.b2.copyWith(color: appColors.gray3), ), const SizedBox(height: 40), // 하단 여백 ], diff --git a/lib/features/auth/signup/screens/bio_setup_screen.dart b/lib/features/auth/signup/screens/bio_setup_screen.dart index bdadd25..7e3f196 100644 --- a/lib/features/auth/signup/screens/bio_setup_screen.dart +++ b/lib/features/auth/signup/screens/bio_setup_screen.dart @@ -62,6 +62,7 @@ class _BioSetupScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; // 사용자가 한 글자라도 입력했는지 여부를 판단하여 버튼 문구 결정 final String dynamicButtonText = _bioController.text.isEmpty ? '건너뛰기' @@ -82,10 +83,10 @@ class _BioSetupScreenState extends ConsumerState { maxLength: 50, maxLines: null, minLines: 1, - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith(color: appColors.blackToWhite), decoration: InputDecoration( hintText: '한 줄 소개를 입력해주세요', - hintStyle: AppTypography.b2.copyWith(color: AppColors.gray3), + hintStyle: AppTypography.b2.copyWith(color: appColors.gray3), counterText: '', contentPadding: const EdgeInsets.symmetric( vertical: 16, @@ -93,12 +94,12 @@ class _BioSetupScreenState extends ConsumerState { ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: AppColors.gray4), + borderSide: BorderSide(color: appColors.gray4), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide( - color: AppColors.primaryAble, + borderSide: BorderSide( + color: appColors.primaryAble, width: 1.5, ), ), @@ -107,7 +108,7 @@ class _BioSetupScreenState extends ConsumerState { const SizedBox(height: 8), Text( '${_bioController.text.length}/50', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/auth/signup/screens/nickname_setup_screen.dart b/lib/features/auth/signup/screens/nickname_setup_screen.dart index fd24bf1..f2ecfd7 100644 --- a/lib/features/auth/signup/screens/nickname_setup_screen.dart +++ b/lib/features/auth/signup/screens/nickname_setup_screen.dart @@ -97,6 +97,8 @@ class _NicknameSetupScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 어떤 에러 메시지를 보여줄지 결정 (형식 에러가 중복 에러보다 우선순위 높음) String? errorMessage; if (_isInvalidFormat) { @@ -116,10 +118,10 @@ class _NicknameSetupScreenState extends ConsumerState { TextField( controller: _nicknameController, maxLength: 15, - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith(color: appColors.blackToWhite), decoration: InputDecoration( hintText: '닉네임을 입력해주세요', - hintStyle: AppTypography.b2.copyWith(color: AppColors.gray3), + hintStyle: AppTypography.b2.copyWith(color: appColors.gray3), counterText: '', contentPadding: const EdgeInsets.symmetric( vertical: 16, @@ -129,16 +131,16 @@ class _NicknameSetupScreenState extends ConsumerState { borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (errorMessage != null) - ? AppColors.notification - : AppColors.gray4, + ? appColors.notification + : appColors.gray4, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (errorMessage != null) - ? AppColors.notification - : AppColors.primaryAble, + ? appColors.notification + : appColors.primaryAble, width: 1.5, ), ), @@ -160,7 +162,7 @@ class _NicknameSetupScreenState extends ConsumerState { Text( '* ', style: AppTypography.c1.copyWith( - color: AppColors.notification, + color: appColors.notification, height: 1.5, ), ), @@ -170,7 +172,7 @@ class _NicknameSetupScreenState extends ConsumerState { // 기존 메시지에서 '* '를 제거하고 전달 errorMessage.replaceFirst('* ', ''), style: AppTypography.c1.copyWith( - color: AppColors.notification, + color: appColors.notification, height: 1.5, ), ), @@ -184,7 +186,7 @@ class _NicknameSetupScreenState extends ConsumerState { // 글자수 카운터 Text( '${_nicknameController.text.length}/15', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/auth/signup/screens/signup_main_screen.dart b/lib/features/auth/signup/screens/signup_main_screen.dart index d4d1909..b7a5f0b 100644 --- a/lib/features/auth/signup/screens/signup_main_screen.dart +++ b/lib/features/auth/signup/screens/signup_main_screen.dart @@ -70,6 +70,8 @@ class _SignupMainScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final bool isSuccessStep = _currentStep == _totalSteps; final bool isLoading = ref.watch(signupProvider).isLoading; @@ -82,8 +84,8 @@ class _SignupMainScreenState extends ConsumerState { child: Stack( children: [ Scaffold( - backgroundColor: isSuccessStep ? null : Colors.white, - appBar: _buildAppBar(isSuccessStep), + backgroundColor: isSuccessStep ? null : appColors.whiteToBlack, + appBar: _buildAppBar(isSuccessStep, appColors), body: Column( children: [ if (!isSuccessStep) _buildProgressBar(), @@ -106,18 +108,21 @@ class _SignupMainScreenState extends ConsumerState { } // 상단 앱바 구성 - PreferredSizeWidget? _buildAppBar(bool isSuccessStep) { + PreferredSizeWidget? _buildAppBar( + bool isSuccessStep, + AppColorsExtension appColors, + ) { if (isSuccessStep) return null; return AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: AppColors.black), + icon: Icon(Icons.arrow_back, color: appColors.blackToWhite), onPressed: _prevPage, ), title: Text( '회원가입', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ); diff --git a/lib/features/auth/signup/screens/signup_success_screen.dart b/lib/features/auth/signup/screens/signup_success_screen.dart index fc8d8b8..1f5d9f0 100644 --- a/lib/features/auth/signup/screens/signup_success_screen.dart +++ b/lib/features/auth/signup/screens/signup_success_screen.dart @@ -7,6 +7,7 @@ import '../providers/signup_provider.dart'; import '../widgets/emoji_firework_widget.dart'; import 'package:haenaem/features/auth/signup/widgets/signup_progress.bar.dart'; import 'package:haenaem/features/main/screens/main_screen.dart'; +import 'package:haenaem/core/theme/app_colors.dart'; // 회원가입 4단계를 모두 끝낸 후 띄우는 가입 성공 화면 class SignupSuccessScreen extends ConsumerStatefulWidget { @@ -77,7 +78,7 @@ class _SignupSuccessScreenState extends ConsumerState { alignment: Alignment.center, child: Text( '회원가입', - style: AppTypography.h3.copyWith(color: Colors.white), + style: AppTypography.h3.copyWith(color: AppColors.white), ), ), // 진행률 바 배치 @@ -86,8 +87,8 @@ class _SignupSuccessScreenState extends ConsumerState { child: SignupProgressBar( currentStep: 5, totalSteps: 5, - activeColor: Colors.white, - inactiveColor: Colors.white, + activeColor: AppColors.white, + inactiveColor: AppColors.white, ), ), @@ -106,14 +107,14 @@ class _SignupSuccessScreenState extends ConsumerState { Text( '가입을 해냈어요! 🎉\n당신의 첫 번째 성취를 축하합니다.', style: AppTypography.h2.copyWith( - color: Colors.white, + color: AppColors.white, ), ), const SizedBox(height: 10), Text( '멋진 챌린지들이 기다리고 있어요.\n함께 해낼 준비 됐나요?', style: AppTypography.h3.copyWith( - color: Colors.white, + color: AppColors.white, ), ), ], @@ -145,7 +146,9 @@ class _SignupSuccessScreenState extends ConsumerState { child: Text( '시작하기', - style: AppTypography.h3.copyWith(color: Colors.white), + style: AppTypography.h3.copyWith( + color: AppColors.white, + ), ), ), ), diff --git a/lib/features/auth/signup/screens/tag_screen.dart b/lib/features/auth/signup/screens/tag_screen.dart index b2563e1..440420a 100644 --- a/lib/features/auth/signup/screens/tag_screen.dart +++ b/lib/features/auth/signup/screens/tag_screen.dart @@ -42,6 +42,8 @@ class _TagScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final signupState = ref.watch(signupProvider); final categorizedTags = signupState.categorizedTags; final selectedTags = signupState.tags; @@ -91,7 +93,7 @@ class _TagScreenState extends ConsumerState { Text( TagMapper.getKoreanCategory(categoryKey), style: AppTypography.b2.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), ), const SizedBox(height: 12), diff --git a/lib/features/auth/signup/widgets/next_button.dart b/lib/features/auth/signup/widgets/next_button.dart index 29b5c25..23d2573 100644 --- a/lib/features/auth/signup/widgets/next_button.dart +++ b/lib/features/auth/signup/widgets/next_button.dart @@ -18,8 +18,10 @@ class NextButton extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Material( - color: isEnabled ? AppColors.primaryAble : AppColors.disable, + color: isEnabled ? appColors.primaryAble : appColors.disable, borderRadius: BorderRadius.circular(10), child: InkWell( onTap: isEnabled ? onPressed : null, @@ -30,7 +32,7 @@ class NextButton extends StatelessWidget { alignment: Alignment.center, child: Text( text, - style: AppTypography.h3.copyWith(color: Colors.white), + style: AppTypography.h3.copyWith(color: appColors.whiteToBlack), ), ), ), diff --git a/lib/features/auth/signup/widgets/signup_page_layout.dart b/lib/features/auth/signup/widgets/signup_page_layout.dart index 3722a1b..14553a1 100644 --- a/lib/features/auth/signup/widgets/signup_page_layout.dart +++ b/lib/features/auth/signup/widgets/signup_page_layout.dart @@ -26,6 +26,8 @@ class SignupPageLayout extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Stack( children: [ // 콘텐츠 영역 (스크롤 가능) @@ -39,12 +41,16 @@ class SignupPageLayout extends StatelessWidget { // 타이틀 + 서브타이틀 Text( title, - style: AppTypography.h2.copyWith(color: AppColors.black), + style: AppTypography.h2.copyWith( + color: appColors.blackToWhite, + ), ), const SizedBox(height: 10), Text( subTitle, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith( + color: appColors.blackToWhite, + ), ), const SizedBox(height: 42), child, // 단계별 본문 콘텐츠 diff --git a/lib/features/auth/signup/widgets/signup_progress.bar.dart b/lib/features/auth/signup/widgets/signup_progress.bar.dart index 81d1069..b88e5d4 100644 --- a/lib/features/auth/signup/widgets/signup_progress.bar.dart +++ b/lib/features/auth/signup/widgets/signup_progress.bar.dart @@ -19,6 +19,8 @@ class SignupProgressBar extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Row( children: List.generate(totalSteps, (index) { final bool isCompleted = index < currentStep; @@ -29,8 +31,8 @@ class SignupProgressBar extends StatelessWidget { decoration: BoxDecoration( // 지정된 색상이 없으면 기본 테마 색상 사용 color: isCompleted - ? (activeColor ?? AppColors.primaryAble) - : (inactiveColor ?? AppColors.gray4), + ? (activeColor ?? appColors.primaryAble) + : (inactiveColor ?? appColors.gray4), borderRadius: BorderRadius.circular(1000), ), ), diff --git a/lib/shared/widgets/app_tag_chip.dart b/lib/shared/widgets/app_tag_chip.dart index afee54d..48e32b3 100644 --- a/lib/shared/widgets/app_tag_chip.dart +++ b/lib/shared/widgets/app_tag_chip.dart @@ -18,6 +18,8 @@ class AppTagChip extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return GestureDetector( onTap: onTap, child: Container( @@ -25,7 +27,7 @@ class AppTagChip extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 11), decoration: ShapeDecoration( // 선택 시 selected 색상 및 primaryAble 테두리, 미선택 시 gray5 - color: isSelected ? AppColors.selected : AppColors.gray5, + color: isSelected ? appColors.selected : appColors.gray5, shape: RoundedRectangleBorder( side: isSelected ? const BorderSide(width: 2, color: AppColors.primaryAble) @@ -40,7 +42,9 @@ class AppTagChip extends StatelessWidget { Text( label, style: AppTypography.b2.copyWith( - color: isSelected ? AppColors.primaryAble : AppColors.black, + color: isSelected + ? appColors.primaryAble + : appColors.blackToWhite, fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400, height: 1.0, // 텍스트 중앙 정렬 ), diff --git a/lib/shared/widgets/bottom_action_button.dart b/lib/shared/widgets/bottom_action_button.dart index 50e0e0f..1e5c0fd 100644 --- a/lib/shared/widgets/bottom_action_button.dart +++ b/lib/shared/widgets/bottom_action_button.dart @@ -29,9 +29,11 @@ class BottomActionButton extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 기본값 설정: 파라미터가 없으면 기존 디자인 시스템 색상 사용 - final Color effectiveBgColor = backgroundColor ?? AppColors.primaryAble; - final Color effectiveTextColor = textColor ?? Colors.white; + final Color effectiveBgColor = backgroundColor ?? appColors.primaryAble; + final Color effectiveTextColor = textColor ?? appColors.whiteToBlack; final Widget buttonWidget = ElevatedButton( onPressed: onPressed, diff --git a/lib/shared/widgets/tag_badge.dart b/lib/shared/widgets/tag_badge.dart index a577d88..771114f 100644 --- a/lib/shared/widgets/tag_badge.dart +++ b/lib/shared/widgets/tag_badge.dart @@ -11,18 +11,20 @@ class TagBadge extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( height: 28, // 고정 높이 padding: const EdgeInsets.symmetric(horizontal: 11), decoration: ShapeDecoration( - color: AppColors.selected, + color: appColors.selected, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), ), child: Center( widthFactor: 1.0, child: Text( label, - style: AppTypography.b2.copyWith(color: AppColors.primaryAble), + style: AppTypography.b2.copyWith(color: appColors.primaryAble), ), ), ); From 003d0c85c1198d487fdaae1322ef850093272e9c Mon Sep 17 00:00:00 2001 From: Seungvin Date: Mon, 3 Aug 2026 18:28:11 +0900 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/challenge_create_screen.dart | 22 ++++++----- .../create/widgets/ai_notice_box.dart | 15 ++++---- .../challenge_calendar_bottom_sheet.dart | 38 ++++++++++++------- .../challenge_duration_bottom_sheet.dart | 35 +++++++++-------- .../challenge_frequency_bottom_sheet.dart | 8 +++- .../widgets/challenge_select_button.dart | 10 +++-- .../widgets/challenge_tag_bottom_sheet.dart | 21 +++++----- .../challenge_visibility_selector.dart | 4 +- .../challenge/create/widgets/plus_button.dart | 14 ++++--- lib/shared/widgets/bottom_sheet_header.dart | 10 +++-- lib/shared/widgets/challenge_input_box.dart | 14 ++++--- lib/shared/widgets/custom_bottom_sheet.dart | 7 +++- 12 files changed, 119 insertions(+), 79 deletions(-) diff --git a/lib/features/challenge/create/screens/challenge_create_screen.dart b/lib/features/challenge/create/screens/challenge_create_screen.dart index e7de616..7c1aa12 100644 --- a/lib/features/challenge/create/screens/challenge_create_screen.dart +++ b/lib/features/challenge/create/screens/challenge_create_screen.dart @@ -324,8 +324,10 @@ class _ChallengeCreateScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( // 뒤로 가기 버튼 leading: IconButton( @@ -338,13 +340,13 @@ class _ChallengeCreateScreenState extends ConsumerState { ), title: Text( "챌린지 만들기", - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, elevation: 0, scrolledUnderElevation: 0, - surfaceTintColor: Colors.white, - backgroundColor: Colors.white, + surfaceTintColor: appColors.whiteToBlack, + backgroundColor: appColors.whiteToBlack, ), // 입력 폼 @@ -373,8 +375,8 @@ class _ChallengeCreateScreenState extends ConsumerState { hintText: _dateHintText, // 선택된 날짜가 없으면 gray3, 날짜가 선택되면 black으로 설정 textColor: _selectedDay == null - ? AppColors.gray3 - : AppColors.black, + ? appColors.gray3 + : appColors.blackToWhite, iconPath: 'assets/images/icons/big_down_arrow.svg', onTap: _showCalendarBottomSheet, ), @@ -385,8 +387,8 @@ class _ChallengeCreateScreenState extends ConsumerState { ChallengeInputBox( hintText: _selectedDuration ?? '인증 기간을 선택하세요', textColor: _selectedDuration == null - ? AppColors.gray3 - : AppColors.black, + ? appColors.gray3 + : appColors.blackToWhite, iconPath: 'assets/images/icons/big_down_arrow.svg', onTap: showDurationBottomSheet, ), @@ -397,8 +399,8 @@ class _ChallengeCreateScreenState extends ConsumerState { ChallengeInputBox( hintText: _selectedFrequency ?? '인증 빈도를 선택하세요', textColor: _selectedFrequency == null - ? AppColors.gray3 - : AppColors.black, + ? appColors.gray3 + : appColors.blackToWhite, iconPath: 'assets/images/icons/big_down_arrow.svg', onTap: showFrequencyBottomSheet, ), diff --git a/lib/features/challenge/create/widgets/ai_notice_box.dart b/lib/features/challenge/create/widgets/ai_notice_box.dart index 75ea836..7744c9a 100644 --- a/lib/features/challenge/create/widgets/ai_notice_box.dart +++ b/lib/features/challenge/create/widgets/ai_notice_box.dart @@ -13,13 +13,15 @@ class AiNoticeBox extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColors.gray4, width: 0.75), + border: Border.all(color: appColors.gray4, width: 0.75), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, // 아이콘이 문구 상단에 맞게 정렬 @@ -28,10 +30,7 @@ class AiNoticeBox extends StatelessWidget { 'assets/images/icons/ai_notice.svg', width: 20, height: 20, - colorFilter: const ColorFilter.mode( - AppColors.gray1, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appColors.gray1, BlendMode.srcIn), ), const SizedBox(width: 5), Expanded( @@ -40,14 +39,14 @@ class AiNoticeBox extends StatelessWidget { children: [ Text( '정확한 인증을 위해 AI 검증 단계를 거치게 됩니다.\n환경에 따라 인식이 지연되거나 재촬영이 필요할 수 있습니다.', - style: AppTypography.c1.copyWith(color: AppColors.gray1), + style: AppTypography.c1.copyWith(color: appColors.gray1), ), if (autoVerifiable == false) ...[ const SizedBox(height: 4), Text( '현재 입력한 챌린지 이름은 AI가 자동으로 판별하기 어려운 주제예요. 인증 시 지연되거나 재촬영이 필요할 가능성이 높아요.', style: AppTypography.c1.copyWith( - color: AppColors.primaryAble, + color: appColors.primaryAble, ), ), ], diff --git a/lib/features/challenge/create/widgets/challenge_calendar_bottom_sheet.dart b/lib/features/challenge/create/widgets/challenge_calendar_bottom_sheet.dart index c7dfb2b..ce2e149 100644 --- a/lib/features/challenge/create/widgets/challenge_calendar_bottom_sheet.dart +++ b/lib/features/challenge/create/widgets/challenge_calendar_bottom_sheet.dart @@ -39,6 +39,8 @@ class _ChallengeCalendarBottomSheetState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + bool isFirstMonth = _focusedDay.year == _today.year && _focusedDay.month == _today.month; @@ -64,7 +66,7 @@ class _ChallengeCalendarBottomSheetState width: 44.62, height: 44.62, decoration: ShapeDecoration( - color: const Color(0xFFE8F5E9), // 해냄-green-selected + color: appColors.selected, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -73,7 +75,7 @@ class _ChallengeCalendarBottomSheetState child: Text( '${date.day}', style: AppTypography.b1.copyWith( - color: const Color(0xFF009951), // 해냄-green-primary-able + color: appColors.primaryAble, // 해냄-green-primary-able fontWeight: FontWeight.bold, ), ), @@ -89,7 +91,9 @@ class _ChallengeCalendarBottomSheetState child: Center( child: Text( '${date.day}', - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith( + color: appColors.blackToWhite, + ), ), ), ), @@ -102,14 +106,16 @@ class _ChallengeCalendarBottomSheetState headerPadding: const EdgeInsets.only(top: 0, bottom: 5), formatButtonVisible: false, titleCentered: true, - titleTextStyle: AppTypography.h3.copyWith(color: AppColors.black), + titleTextStyle: AppTypography.h3.copyWith( + color: appColors.blackToWhite, + ), leftChevronIcon: _buildChevron( 'assets/images/icons/tab_previous.svg', - isFirstMonth ? AppColors.gray3 : AppColors.black, + isFirstMonth ? appColors.gray3 : appColors.blackToWhite, ), rightChevronIcon: _buildChevron( 'assets/images/icons/tab_next.svg', - AppColors.black, + appColors.blackToWhite, ), leftChevronMargin: const EdgeInsets.only(left: 50), rightChevronMargin: const EdgeInsets.only(right: 50), @@ -135,7 +141,7 @@ class _ChallengeCalendarBottomSheetState ), disabledTextStyle: AppTypography.b1.copyWith( - color: AppColors.gray3, + color: appColors.gray3, ), // 오늘 날짜 디자인 @@ -144,22 +150,28 @@ class _ChallengeCalendarBottomSheetState shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(8), ), - todayTextStyle: AppTypography.b1.copyWith(color: AppColors.black), + todayTextStyle: AppTypography.b1.copyWith( + color: appColors.blackToWhite, + ), // 선택된 날짜 디자인 selectedDecoration: BoxDecoration( - color: AppColors.selected, + color: appColors.selected, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(8), ), selectedTextStyle: AppTypography.b1.copyWith( - color: AppColors.primaryAble, + color: appColors.primaryAble, fontWeight: FontWeight.bold, ), - defaultTextStyle: AppTypography.b1.copyWith(color: AppColors.black), - weekendTextStyle: AppTypography.b1.copyWith(color: AppColors.black), - outsideTextStyle: AppTypography.b1.copyWith(color: AppColors.gray3), + defaultTextStyle: AppTypography.b1.copyWith( + color: appColors.blackToWhite, + ), + weekendTextStyle: AppTypography.b1.copyWith( + color: appColors.blackToWhite, + ), + outsideTextStyle: AppTypography.b1.copyWith(color: appColors.gray3), ), // 과거 날짜 선택 차단 diff --git a/lib/features/challenge/create/widgets/challenge_duration_bottom_sheet.dart b/lib/features/challenge/create/widgets/challenge_duration_bottom_sheet.dart index 168c26d..9e0745d 100644 --- a/lib/features/challenge/create/widgets/challenge_duration_bottom_sheet.dart +++ b/lib/features/challenge/create/widgets/challenge_duration_bottom_sheet.dart @@ -64,6 +64,8 @@ class _ChallengeDurationBottomSheetState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return CustomBottomSheet( title: "인증 기간 선택", heightFactor: isCustomMode ? 0.80 : 0.55, @@ -73,6 +75,7 @@ class _ChallengeDurationBottomSheetState // 1. 기본 기간 리스트 ...["7일", "30일", "50일", "100일", "365일"].map((item) { return _buildListItem( + appColors, label: item, isSelected: _tempSelectedDuration == item && !isCustomMode, onTap: () { @@ -86,15 +89,16 @@ class _ChallengeDurationBottomSheetState // 2. 직접 입력 섹션 Container( width: double.infinity, - color: isCustomMode ? AppColors.selected : Colors.transparent, + color: isCustomMode ? appColors.selected : Colors.transparent, child: Column( children: [ _buildListItem( + appColors, label: "직접 입력", isSelected: isCustomMode, onTap: () => setState(() => isCustomMode = true), ), - if (isCustomMode) _buildCustomInputForm(), + if (isCustomMode) _buildCustomInputForm(appColors), ], ), ), @@ -105,7 +109,7 @@ class _ChallengeDurationBottomSheetState } // 직접 입력 폼 위젯 - Widget _buildCustomInputForm() { + Widget _buildCustomInputForm(AppColorsExtension appColors) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 23), child: Column( @@ -114,20 +118,20 @@ class _ChallengeDurationBottomSheetState Text( '• 1일 이상의 기간을 입력해주세요\n• 1~365일까지 설정할 수 있습니다.', style: AppTypography.b2.copyWith( - color: AppColors.gray2, + color: appColors.gray2, height: 1.5, ), ), const SizedBox(height: 24), Text( '인증 기간 (일)', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), const SizedBox(height: 8), Container( - decoration: const BoxDecoration( + decoration: BoxDecoration( border: Border( - bottom: BorderSide(color: AppColors.gray3, width: 0.75), + bottom: BorderSide(color: appColors.gray3, width: 0.75), ), ), child: Row( @@ -141,7 +145,7 @@ class _ChallengeDurationBottomSheetState decoration: InputDecoration( hintText: "숫자를 입력하세요", hintStyle: AppTypography.b1.copyWith( - color: AppColors.gray3, + color: appColors.gray3, ), border: InputBorder.none, isDense: true, @@ -150,7 +154,7 @@ class _ChallengeDurationBottomSheetState ), Text( '일', - style: AppTypography.b1.copyWith(color: AppColors.gray1), + style: AppTypography.b1.copyWith(color: appColors.gray1), ), ], ), @@ -169,13 +173,13 @@ class _ChallengeDurationBottomSheetState alignment: Alignment.center, decoration: BoxDecoration( color: isButtonEnabled - ? AppColors.primaryAble - : AppColors.disable, + ? appColors.primaryAble + : appColors.disable, borderRadius: BorderRadius.circular(10), ), child: Text( '완료', - style: AppTypography.b1.copyWith(color: Colors.white), + style: AppTypography.b1.copyWith(color: appColors.whiteToBlack), ), ), ), @@ -186,7 +190,8 @@ class _ChallengeDurationBottomSheetState } // 내부 리스트 아이템 빌더 - Widget _buildListItem({ + Widget _buildListItem( + AppColorsExtension appColors, { required String label, required bool isSelected, required VoidCallback onTap, @@ -196,12 +201,12 @@ class _ChallengeDurationBottomSheetState child: Container( width: double.infinity, height: 54, - color: isSelected ? AppColors.selected : Colors.transparent, + color: isSelected ? appColors.selected : Colors.transparent, alignment: Alignment.center, child: Text( label, style: AppTypography.b1.copyWith( - color: isSelected ? AppColors.primaryAble : AppColors.black, + color: isSelected ? appColors.primaryAble : appColors.blackToWhite, ), ), ), diff --git a/lib/features/challenge/create/widgets/challenge_frequency_bottom_sheet.dart b/lib/features/challenge/create/widgets/challenge_frequency_bottom_sheet.dart index 1bc29c9..794c66b 100644 --- a/lib/features/challenge/create/widgets/challenge_frequency_bottom_sheet.dart +++ b/lib/features/challenge/create/widgets/challenge_frequency_bottom_sheet.dart @@ -18,6 +18,8 @@ class ChallengeFrequencyBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 선택지 리스트 정의 final items = ["매일", "주 1회", "주 2회", "주 3회", "주 4회", "주 5회", "주 6회"]; @@ -40,13 +42,15 @@ class ChallengeFrequencyBottomSheet extends StatelessWidget { width: double.infinity, height: 54, decoration: BoxDecoration( - color: isSelected ? AppColors.selected : Colors.transparent, + color: isSelected ? appColors.selected : Colors.transparent, ), alignment: Alignment.center, child: Text( item, style: AppTypography.b1.copyWith( - color: isSelected ? AppColors.primaryAble : AppColors.black, + color: isSelected + ? appColors.primaryAble + : appColors.blackToWhite, ), ), ), diff --git a/lib/features/challenge/create/widgets/challenge_select_button.dart b/lib/features/challenge/create/widgets/challenge_select_button.dart index 2597dd5..971837c 100644 --- a/lib/features/challenge/create/widgets/challenge_select_button.dart +++ b/lib/features/challenge/create/widgets/challenge_select_button.dart @@ -19,6 +19,8 @@ class ChallengeSelectButton extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Expanded( child: GestureDetector( onTap: onTap, @@ -26,11 +28,11 @@ class ChallengeSelectButton extends StatelessWidget { height: 48, decoration: BoxDecoration( // 활성화 시 selected 색상, 비활성 시 gray5 - color: isSelected ? AppColors.selected : AppColors.gray5, + color: isSelected ? appColors.selected : appColors.gray5, borderRadius: BorderRadius.circular(8), // 활성화 시에만 primaryAble 외곽선 추가 border: isSelected - ? Border.all(color: AppColors.primaryAble, width: 2) + ? Border.all(color: appColors.primaryAble, width: 2) : null, ), alignment: Alignment.center, @@ -39,7 +41,9 @@ class ChallengeSelectButton extends StatelessWidget { style: AppTypography.b2.copyWith( // 활성화 시 Bold + primaryAble 색상 fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected ? AppColors.primaryAble : AppColors.black, + color: isSelected + ? appColors.primaryAble + : appColors.blackToWhite, ), ), ), diff --git a/lib/features/challenge/create/widgets/challenge_tag_bottom_sheet.dart b/lib/features/challenge/create/widgets/challenge_tag_bottom_sheet.dart index 2d41674..5056610 100644 --- a/lib/features/challenge/create/widgets/challenge_tag_bottom_sheet.dart +++ b/lib/features/challenge/create/widgets/challenge_tag_bottom_sheet.dart @@ -63,6 +63,8 @@ class _ChallengeTagBottomSheetState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final tagsAsync = ref.watch(allTagsProvider); // 💡 화면 전체 높이의 95% 계산 @@ -71,8 +73,8 @@ class _ChallengeTagBottomSheetState return Container( height: targetHeight, // 높이를 95%로 고정 - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: appColors.whiteToBlack, borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), @@ -116,17 +118,14 @@ class _ChallengeTagBottomSheetState Text( '챌린지 태그 선택', style: AppTypography.h3.copyWith( - color: const Color(0xFF1A1D1B), + color: appColors.blackToWhite, ), ), IconButton( onPressed: () => Navigator.pop(context), - icon: const Text( + icon: Text( '×', - style: TextStyle( - fontSize: 28, - color: Color(0xFF616161), - ), + style: TextStyle(fontSize: 28, color: appColors.gray2), ), padding: EdgeInsets.zero, constraints: const BoxConstraints(), @@ -136,13 +135,13 @@ class _ChallengeTagBottomSheetState ), // 2. 서브 타이틀 - const Padding( + Padding( padding: EdgeInsets.symmetric(vertical: 2), child: Text( '태그를 통해 관심 분야를 알려주세요\n1~2개를 골라주세요', textAlign: TextAlign.center, style: TextStyle( - color: Color(0xFF1A1D1B), + color: appColors.blackToWhite, fontSize: 16, fontWeight: FontWeight.w500, height: 1.5, @@ -183,7 +182,7 @@ class _ChallengeTagBottomSheetState Text( TagMapper.getKoreanCategory(categoryKey), style: AppTypography.b2.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), ), const SizedBox(height: 10), diff --git a/lib/features/challenge/create/widgets/challenge_visibility_selector.dart b/lib/features/challenge/create/widgets/challenge_visibility_selector.dart index e8e4c10..9221ac7 100644 --- a/lib/features/challenge/create/widgets/challenge_visibility_selector.dart +++ b/lib/features/challenge/create/widgets/challenge_visibility_selector.dart @@ -18,6 +18,8 @@ class ChallengeVisibilitySelector extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -46,7 +48,7 @@ class ChallengeVisibilitySelector extends StatelessWidget { const SizedBox(height: 4), Text( '비공개, 친구 공개 시 챌린지 검색에서 제외됩니다.', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ); diff --git a/lib/features/challenge/create/widgets/plus_button.dart b/lib/features/challenge/create/widgets/plus_button.dart index dd14dc3..c338d13 100644 --- a/lib/features/challenge/create/widgets/plus_button.dart +++ b/lib/features/challenge/create/widgets/plus_button.dart @@ -19,6 +19,8 @@ class PlusButton extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final bool isEnabled = onPressed != null; // 💡 라벨이 '완료'인 경우 플러스 아이콘을 숨기기 위한 조건 @@ -26,7 +28,7 @@ class PlusButton extends StatelessWidget { return Container( decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, boxShadow: showShadow ? [ const BoxShadow( @@ -50,7 +52,7 @@ class PlusButton extends StatelessWidget { height: 60, alignment: Alignment.center, decoration: ShapeDecoration( - color: isEnabled ? AppColors.primaryAble : AppColors.disable, + color: isEnabled ? appColors.primaryAble : appColors.disable, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -64,8 +66,8 @@ class PlusButton extends StatelessWidget { 'assets/images/icons/plus.svg', width: 20, height: 20, - colorFilter: const ColorFilter.mode( - Colors.white, + colorFilter: ColorFilter.mode( + appColors.whiteToBlack, BlendMode.srcIn, ), ), @@ -73,7 +75,9 @@ class PlusButton extends StatelessWidget { ], Text( label, - style: AppTypography.h3.copyWith(color: Colors.white), + style: AppTypography.h3.copyWith( + color: appColors.whiteToBlack, + ), ), ], ), diff --git a/lib/shared/widgets/bottom_sheet_header.dart b/lib/shared/widgets/bottom_sheet_header.dart index 370728d..a1db705 100644 --- a/lib/shared/widgets/bottom_sheet_header.dart +++ b/lib/shared/widgets/bottom_sheet_header.dart @@ -12,6 +12,8 @@ class BottomSheetHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Column( children: [ Row( @@ -22,7 +24,9 @@ class BottomSheetHeader extends StatelessWidget { child: Center( child: Text( title, - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith( + color: appColors.blackToWhite, + ), ), ), ), @@ -35,8 +39,8 @@ class BottomSheetHeader extends StatelessWidget { 'assets/images/icons/tab_close.svg', width: 14, height: 14, - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), diff --git a/lib/shared/widgets/challenge_input_box.dart b/lib/shared/widgets/challenge_input_box.dart index cdaa8f4..d9bf966 100644 --- a/lib/shared/widgets/challenge_input_box.dart +++ b/lib/shared/widgets/challenge_input_box.dart @@ -31,6 +31,8 @@ class ChallengeInputBox extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return GestureDetector( onTap: onTap, child: Container( @@ -38,9 +40,9 @@ class ChallengeInputBox extends StatelessWidget { height: height, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColors.gray4, width: 1), + border: Border.all(color: appColors.gray4, width: 1), ), // 박스 내부 내용물 배치 child: Row( @@ -57,8 +59,8 @@ class ChallengeInputBox extends StatelessWidget { leadingIconPath!, width: 15, height: 15, - colorFilter: const ColorFilter.mode( - AppColors.gray3, + colorFilter: ColorFilter.mode( + appColors.gray3, BlendMode.srcIn, ), ), @@ -75,7 +77,7 @@ class ChallengeInputBox extends StatelessWidget { enabled: true, onTap: onTap, style: AppTypography.b2.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), // 챌린지 설명 hintText 위로 올리기 @@ -90,7 +92,7 @@ class ChallengeInputBox extends StatelessWidget { decoration: InputDecoration( hintText: hintText, hintStyle: AppTypography.b2.copyWith( - color: textColor ?? AppColors.gray3, + color: textColor ?? appColors.gray3, ), border: InputBorder.none, isDense: true, diff --git a/lib/shared/widgets/custom_bottom_sheet.dart b/lib/shared/widgets/custom_bottom_sheet.dart index e848744..e894986 100644 --- a/lib/shared/widgets/custom_bottom_sheet.dart +++ b/lib/shared/widgets/custom_bottom_sheet.dart @@ -1,6 +1,7 @@ // 최초 작성자 : 김채영 import 'package:flutter/material.dart'; import 'bottom_sheet_header.dart'; +import 'package:haenaem/core/theme/app_colors.dart'; // -- 챌린지 생성화면의 탭 화면의 제목, 콘텐츠들 커스텀할 수 있도록 함 -- class CustomBottomSheet extends StatelessWidget { @@ -17,10 +18,12 @@ class CustomBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( height: MediaQuery.of(context).size.height * heightFactor, - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: appColors.whiteToBlack, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( From 1d9e1eac173973a2f7a944fde78e3b7707600d9f Mon Sep 17 00:00:00 2001 From: Seungvin Date: Mon, 3 Aug 2026 20:44:19 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20=EC=9C=84?= =?UTF-8?q?=EC=A0=AF=EB=93=A4=20=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/challenge_detail_content.dart | 49 +++++++++++-------- .../widgets/challenge_exit_base_dialog.dart | 24 +++++---- lib/shared/widgets/confirm_dialog.dart | 12 +++-- lib/shared/widgets/custom_search_bar.dart | 11 ++--- lib/shared/widgets/custom_switch.dart | 4 +- lib/shared/widgets/custom_tab_bar.dart | 10 ++-- lib/shared/widgets/image_source_sheet.dart | 16 +++--- lib/shared/widgets/select_dialog.dart | 33 ++++++++----- lib/shared/widgets/slider_indicator.dart | 14 ++++-- lib/shared/widgets/user_list_tile.dart | 8 ++- 10 files changed, 108 insertions(+), 73 deletions(-) diff --git a/lib/shared/widgets/challenge_detail_content.dart b/lib/shared/widgets/challenge_detail_content.dart index 85345b4..db85a3c 100644 --- a/lib/shared/widgets/challenge_detail_content.dart +++ b/lib/shared/widgets/challenge_detail_content.dart @@ -23,6 +23,8 @@ class ChallengeDetailContent extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 1. 날짜 데이터 가공 String formattedStart = DateFormat( 'yyyy년 MM월 dd일', @@ -71,25 +73,26 @@ class ChallengeDetailContent extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildInfoSection('챌린지 시작일', formattedStart), - _buildInfoSection('챌린지 마감일', '$formattedEnd $dDayString'), - _buildInfoSection('인증 빈도', frequencyText), + _buildInfoSection('챌린지 시작일', formattedStart, appColors), + _buildInfoSection('챌린지 마감일', '$formattedEnd $dDayString', appColors), + _buildInfoSection('인증 빈도', frequencyText, appColors), _buildInfoSection( '인증 방식', challenge.photoRequired ? '사진 첨부 필수' : '사진 첨부 선택', + appColors, ), // 태그 섹션 Text( '챌린지 태그', - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), const SizedBox(height: 8), challenge.tags.isEmpty ? Text( "-", - style: AppTypography.b1.copyWith(color: AppColors.gray3), + style: AppTypography.b1.copyWith(color: appColors.gray3), ) : Wrap( spacing: 8, @@ -118,13 +121,13 @@ class ChallengeDetailContent extends StatelessWidget { // 챌린지 설명 Text( '챌린지 설명', - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), const SizedBox(height: 8), Text( challenge.description, style: AppTypography.b1.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, height: 1.5, ), ), @@ -132,7 +135,7 @@ class ChallengeDetailContent extends StatelessWidget { const _CustomDivider(), // 방장 정보 - Text('방장', style: AppTypography.b1.copyWith(color: AppColors.gray2)), + Text('방장', style: AppTypography.b1.copyWith(color: appColors.gray2)), const SizedBox(height: 12), Row( children: [ @@ -143,7 +146,7 @@ class ChallengeDetailContent extends StatelessWidget { const SizedBox(width: 12), Text( challenge.leader.nickname, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), ], ), @@ -153,18 +156,18 @@ class ChallengeDetailContent extends StatelessWidget { // 참여자 수 Row( children: [ - const Icon(Icons.person, size: 18, color: AppColors.black), + Icon(Icons.person, size: 18, color: appColors.blackToWhite), const SizedBox(width: 4), Text( '참여자 수', - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), ], ), const SizedBox(height: 4), Text( '${challenge.participantCount}명/50명', - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), const _CustomDivider(), @@ -172,13 +175,13 @@ class ChallengeDetailContent extends StatelessWidget { // 오늘의 인증자 Text( '오늘의 인증자', - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), const SizedBox(height: 12), challenge.todaySuccessUsers.isEmpty ? Text( '아직 오늘의 인증자가 없습니다.', - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ) : SingleChildScrollView( scrollDirection: Axis.horizontal, @@ -199,7 +202,7 @@ class ChallengeDetailContent extends StatelessWidget { child: Text( user.nickname, style: AppTypography.c1.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, @@ -219,17 +222,21 @@ class ChallengeDetailContent extends StatelessWidget { // --- 내부 컴포넌트 메서드 --- - Widget _buildInfoSection(String title, String content) { + Widget _buildInfoSection( + String title, + String content, + AppColorsExtension appColors, + ) { return Padding( padding: const EdgeInsets.only(bottom: 24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: AppTypography.b1.copyWith(color: AppColors.gray2)), + Text(title, style: AppTypography.b1.copyWith(color: appColors.gray2)), const SizedBox(height: 4), Text( content, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), ], ), @@ -259,9 +266,11 @@ class _CustomDivider extends StatelessWidget { const _CustomDivider(); @override Widget build(BuildContext context) { - return const Padding( + final appColors = Theme.of(context).extension()!; + + return Padding( padding: EdgeInsets.symmetric(vertical: 20), - child: Divider(height: 1, thickness: 1, color: AppColors.gray4), + child: Divider(height: 1, thickness: 1, color: appColors.gray4), ); } } diff --git a/lib/shared/widgets/challenge_exit_base_dialog.dart b/lib/shared/widgets/challenge_exit_base_dialog.dart index 921b71e..01a005c 100644 --- a/lib/shared/widgets/challenge_exit_base_dialog.dart +++ b/lib/shared/widgets/challenge_exit_base_dialog.dart @@ -23,9 +23,11 @@ class ChallengeExitBaseDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)), - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, // 다이얼로그 자체가 화면 끝에서 떨어지는 정도 insetPadding: const EdgeInsets.symmetric(horizontal: 20.0), child: Container( @@ -41,7 +43,7 @@ class ChallengeExitBaseDialog extends StatelessWidget { // 2. 제목 Text( title, - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), const SizedBox(height: 8), @@ -55,8 +57,8 @@ class ChallengeExitBaseDialog extends StatelessWidget { 'assets/images/icons/tri_warning_icon.svg', width: 20, height: 20, - colorFilter: const ColorFilter.mode( - AppColors.notification, + colorFilter: ColorFilter.mode( + appColors.notification, BlendMode.srcIn, ), ), @@ -66,7 +68,7 @@ class ChallengeExitBaseDialog extends StatelessWidget { '지금까지의 진행 상황이 모두 사라지며,\n복구할 수 없습니다.', textAlign: TextAlign.center, style: AppTypography.b3.copyWith( - color: AppColors.notification, + color: appColors.notification, ), ), ), @@ -88,7 +90,7 @@ class ChallengeExitBaseDialog extends StatelessWidget { child: ElevatedButton( onPressed: isConfirmEnabled ? onConfirm : null, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.notification, + backgroundColor: appColors.notification, disabledBackgroundColor: const Color(0xFFDBAEAD), elevation: 0, shape: RoundedRectangleBorder( @@ -97,7 +99,9 @@ class ChallengeExitBaseDialog extends StatelessWidget { ), child: Text( confirmButtonText, - style: AppTypography.b1.copyWith(color: Colors.white), + style: AppTypography.b1.copyWith( + color: appColors.whiteToBlack, + ), ), ), ), @@ -111,8 +115,8 @@ class ChallengeExitBaseDialog extends StatelessWidget { // result에 false를 넣지 않고 그냥 pop 합니다. (null 반환) onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( - backgroundColor: AppColors.gray5, - foregroundColor: AppColors.gray2, + backgroundColor: appColors.gray5, + foregroundColor: appColors.gray2, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), @@ -121,7 +125,7 @@ class ChallengeExitBaseDialog extends StatelessWidget { child: Text( '취소', style: AppTypography.b1.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), ), ), diff --git a/lib/shared/widgets/confirm_dialog.dart b/lib/shared/widgets/confirm_dialog.dart index 8b44152..0b33454 100644 --- a/lib/shared/widgets/confirm_dialog.dart +++ b/lib/shared/widgets/confirm_dialog.dart @@ -22,9 +22,11 @@ class ConfirmDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, child: Padding( padding: const EdgeInsets.fromLTRB(16, 24, 16, 12), child: Column( @@ -34,12 +36,12 @@ class ConfirmDialog extends StatelessWidget { if (title != null) ...[ Text( title!, - style: AppTypography.h2.copyWith(color: AppColors.black), + style: AppTypography.h2.copyWith(color: appColors.blackToWhite), ), ], Text( content, - style: AppTypography.b3.copyWith(color: AppColors.gray1), + style: AppTypography.b3.copyWith(color: appColors.gray1), textAlign: TextAlign.center, ), const SizedBox(height: 36), @@ -53,14 +55,14 @@ class ConfirmDialog extends StatelessWidget { width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( - color: AppColors.primaryAble, // 해냄 메인 활성화 색상 + color: appColors.primaryAble, // 해냄 메인 활성화 색상 borderRadius: BorderRadius.circular(10), ), child: Center( child: Text( buttonText, style: AppTypography.b1.copyWith( - color: Colors.white, + color: appColors.whiteToBlack, fontWeight: FontWeight.w600, ), ), diff --git a/lib/shared/widgets/custom_search_bar.dart b/lib/shared/widgets/custom_search_bar.dart index 1d2bd20..b4c3c4a 100644 --- a/lib/shared/widgets/custom_search_bar.dart +++ b/lib/shared/widgets/custom_search_bar.dart @@ -24,12 +24,14 @@ class CustomSearchBar extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( height: 40, decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(10), - border: Border.all(color: AppColors.gray4), + border: Border.all(color: appColors.gray4), ), padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( @@ -37,10 +39,7 @@ class CustomSearchBar extends StatelessWidget { SvgPicture.asset( 'assets/images/icons/search_icon.svg', width: 18, - colorFilter: const ColorFilter.mode( - AppColors.gray3, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appColors.gray3, BlendMode.srcIn), ), const SizedBox(width: 8), Expanded( diff --git a/lib/shared/widgets/custom_switch.dart b/lib/shared/widgets/custom_switch.dart index a34b9ae..b07772c 100644 --- a/lib/shared/widgets/custom_switch.dart +++ b/lib/shared/widgets/custom_switch.dart @@ -11,6 +11,8 @@ class CustomSwitch extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return GestureDetector( onTap: () => onChanged(!value), behavior: HitTestBehavior.opaque, @@ -20,7 +22,7 @@ class CustomSwitch extends StatelessWidget { height: 24, // 디자인 가이드 높이 padding: const EdgeInsets.symmetric(horizontal: 2), decoration: BoxDecoration( - color: value ? AppColors.primaryAble : AppColors.disable, + color: value ? appColors.primaryAble : appColors.disable, borderRadius: BorderRadius.circular(80), ), child: AnimatedAlign( diff --git a/lib/shared/widgets/custom_tab_bar.dart b/lib/shared/widgets/custom_tab_bar.dart index 01f20b8..99461f4 100644 --- a/lib/shared/widgets/custom_tab_bar.dart +++ b/lib/shared/widgets/custom_tab_bar.dart @@ -86,16 +86,18 @@ class _CustomTabBarState extends State @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Column( children: [ TabBar( controller: _tabController, - labelColor: AppColors.primaryAble, - unselectedLabelColor: AppColors.gray2, - indicatorColor: AppColors.primaryAble, + labelColor: appColors.primaryAble, + unselectedLabelColor: appColors.gray2, + indicatorColor: appColors.primaryAble, indicatorWeight: 1, indicatorSize: TabBarIndicatorSize.tab, - labelStyle: AppTypography.b1.copyWith(color: AppColors.primaryAble), + labelStyle: AppTypography.b1.copyWith(color: appColors.primaryAble), tabs: widget.tabs.map((t) => Tab(text: t)).toList(), onTap: (index) { // 이미 선택된 탭을 다시 눌렀을 때 스크롤 최상단 이동 diff --git a/lib/shared/widgets/image_source_sheet.dart b/lib/shared/widgets/image_source_sheet.dart index a53f1ca..c6af632 100644 --- a/lib/shared/widgets/image_source_sheet.dart +++ b/lib/shared/widgets/image_source_sheet.dart @@ -14,6 +14,8 @@ class ImageSourceSheet extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return CustomBottomSheet( title: '사진 추가', heightFactor: 0.4, @@ -22,12 +24,14 @@ class ImageSourceSheet extends StatelessWidget { child: Column( children: [ _buildOptionItem( + appColors, title: '직접 촬영하기', iconPath: 'assets/images/icons/camera.svg', onTap: () => onSourceSelected(ImageSource.camera), ), const SizedBox(height: 10), _buildOptionItem( + appColors, title: '갤러리에서 가져오기', iconPath: 'assets/images/icons/gallery_icon.svg', onTap: () => onSourceSelected(ImageSource.gallery), @@ -38,7 +42,8 @@ class ImageSourceSheet extends StatelessWidget { ); } - Widget _buildOptionItem({ + Widget _buildOptionItem( + AppColorsExtension appColors, { required String title, required String iconPath, required VoidCallback onTap, @@ -50,7 +55,7 @@ class ImageSourceSheet extends StatelessWidget { width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(12), ), child: Row( @@ -59,15 +64,12 @@ class ImageSourceSheet extends StatelessWidget { iconPath, width: 36, height: 36, - colorFilter: const ColorFilter.mode( - AppColors.gray1, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appColors.gray1, BlendMode.srcIn), ), const SizedBox(width: 16), Text( title, - style: AppTypography.b1.copyWith(color: AppColors.gray1), + style: AppTypography.b1.copyWith(color: appColors.gray1), ), ], ), diff --git a/lib/shared/widgets/select_dialog.dart b/lib/shared/widgets/select_dialog.dart index 8248f3b..3cdae73 100644 --- a/lib/shared/widgets/select_dialog.dart +++ b/lib/shared/widgets/select_dialog.dart @@ -29,19 +29,21 @@ class SelectDialog extends StatelessWidget { this.cancelText = '취소', required this.onConfirm, required this.onCancel, - this.titleColor = AppColors.black, - this.contentColor = AppColors.gray2, - this.confirmBackgroundColor = AppColors.gray5, - this.confirmTextColor = AppColors.gray2, - this.cancelBackgroundColor = AppColors.gray5, - this.cancelTextColor = AppColors.gray2, + this.titleColor, + this.contentColor, + this.confirmBackgroundColor, + this.confirmTextColor, + this.cancelBackgroundColor, + this.cancelTextColor, }); @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, child: Container( width: 335, padding: const EdgeInsets.all(16.0), @@ -56,13 +58,17 @@ class SelectDialog extends StatelessWidget { if (title != null) ...[ Text( title!, - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith( + color: titleColor ?? appColors.blackToWhite, + ), ), const SizedBox(height: 8), ], Text( content, - style: AppTypography.b1.copyWith(color: contentColor), + style: AppTypography.b1.copyWith( + color: contentColor ?? appColors.gray2, + ), textAlign: TextAlign.center, ), const SizedBox(height: 20), @@ -72,8 +78,8 @@ class SelectDialog extends StatelessWidget { Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: cancelBackgroundColor, - foregroundColor: cancelTextColor, + backgroundColor: cancelBackgroundColor ?? appColors.gray5, + foregroundColor: cancelTextColor ?? appColors.gray2, elevation: 0, // 다이얼로그 내부 버튼이므로 입체감 제거 shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), // 부드러운 라운딩 처리 @@ -94,8 +100,9 @@ class SelectDialog extends StatelessWidget { Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: confirmBackgroundColor, - foregroundColor: confirmTextColor, + backgroundColor: + confirmBackgroundColor ?? appColors.gray5, + foregroundColor: confirmTextColor ?? appColors.gray2, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), diff --git a/lib/shared/widgets/slider_indicator.dart b/lib/shared/widgets/slider_indicator.dart index 46d88fe..25fa014 100644 --- a/lib/shared/widgets/slider_indicator.dart +++ b/lib/shared/widgets/slider_indicator.dart @@ -10,21 +10,23 @@ class SliderIndicator extends StatelessWidget { final int currentIndex; /// 활성화되었을 때의 인디케이터 색상 - final Color activeColor; + final Color? activeColor; /// 비활성화되었을 때의 인디케이터 색상 - final Color inactiveColor; + final Color? inactiveColor; const SliderIndicator({ super.key, required this.count, required this.currentIndex, - this.activeColor = AppColors.primaryAble, - this.inactiveColor = AppColors.disable, + this.activeColor, + this.inactiveColor, }); @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(count, (index) { @@ -35,7 +37,9 @@ class SliderIndicator extends StatelessWidget { width: isSelected ? 28.0 : 8.0, height: 8.0, decoration: ShapeDecoration( - color: isSelected ? activeColor : inactiveColor, + color: isSelected + ? (activeColor ?? appColors.primaryAble) + : (inactiveColor ?? appColors.disable), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), diff --git a/lib/shared/widgets/user_list_tile.dart b/lib/shared/widgets/user_list_tile.dart index 1903563..b5772ae 100644 --- a/lib/shared/widgets/user_list_tile.dart +++ b/lib/shared/widgets/user_list_tile.dart @@ -22,6 +22,8 @@ class UserListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Padding( padding: padding, child: Row( @@ -35,13 +37,15 @@ class UserListTile extends StatelessWidget { children: [ Text( user.nickname, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith( + color: appColors.blackToWhite, + ), ), /* Text( "해냄 메이트", // TODO 추후 칭호 기능 연동 시 User 모델에서 받아오도록 수정 - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), */ ], From 981f21825c284558ea37fd222fd199a7137da988 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Tue, 4 Aug 2026 01:38:44 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20ui=20=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/challenge_create_screen.dart | 4 + .../detail/screens/challenge_main_screen.dart | 59 +++------ .../detail/screens/member_ranking_screen.dart | 62 ++++++---- .../challenge/detail/views/calendar_view.dart | 79 +++++++----- .../detail/views/information_view.dart | 10 +- .../challenge/detail/views/member_view.dart | 5 +- .../detail/widgets/calendar_grid.dart | 12 +- .../detail/widgets/calendar_post_card.dart | 16 ++- .../widgets/calendar_share_preview.dart | 25 ++-- .../challenge_create_success_dialog.dart | 16 +-- .../detail/widgets/challenge_popup_menu.dart | 27 +++-- .../widgets/notification_settings_dialog.dart | 114 +++++++++++------- .../screens/challenge_invite_screen.dart | 16 ++- .../widgets/challenge_invite_content.dart | 67 +++++----- .../screens/challenge_members_screen.dart | 42 ++++--- .../screens/challenge_settings_screen.dart | 88 ++++++++------ .../challenge_verification_screen.dart | 69 ++++++----- .../screens/custom_gallery_screen.dart | 18 +-- .../verification/widgets/ai_fail_box.dart | 12 +- .../verification/widgets/ai_success_box.dart | 12 +- .../widgets/ai_verification_box.dart | 12 +- .../verification/widgets/photo_free_box.dart | 9 +- .../widgets/reverification_guide_box.dart | 12 +- .../widgets/verification_info_box.dart | 9 +- .../widgets/verification_submit_button.dart | 8 +- .../widgets/verification_tip_box.dart | 9 +- lib/shared/widgets/bottom_action_button.dart | 6 +- 27 files changed, 489 insertions(+), 329 deletions(-) diff --git a/lib/features/challenge/create/screens/challenge_create_screen.dart b/lib/features/challenge/create/screens/challenge_create_screen.dart index 7c1aa12..78d00ff 100644 --- a/lib/features/challenge/create/screens/challenge_create_screen.dart +++ b/lib/features/challenge/create/screens/challenge_create_screen.dart @@ -336,6 +336,10 @@ class _ChallengeCreateScreenState extends ConsumerState { 'assets/images/icons/arrow_left.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), ), title: Text( diff --git a/lib/features/challenge/detail/screens/challenge_main_screen.dart b/lib/features/challenge/detail/screens/challenge_main_screen.dart index a1a93ae..b61faab 100644 --- a/lib/features/challenge/detail/screens/challenge_main_screen.dart +++ b/lib/features/challenge/detail/screens/challenge_main_screen.dart @@ -81,14 +81,16 @@ class _ChallengeDetailScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final detailAsync = ref.watch( challengeDetailProvider(challengeId: widget.challengeId), ); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, scrolledUnderElevation: 0, leading: IconButton( @@ -96,6 +98,10 @@ class _ChallengeDetailScreenState extends ConsumerState { icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), ), title: Text(widget.challengeTitle ?? "챌린지 상세", style: AppTypography.h3), @@ -144,11 +150,11 @@ class _ChallengeDetailScreenState extends ConsumerState { ), ], ), - bottomNavigationBar: _buildBottomButton(), + bottomNavigationBar: _buildBottomButton(appColors), ); } - Widget _buildBottomButton() { + Widget _buildBottomButton(AppColorsExtension appColors) { final myInfo = ref.watch(currentUserProvider); final detailAsync = ref.watch( challengeDetailProvider(challengeId: widget.challengeId), @@ -181,10 +187,10 @@ class _ChallengeDetailScreenState extends ConsumerState { return BottomActionButton( text: isMemberTab ? '내 순위 확인하기' : (isDisabled ? '인증 완료!' : '인증하기'), backgroundColor: isDisabled - ? AppColors.disable - : (isMemberTab ? Colors.white : AppColors.primaryAble), - textColor: isMemberTab ? AppColors.primaryAble : Colors.white, - borderColor: isMemberTab ? AppColors.primaryAble : null, + ? appColors.disable + : (isMemberTab ? appColors.whiteToBlack : appColors.primaryAble), + textColor: isMemberTab ? appColors.primaryAble : appColors.whiteToBlack, + borderColor: isMemberTab ? appColors.primaryAble : null, onPressed: isDisabled ? null // 비활성화 시 탭 자체를 막음 (토스트 제거) : () { @@ -211,41 +217,4 @@ class _ChallengeDetailScreenState extends ConsumerState { }, ); } - - // return BottomActionButton( - // // 1. 텍스트 분기 - // text: isMemberTab ? '내 순위 확인하기' : '인증하기', - - // // 2. 배경색: 멤버 탭이면 흰색, 아니면 기본색(초록) - // backgroundColor: isMemberTab ? Colors.white : AppColors.primaryAble, - - // // 3. 글자색: 멤버 탭이면 초록색, 아니면 흰색 - // textColor: isMemberTab ? AppColors.primaryAble : Colors.white, - - // // 4. 테두리색: 멤버 탭일 때만 초록색 테두리 추가 - // borderColor: isMemberTab ? AppColors.primaryAble : null, - - // onPressed: () { - // if (isMemberTab) { - // // 랭킹 페이지 이동 로직 - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => - // MemberRankingScreen(challengeId: widget.challengeId), - // ), - // ); - // } else { - // // 인증 페이지 이동 로직 - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => - // ChallengeVerificationScreen(challengeId: widget.challengeId), - // ), - // ); - // } - // }, - // ); - // } } diff --git a/lib/features/challenge/detail/screens/member_ranking_screen.dart b/lib/features/challenge/detail/screens/member_ranking_screen.dart index 8947eee..e2d4853 100644 --- a/lib/features/challenge/detail/screens/member_ranking_screen.dart +++ b/lib/features/challenge/detail/screens/member_ranking_screen.dart @@ -13,23 +13,31 @@ class MemberRankingScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + // 1. 프로바이더 구독 (API 호출 및 상태 관리) final rankingAsync = ref.watch( challengeRankingNotifierProvider(challengeId), ); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( - icon: SvgPicture.asset('assets/images/icons/arrow_left.svg'), + icon: SvgPicture.asset( + 'assets/images/icons/arrow_left.svg', + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), + ), onPressed: () => Navigator.pop(context), ), title: Text( '멤버 순위', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), @@ -49,20 +57,23 @@ class MemberRankingScreen extends ConsumerWidget { child: Stack( children: [ ListView(), // RefreshIndicator를 동작하게 하기 위한 빈 리스트뷰 - const Center( + Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.leaderboard_outlined, size: 64, - color: Colors.grey, + color: appColors.gray2, ), SizedBox(height: 16), Text( '아직 랭킹 데이터가 없어요!\n첫 번째로 인증을 완료해 보세요.', textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey, fontSize: 16), + style: TextStyle( + color: appColors.gray2, + fontSize: 16, + ), ), ], ), @@ -87,6 +98,7 @@ class MemberRankingScreen extends ConsumerWidget { delegate: SliverChildBuilderDelegate((context, index) { final user = rankingData.topRankings[index]; return _buildRankingItem( + appColors, rank: user.rank, userName: user.nickname, profileImageUrl: user.profileImageUrl, @@ -107,7 +119,8 @@ class MemberRankingScreen extends ConsumerWidget { } // 랭킹 아이템 리스트 위젯 - Widget _buildRankingItem({ + Widget _buildRankingItem( + AppColorsExtension appColors, { required int rank, required String userName, required String? profileImageUrl, @@ -123,10 +136,10 @@ class MemberRankingScreen extends ConsumerWidget { ), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( - color: isMe ? AppColors.selected : Colors.white, + color: isMe ? appColors.selected : appColors.whiteToBlack, borderRadius: BorderRadius.circular(20), border: Border.all( - color: isMe ? AppColors.primaryAble : AppColors.gray4, + color: isMe ? appColors.primaryAble : appColors.gray4, width: 1, ), boxShadow: [ @@ -139,16 +152,16 @@ class MemberRankingScreen extends ConsumerWidget { ), child: Row( children: [ - SizedBox(width: 28, child: _buildRankBadge(rank)), + SizedBox(width: 28, child: _buildRankBadge(rank, appColors)), const SizedBox(width: 13), - _buildProfileImage(profileImageUrl, isMe: isMe), + _buildProfileImage(profileImageUrl, appColors, isMe: isMe), const SizedBox(width: 12), Expanded( child: Text( userName, style: TextStyle( fontSize: 15, - color: isMe ? AppColors.primaryAble : Colors.black, + color: isMe ? appColors.primaryAble : appColors.blackToWhite, ), ), ), @@ -156,13 +169,15 @@ class MemberRankingScreen extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ _buildStatInfo( + appColors, svgAsset: 'assets/images/icons/mini_success_icon.svg', value: totalCount, - color: AppColors.primaryAble, + color: appColors.primaryAble, isActive: totalCount > 0, ), const SizedBox(height: 6), _buildStatInfo( + appColors, svgAsset: 'assets/images/icons/small_fire_icon.svg', value: streakCount, color: AppColors.fire, @@ -176,7 +191,11 @@ class MemberRankingScreen extends ConsumerWidget { } // 헬퍼 메서드들 - Widget _buildProfileImage(String? url, {required bool isMe}) { + Widget _buildProfileImage( + String? url, + AppColorsExtension appColors, { + required bool isMe, + }) { // isMe 여부에 따라 사이즈 결정 final double size = isMe ? 50 : 40; @@ -188,7 +207,7 @@ class MemberRankingScreen extends ConsumerWidget { color: Colors.grey[200], // 내 순위일 때만 테두리 적용 border: Border.all( - color: isMe ? AppColors.disable : Colors.transparent, + color: isMe ? appColors.disable : Colors.transparent, width: isMe ? 1 : 0, ), ), @@ -228,13 +247,13 @@ class MemberRankingScreen extends ConsumerWidget { ); } - Widget _buildRankBadge(int rank) { + Widget _buildRankBadge(int rank, AppColorsExtension appColors) { if (rank <= 3) return SvgPicture.asset(_getMedalSvg(rank), width: 30, height: 30); return Text( rank.toString(), - style: AppTypography.b3.copyWith(color: AppColors.gray2), + style: AppTypography.b3.copyWith(color: appColors.gray2), textAlign: TextAlign.center, ); } @@ -252,7 +271,8 @@ class MemberRankingScreen extends ConsumerWidget { } } - Widget _buildStatInfo({ + Widget _buildStatInfo( + AppColorsExtension appColors, { required String svgAsset, required int value, required Color color, @@ -266,7 +286,7 @@ class MemberRankingScreen extends ConsumerWidget { width: 20, height: 20, colorFilter: ColorFilter.mode( - isActive ? color : AppColors.gray4, + isActive ? color : appColors.gray4, BlendMode.srcIn, ), ), @@ -276,7 +296,7 @@ class MemberRankingScreen extends ConsumerWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, - color: isActive ? color : AppColors.gray4, + color: isActive ? color : appColors.gray4, ), ), ], diff --git a/lib/features/challenge/detail/views/calendar_view.dart b/lib/features/challenge/detail/views/calendar_view.dart index 902c8d9..7ee780d 100644 --- a/lib/features/challenge/detail/views/calendar_view.dart +++ b/lib/features/challenge/detail/views/calendar_view.dart @@ -77,6 +77,7 @@ class _CalendarViewState extends ConsumerState { // 1. 공유 버튼 클릭 이벤트 핸들러 Future _handleShare() async { + final appColors = Theme.of(context).extension()!; // 로딩 중 및 안내 문구 표시 다이얼로그 showDialog( context: context, @@ -87,21 +88,21 @@ class _CalendarViewState extends ConsumerState { child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20), - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: appColors.whiteToBlack, borderRadius: BorderRadius.all(Radius.circular(16)), ), - child: const Column( + child: Column( mainAxisSize: MainAxisSize.min, children: [ - CircularProgressIndicator(color: AppColors.black), + CircularProgressIndicator(color: appColors.primaryAble), SizedBox(height: 16), Text( '이미지를 생성하고 있습니다...\n(약 5~10초 정도 소요될 수 있습니다)', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, - color: AppColors.black, + color: appColors.blackToWhite, height: 1.4, ), ), @@ -155,6 +156,8 @@ class _CalendarViewState extends ConsumerState { // 모달 위젯을 띄우는 함수 void _showSharePreviewModal(String imageUrl) { + final appColors = Theme.of(context).extension()!; + showDialog( context: context, barrierColor: Colors.black.withOpacity(0.75), // 어두운 백그라운드 @@ -231,6 +234,7 @@ class _CalendarViewState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; // 1. 데이터 구독 final statsAsync = ref.watch(challengeStatsProvider(widget.challengeId)); @@ -244,20 +248,20 @@ class _CalendarViewState extends ConsumerState { // 2. 통합 로딩/에러/데이터 처리 return statsAsync.when( - loading: () => const Scaffold( - backgroundColor: Colors.white, + loading: () => Scaffold( + backgroundColor: appColors.whiteToBlack, body: Center(child: CircularProgressIndicator()), ), error: (e, s) => Scaffold(body: Center(child: Text('통계 로드 실패: $e'))), data: (stats) => postsAsync.when( - loading: () => const Scaffold( - backgroundColor: Colors.white, + loading: () => Scaffold( + backgroundColor: appColors.whiteToBlack, body: Center(child: CircularProgressIndicator()), ), error: (e, s) => Scaffold(body: Center(child: Text('인증글 로드 실패: $e'))), data: (posts) { return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, // [추가] 당겨서 새로고침 위젯 body: RefreshIndicator( onRefresh: () async { @@ -279,11 +283,12 @@ class _CalendarViewState extends ConsumerState { _buildStatCards( stats.totalSuccessDays, stats.currentStreakDays, + appColors, ), const SizedBox(height: 20), - _buildCalendarHeader(_focusedDay), + _buildCalendarHeader(_focusedDay, appColors), const SizedBox(height: 10), - _buildWeekdayHeader(), + _buildWeekdayHeader(appColors), const SizedBox(height: 10), SizedBox( @@ -339,10 +344,10 @@ class _CalendarViewState extends ConsumerState { ), const SizedBox(height: 20), - _buildPostsHeaderForData(posts.length), + _buildPostsHeaderForData(posts.length, appColors), const SizedBox(height: 16), posts.isEmpty - ? _buildEmptyState() + ? _buildEmptyState(appColors) : ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -360,17 +365,25 @@ class _CalendarViewState extends ConsumerState { ); } - Widget _buildStatCards(int totalSuccessDays, int currentStreakDays) { + Widget _buildStatCards( + int totalSuccessDays, + int currentStreakDays, + AppColorsExtension appColors, + ) { return Row( children: [ - _buildStatCard(totalSuccessDays.toString(), '완료 일수'), + _buildStatCard(totalSuccessDays.toString(), '완료 일수', appColors), const SizedBox(width: 12), - _buildStatCard(currentStreakDays.toString(), '연속 일수'), + _buildStatCard(currentStreakDays.toString(), '연속 일수', appColors), ], ); } - Widget _buildStatCard(String value, String label) { + Widget _buildStatCard( + String value, + String label, + AppColorsExtension appColors, + ) { final intValue = int.tryParse(value) ?? 0; final bool showFire = label == '연속 일수' && intValue > 0; @@ -378,7 +391,7 @@ class _CalendarViewState extends ConsumerState { child: Container( padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -405,7 +418,7 @@ class _CalendarViewState extends ConsumerState { ), Text( label, - style: const TextStyle(color: AppColors.black, fontSize: 13), + style: TextStyle(color: appColors.blackToWhite, fontSize: 13), ), ], ), @@ -413,7 +426,7 @@ class _CalendarViewState extends ConsumerState { ); } - Widget _buildCalendarHeader(DateTime date) { + Widget _buildCalendarHeader(DateTime date, AppColorsExtension appColors) { final now = DateTime.now(); bool isFuture = date.year > now.year || @@ -438,7 +451,9 @@ class _CalendarViewState extends ConsumerState { width: 24, height: 24, colorFilter: ColorFilter.mode( - isAtStart ? AppColors.gray3 : AppColors.black, // 색상도 비활성 표시 + isAtStart + ? appColors.gray3 + : appColors.blackToWhite, // 색상도 비활성 표시 BlendMode.srcIn, ), ), @@ -446,7 +461,7 @@ class _CalendarViewState extends ConsumerState { const SizedBox(width: 20), Text( '${date.year}년 ${date.month}월', - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), const SizedBox(width: 20), IconButton( @@ -456,7 +471,7 @@ class _CalendarViewState extends ConsumerState { width: 24, height: 24, colorFilter: ColorFilter.mode( - isFuture ? AppColors.gray3 : AppColors.black, + isFuture ? appColors.gray3 : appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -470,13 +485,17 @@ class _CalendarViewState extends ConsumerState { 'assets/images/icons/share_icon.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), ), ], ); } - Widget _buildWeekdayHeader() { + Widget _buildWeekdayHeader(AppColorsExtension appColors) { final weekdays = ['일', '월', '화', '수', '목', '금', '토']; return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, @@ -484,28 +503,28 @@ class _CalendarViewState extends ConsumerState { .map( (day) => Text( day, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), ) .toList(), ); } - Widget _buildPostsHeaderForData(int count) { + Widget _buildPostsHeaderForData(int count, AppColorsExtension appColors) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('내 인증글', style: AppTypography.b1), Text( '총 $count개', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ); } - Widget _buildEmptyState() => const Padding( + Widget _buildEmptyState(AppColorsExtension appColors) => Padding( padding: EdgeInsets.symmetric(vertical: 40), - child: Text('이번 달 인증글이 없습니다.', style: TextStyle(color: AppColors.gray2)), + child: Text('이번 달 인증글이 없습니다.', style: TextStyle(color: appColors.gray2)), ); } diff --git a/lib/features/challenge/detail/views/information_view.dart b/lib/features/challenge/detail/views/information_view.dart index 175852d..4cf9731 100644 --- a/lib/features/challenge/detail/views/information_view.dart +++ b/lib/features/challenge/detail/views/information_view.dart @@ -17,21 +17,23 @@ class InformationView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + final challengeAsync = ref.watch( challengeDetailProvider(challengeId: challengeId), ); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, body: challengeAsync.when( - loading: () => const Center( - child: CircularProgressIndicator(color: AppColors.primaryAble), + loading: () => Center( + child: CircularProgressIndicator(color: appColors.primaryAble), ), error: (error, stack) => Center( child: Text( '데이터를 불러오지 못했습니다.\n잠시 후 다시 시도해주세요.', textAlign: TextAlign.center, - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), ), data: (challenge) => ChallengeDetailContent( diff --git a/lib/features/challenge/detail/views/member_view.dart b/lib/features/challenge/detail/views/member_view.dart index 00af3ea..5762919 100644 --- a/lib/features/challenge/detail/views/member_view.dart +++ b/lib/features/challenge/detail/views/member_view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:haenaem/features/feed/provider/feed_provider.dart'; import 'package:haenaem/features/feed/views/share_feed_view.dart'; +import 'package:haenaem/core/theme/app_colors.dart'; class MemberView extends ConsumerStatefulWidget { final int challengeId; @@ -30,11 +31,13 @@ class _MemberViewState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // challengeId별로 독립적인 상태를 가진 프로바이더를 참조합니다. final provider = memberFeedProvider(widget.challengeId); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, body: ShareFeedView( scrollController: widget.scrollController, provider: provider, diff --git a/lib/features/challenge/detail/widgets/calendar_grid.dart b/lib/features/challenge/detail/widgets/calendar_grid.dart index b050209..6e52e87 100644 --- a/lib/features/challenge/detail/widgets/calendar_grid.dart +++ b/lib/features/challenge/detail/widgets/calendar_grid.dart @@ -115,23 +115,29 @@ class _CalendarCell extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return GestureDetector( onTap: onTap, child: Container( alignment: Alignment.center, decoration: BoxDecoration( // 인증: 초록색 / 미인증: 회색 - color: isCertified ? AppColors.primaryAble : AppColors.gray5, + color: isCertified ? appColors.primaryAble : appColors.gray5, borderRadius: BorderRadius.circular(5), // 오늘이면서 미인증인 경우 테두리 표시 border: (isToday && !isCertified) - ? Border.all(color: AppColors.gray2, width: 1) + ? Border.all(color: appColors.gray2, width: 1) : null, // 인증 사진이 있으면 썸네일로 표시 image: (isCertified && imageUrl != null) ? DecorationImage( image: NetworkImage(imageUrl!), fit: BoxFit.cover, + colorFilter: ColorFilter.mode( + Colors.black.withValues(alpha: 0.2), + BlendMode.darken, + ), ) : null, ), @@ -139,7 +145,7 @@ class _CalendarCell extends StatelessWidget { '$day', style: TextStyle( // 인증: 흰색 / 미인증: 회색 - color: isCertified ? Colors.white : AppColors.gray2, + color: isCertified ? AppColors.white : appColors.gray2, fontWeight: isToday ? FontWeight.bold : FontWeight.normal, ), ), diff --git a/lib/features/challenge/detail/widgets/calendar_post_card.dart b/lib/features/challenge/detail/widgets/calendar_post_card.dart index 52f7143..e282b25 100644 --- a/lib/features/challenge/detail/widgets/calendar_post_card.dart +++ b/lib/features/challenge/detail/widgets/calendar_post_card.dart @@ -17,6 +17,8 @@ class CalendarPostCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final String formattedDate = DateFormat( 'M월 d일', ).format(DateTime.parse(post.postDate)); @@ -32,9 +34,9 @@ class CalendarPostCard extends StatelessWidget { margin: const EdgeInsets.only(bottom: 15), padding: const EdgeInsets.all(13), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColors.gray4), + border: Border.all(color: appColors.gray4), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -61,12 +63,16 @@ class CalendarPostCard extends StatelessWidget { 'assets/images/icons/green_calendar.svg', width: 12, height: 12, + colorFilter: ColorFilter.mode( + appColors.primaryAble, + BlendMode.srcIn, + ), ), const SizedBox(width: 4), Text( formattedDate, style: AppTypography.c1.copyWith( - color: AppColors.primaryAble, + color: appColors.primaryAble, ), ), ], @@ -74,7 +80,9 @@ class CalendarPostCard extends StatelessWidget { const SizedBox(height: 4), Text( post.content, - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith( + color: appColors.blackToWhite, + ), maxLines: 3, overflow: TextOverflow.ellipsis, ), diff --git a/lib/features/challenge/detail/widgets/calendar_share_preview.dart b/lib/features/challenge/detail/widgets/calendar_share_preview.dart index e3229b3..24e4c99 100644 --- a/lib/features/challenge/detail/widgets/calendar_share_preview.dart +++ b/lib/features/challenge/detail/widgets/calendar_share_preview.dart @@ -18,6 +18,8 @@ class CalendarSharePreview extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Dialog( backgroundColor: Colors.transparent, // 배경 투명 (barrierColor가 어둡게 처리) insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40), @@ -56,18 +58,23 @@ class CalendarSharePreview extends StatelessWidget { if (loadingProgress == null) return child; return Container( height: 300, - color: AppColors.gray5, - child: const Center( + color: appColors.gray5, + child: Center( child: CircularProgressIndicator( - color: AppColors.black, + color: appColors.blackToWhite, ), ), ); }, errorBuilder: (context, error, stackTrace) => Container( height: 200, - color: AppColors.gray5, - child: const Center(child: Text('이미지를 불러올 수 없습니다.')), + color: appColors.gray5, + child: Center( + child: Text( + '이미지를 불러올 수 없습니다.', + style: TextStyle(color: appColors.blackToWhite), + ), + ), ), ), ), @@ -85,8 +92,8 @@ class CalendarSharePreview extends StatelessWidget { text: '저장하기', icon: Icons.download_rounded, onPressed: onSave, - backgroundColor: Colors.white.withValues(alpha: 0.2), - textColor: Colors.white, + backgroundColor: appColors.gray2, + textColor: appColors.whiteToBlack, showContainerDecoration: false, // Row 안에서 배경 중첩 방지 ), ), @@ -98,8 +105,8 @@ class CalendarSharePreview extends StatelessWidget { text: '공유하기', icon: Icons.share_rounded, onPressed: onShare, - backgroundColor: AppColors.primaryAble, - textColor: Colors.white, + backgroundColor: appColors.primaryAble, + textColor: appColors.whiteToBlack, showContainerDecoration: false, // Row 안에서 배경 중첩 방지 ), ), diff --git a/lib/features/challenge/detail/widgets/challenge_create_success_dialog.dart b/lib/features/challenge/detail/widgets/challenge_create_success_dialog.dart index c91b8ed..ef5c697 100644 --- a/lib/features/challenge/detail/widgets/challenge_create_success_dialog.dart +++ b/lib/features/challenge/detail/widgets/challenge_create_success_dialog.dart @@ -18,6 +18,8 @@ class ChallengeCreateSuccessDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Dialog( alignment: const Alignment(0, -0.3), insetPadding: const EdgeInsets.symmetric(horizontal: 20), @@ -27,7 +29,7 @@ class ChallengeCreateSuccessDialog extends StatelessWidget { height: 700, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(16), ), child: Column( @@ -47,7 +49,7 @@ class ChallengeCreateSuccessDialog extends StatelessWidget { ), ), // 하단 닫기 버튼 - buildLaterButton(context), + buildLaterButton(context, appColors), ], ), ), @@ -76,13 +78,13 @@ class ChallengeCreateSuccessDialog extends StatelessWidget { const SizedBox(height: 12), Text( '챌린지 생성 완료!', - style: AppTypography.h2.copyWith(color: Colors.white), + style: AppTypography.h2.copyWith(color: AppColors.white), ), const SizedBox(height: 4), Text( '친구들을 초대해서 함께 도전해보세요', style: AppTypography.b1.copyWith( - color: Colors.white.withAlpha(200), + color: AppColors.white.withAlpha(200), ), ), ], @@ -91,7 +93,7 @@ class ChallengeCreateSuccessDialog extends StatelessWidget { } // 나중에 초대하기 버튼 - Widget buildLaterButton(BuildContext context) { + Widget buildLaterButton(BuildContext context, AppColorsExtension appColors) { return Container( width: double.infinity, padding: const EdgeInsets.all(16), @@ -102,12 +104,12 @@ class ChallengeCreateSuccessDialog extends StatelessWidget { height: 44, alignment: Alignment.center, decoration: BoxDecoration( - border: Border.all(color: AppColors.gray2), + border: Border.all(color: appColors.gray2), borderRadius: BorderRadius.circular(10), ), child: Text( '나중에 초대하기', - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), ), ), diff --git a/lib/features/challenge/detail/widgets/challenge_popup_menu.dart b/lib/features/challenge/detail/widgets/challenge_popup_menu.dart index b31039f..cb25632 100644 --- a/lib/features/challenge/detail/widgets/challenge_popup_menu.dart +++ b/lib/features/challenge/detail/widgets/challenge_popup_menu.dart @@ -26,6 +26,8 @@ class ChallengePopupMenu extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + // WidgetRef 추가 return PopupMenuButton( popUpAnimationStyle: const AnimationStyle( @@ -36,11 +38,12 @@ class ChallengePopupMenu extends ConsumerWidget { 'assets/images/icons/dots_vert_icon.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode(appColors.blackToWhite, BlendMode.srcIn), ), - color: Colors.white, + color: appColors.whiteToBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), - side: const BorderSide(color: AppColors.gray4, width: 1), + side: BorderSide(color: appColors.gray4, width: 1), ), offset: const Offset(0, 40), @@ -51,12 +54,14 @@ class ChallengePopupMenu extends ConsumerWidget { '알림 설정', 'assets/images/icons/notification_icon.svg', 'notification', + appColors, ), const PopupMenuDivider(height: 1), _buildPopupItem( '챌린지 초대', 'assets/images/icons/share_icon.svg', 'invite', + appColors, ), const PopupMenuDivider(height: 1), @@ -66,6 +71,7 @@ class ChallengePopupMenu extends ConsumerWidget { '챌린지 설정', 'assets/images/icons/black_settings_icon.svg', // 방장용 설정 아이콘 'settings', + appColors, ) else _buildPopupItem( @@ -73,12 +79,13 @@ class ChallengePopupMenu extends ConsumerWidget { 'assets/images/icons/exit_icon.svg', 'leave', isDanger: true, + appColors, ), ]; }, onSelected: (String value) => - _handleMenuSelection(context, ref, value), // ref 전달 + _handleMenuSelection(context, ref, value, appColors), // ref 전달 ); } @@ -86,10 +93,13 @@ class ChallengePopupMenu extends ConsumerWidget { PopupMenuItem _buildPopupItem( String title, String iconPath, - String value, { + String value, + AppColorsExtension appColors, { bool isDanger = false, }) { - final Color itemColor = isDanger ? AppColors.notification : AppColors.black; + final Color itemColor = isDanger + ? appColors.notification + : appColors.blackToWhite; return PopupMenuItem( value: value, @@ -118,6 +128,7 @@ class ChallengePopupMenu extends ConsumerWidget { BuildContext context, WidgetRef ref, String value, + AppColorsExtension appColors, ) async { // async 추가 switch (value) { @@ -162,11 +173,11 @@ class ChallengePopupMenu extends ConsumerWidget { emoji: '🥺', title: '챌린지를 나가시겠어요?', content: '지금까지의 진행 상황이 모두 사라지며,\n복구할 수 없습니다.', - contentColor: AppColors.notification, + contentColor: appColors.notification, confirmText: '취소', cancelText: '나가기', - cancelBackgroundColor: AppColors.notification, // 나가기 버튼은 경고색으로 강조 - cancelTextColor: Colors.white, + cancelBackgroundColor: appColors.notification, // 나가기 버튼은 경고색으로 강조 + cancelTextColor: appColors.whiteToBlack, // 나가기 버튼 텍스트는 흰색 onCancel: () async { debugPrint('나가기 버튼 눌림'); final success = await ref diff --git a/lib/features/challenge/detail/widgets/notification_settings_dialog.dart b/lib/features/challenge/detail/widgets/notification_settings_dialog.dart index 85cd219..cc0b8a0 100644 --- a/lib/features/challenge/detail/widgets/notification_settings_dialog.dart +++ b/lib/features/challenge/detail/widgets/notification_settings_dialog.dart @@ -214,10 +214,12 @@ class _NotificationSettingsDialogState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Dialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, clipBehavior: Clip.antiAlias, child: Column( @@ -227,10 +229,10 @@ class _NotificationSettingsDialogState Container( width: double.infinity, padding: const EdgeInsets.all(20), - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: appColors.whiteToBlack, border: Border( - bottom: BorderSide(width: 1, color: AppColors.gray4), + bottom: BorderSide(width: 1, color: appColors.gray4), ), ), child: Row( @@ -238,7 +240,9 @@ class _NotificationSettingsDialogState children: [ Text( '챌린지 알림 설정', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith( + color: appColors.blackToWhite, + ), ), GestureDetector( onTap: () => Navigator.pop(context), @@ -247,6 +251,10 @@ class _NotificationSettingsDialogState height: 24, child: SvgPicture.asset( 'assets/images/icons/close_icon.svg', + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), ), ), @@ -256,10 +264,10 @@ class _NotificationSettingsDialogState // ── 본문 ───────────────────────────────────── _isLoading - ? const Padding( + ? Padding( padding: EdgeInsets.symmetric(vertical: 40), child: CircularProgressIndicator( - color: AppColors.primaryAble, + color: appColors.primaryAble, ), ) : Padding( @@ -275,12 +283,14 @@ class _NotificationSettingsDialogState '모든 알림 받기', allNotifications, _toggleAll, + appColors, ), - _buildDailyReminderSection(), + _buildDailyReminderSection(appColors), _buildSwitchRowWithGlobalWarning( '내 글 좋아요', "내 게시물에 '좋아요' 반응이 올 때 알림", likesNotification, + appColors, _likesDisabledByGlobal ? null : _toggleLikes, disabledByGlobal: _likesDisabledByGlobal, ), @@ -288,6 +298,7 @@ class _NotificationSettingsDialogState '댓글', '내 글에 새로운 댓글이 달릴 때 알림', commentsNotification, + appColors, _commentsDisabledByGlobal ? null : _toggleComments, disabledByGlobal: _commentsDisabledByGlobal, ), @@ -295,6 +306,7 @@ class _NotificationSettingsDialogState '멤버 인증 소식', '다른 참여자들이 인증 완료 시 알림', mateVerification, + appColors, _verificationDisabledByGlobal ? null : _toggleVerification, @@ -308,8 +320,8 @@ class _NotificationSettingsDialogState Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: const BoxDecoration( - border: Border(top: BorderSide(width: 1, color: AppColors.gray4)), + decoration: BoxDecoration( + border: Border(top: BorderSide(width: 1, color: appColors.gray4)), ), child: SizedBox( width: double.infinity, @@ -317,16 +329,16 @@ class _NotificationSettingsDialogState child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryAble, + backgroundColor: appColors.primaryAble, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), elevation: 0, ), - child: const Text( + child: Text( '완료', style: TextStyle( - color: Colors.white, + color: appColors.whiteToBlack, fontSize: 16, fontWeight: FontWeight.w500, ), @@ -346,6 +358,7 @@ class _NotificationSettingsDialogState String subtitle, bool value, ValueChanged? onChanged, + AppColorsExtension appColors, ) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -357,16 +370,16 @@ class _NotificationSettingsDialogState children: [ Text( title, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), Text( subtitle, - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ), ), - _buildSwitch(value, onChanged), + _buildSwitch(value, onChanged, appColors), ], ); } @@ -377,6 +390,7 @@ class _NotificationSettingsDialogState String title, String subtitle, bool value, + AppColorsExtension appColors, ValueChanged? onChanged, { bool disabledByGlobal = false, }) { @@ -395,13 +409,13 @@ class _NotificationSettingsDialogState title, style: AppTypography.b1.copyWith( color: disabledByGlobal - ? AppColors.gray2 - : AppColors.black, + ? appColors.gray2 + : appColors.blackToWhite, ), ), Text( subtitle, - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ), @@ -409,6 +423,7 @@ class _NotificationSettingsDialogState _buildSwitch( value, (_isLoading || disabledByGlobal) ? null : onChanged, + appColors, ), ], ), @@ -417,7 +432,7 @@ class _NotificationSettingsDialogState padding: const EdgeInsets.only(top: 4), child: Text( '전체 푸시 알림 설정에서 해당 알림이 꺼져 있습니다.', - style: AppTypography.c1.copyWith(color: AppColors.primaryAble), + style: AppTypography.c1.copyWith(color: appColors.primaryAble), ), ), ], @@ -426,27 +441,31 @@ class _NotificationSettingsDialogState // ── 공통 스위치 위젯 ──────────────────────────────────── - Widget _buildSwitch(bool value, ValueChanged? onChanged) { + Widget _buildSwitch( + bool value, + ValueChanged? onChanged, + AppColorsExtension appColors, + ) { return Transform.scale( scale: 0.8, alignment: Alignment.centerRight, child: Switch( value: value, onChanged: onChanged, - activeTrackColor: AppColors.primaryAble, - activeThumbColor: Colors.white, - inactiveTrackColor: AppColors.disable, - inactiveThumbColor: Colors.white, + activeTrackColor: appColors.primaryAble, + activeThumbColor: appColors.whiteToBlack, + inactiveTrackColor: appColors.disable, + inactiveThumbColor: appColors.whiteToBlack, trackOutlineColor: const WidgetStatePropertyAll(Colors.transparent), thumbIcon: WidgetStateProperty.all(const Icon(null)), - thumbColor: const WidgetStatePropertyAll(Colors.white), + thumbColor: const WidgetStatePropertyAll(AppColors.white), ), ); } // ── 일일 리마인더 섹션 ────────────────────────────────── - Widget _buildDailyReminderSection() { + Widget _buildDailyReminderSection(AppColorsExtension appColors) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -462,13 +481,13 @@ class _NotificationSettingsDialogState '일일 리마인더', style: AppTypography.b1.copyWith( color: _reminderDisabledByGlobal - ? AppColors.gray2 - : AppColors.black, + ? appColors.gray2 + : appColors.blackToWhite, ), ), Text( '매일 $selectedTime 알림', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ), @@ -478,18 +497,19 @@ class _NotificationSettingsDialogState (_isLoading || _reminderDisabledByGlobal) ? null : _toggleReminder, + appColors, ), ], ), if (dailyReminder && !_reminderDisabledByGlobal) ...[ const SizedBox(height: 4), GestureDetector( - onTap: () => _showTimePicker(context), + onTap: () => _showTimePicker(context, appColors), child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(8), ), child: Row( @@ -497,7 +517,7 @@ class _NotificationSettingsDialogState children: [ Text( selectedTime, - style: AppTypography.b2.copyWith(color: AppColors.gray3), + style: AppTypography.b2.copyWith(color: appColors.gray3), ), Opacity( opacity: 0.5, @@ -505,8 +525,8 @@ class _NotificationSettingsDialogState 'assets/images/icons/big_down_arrow.svg', width: 16, height: 16, - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), @@ -521,7 +541,7 @@ class _NotificationSettingsDialogState padding: const EdgeInsets.only(top: 4), child: Text( '전체 푸시 알림 설정에서 해당 알림이 꺼져 있습니다.', - style: AppTypography.c1.copyWith(color: AppColors.primaryAble), + style: AppTypography.c1.copyWith(color: appColors.primaryAble), ), ), ], @@ -530,7 +550,7 @@ class _NotificationSettingsDialogState // ── 시간 피커 ─────────────────────────────────────────── - void _showTimePicker(BuildContext context) { + void _showTimePicker(BuildContext context, AppColorsExtension appColors) { final List hours = List.generate(12, (i) => '${i + 1}시'); String currentPeriod = selectedTime.contains('오후') ? '오후' : '오전'; String currentHour = selectedTime.split(' ').last; @@ -539,13 +559,13 @@ class _NotificationSettingsDialogState showDialog( context: context, - barrierColor: Colors.black.withAlpha(100), + barrierColor: appColors.blackToWhite.withAlpha(100), builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, contentPadding: const EdgeInsets.fromLTRB(20, 20, 20, 0), content: SizedBox( width: MediaQuery.of(context).size.width * 0.8, @@ -558,6 +578,7 @@ class _NotificationSettingsDialogState currentPeriod, (newPeriod) => setDialogState(() => currentPeriod = newPeriod), + appColors, ), ), const SizedBox(height: 24), @@ -570,7 +591,7 @@ class _NotificationSettingsDialogState height: 40, margin: const EdgeInsets.symmetric(horizontal: 24), decoration: BoxDecoration( - color: AppColors.gray4.withAlpha(100), + color: appColors.gray4.withAlpha(100), borderRadius: BorderRadius.circular(10), ), ), @@ -614,7 +635,7 @@ class _NotificationSettingsDialogState }, style: TextButton.styleFrom( backgroundColor: Colors.transparent, - foregroundColor: AppColors.primaryAble, + foregroundColor: appColors.primaryAble, minimumSize: const Size(double.infinity, 52), padding: EdgeInsets.zero, shape: RoundedRectangleBorder( @@ -624,7 +645,7 @@ class _NotificationSettingsDialogState child: Text( '완료', style: AppTypography.b1.copyWith( - color: AppColors.primaryAble, + color: appColors.primaryAble, fontWeight: FontWeight.bold, ), ), @@ -641,12 +662,13 @@ class _NotificationSettingsDialogState Widget _buildAnimatedPeriodSelector( String currentPeriod, Function(String) onPeriodChanged, + AppColorsExtension appColors, ) { return Container( height: 48, padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: AppColors.gray4, + color: appColors.gray4, borderRadius: BorderRadius.circular(12), ), child: LayoutBuilder( @@ -664,7 +686,7 @@ class _NotificationSettingsDialogState width: width - 4, height: double.infinity, decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( @@ -687,7 +709,9 @@ class _NotificationSettingsDialogState child: AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 250), style: AppTypography.b2.copyWith( - color: isSelected ? Colors.black : AppColors.gray2, + color: isSelected + ? appColors.blackToWhite + : appColors.gray2, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, diff --git a/lib/features/challenge/invite/screens/challenge_invite_screen.dart b/lib/features/challenge/invite/screens/challenge_invite_screen.dart index b6093bd..a3f8c68 100644 --- a/lib/features/challenge/invite/screens/challenge_invite_screen.dart +++ b/lib/features/challenge/invite/screens/challenge_invite_screen.dart @@ -13,19 +13,27 @@ class ChallengeInviteScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, scrolledUnderElevation: 0, leading: IconButton( - icon: SvgPicture.asset('assets/images/icons/arrow_left.svg'), + icon: SvgPicture.asset( + 'assets/images/icons/arrow_left.svg', + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), + ), onPressed: () => Navigator.pop(context), ), title: Text( '챌린지 초대', - style: AppTypography.h3.copyWith(color: Colors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), diff --git a/lib/features/challenge/invite/widgets/challenge_invite_content.dart b/lib/features/challenge/invite/widgets/challenge_invite_content.dart index 646562e..28557f5 100644 --- a/lib/features/challenge/invite/widgets/challenge_invite_content.dart +++ b/lib/features/challenge/invite/widgets/challenge_invite_content.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter/services.dart'; +import 'package:haenaem/app.dart'; import 'package:haenaem/core/theme/app_colors.dart'; import 'package:haenaem/core/theme/app_typography.dart'; import 'package:haenaem/core/utils/korean_string_utils.dart'; @@ -85,6 +86,8 @@ class _ChallengeInviteContentState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 챌린지 전용 초대 정보 Provider 사용 final inviteInfoAsync = ref.watch( challengeInviteProvider(widget.challengeId), @@ -97,15 +100,15 @@ class _ChallengeInviteContentState return Column( children: [ // 1. 링크 공유 섹션 (API에서 받아온 링크 사용) - _buildLinkShareBox(info.challengeLink), + _buildLinkShareBox(info.challengeLink, appColors), const SizedBox(height: 10), // 2. 검색창 - _buildSearchBar(), + _buildSearchBar(appColors), const SizedBox(height: 10), // 3. 친구 리스트 (API 데이터 + 검색 필터링) - Expanded(child: _buildFriendList(info.friends)), + Expanded(child: _buildFriendList(info.friends, appColors)), ], ); }, @@ -114,9 +117,9 @@ class _ChallengeInviteContentState debugPrint('⏳ [UI 로딩 중...]'); return Column( children: [ - _buildLinkShareBox(widget.challengeUrl), + _buildLinkShareBox(widget.challengeUrl, appColors), const SizedBox(height: 10), - _buildSearchBar(), + _buildSearchBar(appColors), const SizedBox(height: 50), const CircularProgressIndicator(), ], @@ -127,7 +130,7 @@ class _ChallengeInviteContentState return Center( child: Text( '정보를 불러오지 못했습니다.', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ); }, @@ -135,7 +138,10 @@ class _ChallengeInviteContentState } // 친구 리스트 빌더 메서드 분리 - Widget _buildFriendList(List friends) { + Widget _buildFriendList( + List friends, + AppColorsExtension appColors, + ) { debugPrint('🔍 [검색어] "$_searchQuery"'); // 검색 필터링 final filteredFriends = friends.where((friend) { @@ -157,7 +163,7 @@ class _ChallengeInviteContentState padding: const EdgeInsets.only(top: 50), child: Text( friends.isEmpty ? '초대할 수 있는 친구가 없습니다.' : '검색 결과가 없습니다.', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ); } @@ -166,13 +172,13 @@ class _ChallengeInviteContentState shrinkWrap: true, itemCount: filteredFriends.length, itemBuilder: (context, index) { - return _buildFriendInviteItem(filteredFriends[index]); + return _buildFriendInviteItem(filteredFriends[index], appColors); }, ); } // 링크 공유 박스 UI - Widget _buildLinkShareBox(String link) { + Widget _buildLinkShareBox(String link, AppColorsExtension appColors) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 10), @@ -186,7 +192,7 @@ class _ChallengeInviteContentState children: [ Text( '챌린지 링크 공유', - style: AppTypography.b2.copyWith(color: AppColors.gray1), + style: AppTypography.b2.copyWith(color: appColors.gray1), ), const SizedBox(height: 8), // 링크 표시창 @@ -194,9 +200,9 @@ class _ChallengeInviteContentState width: double.infinity, padding: const EdgeInsets.all(10), decoration: ShapeDecoration( - color: Colors.white, + color: appColors.whiteToBlack, shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.gray4), + side: BorderSide(width: 1, color: appColors.gray4), borderRadius: BorderRadius.circular(10), ), ), @@ -214,8 +220,9 @@ class _ChallengeInviteContentState children: [ Expanded( child: _buildActionItem( + appColors, label: '복사', - color: AppColors.gray5, + color: appColors.gray5, iconPath: 'assets/images/icons/link_copy.svg', onTap: () => _copyToClipboard(link), ), @@ -223,8 +230,9 @@ class _ChallengeInviteContentState const SizedBox(width: 10), Expanded( child: _buildActionItem( + appColors, label: '공유', - color: AppColors.gray5, + color: appColors.gray5, iconPath: 'assets/images/icons/share_icon.svg', onTap: () { _shareSystemLink(link); @@ -240,7 +248,8 @@ class _ChallengeInviteContentState } // 공통 버튼 아이템 빌더 (아이콘 + 텍스트) - Widget _buildActionItem({ + Widget _buildActionItem( + AppColorsExtension appColors, { required String label, required Color color, required String iconPath, @@ -266,16 +275,13 @@ class _ChallengeInviteContentState iconPath, width: 16, height: 16, - colorFilter: const ColorFilter.mode( - AppColors.gray2, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appColors.gray2, BlendMode.srcIn), ), const SizedBox(width: 4), Text( label, textAlign: TextAlign.center, - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ), @@ -284,13 +290,13 @@ class _ChallengeInviteContentState } // 검색창 UI - Widget _buildSearchBar() { + Widget _buildSearchBar(AppColorsExtension appColors) { return TextField( controller: _searchController, decoration: InputDecoration( isDense: true, hintText: '친구 검색', - hintStyle: AppTypography.b2.copyWith(color: AppColors.gray3), + hintStyle: AppTypography.b2.copyWith(color: appColors.gray3), prefixIcon: Padding( padding: const EdgeInsets.all(14.0), child: SvgPicture.asset('assets/images/icons/search_icon.svg'), @@ -298,18 +304,21 @@ class _ChallengeInviteContentState contentPadding: const EdgeInsets.symmetric(vertical: 8), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide(color: AppColors.gray4), + borderSide: BorderSide(color: appColors.gray4), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide(color: AppColors.gray4), + borderSide: BorderSide(color: appColors.gray4), ), ), ); } // 친구 리스트 아이템 + 실제 API 로직 - Widget _buildFriendInviteItem(InviteFriend friend) { + Widget _buildFriendInviteItem( + InviteFriend friend, + AppColorsExtension appColors, + ) { // 1. 서버에서 온 상태(friend.isInvited)이거나 // 2. 방금 내가 버튼 눌러서 초대한 상태(_newlyInvitedFriends 포함)인지 확인 bool isInvited = @@ -376,8 +385,8 @@ class _ChallengeInviteContentState }, style: ElevatedButton.styleFrom( backgroundColor: isInvited - ? AppColors.disable - : AppColors.primaryAble, + ? appColors.disable + : appColors.primaryAble, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -387,7 +396,7 @@ class _ChallengeInviteContentState child: Text( isInvited ? '초대됨' : '초대', style: AppTypography.c1.copyWith( - color: isInvited ? AppColors.gray2 : Colors.white, + color: isInvited ? appColors.gray2 : Colors.white, ), ), ), diff --git a/lib/features/challenge/settings/screens/challenge_members_screen.dart b/lib/features/challenge/settings/screens/challenge_members_screen.dart index ed75edd..110616b 100644 --- a/lib/features/challenge/settings/screens/challenge_members_screen.dart +++ b/lib/features/challenge/settings/screens/challenge_members_screen.dart @@ -70,6 +70,8 @@ class _ScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 클라이언트 필터링을 위해 nickname에 null 전달 // (서버에서 전체 목록을 받아온 후 앱 내에서 초성 검색 수행) final filter = MemberFilter( @@ -81,17 +83,17 @@ class _ScreenState extends ConsumerState { final currentUserId = ref.watch(currentUserProvider)?.id; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -100,7 +102,7 @@ class _ScreenState extends ConsumerState { centerTitle: true, title: Text( '챌린지 멤버 관리', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), ), body: Column( @@ -109,7 +111,7 @@ class _ScreenState extends ConsumerState { Center( child: Text( '멤버를 검색하고 관리할 수 있습니다.', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ), const SizedBox(height: 10), @@ -120,7 +122,7 @@ class _ScreenState extends ConsumerState { height: 46, decoration: BoxDecoration( borderRadius: BorderRadius.circular(9.5), - border: Border.all(color: AppColors.gray4), + border: Border.all(color: appColors.gray4), ), padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( @@ -128,8 +130,8 @@ class _ScreenState extends ConsumerState { SvgPicture.asset( 'assets/images/icons/search_icon.svg', width: 18, - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), @@ -146,13 +148,15 @@ class _ScreenState extends ConsumerState { decoration: InputDecoration( hintText: '닉네임으로 검색', hintStyle: AppTypography.b2.copyWith( - color: AppColors.gray3, + color: appColors.gray3, ), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, ), - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith( + color: appColors.blackToWhite, + ), ), ), ], @@ -203,7 +207,7 @@ class _ScreenState extends ConsumerState { child: Text( '멤버 ${filteredMembers.length}', style: AppTypography.b2.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), ), ), @@ -223,7 +227,7 @@ class _ScreenState extends ConsumerState { return _MemberTile( member: member, isMe: isMe, // 상태 전달 - notificationRed: AppColors.notification, + notificationRed: appColors.notification, // 콜백 함수 전달 onKick: () => _handleKickMember(member.id, member.nickname), @@ -257,6 +261,8 @@ class _MemberTile extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: Row( @@ -270,12 +276,12 @@ class _MemberTile extends StatelessWidget { children: [ Text( member.nickname, - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith(color: appColors.blackToWhite), ), Text( '사용자 칭호', // TODO: 실제 칭호 데이터로 교체 필요 - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), @@ -286,12 +292,12 @@ class _MemberTile extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: AppColors.selected, + color: appColors.selected, borderRadius: BorderRadius.circular(8), ), child: Text( '챌린지장', - style: AppTypography.c1.copyWith(color: AppColors.primaryAble), + style: AppTypography.c1.copyWith(color: appColors.primaryAble), ), ) else @@ -303,7 +309,7 @@ class _MemberTile extends StatelessWidget { title: '강제 퇴장', content: '\'${member.nickname}\' 님을 강퇴하시겠습니까?', confirmText: '강제 퇴장', - confirmTextColor: AppColors.notification, + confirmTextColor: appColors.notification, onConfirm: () { onKick(); }, diff --git a/lib/features/challenge/settings/screens/challenge_settings_screen.dart b/lib/features/challenge/settings/screens/challenge_settings_screen.dart index 95bbb5d..e3a5b58 100644 --- a/lib/features/challenge/settings/screens/challenge_settings_screen.dart +++ b/lib/features/challenge/settings/screens/challenge_settings_screen.dart @@ -20,18 +20,20 @@ class ChallengeSettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -39,7 +41,7 @@ class ChallengeSettingsScreen extends ConsumerWidget { ), title: Text( '챌린지 설정', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), @@ -50,9 +52,10 @@ class ChallengeSettingsScreen extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ // --- 챌린지 운영 관리 섹션 --- - _buildSectionTitle('챌린지 운영 관리', AppColors.black), + _buildSectionTitle('챌린지 운영 관리', appColors.blackToWhite), const SizedBox(height: 8), _buildManageTile( + appColors, title: '챌린지 멤버 관리', iconPath: 'assets/images/icons/friend_icon_on.svg', onTap: () { @@ -70,12 +73,9 @@ class ChallengeSettingsScreen extends ConsumerWidget { const SizedBox(height: 30), // --- 위험 구역 섹션 (통합 박스 구조) --- - Text( - '위험구역', - style: AppTypography.b3.copyWith(color: AppColors.notification), - ), + _buildSectionTitle('위험구역', appColors.notification), const SizedBox(height: 10), - _buildDangerZoneContainer(context, ref), + _buildDangerZoneContainer(context, ref, appColors), ], ), ), @@ -85,11 +85,12 @@ class ChallengeSettingsScreen extends ConsumerWidget { // 섹션 타이틀 헬퍼 Widget _buildSectionTitle(String title, Color color) { - return Text(title, style: AppTypography.b3.copyWith(color: color)); + return Text(title, style: AppTypography.h3.copyWith(color: color)); } // 관리 메뉴 타일 - Widget _buildManageTile({ + Widget _buildManageTile( + AppColorsExtension appColors, { required String title, required String iconPath, required VoidCallback onTap, @@ -100,7 +101,7 @@ class ChallengeSettingsScreen extends ConsumerWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(10), ), child: Row( @@ -112,21 +113,21 @@ class ChallengeSettingsScreen extends ConsumerWidget { iconPath, width: 16, height: 16, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), const SizedBox(width: 10), - Text(title, style: AppTypography.b2), + Text(title, style: AppTypography.b1), ], ), SvgPicture.asset( 'assets/images/icons/right_arrow_icon.svg', width: 16, height: 16, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -137,13 +138,17 @@ class ChallengeSettingsScreen extends ConsumerWidget { } // 위험 구역 통합 컨테이너 - Widget _buildDangerZoneContainer(BuildContext context, WidgetRef ref) { + Widget _buildDangerZoneContainer( + BuildContext context, + WidgetRef ref, + AppColorsExtension appColors, + ) { return Container( width: double.infinity, decoration: ShapeDecoration( - color: AppColors.gray5, + color: appColors.gray5, shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.gray4), + side: BorderSide(width: 1, color: appColors.gray4), borderRadius: BorderRadius.circular(10), ), ), @@ -151,6 +156,7 @@ class ChallengeSettingsScreen extends ConsumerWidget { children: [ // 상단: 챌린지 나가기 구역 _buildDangerItem( + appColors, context: context, title: '챌린지 나가기', description: '다른 멤버에게 챌린지장을 위임한 후, 챌린지를 나갑니다.', @@ -163,8 +169,8 @@ class ChallengeSettingsScreen extends ConsumerWidget { title: '챌린지장 위임하기', content: '다른 멤버에게 챌린지장을 위임하고\n정말 이 챌린지에서 나가시겠습니까?', confirmText: '나가기', - confirmBackgroundColor: AppColors.notification, - confirmTextColor: Colors.white, + confirmBackgroundColor: appColors.notification, + confirmTextColor: appColors.whiteToBlack, cancelText: '취소', onConfirm: () async { final currentFilter = MemberFilter( @@ -188,13 +194,14 @@ class ChallengeSettingsScreen extends ConsumerWidget { ), // 중간 구분선 - const Padding( + Padding( padding: EdgeInsets.symmetric(horizontal: 0), - child: Divider(height: 1, color: AppColors.gray4), + child: Divider(height: 1, color: appColors.gray4), ), // 하단: 챌린지 삭제 구역 _buildDangerItem( + appColors, context: context, title: '챌린지 삭제', description: @@ -211,10 +218,10 @@ class ChallengeSettingsScreen extends ConsumerWidget { content: '이 작업은 되돌릴 수 없습니다.\n\n삭제 시 영향:\n• 모든 멤버가 퇴장됩니다.\n• 성취 그래프와 인증 기록이 삭제됩니다.\n• 챌린지 정보가 완전히 제거됩니다.', confirmText: '영구 삭제', - confirmBackgroundColor: AppColors.notification, - confirmTextColor: Colors.white, + confirmBackgroundColor: appColors.notification, + confirmTextColor: appColors.whiteToBlack, cancelText: '취소', - cancelTextColor: AppColors.gray2, + cancelTextColor: appColors.gray2, onConfirm: () async { // 기존 다이얼로그에 있던 삭제 로직 그대로 이식 final success = await ref @@ -241,7 +248,8 @@ class ChallengeSettingsScreen extends ConsumerWidget { } // 위험 구역 내부 아이템 빌더 - Widget _buildDangerItem({ + Widget _buildDangerItem( + AppColorsExtension appColors, { required BuildContext context, required String title, required String description, @@ -256,25 +264,27 @@ class ChallengeSettingsScreen extends ConsumerWidget { children: [ Text( title, - style: AppTypography.b2.copyWith(color: AppColors.notification), + style: AppTypography.b3.copyWith(color: appColors.notification), ), const SizedBox(height: 4), Text( description, - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), const SizedBox(height: 12), ElevatedButton( onPressed: onTap, style: ElevatedButton.styleFrom( - backgroundColor: isDelete ? AppColors.notification : Colors.white, + backgroundColor: isDelete + ? appColors.notification + : appColors.whiteToBlack, elevation: 0, minimumSize: const Size(double.infinity, 40), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), side: isDelete ? BorderSide.none - : const BorderSide(color: AppColors.gray4), + : BorderSide(color: appColors.gray4), ), ), child: Row( @@ -285,8 +295,8 @@ class ChallengeSettingsScreen extends ConsumerWidget { 'assets/images/icons/small_trash_icon.svg', width: 16, height: 16, - colorFilter: const ColorFilter.mode( - Colors.white, + colorFilter: ColorFilter.mode( + appColors.whiteToBlack, BlendMode.srcIn, ), ), @@ -294,8 +304,10 @@ class ChallengeSettingsScreen extends ConsumerWidget { ], Text( buttonText, - style: AppTypography.b2.copyWith( - color: isDelete ? Colors.white : AppColors.black, + style: AppTypography.b1.copyWith( + color: isDelete + ? appColors.whiteToBlack + : appColors.blackToWhite, ), ), ], diff --git a/lib/features/challenge/verification/screens/challenge_verification_screen.dart b/lib/features/challenge/verification/screens/challenge_verification_screen.dart index c636827..bcad63e 100644 --- a/lib/features/challenge/verification/screens/challenge_verification_screen.dart +++ b/lib/features/challenge/verification/screens/challenge_verification_screen.dart @@ -1,6 +1,7 @@ // 최초 작성자 : 김채영 import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:haenaem/app.dart'; import 'package:haenaem/core/utils/image_utils.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -241,7 +242,7 @@ class _ChallengeVerificationScreenState } // 사진 업로드/미리보기 - Widget _buildPhotoUploadBox() { + Widget _buildPhotoUploadBox(AppColorsExtension appColors) { return SizedBox( height: 100, child: ListView.builder( @@ -252,7 +253,7 @@ class _ChallengeVerificationScreenState if (index < _newImages.length) { return _buildImagePreview(index); } else { - return _buildAddPhotoButton(); + return _buildAddPhotoButton(appColors); } }, ), @@ -260,16 +261,16 @@ class _ChallengeVerificationScreenState } // 사진 추가 버튼 (+) - Widget _buildAddPhotoButton() { + Widget _buildAddPhotoButton(AppColorsExtension appColors) { return GestureDetector( onTap: _showImageSourceSheet, child: Container( width: 100, height: 100, decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColors.gray3, width: 1.08), + border: Border.all(color: appColors.gray3, width: 1.08), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -278,15 +279,12 @@ class _ChallengeVerificationScreenState 'assets/images/icons/plus.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.gray3, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appColors.gray3, BlendMode.srcIn), ), const SizedBox(height: 2), Text( '사진 추가', - style: AppTypography.c1.copyWith(color: AppColors.gray3), + style: AppTypography.c1.copyWith(color: appColors.gray3), ), ], ), @@ -338,7 +336,7 @@ class _ChallengeVerificationScreenState } // 입력창 하단 정보 영역 - Widget _buildInputFooter() { + Widget _buildInputFooter(AppColorsExtension appColors) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 4), @@ -355,7 +353,7 @@ class _ChallengeVerificationScreenState top: -3.19, child: Text( '챌린지 목표 달성을 자유롭게 표현해주세요', - style: AppTypography.c1.copyWith(color: AppColors.gray3), + style: AppTypography.c1.copyWith(color: appColors.gray3), ), ), ], @@ -366,7 +364,7 @@ class _ChallengeVerificationScreenState child: Text( '${_contentController.text.length}/500', textAlign: TextAlign.right, - style: AppTypography.c1.copyWith(color: AppColors.gray3), + style: AppTypography.c1.copyWith(color: appColors.gray3), ), ), ], @@ -375,7 +373,11 @@ class _ChallengeVerificationScreenState } // 인증 사진 옆 사진 개수 - Widget _buildDynamicLabel(String label, int count) { + Widget _buildDynamicLabel( + String label, + int count, + AppColorsExtension appColors, + ) { return Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 8), // 아래 박스와의 간격 @@ -384,11 +386,14 @@ class _ChallengeVerificationScreenState mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: AppTypography.b3.copyWith(color: AppColors.black)), + Text( + label, + style: AppTypography.b3.copyWith(color: appColors.blackToWhite), + ), const SizedBox(width: 4), Text( '($count)', - style: AppTypography.b1.copyWith(color: AppColors.primaryAble), + style: AppTypography.b1.copyWith(color: appColors.primaryAble), ), ], ), @@ -570,7 +575,7 @@ class _ChallengeVerificationScreenState } // 기존 이미지 리스트 UI (수정 모드일 때만 호출) - Widget _buildExistingImagesList() { + Widget _buildExistingImagesList(AppColorsExtension appColors) { // articleImageUrl이 String 리스트이므로 인덱스로 관리하거나 ID가 필요함 // 만약 post.images 객체 리스트가 있다면 ID 추출이 가능합니다. return Column( @@ -579,6 +584,7 @@ class _ChallengeVerificationScreenState _buildDynamicLabel( '기존 인증 사진', _existingImages.length - _imageIdsToDelete.length, + appColors, ), SizedBox( height: 100, @@ -637,7 +643,7 @@ class _ChallengeVerificationScreenState } // 인증글 공통 팝업 호출 로직 (작성 취소할 경우, 수정 취소할 경우) - void _handleBackAction() async { + void _handleBackAction(AppColorsExtension appColors) async { final bool? shouldExit = await showDialog( context: context, barrierColor: const Color(0x7F1A1D1B), @@ -647,7 +653,7 @@ class _ChallengeVerificationScreenState ? '지금 나가면 수정 중인 내용은\n저장되지 않고 모두 삭제됩니다.' : '지금 나가면 작성 중인 내용은\n저장되지 않고 모두 삭제됩니다.', confirmText: isEditMode ? '수정 취소' : '작성 취소', - confirmTextColor: AppColors.notification, + confirmTextColor: appColors.notification, onConfirm: () { Navigator.pop(context); }, @@ -667,31 +673,37 @@ class _ChallengeVerificationScreenState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) { if (didPop) return; - _handleBackAction(); + _handleBackAction(appColors); }, child: Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( leading: IconButton( - onPressed: _handleBackAction, + onPressed: () => _handleBackAction(appColors), icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), ), title: Text( isEditMode ? '인증글 수정' : '챌린지 인증하기', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, scrolledUnderElevation: 0, elevation: 0, - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, ), body: Column( children: [ @@ -706,12 +718,15 @@ class _ChallengeVerificationScreenState crossAxisAlignment: CrossAxisAlignment.start, children: [ if (isEditMode && _existingImages.isNotEmpty) - _buildExistingImagesList(), + _buildExistingImagesList( + appColors, + ), // 기존 이미지 리스트 UI (수정 모드 전용) _buildDynamicLabel( isEditMode ? '새 사진 추가' : '인증 사진', _newImages.length, + appColors, ), - _buildPhotoUploadBox(), // 헬퍼 함수 호출 + _buildPhotoUploadBox(appColors), // 헬퍼 함수 호출 const SizedBox(height: 8), // 검증 상태에 따른 박스 로직 @@ -726,7 +741,7 @@ class _ChallengeVerificationScreenState height: 176, ), const SizedBox(height: 8), - _buildInputFooter(), // 헬퍼 함수 호출 + _buildInputFooter(appColors), // 헬퍼 함수 호출 const SizedBox(height: 40), ], ), diff --git a/lib/features/challenge/verification/screens/custom_gallery_screen.dart b/lib/features/challenge/verification/screens/custom_gallery_screen.dart index 6b5c74f..b0b0ae8 100644 --- a/lib/features/challenge/verification/screens/custom_gallery_screen.dart +++ b/lib/features/challenge/verification/screens/custom_gallery_screen.dart @@ -142,12 +142,14 @@ class _CustomGalleryScreenState extends State { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( leading: IconButton( onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.arrow_back, color: AppColors.black), + icon: Icon(Icons.arrow_back, color: appColors.blackToWhite), ), title: GestureDetector( onTap: _showFolderList, // 클릭 시 폴더 목록 표시 @@ -158,7 +160,7 @@ class _CustomGalleryScreenState extends State { (_selectedPath?.isAll == true) ? '갤러리' : (_selectedPath?.name ?? '갤러리'), - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), const SizedBox(width: 4), SvgPicture.asset( @@ -200,13 +202,13 @@ class _CustomGalleryScreenState extends State { '완료', style: AppTypography.b1.copyWith( color: _selectedAssets.isNotEmpty - ? AppColors.black - : AppColors.gray3, + ? appColors.blackToWhite + : appColors.gray3, ), ), ), ], - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, scrolledUnderElevation: 0, elevation: 0, ), @@ -257,7 +259,7 @@ class _CustomGalleryScreenState extends State { decoration: BoxDecoration( shape: BoxShape.circle, color: isSelected - ? AppColors.primaryAble + ? appColors.primaryAble : Colors.transparent, border: Border.all(color: Colors.white, width: 2), ), @@ -266,7 +268,7 @@ class _CustomGalleryScreenState extends State { ? Text( '$selectOrder', style: const TextStyle( - color: Colors.white, + color: AppColors.white, fontSize: 14, fontWeight: FontWeight.w600, ), diff --git a/lib/features/challenge/verification/widgets/ai_fail_box.dart b/lib/features/challenge/verification/widgets/ai_fail_box.dart index 0458e56..b1f1026 100644 --- a/lib/features/challenge/verification/widgets/ai_fail_box.dart +++ b/lib/features/challenge/verification/widgets/ai_fail_box.dart @@ -10,11 +10,13 @@ class AiFailBox extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: AppColors.warning, + color: appColors.warning, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( @@ -24,6 +26,10 @@ class AiFailBox extends StatelessWidget { 'assets/images/icons/warning.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode( + appColors.notification, + BlendMode.srcIn, + ), ), const SizedBox(width: 10), Expanded( @@ -34,12 +40,12 @@ class AiFailBox extends StatelessWidget { Text( '챌린지에 맞는 사진인지 확인해 주세요!', style: AppTypography.b2.copyWith( - color: AppColors.notification, + color: appColors.notification, ), ), Text( '다른 사진으로 다시 시도해주세요.', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/challenge/verification/widgets/ai_success_box.dart b/lib/features/challenge/verification/widgets/ai_success_box.dart index 8b49e52..e18771c 100644 --- a/lib/features/challenge/verification/widgets/ai_success_box.dart +++ b/lib/features/challenge/verification/widgets/ai_success_box.dart @@ -10,11 +10,13 @@ class AiSuccessBox extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: AppColors.selected, + color: appColors.selected, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( @@ -24,6 +26,10 @@ class AiSuccessBox extends StatelessWidget { 'assets/images/icons/success_check.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode( + appColors.primaryAble, + BlendMode.srcIn, + ), ), const SizedBox(width: 10), Expanded( @@ -34,12 +40,12 @@ class AiSuccessBox extends StatelessWidget { Text( '챌린지 내용과 적합합니다!', style: AppTypography.b2.copyWith( - color: AppColors.primaryAble, + color: appColors.primaryAble, ), ), Text( '인증 사진이 검증되었습니다. 인증을 완료하세요.', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/challenge/verification/widgets/ai_verification_box.dart b/lib/features/challenge/verification/widgets/ai_verification_box.dart index b0c8349..8336ee4 100644 --- a/lib/features/challenge/verification/widgets/ai_verification_box.dart +++ b/lib/features/challenge/verification/widgets/ai_verification_box.dart @@ -10,22 +10,24 @@ class AiVerificationBox extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: AppColors.gray5, + color: appColors.gray5, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, // 원의 선 두께 - valueColor: AlwaysStoppedAnimation(AppColors.gray2), + valueColor: AlwaysStoppedAnimation(appColors.gray2), ), ), const SizedBox(width: 10), @@ -36,11 +38,11 @@ class AiVerificationBox extends StatelessWidget { children: [ Text( 'AI가 사진을 검증 중입니다...', - style: AppTypography.b2.copyWith(color: AppColors.gray1), + style: AppTypography.b2.copyWith(color: appColors.gray1), ), Text( '챌린지 내용과 적합한지 확인하고 있어요', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/challenge/verification/widgets/photo_free_box.dart b/lib/features/challenge/verification/widgets/photo_free_box.dart index 63b4780..9592391 100644 --- a/lib/features/challenge/verification/widgets/photo_free_box.dart +++ b/lib/features/challenge/verification/widgets/photo_free_box.dart @@ -9,11 +9,13 @@ class PhotoFreeBox extends StatelessWidget { const PhotoFreeBox({super.key}); @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: AppColors.gray5, + color: appColors.gray5, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( @@ -23,6 +25,7 @@ class PhotoFreeBox extends StatelessWidget { 'assets/images/icons/gallery_icon.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode(appColors.gray1, BlendMode.srcIn), ), const SizedBox(width: 10), Expanded( @@ -31,11 +34,11 @@ class PhotoFreeBox extends StatelessWidget { children: [ Text( '사진 첨부는 선택이에요', - style: AppTypography.b2.copyWith(color: AppColors.gray1), + style: AppTypography.b2.copyWith(color: appColors.gray1), ), Text( '사진을 함께 올리면 더 생생한 인증글이 완성돼요😎', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/challenge/verification/widgets/reverification_guide_box.dart b/lib/features/challenge/verification/widgets/reverification_guide_box.dart index 791067a..fe82b68 100644 --- a/lib/features/challenge/verification/widgets/reverification_guide_box.dart +++ b/lib/features/challenge/verification/widgets/reverification_guide_box.dart @@ -10,10 +10,12 @@ class ReverificationGuideBox extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: AppColors.warning, + color: appColors.warning, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( @@ -23,6 +25,10 @@ class ReverificationGuideBox extends StatelessWidget { 'assets/images/icons/tip_bulb.svg', width: 18, height: 18, + colorFilter: ColorFilter.mode( + appColors.notification, + BlendMode.srcIn, + ), ), const SizedBox(width: 10), Expanded( @@ -34,14 +40,14 @@ class ReverificationGuideBox extends StatelessWidget { Text( '재인증 가이드', style: AppTypography.b2.copyWith( - color: AppColors.notification, + color: appColors.notification, ), ), const SizedBox(height: 4), // 가이드 상세 내용 Text( '• 챌린지 활동이 명확히 보이는 사진을 촬영해주세요.\n• 흐릿하거나 어두운 사진은 인식되지 않을 수 있습니다.\n• 다른 각도나 조명에서 다시 시도해보세요.', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/challenge/verification/widgets/verification_info_box.dart b/lib/features/challenge/verification/widgets/verification_info_box.dart index 8d468fd..0f75a8c 100644 --- a/lib/features/challenge/verification/widgets/verification_info_box.dart +++ b/lib/features/challenge/verification/widgets/verification_info_box.dart @@ -9,11 +9,13 @@ class VerificationInfoBox extends StatelessWidget { const VerificationInfoBox({super.key}); @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: AppColors.gray5, + color: appColors.gray5, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( @@ -23,6 +25,7 @@ class VerificationInfoBox extends StatelessWidget { 'assets/images/icons/gallery_icon.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode(appColors.gray1, BlendMode.srcIn), ), const SizedBox(width: 10), Expanded( @@ -31,11 +34,11 @@ class VerificationInfoBox extends StatelessWidget { children: [ Text( '사진을 추가해주세요', - style: AppTypography.b2.copyWith(color: AppColors.gray1), + style: AppTypography.b2.copyWith(color: appColors.gray1), ), Text( '챌린지를 수행한 증거 사진을 올려주세요. \n최대 3장 첨부할 수 있습니다.', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/challenge/verification/widgets/verification_submit_button.dart b/lib/features/challenge/verification/widgets/verification_submit_button.dart index 0c26d88..e54f5dc 100644 --- a/lib/features/challenge/verification/widgets/verification_submit_button.dart +++ b/lib/features/challenge/verification/widgets/verification_submit_button.dart @@ -18,10 +18,12 @@ class VerificationSubmitButton extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final bool isEnabled = onPressed != null; return Container( decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, boxShadow: showShadow ? [ const BoxShadow( @@ -42,12 +44,12 @@ class VerificationSubmitButton extends StatelessWidget { height: 56, alignment: Alignment.center, decoration: BoxDecoration( - color: isEnabled ? AppColors.primaryAble : AppColors.disable, + color: isEnabled ? appColors.primaryAble : appColors.disable, borderRadius: BorderRadius.circular(10), ), child: Text( label, - style: AppTypography.h3.copyWith(color: Colors.white), + style: AppTypography.h3.copyWith(color: appColors.whiteToBlack), ), ), ), diff --git a/lib/features/challenge/verification/widgets/verification_tip_box.dart b/lib/features/challenge/verification/widgets/verification_tip_box.dart index 526670c..1e4b4ce 100644 --- a/lib/features/challenge/verification/widgets/verification_tip_box.dart +++ b/lib/features/challenge/verification/widgets/verification_tip_box.dart @@ -9,11 +9,16 @@ class VerificationTipBox extends StatelessWidget { const VerificationTipBox({super.key}); @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + Color tipboxYellow = Theme.of(context).brightness == Brightness.light + ? const Color(0xFFFFF4D1) + : const Color(0xFF575046); + return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: const Color(0xFFFFF4D1), + color: tipboxYellow, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( @@ -38,7 +43,7 @@ class VerificationTipBox extends StatelessWidget { const SizedBox(height: 4), Text( '• 날짜/시간이 보이는 사진이 더 좋아요.\n• 구체적인 내용을 작성하면 동기부여에 도움이 됩니다.\n• 긍정적인 에너지를 공유해보세요!', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/shared/widgets/bottom_action_button.dart b/lib/shared/widgets/bottom_action_button.dart index 1e5c0fd..b7d79a8 100644 --- a/lib/shared/widgets/bottom_action_button.dart +++ b/lib/shared/widgets/bottom_action_button.dart @@ -79,9 +79,9 @@ class BottomActionButton extends StatelessWidget { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.white.withValues(alpha: 0), - Colors.white.withValues(alpha: 0.9), - Colors.white, + appColors.whiteToBlack.withValues(alpha: 0), + appColors.whiteToBlack.withValues(alpha: 0.9), + appColors.whiteToBlack, ], stops: const [0.0, 0.3, 1.0], ), From ed00af7d94409638b7589a6191f4ae3429e0949a Mon Sep 17 00:00:00 2001 From: Seungvin Date: Tue, 4 Aug 2026 01:45:41 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20FAB,=20=ED=95=98=EB=8B=A8=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=20=EB=B0=94=20=EB=8B=A4=ED=81=AC=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/home/screens/home_screen.dart | 12 ++++++--- lib/features/main/widgets/bottom_nav_bar.dart | 26 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/features/home/screens/home_screen.dart b/lib/features/home/screens/home_screen.dart index b437291..46ea8eb 100644 --- a/lib/features/home/screens/home_screen.dart +++ b/lib/features/home/screens/home_screen.dart @@ -31,7 +31,7 @@ class HomeScreen extends ConsumerWidget { ); return Scaffold( - // backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, body: SafeArea( child: homeDataAsync.when( loading: () => const Center(child: CircularProgressIndicator()), @@ -197,6 +197,12 @@ class HomeScreen extends ConsumerWidget { // FAB 빌더 Widget _buildFAB(BuildContext context) { final appColors = Theme.of(context).extension()!; + Color FABColor = Theme.of(context).brightness == Brightness.light + ? AppColors.white + : appColors.primaryAble; + Color FABplusColor = Theme.of(context).brightness == Brightness.light + ? appColors.primaryAble + : AppColors.black; return Positioned( right: 20, @@ -208,9 +214,9 @@ class HomeScreen extends ConsumerWidget { builder: (context) => const ChallengeCreateScreen(), ), ), - backgroundColor: appColors.blackToWhite, + backgroundColor: FABColor, shape: const CircleBorder(), - child: Icon(Icons.add, size: 32, color: appColors.primaryAble), + child: Icon(Icons.add, size: 32, color: FABplusColor), ), ); } diff --git a/lib/features/main/widgets/bottom_nav_bar.dart b/lib/features/main/widgets/bottom_nav_bar.dart index 7819d8f..1dea0b4 100644 --- a/lib/features/main/widgets/bottom_nav_bar.dart +++ b/lib/features/main/widgets/bottom_nav_bar.dart @@ -17,37 +17,43 @@ class MainBottomNavBar extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: currentIndex, - selectedItemColor: AppColors.black, - unselectedItemColor: AppColors.gray3, + selectedItemColor: appColors.blackToWhite, + unselectedItemColor: appColors.gray3, selectedLabelStyle: AppTypography.c2, unselectedLabelStyle: AppTypography.c2, onTap: onTap, items: [ - _buildNavItem('home_icon_off.svg', '홈'), - _buildNavItem('graph_icon.svg', '통계'), - _buildNavItem('feed_icon.svg', '피드'), - _buildNavItem('friend_icon_off.svg', '친구'), - _buildNavItem('user_icon.svg', '내 페이지'), + _buildNavItem('home_icon_off.svg', '홈', appColors), + _buildNavItem('graph_icon.svg', '통계', appColors), + _buildNavItem('feed_icon.svg', '피드', appColors), + _buildNavItem('friend_icon_off.svg', '친구', appColors), + _buildNavItem('user_icon.svg', '내 페이지', appColors), ], ); } - BottomNavigationBarItem _buildNavItem(String iconName, String label) { + BottomNavigationBarItem _buildNavItem( + String iconName, + String label, + AppColorsExtension appColors, + ) { return BottomNavigationBarItem( icon: SvgPicture.asset( 'assets/images/icons/$iconName', width: 24, height: 24, - colorFilter: const ColorFilter.mode(AppColors.gray3, BlendMode.srcIn), + colorFilter: ColorFilter.mode(appColors.gray3, BlendMode.srcIn), ), activeIcon: SvgPicture.asset( 'assets/images/icons/$iconName', width: 24, height: 24, - colorFilter: const ColorFilter.mode(AppColors.black, BlendMode.srcIn), + colorFilter: ColorFilter.mode(appColors.blackToWhite, BlendMode.srcIn), ), label: label, ); From dd263f763ed089c031382d8adb6195a0095cffb2 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Tue, 4 Aug 2026 03:05:07 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=ED=85=9C=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0=20=EB=B0=8F=20=EB=8B=A4=ED=81=AC?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/feed/widgets/comment_item.dart | 137 +++++++++++--------- 1 file changed, 75 insertions(+), 62 deletions(-) diff --git a/lib/features/feed/widgets/comment_item.dart b/lib/features/feed/widgets/comment_item.dart index 69b127a..9f9473f 100644 --- a/lib/features/feed/widgets/comment_item.dart +++ b/lib/features/feed/widgets/comment_item.dart @@ -22,87 +22,100 @@ class CommentItem extends StatelessWidget { @override Widget build(BuildContext context) { - String commentDate = DateFormat('yyyy.MM.dd HH:mm').format(comment.date); + final appColors = Theme.of(context).extension()!; + + // 피그마 디자인에 맞춰 날짜 포맷 변경 + String commentDate = DateFormat('yyyy년 MM월 dd일 HH:mm').format(comment.date); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), - child: Column( - mainAxisSize: MainAxisSize.min, // 💡 무한 루프 방지: 최소 크기만 확보 + padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), + child: Row( + // 💡 중요: 아바타와 텍스트가 모두 위쪽을 기준으로 정렬되도록 수정 crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 1층: 프로필 이미지 + 닉네임 (세로 중앙 정렬) + 우측 팝업 메뉴 - Row( - crossAxisAlignment: - CrossAxisAlignment.center, // 💡 이름이 이미지 중간 위치에 오도록 설정 - children: [ - // 프로필 이미지 - CircleAvatar( - radius: 18, - backgroundImage: - (comment.writer.profileUrl != null && - comment.writer.profileUrl!.isNotEmpty) - ? NetworkImage(comment.writer.profileUrl!) as ImageProvider - : null, - child: - (comment.writer.profileUrl == null || - comment.writer.profileUrl!.isEmpty) - ? SvgPicture.asset( - 'assets/images/icons/default_profile_icon.svg', - ) - : null, - ), - const SizedBox(width: 12), - - // 유저 닉네임 - Expanded( - child: Text( - comment.writer.nickname, - style: AppTypography.b1, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - - // 우측 팝업 메뉴 (크기를 제한하여 레이아웃 루프 차단) - SizedBox( - width: 40, - height: 40, - child: CommentPopupMenu( - postId: postId, - comment: comment, - feedProvider: feedProvider, - ), - ), - ], + // 1. 좌측: 프로필 이미지 + CircleAvatar( + radius: 20, // 40x40 크기 + backgroundImage: + (comment.writer.profileUrl != null && + comment.writer.profileUrl!.isNotEmpty) + ? NetworkImage(comment.writer.profileUrl!) as ImageProvider + : null, + backgroundColor: appColors.gray5, + child: + (comment.writer.profileUrl == null || + comment.writer.profileUrl!.isEmpty) + ? SvgPicture.asset( + 'assets/images/icons/default_profile_icon.svg', + width: 40, + height: 40, + ) + : null, ), - const SizedBox(width: 12), - // 댓글 내용 및 팝업 메뉴 + + const SizedBox(width: 10), // 피그마 spacing: 10 적용 + // 2. 우측: 닉네임, 내용, 날짜 (Expanded는 여기서 가로 공간만 차지하므로 안전함) Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, // 💡 세로 무한 루프 에러 완전 차단 children: [ + // 2-1. 상단 헤더: 닉네임 + 서브텍스트 + 팝업 메뉴 Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(comment.writer.nickname, style: AppTypography.b1), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + comment.writer.nickname, + style: AppTypography.b3.copyWith( + color: appColors.blackToWhite, + ), + ), + const SizedBox(height: 2), // 피그마 spacing: 6 적용 + // 2-2. 본문 내용 (누락되었던 content 복구) + Text( + comment.content, + style: AppTypography.b2.copyWith( + color: appColors.blackToWhite, + height: 1.5, + ), + ), + ], + ), + ), + // 우측 팝업 메뉴 SizedBox( - height: 24, - width: 24, child: CommentPopupMenu( postId: postId, comment: comment, feedProvider: feedProvider, ), ), + ], + ), + + const SizedBox(height: 8), // 내용과 날짜 사이 간격 + // 2-3. 하단 날짜 + Row( + children: [ + Text( + commentDate, + style: AppTypography.c1.copyWith( + color: appColors.gray2, + height: 1.5, + ), + ), + const SizedBox(width: 20), // 날짜와 '수정됨' 사이 간격 if (comment.isEdited) - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: Text( - '수정됨', - style: AppTypography.b2.copyWith( - color: AppColors.gray3, - ), + Text( + '수정됨', + style: AppTypography.c1.copyWith( + color: appColors.gray3, ), ), ], From 0f6d9e5ada0001467ccafffff0ca19d85269deb7 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Tue, 4 Aug 2026 03:27:54 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C=20=EB=8B=A4?= =?UTF-8?q?=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/theme/app_colors.dart | 2 +- .../feed/screens/challenge_search_screen.dart | 43 ++++++++++--------- lib/features/feed/screens/feed_screen.dart | 18 ++++---- .../feed/screens/post_detail_screen.dart | 23 ++++++---- lib/features/feed/views/share_feed_view.dart | 4 +- .../feed/widgets/challenge_search_card.dart | 26 ++++++----- .../feed/widgets/comment_input_field.dart | 18 +++++--- .../feed/widgets/comment_popup_menu.dart | 25 ++++++++--- .../feed/widgets/edit_article_dialog.dart | 16 +++---- .../feed/widgets/enter_confirm_dialog.dart | 23 ++++++---- lib/features/feed/widgets/feed_post_card.dart | 38 ++++++++-------- .../feed/widgets/post_popup_menu.dart | 25 ++++++++--- .../widgets/recommended_challenge_card.dart | 33 +++++++------- lib/features/main/widgets/bottom_nav_bar.dart | 1 + .../screens/challenge_detail_screen.dart | 24 ++++++----- 15 files changed, 194 insertions(+), 125 deletions(-) diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index f33a4d8..7917b36 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -88,7 +88,7 @@ class AppColorsExtension extends ThemeExtension { gray4: AppColors.gray2, // gray4 -> gray2 gray5: Color(0xFF2A2A2A), // gray5 -> d-gray5 primaryAble: Color(0xFF0EBC6A), // primarygreen -> d-primarygreen - selected: Color(0xFF264F36), // selected -> d-selected + selected: Color(0xFF1D2C23), // selected -> d-selected disable: Color(0xFF697C73), // disable -> d-disable success: Color(0xFF1A3A1E), // success -> d-success warning: Color(0xFF3A2522), // warning -> d-warning diff --git a/lib/features/feed/screens/challenge_search_screen.dart b/lib/features/feed/screens/challenge_search_screen.dart index 1415487..04850dd 100644 --- a/lib/features/feed/screens/challenge_search_screen.dart +++ b/lib/features/feed/screens/challenge_search_screen.dart @@ -31,6 +31,8 @@ class _ChallengeSearchScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 사용자 프로필 정보 가져오기 (헤더 이름용) final currentUser = ref.watch(currentUserProvider); @@ -40,18 +42,18 @@ class _ChallengeSearchScreenState extends ConsumerState { ); return Scaffold( - backgroundColor: Color(0xFFE0E2DC).withValues(alpha: 50), + backgroundColor: appColors.gray5, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, - surfaceTintColor: Colors.white, + surfaceTintColor: appColors.whiteToBlack, leading: IconButton( icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -59,20 +61,20 @@ class _ChallengeSearchScreenState extends ConsumerState { ), title: Text( '챌린지 탐색', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), body: Column( children: [ - const Divider(height: 1, color: AppColors.gray4), // 검색창 영역 - _buildSearchBar(), + _buildSearchBar(appColors), // 챌린지 카드 리스트 영역 Expanded( child: _currentKeyword.isEmpty ? _buildRecommendationSection( currentUser, + appColors, ) // 💡 검색어 없을 때 추천 섹션 노출 : _buildSearchResults(searchResults), // 검색어 있을 때 결과 노출 ), @@ -82,7 +84,10 @@ class _ChallengeSearchScreenState extends ConsumerState { } // ── AI 추천 섹션 구현 ──────────────────────────── - Widget _buildRecommendationSection(User? currentUser) { + Widget _buildRecommendationSection( + User? currentUser, + AppColorsExtension appColors, + ) { final userName = currentUser?.nickname ?? "해냄"; final recommendedAsync = ref.watch(recommendedChallengesProvider); @@ -98,7 +103,9 @@ class _ChallengeSearchScreenState extends ConsumerState { alignment: Alignment.centerLeft, child: Text( "추천 챌린지", - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith( + color: appColors.blackToWhite, + ), ), ), ), @@ -120,7 +127,7 @@ class _ChallengeSearchScreenState extends ConsumerState { } // 검색창 위젯 분리 (가독성용) - Widget _buildSearchBar() { + Widget _buildSearchBar(AppColorsExtension appColors) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: TextField( @@ -132,18 +139,14 @@ class _ChallengeSearchScreenState extends ConsumerState { }, decoration: InputDecoration( hintText: '이름으로 탐색하기', - hintStyle: AppTypography.b2.copyWith(color: AppColors.gray3), + hintStyle: AppTypography.b2.copyWith(color: appColors.gray3), prefixIcon: Padding( padding: const EdgeInsets.all(14.0), child: SvgPicture.asset('assets/images/icons/search_icon.svg'), ), suffixIcon: _searchController.text.isNotEmpty ? IconButton( - icon: const Icon( - Icons.clear, - size: 20, - color: AppColors.gray3, - ), + icon: Icon(Icons.clear, size: 20, color: appColors.gray3), onPressed: () { _searchController.clear(); setState(() => _currentKeyword = ""); @@ -151,15 +154,15 @@ class _ChallengeSearchScreenState extends ConsumerState { ) : null, filled: true, - fillColor: Colors.white, + fillColor: appColors.whiteToBlack, contentPadding: const EdgeInsets.symmetric(vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(9.5), - borderSide: const BorderSide(color: AppColors.gray4), + borderSide: BorderSide(color: appColors.gray4), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(9.5), - borderSide: BorderSide(color: Colors.grey.shade300), + borderSide: BorderSide(color: appColors.gray4), ), ), ), diff --git a/lib/features/feed/screens/feed_screen.dart b/lib/features/feed/screens/feed_screen.dart index 2c5079c..da8a249 100644 --- a/lib/features/feed/screens/feed_screen.dart +++ b/lib/features/feed/screens/feed_screen.dart @@ -57,15 +57,17 @@ class _FeedScreenState extends State @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, scrolledUnderElevation: 0, title: Text( '피드', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, actions: [ @@ -87,8 +89,8 @@ class _FeedScreenState extends State 'assets/images/icons/search_icon.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -97,9 +99,9 @@ class _FeedScreenState extends State ], bottom: TabBar( controller: _tabController, - labelColor: AppColors.primaryAble, - unselectedLabelColor: AppColors.gray4, - indicatorColor: AppColors.primaryAble, + labelColor: appColors.primaryAble, + unselectedLabelColor: appColors.gray4, + indicatorColor: appColors.primaryAble, indicatorWeight: 1, indicatorSize: TabBarIndicatorSize.tab, labelStyle: AppTypography.b1, diff --git a/lib/features/feed/screens/post_detail_screen.dart b/lib/features/feed/screens/post_detail_screen.dart index c49d268..2a9b05c 100644 --- a/lib/features/feed/screens/post_detail_screen.dart +++ b/lib/features/feed/screens/post_detail_screen.dart @@ -28,15 +28,17 @@ class PostDetailScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + // 상세 정보 및 댓글 구독 final detailAsync = ref.watch(postDetailProvider(postId: postId)); final commentsAsync = ref.watch(postCommentsProvider(postId: postId)); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, - surfaceTintColor: Colors.white, + backgroundColor: appColors.whiteToBlack, + surfaceTintColor: appColors.whiteToBlack, scrolledUnderElevation: 0, elevation: 0, leading: IconButton( @@ -44,12 +46,16 @@ class PostDetailScreen extends ConsumerWidget { 'assets/images/icons/arrow_left.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), onPressed: () => Navigator.pop(context), ), title: Text( '피드', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), @@ -84,7 +90,8 @@ class PostDetailScreen extends ConsumerWidget { child: Center(child: Text('댓글 로드 실패')), ), data: (comments) { - if (comments.isEmpty) return _buildEmptyComments(); + if (comments.isEmpty) + return _buildEmptyComments(appColors); return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -113,11 +120,11 @@ class PostDetailScreen extends ConsumerWidget { ); } - Widget _buildEmptyComments() { - return const Padding( + Widget _buildEmptyComments(AppColorsExtension appColors) { + return Padding( padding: EdgeInsets.symmetric(vertical: 60), child: Center( - child: Text('첫 댓글을 남겨보세요!', style: TextStyle(color: AppColors.gray2)), + child: Text('첫 댓글을 남겨보세요!', style: TextStyle(color: appColors.gray2)), ), ); } diff --git a/lib/features/feed/views/share_feed_view.dart b/lib/features/feed/views/share_feed_view.dart index 1ffb1fc..47ba1bb 100644 --- a/lib/features/feed/views/share_feed_view.dart +++ b/lib/features/feed/views/share_feed_view.dart @@ -64,6 +64,8 @@ class _ShareFeedViewState extends ConsumerState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + super.build(context); final feedState = ref.watch(widget.provider); @@ -84,7 +86,7 @@ class _ShareFeedViewState extends ConsumerState Center( child: Text( widget.emptyMessage, - style: AppTypography.b1.copyWith(color: AppColors.gray4), + style: AppTypography.b1.copyWith(color: appColors.gray4), ), ), ], diff --git a/lib/features/feed/widgets/challenge_search_card.dart b/lib/features/feed/widgets/challenge_search_card.dart index 35d4e7c..38be531 100644 --- a/lib/features/feed/widgets/challenge_search_card.dart +++ b/lib/features/feed/widgets/challenge_search_card.dart @@ -13,11 +13,13 @@ class ChallengeSearchCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.fromLTRB(16, 10, 0, 10), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( @@ -37,7 +39,9 @@ class ChallengeSearchCard extends StatelessWidget { // 챌린지 제목 Text( challenge.base.title, - style: AppTypography.b3.copyWith(color: AppColors.black), + style: AppTypography.b3.copyWith( + color: appColors.blackToWhite, + ), ), const SizedBox(height: 10), @@ -45,6 +49,7 @@ class ChallengeSearchCard extends StatelessWidget { Row( children: [ _buildInfoItem( + appColors, iconPath: 'assets/images/icons/person_icon.svg', text: '${challenge.participantCount}명/50명', ), @@ -53,7 +58,7 @@ class ChallengeSearchCard extends StatelessWidget { Text( '완료까지 D-${challenge.dDay}', style: AppTypography.b2.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), ), ], @@ -94,10 +99,7 @@ class ChallengeSearchCard extends StatelessWidget { }, icon: SvgPicture.asset( 'assets/images/icons/thick_right_arrow_icon.svg', - colorFilter: const ColorFilter.mode( - AppColors.gray2, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appColors.gray2, BlendMode.srcIn), ), ), ], @@ -107,17 +109,21 @@ class ChallengeSearchCard extends StatelessWidget { } // 정보 아이템(인원수 등) 위젯 - Widget _buildInfoItem({required String iconPath, required String text}) { + Widget _buildInfoItem( + AppColorsExtension appColors, { + required String iconPath, + required String text, + }) { return Row( children: [ SvgPicture.asset( iconPath, width: 18, height: 18, - colorFilter: const ColorFilter.mode(AppColors.gray2, BlendMode.srcIn), + colorFilter: ColorFilter.mode(appColors.gray2, BlendMode.srcIn), ), const SizedBox(width: 2), - Text(text, style: AppTypography.b2.copyWith(color: AppColors.gray2)), + Text(text, style: AppTypography.b2.copyWith(color: appColors.gray2)), ], ); } diff --git a/lib/features/feed/widgets/comment_input_field.dart b/lib/features/feed/widgets/comment_input_field.dart index 10c2890..9880ede 100644 --- a/lib/features/feed/widgets/comment_input_field.dart +++ b/lib/features/feed/widgets/comment_input_field.dart @@ -40,6 +40,8 @@ class _CommentInputFieldState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 키보드가 올라왔을 때 하단 여백 자동 조절 final double systemBottomPadding = MediaQuery.of(context).viewInsets.bottom > 0 @@ -54,7 +56,7 @@ class _CommentInputFieldState extends ConsumerState { bottom: systemBottomPadding, ), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), @@ -62,7 +64,7 @@ class _CommentInputFieldState extends ConsumerState { blurRadius: 4, ), ], - border: Border(top: BorderSide(color: Colors.grey.shade200)), + border: Border(top: BorderSide(color: appColors.gray4)), ), child: Row( children: [ @@ -71,9 +73,9 @@ class _CommentInputFieldState extends ConsumerState { controller: _commentController, decoration: InputDecoration( hintText: '댓글을 입력하세요...', - hintStyle: AppTypography.b2.copyWith(color: AppColors.gray3), + hintStyle: AppTypography.b2.copyWith(color: appColors.gray3), filled: true, - fillColor: AppColors.gray5, + fillColor: appColors.gray5, contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10, @@ -113,10 +115,14 @@ class _CommentInputFieldState extends ConsumerState { child: CircleAvatar( radius: 22, backgroundColor: _isButtonActive - ? AppColors.primaryAble - : AppColors.disable, + ? appColors.primaryAble + : appColors.disable, child: SvgPicture.asset( 'assets/images/icons/comment_upload_icon.svg', + colorFilter: ColorFilter.mode( + appColors.whiteToBlack, + BlendMode.srcIn, + ), ), ), ), diff --git a/lib/features/feed/widgets/comment_popup_menu.dart b/lib/features/feed/widgets/comment_popup_menu.dart index dcca180..69a5a01 100644 --- a/lib/features/feed/widgets/comment_popup_menu.dart +++ b/lib/features/feed/widgets/comment_popup_menu.dart @@ -31,18 +31,21 @@ class CommentPopupMenu extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + return PopupMenuButton( - icon: SvgPicture.asset( + child: SvgPicture.asset( 'assets/images/icons/dots_vert_icon.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode(appColors.gray3, BlendMode.srcIn), ), offset: const Offset(0, 40), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), - side: const BorderSide(color: AppColors.gray4, width: 1), + side: BorderSide(color: appColors.gray4, width: 1), ), - color: Colors.white, + color: appColors.whiteToBlack, itemBuilder: (context) { // 내 댓글인 경우 : 수정/삭제 if (comment.isMine) { @@ -51,12 +54,14 @@ class CommentPopupMenu extends ConsumerWidget { '수정하기', 'assets/images/icons/edit_icon.svg', 'edit', + appColors, ), const PopupMenuDivider(height: 1), _buildPopupItem( '삭제하기', 'assets/images/icons/small_trash_icon.svg', 'delete', + appColors, isDanger: true, ), ]; @@ -68,22 +73,27 @@ class CommentPopupMenu extends ConsumerWidget { '신고하기', 'assets/images/icons/complaint.svg', 'report', + appColors, isDanger: true, ), ]; } }, - onSelected: (value) => _handleMenuSelection(context, ref, value), + onSelected: (value) => + _handleMenuSelection(context, ref, value, appColors), ); } PopupMenuItem _buildPopupItem( String title, String iconPath, - String value, { + String value, + AppColorsExtension appColors, { bool isDanger = false, }) { - final Color itemColor = isDanger ? AppColors.notification : AppColors.black; + final Color itemColor = isDanger + ? appColors.notification + : appColors.blackToWhite; return PopupMenuItem( value: value, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), @@ -107,6 +117,7 @@ class CommentPopupMenu extends ConsumerWidget { BuildContext context, WidgetRef ref, String value, + AppColorsExtension appColors, ) async { switch (value) { case 'edit': @@ -141,7 +152,7 @@ class CommentPopupMenu extends ConsumerWidget { title: '댓글을 삭제하시겠습니까?', content: '작성하신 댓글을 삭제하시겠습니까?\n삭제된 댓글은 되돌릴 수 없습니다.', confirmText: '삭제', - confirmTextColor: AppColors.notification, + confirmTextColor: appColors.notification, cancelText: '취소', onCancel: () { Navigator.of(context).pop(true); diff --git a/lib/features/feed/widgets/edit_article_dialog.dart b/lib/features/feed/widgets/edit_article_dialog.dart index 6c88b56..fbb6c29 100644 --- a/lib/features/feed/widgets/edit_article_dialog.dart +++ b/lib/features/feed/widgets/edit_article_dialog.dart @@ -22,7 +22,10 @@ class _EditArticleDialogState extends State { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Dialog( + backgroundColor: appColors.whiteToBlack, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Padding( padding: const EdgeInsets.all(20), @@ -37,7 +40,7 @@ class _EditArticleDialogState extends State { maxLines: 5, decoration: InputDecoration( filled: true, - fillColor: AppColors.gray5, + fillColor: appColors.gray5, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none, @@ -50,24 +53,21 @@ class _EditArticleDialogState extends State { Expanded( child: TextButton( onPressed: () => Navigator.pop(context), - child: const Text( - '취소', - style: TextStyle(color: AppColors.gray2), - ), + child: Text('취소', style: TextStyle(color: appColors.gray2)), ), ), Expanded( child: ElevatedButton( onPressed: () => Navigator.pop(context, _controller.text), style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryAble, + backgroundColor: appColors.primaryAble, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), - child: const Text( + child: Text( '수정완료', - style: TextStyle(color: Colors.white), + style: TextStyle(color: appColors.whiteToBlack), ), ), ), diff --git a/lib/features/feed/widgets/enter_confirm_dialog.dart b/lib/features/feed/widgets/enter_confirm_dialog.dart index 7e72c08..85316b2 100644 --- a/lib/features/feed/widgets/enter_confirm_dialog.dart +++ b/lib/features/feed/widgets/enter_confirm_dialog.dart @@ -21,12 +21,13 @@ class EnterConfirmDialog extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; final state = ref.watch(challengeParticipateNotifierProvider); return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - backgroundColor: Colors.white, - surfaceTintColor: Colors.white, + backgroundColor: appColors.whiteToBlack, + surfaceTintColor: appColors.whiteToBlack, insetPadding: const EdgeInsets.symmetric(horizontal: 25.0), child: Padding( padding: const EdgeInsets.fromLTRB(15, 25, 15, 15), @@ -41,24 +42,28 @@ class EnterConfirmDialog extends ConsumerWidget { 'assets/images/icons/round_check_icon.svg', width: 42, height: 42, + colorFilter: ColorFilter.mode( + appColors.primaryAble, + BlendMode.srcIn, + ), ), ), const SizedBox(height: 10), // 제목 Text( '챌린지 참여 완료!', - style: AppTypography.h2.copyWith(color: AppColors.black), + style: AppTypography.h2.copyWith(color: appColors.blackToWhite), ), const SizedBox(height: 3), // 챌린지 명 넣기 Text( '‘$challengeTitle’', - style: AppTypography.b3.copyWith(color: AppColors.gray1), + style: AppTypography.b3.copyWith(color: appColors.gray1), textAlign: TextAlign.center, ), Text( '지금부터 함께 도전해요!', - style: AppTypography.b1.copyWith(color: AppColors.gray1), + style: AppTypography.b1.copyWith(color: appColors.gray1), ), const SizedBox(height: 24), // 확인 버튼 @@ -81,8 +86,8 @@ class EnterConfirmDialog extends ConsumerWidget { route.isFirst, // 못 찾으면(다른 경로로 들어온 경우) 기존처럼 맨 처음까지 ), style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryAble, - foregroundColor: Colors.white, + backgroundColor: appColors.primaryAble, + foregroundColor: appColors.whiteToBlack, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), @@ -90,7 +95,9 @@ class EnterConfirmDialog extends ConsumerWidget { ), child: Text( '확인', - style: AppTypography.b1.copyWith(color: Colors.white), + style: AppTypography.b1.copyWith( + color: appColors.whiteToBlack, + ), ), ), ), diff --git a/lib/features/feed/widgets/feed_post_card.dart b/lib/features/feed/widgets/feed_post_card.dart index 7cf89d3..00af64a 100644 --- a/lib/features/feed/widgets/feed_post_card.dart +++ b/lib/features/feed/widgets/feed_post_card.dart @@ -28,6 +28,7 @@ class FeedPostCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; /* final displayDate = post.updatedAt ?? post.createdAt; @@ -106,7 +107,7 @@ class FeedPostCard extends ConsumerWidget { child: Text( post.title, style: AppTypography.b3.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -118,7 +119,7 @@ class FeedPostCard extends ConsumerWidget { child: Text( '수정됨', style: AppTypography.b2.copyWith( - color: AppColors.gray3, + color: appColors.gray3, ), // 조금 연한 회색 스타일 적용 ), ), @@ -127,7 +128,7 @@ class FeedPostCard extends ConsumerWidget { const SizedBox(height: 4), // 간격 추가 Text( post.content, - style: AppTypography.b1.copyWith(color: AppColors.gray1), + style: AppTypography.b1.copyWith(color: appColors.gray1), // 사진이 없을 때는 본문을 더 길게 보여줘도 좋습니다. maxLines: post.hasImage ? 3 : 10, overflow: TextOverflow.ellipsis, @@ -170,8 +171,8 @@ class FeedPostCard extends ConsumerWidget { height: 20, colorFilter: ColorFilter.mode( post.isLiked - ? AppColors.notification - : AppColors.gray2, + ? appColors.notification + : appColors.gray2, BlendMode.srcIn, ), ), @@ -180,8 +181,8 @@ class FeedPostCard extends ConsumerWidget { post.likeCount.toString(), style: AppTypography.b2.copyWith( color: post.isLiked - ? AppColors.notification - : AppColors.gray2, + ? appColors.notification + : appColors.gray2, ), ), ], @@ -191,11 +192,12 @@ class FeedPostCard extends ConsumerWidget { _buildIconInfo( 'assets/images/icons/comment_icon.svg', post.commentCount.toString(), + appColors, ), const Spacer(), Text( formattedDate, - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ), @@ -207,19 +209,19 @@ class FeedPostCard extends ConsumerWidget { Widget _buildIconInfo( String iconPath, - String count, { - Color color = AppColors.gray2, - }) { + String count, + AppColorsExtension appColors, + ) { return Row( children: [ SvgPicture.asset( iconPath, width: 20, height: 20, - colorFilter: ColorFilter.mode(color, BlendMode.srcIn), + colorFilter: ColorFilter.mode(appColors.gray2, BlendMode.srcIn), ), const SizedBox(width: 4), - Text(count, style: AppTypography.b2.copyWith(color: color)), + Text(count, style: AppTypography.b2.copyWith(color: appColors.gray2)), ], ); } @@ -240,6 +242,8 @@ class _PostImageSliderState extends State<_PostImageSlider> { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Column( children: [ SizedBox( @@ -273,15 +277,15 @@ class _PostImageSliderState extends State<_PostImageSlider> { loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Container( - color: AppColors.gray5, + color: appColors.gray5, child: const Center(child: CircularProgressIndicator()), ); }, errorBuilder: (context, error, stackTrace) { return Container( - color: AppColors.gray5, - child: const Center( - child: Icon(Icons.broken_image, color: AppColors.gray3), + color: appColors.gray5, + child: Center( + child: Icon(Icons.broken_image, color: appColors.gray3), ), ); }, diff --git a/lib/features/feed/widgets/post_popup_menu.dart b/lib/features/feed/widgets/post_popup_menu.dart index e2a9736..296c537 100644 --- a/lib/features/feed/widgets/post_popup_menu.dart +++ b/lib/features/feed/widgets/post_popup_menu.dart @@ -27,6 +27,8 @@ class PostPopupMenu extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + // 💡 Post 객체 내부 데이터 상태 확인용 로그 debugPrint('--- [Post Data Check] ---'); debugPrint('Post ID: ${post.id}'); @@ -60,16 +62,17 @@ class PostPopupMenu extends ConsumerWidget { constraints: const BoxConstraints(maxWidth: 206), offset: const Offset(0, 40), // 버튼 아래로 띄우기 shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.gray4), + side: BorderSide(width: 1, color: appColors.gray4), borderRadius: BorderRadius.circular(10), ), - color: Colors.white, + color: appColors.whiteToBlack, elevation: 4, // 그림자 효과 icon: SvgPicture.asset( 'assets/images/icons/dots_vert_icon.svg', width: 24, height: 24, + colorFilter: ColorFilter.mode(appColors.blackToWhite, BlendMode.srcIn), ), // 내 글이냐 아니냐에 따라 아이템 리스트만 교체 @@ -82,6 +85,7 @@ class PostPopupMenu extends ConsumerWidget { '수정하기', 'assets/images/icons/edit_icon.svg', 'edit', + appColors, ), _buildDivider(), _buildPopupItem( @@ -89,6 +93,7 @@ class PostPopupMenu extends ConsumerWidget { 'assets/images/icons/small_trash_icon.svg', 'delete', isDanger: true, + appColors, ), ]; } else { @@ -99,6 +104,7 @@ class PostPopupMenu extends ConsumerWidget { '챌린지 보기', 'assets/images/icons/eye.svg', 'view_challenge', + appColors, ), ]; } @@ -109,6 +115,7 @@ class PostPopupMenu extends ConsumerWidget { '챌린지 보기', 'assets/images/icons/eye.svg', 'view_challenge', + appColors, ), _buildDivider(), _buildPopupItem( @@ -116,21 +123,26 @@ class PostPopupMenu extends ConsumerWidget { 'assets/images/icons/complaint.svg', 'report', isDanger: true, + appColors, ), ]; } }, - onSelected: (value) => _handleMenuSelection(context, ref, value), + onSelected: (value) => + _handleMenuSelection(context, ref, value, appColors), ); } PopupMenuItem _buildPopupItem( String title, String iconPath, - String value, { + String value, + AppColorsExtension appColors, { bool isDanger = false, }) { - final Color textColor = isDanger ? AppColors.notification : AppColors.black; + final Color textColor = isDanger + ? appColors.notification + : appColors.blackToWhite; return PopupMenuItem( value: value, @@ -170,6 +182,7 @@ class PostPopupMenu extends ConsumerWidget { BuildContext context, WidgetRef ref, String value, + AppColorsExtension appColors, ) async { FocusManager.instance.primaryFocus?.unfocus(); @@ -195,7 +208,7 @@ class PostPopupMenu extends ConsumerWidget { title: '인증글 삭제', content: '작성하신 인증글을 삭제하시겠습니까?\n삭제된 인증글은 되돌릴 수 없습니다.', confirmText: '삭제', - confirmTextColor: AppColors.notification, + confirmTextColor: appColors.notification, cancelText: '취소', onConfirm: () async { try { diff --git a/lib/features/feed/widgets/recommended_challenge_card.dart b/lib/features/feed/widgets/recommended_challenge_card.dart index 08479c2..a3d2a35 100644 --- a/lib/features/feed/widgets/recommended_challenge_card.dart +++ b/lib/features/feed/widgets/recommended_challenge_card.dart @@ -25,6 +25,7 @@ class _RecommendedChallengeCardState extends State { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; final isExpanded = _expandedStates[_currentIndex] ?? false; final currentItem = widget.items[_currentIndex]; @@ -34,7 +35,7 @@ class _RecommendedChallengeCardState extends State { // ✅ 패딩 제거 → 이미지가 카드 전체 너비를 꽉 채움(풀블리드) clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: Colors.white, + color: appColors.whiteToBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -79,7 +80,7 @@ class _RecommendedChallengeCardState extends State { child: Text( currentItem.recommendReason, style: AppTypography.b2.copyWith( - color: AppColors.gray1, + color: appColors.gray1, ), ), ) @@ -97,7 +98,7 @@ class _RecommendedChallengeCardState extends State { isExpanded ? '추천 이유 닫기' : '추천 이유 보기', textAlign: TextAlign.right, style: AppTypography.b2.copyWith( - color: AppColors.gray1, + color: appColors.gray1, decoration: TextDecoration.underline, ), ), @@ -127,6 +128,8 @@ class _RecommendedChallengeTop extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -142,16 +145,16 @@ class _RecommendedChallengeTop extends StatelessWidget { loadingBuilder: (context, child, progress) { if (progress == null) return child; return Container( - color: AppColors.gray4, + color: appColors.gray4, child: const Center(child: CircularProgressIndicator()), ); }, errorBuilder: (context, error, stackTrace) => Container( - color: AppColors.gray4, + color: appColors.gray4, child: const Icon(Icons.image_not_supported_outlined), ), ) - : Container(color: AppColors.gray4), + : Container(color: appColors.gray4), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), @@ -162,7 +165,7 @@ class _RecommendedChallengeTop extends StatelessWidget { // ── 제목 ── Text( item.title, - style: AppTypography.b3.copyWith(color: AppColors.black), + style: AppTypography.b3.copyWith(color: appColors.blackToWhite), ), Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -183,15 +186,15 @@ class _RecommendedChallengeTop extends StatelessWidget { 'assets/images/icons/person_icon.svg', width: 16, height: 16, - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), Text( '${item.participantNumber}명', style: AppTypography.b2.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), ), ], @@ -199,7 +202,7 @@ class _RecommendedChallengeTop extends StatelessWidget { Text( '완료까지 D-${item.remainingDays}', style: AppTypography.b2.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), ), ], @@ -228,8 +231,8 @@ class _RecommendedChallengeTop extends StatelessWidget { child: SizedBox( child: SvgPicture.asset( 'assets/images/icons/thick_right_arrow_icon.svg', - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), @@ -243,11 +246,11 @@ class _RecommendedChallengeTop extends StatelessWidget { children: [ Text( '챌린지 설명', - style: AppTypography.b1.copyWith(color: AppColors.gray1), + style: AppTypography.b1.copyWith(color: appColors.gray1), ), Text( item.content, - style: AppTypography.b2.copyWith(color: AppColors.gray1), + style: AppTypography.b2.copyWith(color: appColors.gray1), ), ], ), diff --git a/lib/features/main/widgets/bottom_nav_bar.dart b/lib/features/main/widgets/bottom_nav_bar.dart index 1dea0b4..807a301 100644 --- a/lib/features/main/widgets/bottom_nav_bar.dart +++ b/lib/features/main/widgets/bottom_nav_bar.dart @@ -20,6 +20,7 @@ class MainBottomNavBar extends StatelessWidget { final appColors = Theme.of(context).extension()!; return BottomNavigationBar( + backgroundColor: appColors.whiteToBlack, type: BottomNavigationBarType.fixed, currentIndex: currentIndex, selectedItemColor: appColors.blackToWhite, diff --git a/lib/shared/screens/challenge_detail_screen.dart b/lib/shared/screens/challenge_detail_screen.dart index 3f7a193..7e1b717 100644 --- a/lib/shared/screens/challenge_detail_screen.dart +++ b/lib/shared/screens/challenge_detail_screen.dart @@ -39,24 +39,26 @@ class _ChallengeDetailScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 1. 상세 데이터 구독 final challengeAsync = ref.watch( challengeDetailProvider(challengeId: widget.challengeId), ); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, - surfaceTintColor: Colors.white, + surfaceTintColor: appColors.whiteToBlack, leading: IconButton( icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -64,18 +66,18 @@ class _ChallengeDetailScreenState extends ConsumerState { ), title: Text( widget.challengeTitle, - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), body: Column( children: [ - const Divider(height: 1, color: AppColors.gray4), + Divider(height: 1, color: appColors.gray4), Expanded( // 2. 상태별 화면 렌더링 child: challengeAsync.when( - loading: () => const Center( - child: CircularProgressIndicator(color: AppColors.primaryAble), + loading: () => Center( + child: CircularProgressIndicator(color: appColors.primaryAble), ), error: (err, stack) { // 터미널에 에러의 진짜 이유를 출력합니다. @@ -86,7 +88,9 @@ class _ChallengeDetailScreenState extends ConsumerState { child: Text( '에러 내용: $err', // 화면에도 에러를 표시해서 확인 textAlign: TextAlign.center, - style: AppTypography.b2.copyWith(color: Colors.red), + style: AppTypography.b2.copyWith( + color: appColors.notification, + ), ), ); }, From 7b599ebf6549da747d3e472031aba04a78978a40 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Tue, 4 Aug 2026 03:48:28 +0900 Subject: [PATCH 11/18] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EB=8B=A4?= =?UTF-8?q?=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge_invite_detail_screen.dart | 21 ++++++---- .../screens/notification_main_screen.dart | 13 +++--- .../views/all_notifications_view.dart | 21 +++++----- .../views/challenge_invites_view.dart | 14 +++++-- .../widgets/challenge_invite_card.dart | 41 +++++++++++-------- .../widgets/notification_date_header.dart | 4 +- .../widgets/notification_list_tile.dart | 16 ++++---- 7 files changed, 76 insertions(+), 54 deletions(-) diff --git a/lib/features/notification/screens/challenge_invite_detail_screen.dart b/lib/features/notification/screens/challenge_invite_detail_screen.dart index 9b3f686..6cdb6e7 100644 --- a/lib/features/notification/screens/challenge_invite_detail_screen.dart +++ b/lib/features/notification/screens/challenge_invite_detail_screen.dart @@ -30,15 +30,16 @@ class ChallengeInviteDetailScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; // 💡 기존 InformationView에서 쓰던 Provider를 똑같이 사용하여 상세 데이터를 불러옵니다. final challengeAsync = ref.watch( challengeDetailProvider(challengeId: challengeId), ); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, scrolledUnderElevation: 0, leading: IconButton( @@ -46,14 +47,18 @@ class ChallengeInviteDetailScreen extends ConsumerWidget { icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', // 기존 앱바 아이콘 통일 width: 24, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), ), title: const Text('챌린지 상세정보', style: AppTypography.h3), centerTitle: true, ), body: challengeAsync.when( - loading: () => const Center( - child: CircularProgressIndicator(color: AppColors.primaryAble), + loading: () => Center( + child: CircularProgressIndicator(color: appColors.primaryAble), ), error: (error, stack) { // 에러 메시지에 404 또는 NOT_FOUND가 포함되어 있는지 확인합니다. @@ -68,7 +73,7 @@ class ChallengeInviteDetailScreen extends ConsumerWidget { ? '이미 삭제되었거나\n존재하지 않는 챌린지입니다.' : '데이터를 불러오지 못했습니다.\n잠시 후 다시 시도해주세요.', textAlign: TextAlign.center, - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), ); }, @@ -84,7 +89,7 @@ class ChallengeInviteDetailScreen extends ConsumerWidget { const SizedBox(width: 8), Text( '$inviterName님의 초대', - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), ), ], ), @@ -107,8 +112,8 @@ class ChallengeInviteDetailScreen extends ConsumerWidget { // 데이터 로드 성공 시에만 버튼 표시 data: (challenge) => BottomActionButton( text: '초대 수락하기', - backgroundColor: AppColors.primaryAble, - textColor: Colors.white, + backgroundColor: appColors.primaryAble, + textColor: appColors.whiteToBlack, onPressed: () async { // 💡 1. 다이얼로그에 넘겨줄 챌린지 제목을 가져옵니다. final challenge = ref diff --git a/lib/features/notification/screens/notification_main_screen.dart b/lib/features/notification/screens/notification_main_screen.dart index cbd61ed..489df70 100644 --- a/lib/features/notification/screens/notification_main_screen.dart +++ b/lib/features/notification/screens/notification_main_screen.dart @@ -33,20 +33,23 @@ class _NotificationMainScreenState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return DefaultTabController( length: 2, // 탭 개수 child: Scaffold( + backgroundColor: appColors.whiteToBlack, appBar: AppBar( leading: IconButton( - icon: const Icon(Icons.arrow_back, color: AppColors.black), + icon: Icon(Icons.arrow_back, color: appColors.blackToWhite), onPressed: () => Navigator.of(context).pop(), ), title: const Text('알림', style: AppTypography.h3), - bottom: const TabBar( - indicatorColor: AppColors.primaryAble, + bottom: TabBar( + indicatorColor: appColors.primaryAble, indicatorSize: TabBarIndicatorSize.tab, - labelColor: AppColors.primaryAble, - unselectedLabelColor: AppColors.gray2, + labelColor: appColors.primaryAble, + unselectedLabelColor: appColors.gray2, labelStyle: AppTypography.b1, tabs: [ Tab(text: '모두'), diff --git a/lib/features/notification/views/all_notifications_view.dart b/lib/features/notification/views/all_notifications_view.dart index ffae915..8ccfc7a 100644 --- a/lib/features/notification/views/all_notifications_view.dart +++ b/lib/features/notification/views/all_notifications_view.dart @@ -66,16 +66,19 @@ class _AllNotificationsViewState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; final state = ref.watch(notificationProvider); if (state.isLoading) { - return const Center( - child: CircularProgressIndicator(color: AppColors.primaryAble), + return Center( + child: CircularProgressIndicator(color: appColors.primaryAble), ); } if (state.notifications.isEmpty) { - return const Center(child: Text('새로운 알림이 없습니다.')); + return Center( + child: Text('새로운 알림이 없습니다.', style: TextStyle(color: appColors.gray2)), + ); } // 날짜별로 그룹화 @@ -112,11 +115,7 @@ class _AllNotificationsViewState extends ConsumerState { listItems.add( InkWell( onTap: () { - // 1. 클릭 시 읽음 처리 (그냥 하얀색으로 바뀜) - // TODO API 구현된 게 없으므로 임시 설정 - // ref.read(notificationProvider.notifier).markAsRead(noti); - - // 2. 알림 타입(type)별 페이지 이동 분기 + // 알림 타입(type)별 페이지 이동 분기 if (noti.type == 'FRIEND_REQUEST') { // 친구 요청 알림 -> 소셜(친구추가) 페이지로 이동 Navigator.push( @@ -196,17 +195,17 @@ class _AllNotificationsViewState extends ConsumerState { return RefreshIndicator( onRefresh: () => ref.read(notificationProvider.notifier).refresh(), - color: AppColors.primaryAble, + color: appColors.primaryAble, child: ListView.builder( controller: _scrollController, itemCount: listItems.length + (state.isFetchingMore ? 1 : 0), itemBuilder: (context, index) { // 마지막 아이템이고 추가 로딩 중이면 로딩 인디케이터 표시 if (index == listItems.length) { - return const Padding( + return Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Center( - child: CircularProgressIndicator(color: AppColors.primaryAble), + child: CircularProgressIndicator(color: appColors.primaryAble), ), ); } diff --git a/lib/features/notification/views/challenge_invites_view.dart b/lib/features/notification/views/challenge_invites_view.dart index 8014493..145c7be 100644 --- a/lib/features/notification/views/challenge_invites_view.dart +++ b/lib/features/notification/views/challenge_invites_view.dart @@ -13,23 +13,29 @@ class ChallengeInvitesView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; // 데이터 감지 final state = ref.watch(challengeInviteProvider); final notifier = ref.read(challengeInviteProvider.notifier); if (state.isLoading) { - return const Center( - child: CircularProgressIndicator(color: AppColors.primaryAble), + return Center( + child: CircularProgressIndicator(color: appColors.primaryAble), ); } if (state.invites.isEmpty) { - return const Center(child: Text('받은 챌린지 초대가 없습니다.')); + return Center( + child: Text( + '받은 챌린지 초대가 없습니다.', + style: TextStyle(color: appColors.gray2), + ), + ); } return RefreshIndicator( onRefresh: () => notifier.fetchInvites(), - color: AppColors.primaryAble, + color: appColors.primaryAble, child: ListView.builder( padding: const EdgeInsets.only(top: 10, bottom: 40), itemCount: state.invites.length, diff --git a/lib/features/notification/widgets/challenge_invite_card.dart b/lib/features/notification/widgets/challenge_invite_card.dart index 0494432..ba16f50 100644 --- a/lib/features/notification/widgets/challenge_invite_card.dart +++ b/lib/features/notification/widgets/challenge_invite_card.dart @@ -21,6 +21,7 @@ class ChallengeInviteCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; final challengeInfo = inviteChallenge.challengeInfo; final inviterUser = inviteChallenge.inviterUser; @@ -29,7 +30,7 @@ class ChallengeInviteCard extends StatelessWidget { // 디자인 가이드에 맞춘 패딩 적용 padding: const EdgeInsets.only(top: 10, left: 16, right: 4, bottom: 10), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( @@ -56,12 +57,12 @@ class ChallengeInviteCard extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ - _buildIconBox(), + _buildIconBox(appColors), // 프로필 이미지 또는 기본 아이콘 const SizedBox(width: 6), Text( '${inviterUser.nickname}님의 초대', style: AppTypography.b2.copyWith( - color: AppColors.gray1, // 디자인 코드 참조 + color: appColors.gray1, // 디자인 코드 참조 ), ), ], @@ -69,7 +70,9 @@ class ChallengeInviteCard extends StatelessWidget { ), Text( challengeInfo.base.title, - style: AppTypography.b3.copyWith(color: AppColors.black), + style: AppTypography.b3.copyWith( + color: appColors.blackToWhite, + ), overflow: TextOverflow.ellipsis, ), @@ -81,15 +84,15 @@ class ChallengeInviteCard extends StatelessWidget { 'assets/images/icons/person_icon.svg', width: 16, height: 16, - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), Text( '${challengeInfo.participantCount.toString()}명', style: AppTypography.b2.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), ), const SizedBox(width: 16), @@ -98,7 +101,7 @@ class ChallengeInviteCard extends StatelessWidget { ? '오늘 완료' : '완료까지 D-${challengeInfo.dDay}', style: AppTypography.b2.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), ), ], @@ -115,13 +118,13 @@ class ChallengeInviteCard extends StatelessWidget { vertical: 4, ), // 내부 여백 decoration: BoxDecoration( - color: AppColors.selected, + color: appColors.selected, borderRadius: BorderRadius.circular(16), // 모서리 둥글기 ), child: Text( tag.tag, style: AppTypography.b2.copyWith( - color: AppColors.primaryAble, + color: appColors.primaryAble, ), ), ); @@ -152,8 +155,8 @@ class ChallengeInviteCard extends StatelessWidget { }, icon: SvgPicture.asset( 'assets/images/icons/thick_right_arrow_icon.svg', - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), @@ -177,13 +180,13 @@ class ChallengeInviteCard extends StatelessWidget { height: 48, alignment: Alignment.center, decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(10), ), child: Text( '거절', style: AppTypography.b1.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), ), ), @@ -199,12 +202,14 @@ class ChallengeInviteCard extends StatelessWidget { height: 48, alignment: Alignment.center, decoration: BoxDecoration( - color: AppColors.primaryAble, + color: appColors.primaryAble, borderRadius: BorderRadius.circular(10), ), child: Text( '수락', - style: AppTypography.b1.copyWith(color: Colors.white), + style: AppTypography.b1.copyWith( + color: appColors.whiteToBlack, + ), ), ), ), @@ -218,12 +223,12 @@ class ChallengeInviteCard extends StatelessWidget { } // 아이콘 또는 프로필 이미지 렌더링 영역 - Widget _buildIconBox() { + Widget _buildIconBox(AppColorsExtension appColors) { return Container( width: 24, height: 24, decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, shape: BoxShape.circle, image: inviteChallenge.inviterUser.profileUrl != null && diff --git a/lib/features/notification/widgets/notification_date_header.dart b/lib/features/notification/widgets/notification_date_header.dart index 8d5afd3..677598f 100644 --- a/lib/features/notification/widgets/notification_date_header.dart +++ b/lib/features/notification/widgets/notification_date_header.dart @@ -11,11 +11,13 @@ class NotificationDateHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Padding( padding: const EdgeInsets.only(top: 16, left: 20, right: 20, bottom: 4), child: Text( dateText, - style: AppTypography.h3.copyWith(color: AppColors.gray1), + style: AppTypography.h3.copyWith(color: appColors.gray1), ), ); } diff --git a/lib/features/notification/widgets/notification_list_tile.dart b/lib/features/notification/widgets/notification_list_tile.dart index ad802b4..0771a49 100644 --- a/lib/features/notification/widgets/notification_list_tile.dart +++ b/lib/features/notification/widgets/notification_list_tile.dart @@ -23,21 +23,23 @@ class NotificationListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), // isRead 값에 따라 배경색 분기 처리 - color: isRead ? Colors.white : AppColors.selected, + color: isRead ? appColors.whiteToBlack : appColors.selected, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - _buildIconBox(), + _buildIconBox(appColors), // 아이콘 또는 프로필 이미지 렌더링 const SizedBox(width: 16), // 긴 텍스트가 화면을 넘어갈 때 줄바꿈되도록 Expanded 사용 Expanded( child: Text( message, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), ), ], @@ -46,14 +48,14 @@ class NotificationListTile extends StatelessWidget { } // 아이콘 또는 프로필 이미지 렌더링 영역 - Widget _buildIconBox() { + Widget _buildIconBox(AppColorsExtension appColors) { // 1. 프로필 타입일 경우 (친구 페이지 로직 적용) if (iconType == NotiIconType.profile) { return Container( width: 40, height: 40, decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, shape: BoxShape.circle, image: profileImageUrl != null && profileImageUrl!.startsWith('http') ? DecorationImage( @@ -79,7 +81,7 @@ class NotificationListTile extends StatelessWidget { } // 2. 시스템 알림 (성공, 실패, 일반) 로직 - Color bgColor = AppColors.gray5; + Color bgColor = appColors.gray5; Widget iconWidget = const Icon( Icons.eco, color: Colors.white, @@ -90,7 +92,7 @@ class NotificationListTile extends StatelessWidget { bgColor = AppColors.blue; iconWidget = const Icon(Icons.star, color: Colors.white, size: 20); } else if (iconType == NotiIconType.fail) { - bgColor = AppColors.notification; + bgColor = appColors.notification; iconWidget = const Icon(Icons.close, color: Colors.white, size: 20); } From aca8f7133c859dd67464499d39db890e82ddacb1 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Tue, 4 Aug 2026 03:55:44 +0900 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20=EC=8B=A0=EA=B3=A0=20ui=20?= =?UTF-8?q?=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/screens/report_screen.dart | 12 ++++++------ .../report/screens/report_success_screen.dart | 14 +++++++++----- .../report/widgets/report_reason_tile.dart | 19 +++++++++++-------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/features/report/screens/report_screen.dart b/lib/features/report/screens/report_screen.dart index db46f99..5e9b454 100644 --- a/lib/features/report/screens/report_screen.dart +++ b/lib/features/report/screens/report_screen.dart @@ -27,7 +27,6 @@ class _ReportScreenState extends ConsumerState { int? _selectedReasonIndex; final TextEditingController _otherReasonController = TextEditingController(); - // 백엔드 API 명세에 맞게 keys 값을 꼭 수정해주세요! final List> _reasons = [ {'key': 'SPAM', 'title': '영리목적/홍보성', 'desc': '상업적 광고, 도배성 게시글, 링크 유도 등'}, { @@ -64,7 +63,7 @@ class _ReportScreenState extends ConsumerState { ? _otherReasonController.text : ''; - // 바뀐 부분: 모델 객체 생성 없이 파라미터로 바로 전달 + // 모델 객체 생성 없이 파라미터로 바로 전달 final success = await ref .read(reportControllerProvider.notifier) .submitReport( @@ -85,13 +84,14 @@ class _ReportScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; // 항목이 하나라도 선택되었는지 여부로 하단 버튼 활성화 상태 결정 final bool isButtonActive = _selectedReasonIndex != null; return Scaffold( appBar: AppBar( leading: IconButton( - icon: const Icon(Icons.arrow_back, color: AppColors.black), + icon: Icon(Icons.arrow_back, color: appColors.blackToWhite), onPressed: () => Navigator.of(context).pop(), ), title: const Text('신고하기', style: AppTypography.h3), @@ -104,7 +104,7 @@ class _ReportScreenState extends ConsumerState { child: Text( '더 깨끗한 \'해냄\'을 위해 부적절한 콘텐츠를 알려주세요.\n신고하신 내용은 운영 정책에 따라 검토 후 조치됩니다.', textAlign: TextAlign.center, - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ), @@ -140,8 +140,8 @@ class _ReportScreenState extends ConsumerState { bottomNavigationBar: BottomActionButton( text: '신고하기', backgroundColor: isButtonActive - ? AppColors.primaryAble - : AppColors.disable, + ? appColors.primaryAble + : appColors.disable, onPressed: isButtonActive ? _submitReport : () {}, ), ); diff --git a/lib/features/report/screens/report_success_screen.dart b/lib/features/report/screens/report_success_screen.dart index 2778e33..104319d 100644 --- a/lib/features/report/screens/report_success_screen.dart +++ b/lib/features/report/screens/report_success_screen.dart @@ -10,6 +10,8 @@ class ReportSuccessScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Scaffold( body: SafeArea( child: Column( @@ -24,8 +26,8 @@ class ReportSuccessScreen extends StatelessWidget { Container( width: 84, height: 84, - decoration: const BoxDecoration( - color: AppColors.primaryAble, + decoration: BoxDecoration( + color: appColors.primaryAble, shape: BoxShape.circle, ), child: const Icon( @@ -37,10 +39,12 @@ class ReportSuccessScreen extends StatelessWidget { const SizedBox(height: 24), // 완료 안내 제목 - const Text( + Text( '신고가 접수되었습니다.', textAlign: TextAlign.center, - style: AppTypography.h1, // 24px, Bold + style: AppTypography.h1.copyWith( + color: appColors.blackToWhite, + ), // 24px, Bold ), const SizedBox(height: 12), @@ -49,7 +53,7 @@ class ReportSuccessScreen extends StatelessWidget { '검토 결과에 따라 적절한 조치가 취해질 예정이며,\n깨끗한 \'해냄\'을 위해 항상 노력하겠습니다.', textAlign: TextAlign.center, style: AppTypography.b3.copyWith( - color: AppColors.gray1, + color: appColors.gray1, ), // 16px, SemiBold ), ], diff --git a/lib/features/report/widgets/report_reason_tile.dart b/lib/features/report/widgets/report_reason_tile.dart index 2a7e062..a772373 100644 --- a/lib/features/report/widgets/report_reason_tile.dart +++ b/lib/features/report/widgets/report_reason_tile.dart @@ -24,13 +24,14 @@ class ReportReasonTile extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - // color: isSelected ? AppColors.selected : AppColors.gray5, - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -47,14 +48,14 @@ class ReportReasonTile extends StatelessWidget { Text( title, style: AppTypography.b3.copyWith( - color: AppColors.gray1, + color: appColors.gray1, ), ), const SizedBox(height: 4), Text( subtitle, style: AppTypography.b2.copyWith( - color: AppColors.gray3, + color: appColors.gray3, ), ), ], @@ -66,7 +67,7 @@ class ReportReasonTile extends StatelessWidget { isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, - color: isSelected ? AppColors.primaryAble : AppColors.gray4, + color: isSelected ? appColors.primaryAble : appColors.gray4, size: 24, ), ], @@ -83,16 +84,18 @@ class ReportReasonTile extends StatelessWidget { decoration: BoxDecoration( // color: Colors.white, borderRadius: BorderRadius.circular(10), - border: Border.all(color: AppColors.gray4), + border: Border.all(color: appColors.gray4), ), child: TextField( controller: textController, maxLines: 3, - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith( + color: appColors.blackToWhite, + ), decoration: InputDecoration( hintText: '예: 챌린지 주제와 상관없는 사진을 반복적으로 올립니다.', hintStyle: AppTypography.b2.copyWith( - color: AppColors.gray3, + color: appColors.gray3, ), border: InputBorder.none, isDense: true, From bcfbf1306193e79efc6a954004af3ebfa24f25d6 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Tue, 4 Aug 2026 04:11:27 +0900 Subject: [PATCH 13/18] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20ui=20?= =?UTF-8?q?=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../social/screens/friend_add_screen.dart | 14 ++++---- .../social/screens/friend_edit_screen.dart | 18 +++++----- .../social/screens/social_main_screen.dart | 36 ++++++++++++++----- .../social/views/received_request_view.dart | 5 +-- .../social/views/sent_request_view.dart | 5 +-- .../social/widgets/delete_confirm_dialog.dart | 11 +++--- .../social/widgets/friend_edit_tile.dart | 5 +-- .../social/widgets/received_request_card.dart | 22 ++++++------ .../social/widgets/sent_request_card.dart | 12 ++++--- .../social/widgets/user_search_tile.dart | 10 +++--- 10 files changed, 85 insertions(+), 53 deletions(-) diff --git a/lib/features/social/screens/friend_add_screen.dart b/lib/features/social/screens/friend_add_screen.dart index 7845ab3..c567686 100644 --- a/lib/features/social/screens/friend_add_screen.dart +++ b/lib/features/social/screens/friend_add_screen.dart @@ -14,16 +14,18 @@ class FriendAddScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return DefaultTabController( length: 3, initialIndex: initialTabIndex, // 초기 탭 인덱스 설정 child: Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: AppColors.black), + icon: Icon(Icons.arrow_back, color: appColors.blackToWhite), onPressed: () => Navigator.pop(context), ), centerTitle: true, @@ -31,9 +33,9 @@ class FriendAddScreen extends StatelessWidget { bottom: PreferredSize( preferredSize: const Size.fromHeight(48), child: TabBar( - indicatorColor: AppColors.primaryAble, - labelColor: AppColors.primaryAble, - unselectedLabelColor: AppColors.gray2, + indicatorColor: appColors.primaryAble, + labelColor: appColors.primaryAble, + unselectedLabelColor: appColors.gray2, labelStyle: AppTypography.b1.copyWith( fontWeight: FontWeight.w500, ), diff --git a/lib/features/social/screens/friend_edit_screen.dart b/lib/features/social/screens/friend_edit_screen.dart index 80204b7..1dabee4 100644 --- a/lib/features/social/screens/friend_edit_screen.dart +++ b/lib/features/social/screens/friend_edit_screen.dart @@ -68,14 +68,14 @@ class _FriendEditScreenState extends ConsumerState { } // 삭제 확인 다이얼로그 표시 - void showDeleteDialog(User user) { + void showDeleteDialog(User user, AppColorsExtension appColors) { showDialog( context: context, builder: (dialogContext) => SelectDialog( title: '친구 삭제', content: '${user.nickname} 님을 삭제하시겠습니까?', confirmText: '삭제하기', - confirmTextColor: AppColors.notification, + confirmTextColor: appColors.notification, cancelText: '취소', // [취소] 클릭 시 단순히 다이얼로그 닫기 @@ -122,13 +122,15 @@ class _FriendEditScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: AppColors.black), + icon: Icon(Icons.arrow_back, color: appColors.blackToWhite), onPressed: () => Navigator.pop(context, totalList), ), centerTitle: true, @@ -137,7 +139,7 @@ class _FriendEditScreenState extends ConsumerState { body: Column( children: [ _buildSearchHeader(), - Expanded(child: _buildEditListView()), + Expanded(child: _buildEditListView(appColors)), ], ), ); @@ -157,7 +159,7 @@ class _FriendEditScreenState extends ConsumerState { } // 편집 리스트 빌드 - Widget _buildEditListView() { + Widget _buildEditListView(AppColorsExtension appColors) { if (filteredList.isEmpty) { return const Center(child: Text('검색 결과가 없습니다.', style: AppTypography.b2)); } @@ -168,7 +170,7 @@ class _FriendEditScreenState extends ConsumerState { final user = filteredList[index]; return FriendEditTile( user: user, - onDeleteTap: () => showDeleteDialog(user), + onDeleteTap: () => showDeleteDialog(user, appColors), ); }, ); diff --git a/lib/features/social/screens/social_main_screen.dart b/lib/features/social/screens/social_main_screen.dart index 1c69b8d..051291b 100644 --- a/lib/features/social/screens/social_main_screen.dart +++ b/lib/features/social/screens/social_main_screen.dart @@ -32,12 +32,13 @@ class _SocialMainScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; // 새로운 Provider 구독 final friendListAsync = ref.watch(friendListProvider); return Scaffold( - backgroundColor: Colors.white, - appBar: _buildAppBar(context), + backgroundColor: appColors.whiteToBlack, + appBar: _buildAppBar(context, appColors), body: Column( children: [ _buildSearchBar(), @@ -64,10 +65,14 @@ class _SocialMainScreenState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildListHeader(filteredFriends.length, totalFriends), + _buildListHeader( + filteredFriends.length, + totalFriends, + appColors, + ), Expanded( child: RefreshIndicator( - color: AppColors.primaryAble, + color: appColors.primaryAble, onRefresh: () async { // 스크롤을 당기면 데이터를 강제로 다시 불러옵니다. return await ref.refresh(friendListProvider.future); @@ -92,15 +97,24 @@ class _SocialMainScreenState extends ConsumerState { } // AppBar 위젯을 별도의 메서드로 분리 - PreferredSizeWidget _buildAppBar(BuildContext context) { + PreferredSizeWidget _buildAppBar( + BuildContext context, + AppColorsExtension appColors, + ) { return AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, centerTitle: true, title: const Text('친구', style: AppTypography.h3), actions: [ IconButton( - icon: SvgPicture.asset('assets/images/icons/friend_add_icon.svg'), + icon: SvgPicture.asset( + 'assets/images/icons/friend_add_icon.svg', + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), + ), onPressed: () async { // await를 사용하여 FriendAddScreen이 닫힐 때까지 기다림 await Navigator.push( @@ -131,7 +145,11 @@ class _SocialMainScreenState extends ConsumerState { } // 친구 목록 헤더 위젯 - Widget _buildListHeader(int count, List totalFriends) { + Widget _buildListHeader( + int count, + List totalFriends, + AppColorsExtension appColors, + ) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: Row( @@ -156,7 +174,7 @@ class _SocialMainScreenState extends ConsumerState { }, child: Text( '편집', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ), ], diff --git a/lib/features/social/views/received_request_view.dart b/lib/features/social/views/received_request_view.dart index 9a13cf2..484ad19 100644 --- a/lib/features/social/views/received_request_view.dart +++ b/lib/features/social/views/received_request_view.dart @@ -12,11 +12,12 @@ class ReceivedRequestView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; // 1. 상태 구독 final receivedRequestsAsync = ref.watch(receivedRequestsProvider); return Container( - color: const Color(0x7FDFE1DC), + color: appColors.gray5, child: receivedRequestsAsync.when( data: (requests) { if (requests.isEmpty) { @@ -25,7 +26,7 @@ class ReceivedRequestView extends ConsumerWidget { ); } return RefreshIndicator( - color: AppColors.primaryAble, + color: appColors.primaryAble, onRefresh: () async { // 새로고침 시 Provider의 Future를 다시 호출하여 최신 데이터 가져오기 return await ref.refresh(receivedRequestsProvider.future); diff --git a/lib/features/social/views/sent_request_view.dart b/lib/features/social/views/sent_request_view.dart index b06c751..d287845 100644 --- a/lib/features/social/views/sent_request_view.dart +++ b/lib/features/social/views/sent_request_view.dart @@ -12,10 +12,11 @@ class SentRequestView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; final sentRequestsAsync = ref.watch(sentRequestsProvider); return Container( - color: const Color(0x7FDFE1DC), + color: appColors.gray5, child: sentRequestsAsync.when( data: (requests) { if (requests.isEmpty) { @@ -24,7 +25,7 @@ class SentRequestView extends ConsumerWidget { ); } return RefreshIndicator( - color: AppColors.primaryAble, + color: appColors.primaryAble, onRefresh: () async { // 새로고침 시 Provider의 Future를 다시 호출하여 최신 데이터 가져오기 return await ref.refresh(sentRequestsProvider.future); diff --git a/lib/features/social/widgets/delete_confirm_dialog.dart b/lib/features/social/widgets/delete_confirm_dialog.dart index acf282e..f9b7b6a 100644 --- a/lib/features/social/widgets/delete_confirm_dialog.dart +++ b/lib/features/social/widgets/delete_confirm_dialog.dart @@ -16,9 +16,10 @@ class DeleteConfirmDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, child: Padding( padding: const EdgeInsets.all(20), child: Column( @@ -28,7 +29,7 @@ class DeleteConfirmDialog extends StatelessWidget { const SizedBox(height: 12), Text( '$userNickname 님을 삭제하시겠습니까?', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), textAlign: TextAlign.center, ), const SizedBox(height: 24), @@ -40,7 +41,7 @@ class DeleteConfirmDialog extends StatelessWidget { child: Container( height: 48, decoration: BoxDecoration( - color: const Color(0x7FDFE1DC), + color: appColors.gray5, borderRadius: BorderRadius.circular(10), ), child: const Center( @@ -56,14 +57,14 @@ class DeleteConfirmDialog extends StatelessWidget { child: Container( height: 48, decoration: BoxDecoration( - color: const Color(0x7FDFE1DC), + color: appColors.gray5, borderRadius: BorderRadius.circular(10), ), child: Center( child: Text( '삭제하기', style: AppTypography.b1.copyWith( - color: AppColors.notification, + color: appColors.notification, fontWeight: FontWeight.w600, ), ), diff --git a/lib/features/social/widgets/friend_edit_tile.dart b/lib/features/social/widgets/friend_edit_tile.dart index b4996a3..b2543c4 100644 --- a/lib/features/social/widgets/friend_edit_tile.dart +++ b/lib/features/social/widgets/friend_edit_tile.dart @@ -20,6 +20,7 @@ class FriendEditTile extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; return UserListTile( user: user, trailing: IconButton( @@ -27,8 +28,8 @@ class FriendEditTile extends StatelessWidget { icon: SvgPicture.asset( 'assets/images/icons/big_trash_icon.svg', width: 24, - colorFilter: const ColorFilter.mode( - AppColors.notification, + colorFilter: ColorFilter.mode( + appColors.notification, BlendMode.srcIn, ), ), diff --git a/lib/features/social/widgets/received_request_card.dart b/lib/features/social/widgets/received_request_card.dart index 0152278..a49be81 100644 --- a/lib/features/social/widgets/received_request_card.dart +++ b/lib/features/social/widgets/received_request_card.dart @@ -23,11 +23,13 @@ class ReceivedRequestCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -41,13 +43,13 @@ class ReceivedRequestCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(request.user.nickname, style: AppTypography.h3), - Text( - "함께 아는 친구 0명", // 추후 데이터 연동 필요 - style: AppTypography.c1.copyWith(color: AppColors.gray2), - ), + // Text( + // "함께 아는 친구 0명", // 추후 데이터 연동 필요 + // style: AppTypography.c1.copyWith(color: appColors.gray2), + // ), Text( DateFormat('yyyy년 MM월 dd일').format(request.requestDate), - style: AppTypography.c2.copyWith(color: AppColors.gray3), + style: AppTypography.b2.copyWith(color: appColors.gray3), ), ], ), @@ -60,8 +62,8 @@ class ReceivedRequestCard extends StatelessWidget { Expanded( child: _buildActionButton( label: '거절', - bg: const Color(0x7FDFE1DC), - text: AppColors.gray2, + bg: appColors.gray5, + text: appColors.gray2, onTap: onReject, ), ), @@ -69,8 +71,8 @@ class ReceivedRequestCard extends StatelessWidget { Expanded( child: _buildActionButton( label: '수락', - bg: AppColors.primaryAble, - text: Colors.white, + bg: appColors.primaryAble, + text: appColors.whiteToBlack, onTap: onAccept, ), ), diff --git a/lib/features/social/widgets/sent_request_card.dart b/lib/features/social/widgets/sent_request_card.dart index d160f18..0d9cff4 100644 --- a/lib/features/social/widgets/sent_request_card.dart +++ b/lib/features/social/widgets/sent_request_card.dart @@ -21,11 +21,13 @@ class SentRequestCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -47,8 +49,8 @@ class SentRequestCard extends StatelessWidget { Text(request.user.nickname, style: AppTypography.h3), Text( DateFormat('yyyy년 MM월 dd일').format(request.requestDate), - style: AppTypography.c2.copyWith( - color: AppColors.gray3, + style: AppTypography.b2.copyWith( + color: appColors.gray3, ), ), ], @@ -64,14 +66,14 @@ class SentRequestCard extends StatelessWidget { width: double.infinity, height: 48, decoration: BoxDecoration( - color: const Color(0x7FDFE1DC), + color: appColors.gray5, borderRadius: BorderRadius.circular(10), ), alignment: Alignment.center, child: Text( '요청 취소', style: AppTypography.b1.copyWith( - color: AppColors.gray2, + color: appColors.gray2, fontWeight: FontWeight.w500, ), ), diff --git a/lib/features/social/widgets/user_search_tile.dart b/lib/features/social/widgets/user_search_tile.dart index 6f04148..f4d115e 100644 --- a/lib/features/social/widgets/user_search_tile.dart +++ b/lib/features/social/widgets/user_search_tile.dart @@ -20,14 +20,16 @@ class UserSearchTile extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return UserListTile( user: searchCard.user, padding: const EdgeInsets.symmetric(vertical: 12), // 여기만 12 - trailing: _buildRequestButton(), + trailing: _buildRequestButton(appColors), ); } - Widget _buildRequestButton() { + Widget _buildRequestButton(AppColorsExtension appColors) { // 1. 이미 친구인 경우 버튼을 숨김 if (searchCard.state == FriendState.friend) { return const SizedBox.shrink(); @@ -41,13 +43,13 @@ class UserSearchTile extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( - color: isRequested ? AppColors.disable : AppColors.primaryAble, + color: isRequested ? appColors.disable : appColors.primaryAble, borderRadius: BorderRadius.circular(8), ), child: Text( isRequested ? '신청됨' : '친구 신청', style: AppTypography.c1.copyWith( - color: isRequested ? AppColors.gray2 : Colors.white, + color: isRequested ? appColors.gray2 : appColors.whiteToBlack, fontWeight: FontWeight.w500, ), ), From 77df96fea3fe4eb5083353073507059b0109980f Mon Sep 17 00:00:00 2001 From: Seungvin Date: Thu, 6 Aug 2026 17:52:07 +0900 Subject: [PATCH 14/18] =?UTF-8?q?feat:=20=ED=86=B5=EA=B3=84=20ui=20?= =?UTF-8?q?=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/screens/statistics_screen.dart | 21 ++++---- .../statistics/widgets/ai_coaching_card.dart | 12 +++-- .../widgets/ai_coaching_section.dart | 6 ++- .../statistics/widgets/haenaem_grass.dart | 18 +++---- .../widgets/line_graph/line_chart_grid.dart | 32 ++++++++----- .../widgets/line_graph/line_graph.dart | 48 ++++++++++++++----- .../statistics/widgets/pie_graph.dart | 40 ++++++++++------ .../statistics/widgets/statistics_card.dart | 8 +++- 8 files changed, 123 insertions(+), 62 deletions(-) diff --git a/lib/features/statistics/screens/statistics_screen.dart b/lib/features/statistics/screens/statistics_screen.dart index 0f572e0..b1e4fd9 100644 --- a/lib/features/statistics/screens/statistics_screen.dart +++ b/lib/features/statistics/screens/statistics_screen.dart @@ -18,19 +18,20 @@ class StatisticsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; final activityAsync = ref.watch(activityRepositoryProvider); final distributionAsync = ref.watch(distributionRepositoryProvider); final monthlyWeeklyAsync = ref.watch(monthlyWeeklyRepositoryProvider); return Scaffold( - backgroundColor: AppColors.gray5, + backgroundColor: appColors.gray5, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, scrolledUnderElevation: 0, title: Text( '통계', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), @@ -53,7 +54,7 @@ class StatisticsScreen extends ConsumerWidget { Text( '데이터를 불러오지 못했어요', style: AppTypography.b2.copyWith( - color: AppColors.gray4, + color: appColors.gray4, ), ), const SizedBox(height: 8), @@ -73,7 +74,7 @@ class StatisticsScreen extends ConsumerWidget { activity: data.activity, ), ), - const SizedBox(height: 20), + const SizedBox(height: 10), // 나의 해냄 분포 distributionAsync.when( loading: () => const SizedBox( @@ -89,7 +90,7 @@ class StatisticsScreen extends ConsumerWidget { Text( '데이터를 불러오지 못했어요', style: AppTypography.b2.copyWith( - color: AppColors.gray4, + color: appColors.gray4, ), ), const SizedBox(height: 8), @@ -110,7 +111,7 @@ class StatisticsScreen extends ConsumerWidget { totalCount: data.totalCount, ), ), - const SizedBox(height: 20), + const SizedBox(height: 10), // 나의 해냄 추이 monthlyWeeklyAsync.when( loading: () => const SizedBox( @@ -126,7 +127,7 @@ class StatisticsScreen extends ConsumerWidget { Text( '데이터를 불러오지 못했어요', style: AppTypography.b2.copyWith( - color: AppColors.gray4, + color: appColors.gray4, ), ), const SizedBox(height: 8), @@ -143,10 +144,10 @@ class StatisticsScreen extends ConsumerWidget { data: (data) => LineGraph(monthlyData: data.monthly, weeklyData: data.weekly), ), - const SizedBox(height: 20), + const SizedBox(height: 10), // AI 코칭 카드 (나의 해냄 포인트 / 이것만 해내면 완벽해요 / 다음 단계 해내기) const AiCoachingSection(), - const SizedBox(height: 20), + const SizedBox(height: 10), ], ), ), diff --git a/lib/features/statistics/widgets/ai_coaching_card.dart b/lib/features/statistics/widgets/ai_coaching_card.dart index a3a143b..7581369 100644 --- a/lib/features/statistics/widgets/ai_coaching_card.dart +++ b/lib/features/statistics/widgets/ai_coaching_card.dart @@ -14,6 +14,8 @@ class AiCoachingCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + const borderWidth = 4.0; const outerRadius = 12.0; const innerRadius = outerRadius - borderWidth; @@ -40,7 +42,7 @@ class AiCoachingCard extends StatelessWidget { padding: const EdgeInsets.fromLTRB(20, 20, 20, 30), clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: Colors.white, + color: appColors.whiteToBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(innerRadius), ), @@ -76,6 +78,8 @@ class _CardHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -83,7 +87,7 @@ class _CardHeader extends StatelessWidget { Expanded( child: Text( '$emoji $title', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), ), const SizedBox(width: 8), @@ -101,6 +105,8 @@ class _BulletText extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -120,7 +126,7 @@ class _BulletText extends StatelessWidget { Expanded( child: Text( text, - style: AppTypography.b1.copyWith(color: AppColors.black), + style: AppTypography.b1.copyWith(color: appColors.blackToWhite), ), ), ], diff --git a/lib/features/statistics/widgets/ai_coaching_section.dart b/lib/features/statistics/widgets/ai_coaching_section.dart index 13ac083..0a352b5 100644 --- a/lib/features/statistics/widgets/ai_coaching_section.dart +++ b/lib/features/statistics/widgets/ai_coaching_section.dart @@ -14,6 +14,8 @@ class AiCoachingSection extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + final coachingAsync = ref.watch(aiCoachingRepositoryProvider); return Padding( @@ -42,7 +44,7 @@ class AiCoachingSection extends ConsumerWidget { children: [ Text( '데이터를 불러오지 못했어요', - style: AppTypography.b2.copyWith(color: AppColors.gray4), + style: AppTypography.b2.copyWith(color: appColors.gray4), ), const SizedBox(height: 8), TextButton( @@ -59,7 +61,7 @@ class AiCoachingSection extends ConsumerWidget { data: (cards) => Column( children: [ for (int i = 0; i < cards.length; i++) ...[ - if (i != 0) const SizedBox(height: 20), + if (i != 0) const SizedBox(height: 10), AiCoachingCard(data: cards[i]), ], ], diff --git a/lib/features/statistics/widgets/haenaem_grass.dart b/lib/features/statistics/widgets/haenaem_grass.dart index 2bf27dc..fe36eb5 100644 --- a/lib/features/statistics/widgets/haenaem_grass.dart +++ b/lib/features/statistics/widgets/haenaem_grass.dart @@ -28,6 +28,8 @@ class HaenaemGrass extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + debugPrint('🌿 [HaenaemGrass] successDays: $successDays'); debugPrint('🔥 [HaenaemGrass] currentStreak: $currentStreak'); debugPrint('📊 [HaenaemGrass] activity length: ${activity.length}'); @@ -50,7 +52,7 @@ class HaenaemGrass extends StatelessWidget { _buildSummaryItem( 'assets/images/icons/mini_success_icon.svg', '$successDays일', - AppColors.primaryAble, + appColors.primaryAble, ), const SizedBox(width: 10), _buildSummaryItem( @@ -83,7 +85,7 @@ class HaenaemGrass extends StatelessWidget { int count = activityIndex < activity.length ? activity[activityIndex] : 0; - return _buildGrassNode(_getLevel(count)); + return _buildGrassNode(_getLevel(count), appColors); }), ); }), @@ -107,23 +109,23 @@ class HaenaemGrass extends StatelessWidget { ); } - Widget _buildGrassNode(int level) { + Widget _buildGrassNode(int level, AppColorsExtension appColors) { Color nodeColor; switch (level) { case 1: - nodeColor = AppColors.primaryAble.withValues(alpha: 0.30); + nodeColor = appColors.primaryAble.withValues(alpha: 0.30); break; case 2: - nodeColor = AppColors.primaryAble.withValues(alpha: 0.55); + nodeColor = appColors.primaryAble.withValues(alpha: 0.55); break; case 3: - nodeColor = AppColors.primaryAble.withValues(alpha: 0.80); + nodeColor = appColors.primaryAble.withValues(alpha: 0.80); break; case 4: - nodeColor = AppColors.primaryAble; + nodeColor = appColors.primaryAble; break; default: - nodeColor = AppColors.gray5; + nodeColor = appColors.gray5; } return Container( diff --git a/lib/features/statistics/widgets/line_graph/line_chart_grid.dart b/lib/features/statistics/widgets/line_graph/line_chart_grid.dart index 4b56fa8..7155a5a 100644 --- a/lib/features/statistics/widgets/line_graph/line_chart_grid.dart +++ b/lib/features/statistics/widgets/line_graph/line_chart_grid.dart @@ -41,7 +41,8 @@ class LineChartGrid extends StatelessWidget { @override Widget build(BuildContext context) { - final axisColor = labelColor ?? AppColors.gray1; + final appColors = Theme.of(context).extension()!; + final axisColor = labelColor ?? appColors.gray1; // Y축 레이블: yMax에서 0까지 rowCount+1개 균등 분할 final yLabels = List.generate( @@ -89,6 +90,7 @@ class LineChartGrid extends StatelessWidget { yMax: yMax, thisData: thisData, lastData: lastData, + appColors: appColors, ), ), ), @@ -154,6 +156,7 @@ class _LineChartPainter extends CustomPainter { final double yMax; final List thisData; final List lastData; + final AppColorsExtension appColors; const _LineChartPainter({ required this.columnCount, @@ -161,31 +164,32 @@ class _LineChartPainter extends CustomPainter { required this.yMax, required this.thisData, required this.lastData, + required this.appColors, }); @override void paint(Canvas canvas, Size size) { - _drawGrid(canvas, size); + _drawGrid(canvas, size, appColors); if (lastData.length >= columnCount) { - _drawLine(canvas, size, lastData, AppColors.gray4); - _drawDots(canvas, size, lastData, AppColors.gray4); + _drawLine(canvas, size, lastData, appColors.gray3); + _drawDots(canvas, size, lastData, appColors.gray3, appColors); } if (thisData.length >= columnCount) { - _drawLine(canvas, size, thisData, AppColors.primaryAble); - _drawDots(canvas, size, thisData, AppColors.primaryAble); + _drawLine(canvas, size, thisData, appColors.primaryAble); + _drawDots(canvas, size, thisData, appColors.primaryAble, appColors); } } /// 격자선 /// - 가로: rowCount+1개, 맨 아래만 실선 나머지는 점선 /// - 세로: columnCount+1개 점선 (양끝 포함) - void _drawGrid(Canvas canvas, Size size) { + void _drawGrid(Canvas canvas, Size size, AppColorsExtension appColors) { final solidPaint = Paint() - ..color = AppColors.gray4 + ..color = appColors.gray4 ..strokeWidth = 1; final dashedPaint = Paint() - ..color = AppColors.gray4 + ..color = appColors.gray4 ..strokeWidth = 1; // 가로선: rowCount+1개 (i=0: top, i=rowCount: bottom) @@ -263,9 +267,15 @@ class _LineChartPainter extends CustomPainter { } /// 데이터 포인트 점 그리기 (흰색 채우기 + 컬러 테두리) - void _drawDots(Canvas canvas, Size size, List values, Color color) { + void _drawDots( + Canvas canvas, + Size size, + List values, + Color color, + AppColorsExtension appColors, + ) { final fillPaint = Paint() - ..color = Colors.white + ..color = appColors.whiteToBlack ..style = PaintingStyle.fill; final borderPaint = Paint() ..color = color diff --git a/lib/features/statistics/widgets/line_graph/line_graph.dart b/lib/features/statistics/widgets/line_graph/line_graph.dart index aa03195..2e80e83 100644 --- a/lib/features/statistics/widgets/line_graph/line_graph.dart +++ b/lib/features/statistics/widgets/line_graph/line_graph.dart @@ -22,6 +22,7 @@ class LineGraph extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; final isMonthly = ref.watch(graphTypeProvider); return StatisticsCard( @@ -31,9 +32,9 @@ class LineGraph extends ConsumerWidget { children: [ Column( children: [ - _buildToggleButtons(ref, isMonthly), + _buildToggleButtons(ref, isMonthly, appColors), const SizedBox(height: 20), - _buildLegend(isMonthly), + _buildLegend(context, isMonthly, appColors), const SizedBox(height: 12), AnimatedSwitcher( duration: const Duration(milliseconds: 300), @@ -51,35 +52,55 @@ class LineGraph extends ConsumerWidget { ); } - Widget _buildLegend(bool isMonthly) { + Widget _buildLegend( + BuildContext context, + bool isMonthly, + AppColorsExtension appColors, + ) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Row( mainAxisAlignment: MainAxisAlignment.center, spacing: 8, children: [ _buildLegendItem( - iconPath: 'assets/images/icons/green_line_graph_icon.svg', + appColors, + iconPath: isDarkMode + ? 'assets/images/icons/green_line_graph_icon_dark.svg' + : 'assets/images/icons/green_line_graph_icon.svg', label: isMonthly ? '이번 달' : '이번 주', ), _buildLegendItem( - iconPath: 'assets/images/icons/gray_line_graph_icon.svg', + appColors, + iconPath: isDarkMode + ? 'assets/images/icons/gray_line_graph_icon_dark.svg' + : 'assets/images/icons/gray_line_graph_icon.svg', label: isMonthly ? '저번 달' : '저번 주', ), ], ); } - Widget _buildLegendItem({required String iconPath, required String label}) { + Widget _buildLegendItem( + AppColorsExtension appColors, { + required String iconPath, + required String label, + }) { return Row( mainAxisSize: MainAxisSize.min, spacing: 4, children: [ SvgPicture.asset(iconPath, width: 16, height: 16), - Text(label, style: AppTypography.c1.copyWith(color: AppColors.gray1)), + Text(label, style: AppTypography.c1.copyWith(color: appColors.gray1)), ], ); } - Widget _buildToggleButtons(WidgetRef ref, bool isMonthly) { + Widget _buildToggleButtons( + WidgetRef ref, + bool isMonthly, + AppColorsExtension appColors, + ) { return Container( width: double.infinity, height: 35.99, @@ -90,12 +111,14 @@ class LineGraph extends ConsumerWidget { child: Row( children: [ _buildTab( + appColors, ref: ref, label: '월간', isSelected: isMonthly, onTap: () => ref.read(graphTypeProvider.notifier).state = true, ), _buildTab( + appColors, ref: ref, label: '주간', isSelected: !isMonthly, @@ -106,7 +129,8 @@ class LineGraph extends ConsumerWidget { ); } - Widget _buildTab({ + Widget _buildTab( + AppColorsExtension appColors, { required WidgetRef ref, required String label, required bool isSelected, @@ -120,14 +144,16 @@ class LineGraph extends ConsumerWidget { height: double.infinity, alignment: Alignment.center, decoration: ShapeDecoration( - color: isSelected ? AppColors.primaryAble : Colors.transparent, + color: isSelected ? appColors.primaryAble : Colors.transparent, shape: const StadiumBorder(), ), child: Text( label, textAlign: TextAlign.center, style: AppTypography.b2.copyWith( - color: isSelected ? Colors.white : AppColors.black, + color: isSelected + ? appColors.whiteToBlack + : appColors.blackToWhite, ), ), ), diff --git a/lib/features/statistics/widgets/pie_graph.dart b/lib/features/statistics/widgets/pie_graph.dart index 8cb0875..af9c375 100644 --- a/lib/features/statistics/widgets/pie_graph.dart +++ b/lib/features/statistics/widgets/pie_graph.dart @@ -31,6 +31,7 @@ class PieGraph extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; // ✅ 데이터 없을 때 빈 상태 UI if (top3.isEmpty) { return StatisticsCard( @@ -40,7 +41,7 @@ class PieGraph extends StatelessWidget { child: Center( child: Text( '아직 완료한 챌린지가 없어요', - style: AppTypography.b2.copyWith(color: AppColors.gray3), + style: AppTypography.b2.copyWith(color: appColors.gray3), ), ), ), @@ -51,7 +52,7 @@ class PieGraph extends StatelessWidget { final pieData = [ ...top3, if (restCount > 0) - TagCount(tag: '기타', count: restCount, color: AppColors.gray4), + TagCount(tag: '기타', count: restCount, color: appColors.gray4), ]; return StatisticsCard( @@ -68,7 +69,10 @@ class PieGraph extends StatelessWidget { aspectRatio: 1, child: CustomPaint( // ✅ 파이 차트는 pieData (top3 + 기타 합산) 사용 - painter: _PieChartPainter(tagCounts: pieData), + painter: _PieChartPainter( + tagCounts: pieData, + appColors: appColors, + ), ), ), ), @@ -81,15 +85,15 @@ class PieGraph extends StatelessWidget { children: [ Text( '총', - style: AppTypography.b3.copyWith(color: AppColors.black), + style: AppTypography.b3.copyWith(color: appColors.blackToWhite), ), Text( '$totalCount번', - style: AppTypography.h3.copyWith(color: AppColors.primaryAble), + style: AppTypography.h3.copyWith(color: appColors.primaryAble), ), Text( '해냄!', - style: AppTypography.b3.copyWith(color: AppColors.black), + style: AppTypography.b3.copyWith(color: appColors.blackToWhite), ), ], ), @@ -100,7 +104,9 @@ class PieGraph extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, // ✅ 텍스트는 top3 그대로 사용 - children: top3.map((tag) => _buildTopTagItem(tag)).toList(), + children: top3 + .map((tag) => _buildTopTagItem(tag, appColors)) + .toList(), ), ), @@ -111,7 +117,9 @@ class PieGraph extends StatelessWidget { child: Column( spacing: 1, // ✅ rest는 각각 따로 표시 - children: rest.map((tag) => _buildRestTagRow(tag)).toList(), + children: rest + .map((tag) => _buildRestTagRow(tag, appColors)) + .toList(), ), ), ], @@ -120,7 +128,7 @@ class PieGraph extends StatelessWidget { } // 1~3위 태그 아이템 - Widget _buildTopTagItem(TagCount tag) { + Widget _buildTopTagItem(TagCount tag, AppColorsExtension appColors) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, // ✅ start → center @@ -128,7 +136,7 @@ class PieGraph extends StatelessWidget { children: [ Text( tag.tag, - style: AppTypography.b1.copyWith(color: AppColors.gray2), + style: AppTypography.b1.copyWith(color: appColors.gray2), textAlign: TextAlign.center, // ✅ 추가 ), Text( @@ -141,14 +149,14 @@ class PieGraph extends StatelessWidget { } // 4위~ 태그 행 - Widget _buildRestTagRow(TagCount tag) { + Widget _buildRestTagRow(TagCount tag, AppColorsExtension appColors) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(tag.tag, style: AppTypography.b2.copyWith(color: AppColors.gray2)), + Text(tag.tag, style: AppTypography.b2.copyWith(color: appColors.gray2)), Text( '${tag.count}번', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ); @@ -159,7 +167,9 @@ class PieGraph extends StatelessWidget { class _PieChartPainter extends CustomPainter { final List tagCounts; - const _PieChartPainter({required this.tagCounts}); + final AppColorsExtension appColors; + + const _PieChartPainter({required this.tagCounts, required this.appColors}); @override void paint(Canvas canvas, Size size) { @@ -184,7 +194,7 @@ class _PieChartPainter extends CustomPainter { // 중앙 흰색 원 (도넛 효과) final innerPaint = Paint() - ..color = Colors.white + ..color = appColors.whiteToBlack ..style = PaintingStyle.fill; canvas.drawCircle(center, radius * 0.50, innerPaint); } diff --git a/lib/features/statistics/widgets/statistics_card.dart b/lib/features/statistics/widgets/statistics_card.dart index aaed8f6..37e8def 100644 --- a/lib/features/statistics/widgets/statistics_card.dart +++ b/lib/features/statistics/widgets/statistics_card.dart @@ -18,12 +18,14 @@ class StatisticsCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), // 화면 양끝과 20 차이 child: Container( padding: const EdgeInsets.all(20), // 카드 내부 기본 패딩 (상단 20 포함) decoration: ShapeDecoration( - color: Colors.white, + color: appColors.whiteToBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -47,7 +49,9 @@ class StatisticsCard extends StatelessWidget { children: [ Text( title, - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith( + color: appColors.blackToWhite, + ), ), if (headerAction != null) headerAction!, ], From 0ce030a680522541183343e97104b4c60b753a5d Mon Sep 17 00:00:00 2001 From: Seungvin Date: Thu, 6 Aug 2026 17:52:43 +0900 Subject: [PATCH 15/18] =?UTF-8?q?feat:=20=ED=86=B5=EA=B3=84=20=ED=95=B4?= =?UTF-8?q?=EB=83=84=20=EC=B6=94=EC=9D=B4=20=EA=B7=B8=EB=9E=98=ED=94=84=20?= =?UTF-8?q?=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/icons/gray_line_graph_icon.svg | 4 ++-- assets/images/icons/gray_line_graph_icon_dark.svg | 4 ++++ assets/images/icons/green_line_graph_icon_dark.svg | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 assets/images/icons/gray_line_graph_icon_dark.svg create mode 100644 assets/images/icons/green_line_graph_icon_dark.svg diff --git a/assets/images/icons/gray_line_graph_icon.svg b/assets/images/icons/gray_line_graph_icon.svg index 77bd908..5c284c6 100644 --- a/assets/images/icons/gray_line_graph_icon.svg +++ b/assets/images/icons/gray_line_graph_icon.svg @@ -1,4 +1,4 @@ - - + + diff --git a/assets/images/icons/gray_line_graph_icon_dark.svg b/assets/images/icons/gray_line_graph_icon_dark.svg new file mode 100644 index 0000000..78196d7 --- /dev/null +++ b/assets/images/icons/gray_line_graph_icon_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/icons/green_line_graph_icon_dark.svg b/assets/images/icons/green_line_graph_icon_dark.svg new file mode 100644 index 0000000..2b92ce5 --- /dev/null +++ b/assets/images/icons/green_line_graph_icon_dark.svg @@ -0,0 +1,4 @@ + + + + From 5847b2610c454505c3eaf65226fab1f41cfeca8c Mon Sep 17 00:00:00 2001 From: Seungvin Date: Thu, 6 Aug 2026 18:18:54 +0900 Subject: [PATCH 16/18] =?UTF-8?q?fix:=20=ED=95=98=EB=8B=A8=20=EB=84=A4?= =?UTF-8?q?=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=B0=94=20=EC=83=81?= =?UTF-8?q?=EB=8B=A8=20=EC=99=B8=EA=B3=BD=EC=84=A0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner.xcodeproj/project.pbxproj | 8 ---- lib/features/main/widgets/bottom_nav_bar.dart | 43 ++++++++++++------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index f21b99e..981c011 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -282,14 +282,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -356,14 +352,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; diff --git a/lib/features/main/widgets/bottom_nav_bar.dart b/lib/features/main/widgets/bottom_nav_bar.dart index 807a301..95ebdbe 100644 --- a/lib/features/main/widgets/bottom_nav_bar.dart +++ b/lib/features/main/widgets/bottom_nav_bar.dart @@ -19,22 +19,33 @@ class MainBottomNavBar extends StatelessWidget { Widget build(BuildContext context) { final appColors = Theme.of(context).extension()!; - return BottomNavigationBar( - backgroundColor: appColors.whiteToBlack, - type: BottomNavigationBarType.fixed, - currentIndex: currentIndex, - selectedItemColor: appColors.blackToWhite, - unselectedItemColor: appColors.gray3, - selectedLabelStyle: AppTypography.c2, - unselectedLabelStyle: AppTypography.c2, - onTap: onTap, - items: [ - _buildNavItem('home_icon_off.svg', '홈', appColors), - _buildNavItem('graph_icon.svg', '통계', appColors), - _buildNavItem('feed_icon.svg', '피드', appColors), - _buildNavItem('friend_icon_off.svg', '친구', appColors), - _buildNavItem('user_icon.svg', '내 페이지', appColors), - ], + return Container( + decoration: BoxDecoration( + // 상단에만 외곽선 추가 + border: Border( + top: BorderSide( + color: appColors.gray5, // 원하는 테두리 색상으로 변경 가능 + width: 1.0, // 테두리 두께 설정 + ), + ), + ), + child: BottomNavigationBar( + backgroundColor: appColors.whiteToBlack, + type: BottomNavigationBarType.fixed, + currentIndex: currentIndex, + selectedItemColor: appColors.blackToWhite, + unselectedItemColor: appColors.gray3, + selectedLabelStyle: AppTypography.c2, + unselectedLabelStyle: AppTypography.c2, + onTap: onTap, + items: [ + _buildNavItem('home_icon_off.svg', '홈', appColors), + _buildNavItem('graph_icon.svg', '통계', appColors), + _buildNavItem('feed_icon.svg', '피드', appColors), + _buildNavItem('friend_icon_off.svg', '친구', appColors), + _buildNavItem('user_icon.svg', '내 페이지', appColors), + ], + ), ); } From 38e7eba52fe419a6356c2ea788d21eba9ffe1762 Mon Sep 17 00:00:00 2001 From: Seungvin Date: Thu, 6 Aug 2026 19:05:46 +0900 Subject: [PATCH 17/18] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80ui=20=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/challenge_list_screen.dart | 34 ++++---- .../user/screens/my_page_main_screen.dart | 19 +++-- .../screens/profile/profile_edit_screen.dart | 65 ++++++++++------ .../push_notification_settings_screen.dart | 78 ++++++++++++------- .../screens/settings/withdrawal_screen.dart | 48 +++++++----- .../user/views/my_challenge_section_view.dart | 60 ++++++++------ .../user/views/my_page_menu_view.dart | 4 +- .../user/views/profile_header_view.dart | 4 +- .../user/widgets/my_challenge_card.dart | 42 ++++++---- .../user/widgets/my_page_menu_item.dart | 15 +++- .../user/widgets/profile_image_menu.dart | 21 ++--- 11 files changed, 235 insertions(+), 155 deletions(-) diff --git a/lib/features/user/screens/challenge/challenge_list_screen.dart b/lib/features/user/screens/challenge/challenge_list_screen.dart index 5b2e467..49f0fb6 100644 --- a/lib/features/user/screens/challenge/challenge_list_screen.dart +++ b/lib/features/user/screens/challenge/challenge_list_screen.dart @@ -16,6 +16,8 @@ class ChallengeListScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + final inProgressAsync = ref.watch( myInProgressChallengesProvider(onlyTwo: false), ); @@ -25,9 +27,9 @@ class ChallengeListScreen extends ConsumerWidget { return DefaultTabController( length: 3, child: Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, centerTitle: true, leading: IconButton( @@ -36,23 +38,23 @@ class ChallengeListScreen extends ConsumerWidget { 'assets/images/icons/arrow_left.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), ), title: Text( '나의 챌린지', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), ), body: Column( children: [ - _buildTabBar(), + _buildTabBar(appColors), Expanded( child: Container( - color: AppColors.gray5, + color: appColors.gray5, child: TabBarView( children: [ inProgressAsync.when( @@ -83,16 +85,16 @@ class ChallengeListScreen extends ConsumerWidget { ); } - Widget _buildTabBar() { + Widget _buildTabBar(AppColorsExtension appColors) { return Container( - decoration: const BoxDecoration( - color: Colors.white, - border: Border(bottom: BorderSide(color: AppColors.gray4, width: 1)), + decoration: BoxDecoration( + color: appColors.whiteToBlack, + border: Border(bottom: BorderSide(color: appColors.gray4, width: 1)), ), - child: const TabBar( - indicatorColor: AppColors.primaryAble, - labelColor: AppColors.primaryAble, - unselectedLabelColor: AppColors.gray2, + child: TabBar( + indicatorColor: appColors.primaryAble, + labelColor: appColors.primaryAble, + unselectedLabelColor: appColors.gray2, tabs: [ Tab(text: '진행중'), Tab(text: '완료'), @@ -110,7 +112,7 @@ class ChallengeListScreen extends ConsumerWidget { return ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), itemCount: list.length, - separatorBuilder: (_, __) => const SizedBox(height: 12), + separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, index) => MyChallengeCard(item: list[index]), ); } diff --git a/lib/features/user/screens/my_page_main_screen.dart b/lib/features/user/screens/my_page_main_screen.dart index 1096a4f..9179e99 100644 --- a/lib/features/user/screens/my_page_main_screen.dart +++ b/lib/features/user/screens/my_page_main_screen.dart @@ -26,14 +26,16 @@ class MyPageMainScreen extends ConsumerStatefulWidget { class _MyPageMainScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // myProfileProvider가 로딩중일 때 // 전역 관리하는 currentUserProvider가 닉네임/이미지라도 먼저 가져옴 final currentUser = ref.watch(currentUserProvider); final profileAsync = ref.watch(myProfileProvider); return Scaffold( - backgroundColor: Colors.white, - appBar: _buildAppBar(profileAsync.value), + backgroundColor: appColors.whiteToBlack, + appBar: _buildAppBar(profileAsync.value, appColors), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), @@ -84,9 +86,12 @@ class _MyPageMainScreenState extends ConsumerState { ); } - PreferredSizeWidget _buildAppBar(UserDetail? detail) { + PreferredSizeWidget _buildAppBar( + UserDetail? detail, + AppColorsExtension appColors, + ) { return AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, automaticallyImplyLeading: false, title: Row( @@ -95,7 +100,7 @@ class _MyPageMainScreenState extends ConsumerState { const SizedBox(width: 24), Text( '내 페이지', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), InkWell( onTap: () { @@ -120,8 +125,8 @@ class _MyPageMainScreenState extends ConsumerState { 'assets/images/icons/my_page_edit.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), diff --git a/lib/features/user/screens/profile/profile_edit_screen.dart b/lib/features/user/screens/profile/profile_edit_screen.dart index 63af909..2d37137 100644 --- a/lib/features/user/screens/profile/profile_edit_screen.dart +++ b/lib/features/user/screens/profile/profile_edit_screen.dart @@ -160,6 +160,8 @@ class _ProfileEditScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final tagState = ref.watch(tagProvider); final profileEditState = ref.watch(userProfileProvider); // 💡 프로필 저장 상태 구독 @@ -176,8 +178,8 @@ class _ProfileEditScreenState extends ConsumerState { !profileEditState.isLoading; return Scaffold( - backgroundColor: Colors.white, - appBar: _buildAppBar(context), + backgroundColor: appColors.whiteToBlack, + appBar: _buildAppBar(context, appColors), body: Stack( children: [ Column( @@ -195,15 +197,16 @@ class _ProfileEditScreenState extends ConsumerState { const SizedBox(height: 20), _buildProfileImageSection(), const SizedBox(height: 40), - _buildNicknameSection(), + _buildNicknameSection(appColors), const SizedBox(height: 10), _buildTextFieldSection( + appColors, label: '한 줄 소개', controller: _introController, maxLength: 50, ), const SizedBox(height: 10), - _buildTagSection(), + _buildTagSection(appColors), const SizedBox(height: 120), ], ), @@ -239,8 +242,8 @@ class _ProfileEditScreenState extends ConsumerState { child: BottomActionButton( text: '수정사항 저장하기', backgroundColor: isEnabled - ? AppColors.primaryAble - : AppColors.disable, + ? appColors.primaryAble + : appColors.disable, onPressed: isEnabled ? _handleSave : () {}, ), ), @@ -307,7 +310,7 @@ class _ProfileEditScreenState extends ConsumerState { ); } - Widget _buildNicknameSection() { + Widget _buildNicknameSection(AppColorsExtension appColors) { String? errorMessage; if (_isInvalidFormat) { errorMessage = '마침표(.), 언더바(_), 하이픈(-) 외의 특수문자나 띄어쓰기는 포함할 수 없어요'; @@ -330,16 +333,16 @@ class _ProfileEditScreenState extends ConsumerState { borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: errorMessage != null - ? AppColors.notification - : AppColors.gray4, + ? appColors.notification + : appColors.gray4, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: errorMessage != null - ? AppColors.notification - : AppColors.primaryAble, + ? appColors.notification + : appColors.primaryAble, width: 1.5, ), ), @@ -354,14 +357,14 @@ class _ProfileEditScreenState extends ConsumerState { ? Text( '* $errorMessage', style: AppTypography.c1.copyWith( - color: AppColors.notification, + color: appColors.notification, ), ) : const SizedBox.shrink(), ), Text( '${_nicknameController.text.length}/15', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ], ), @@ -369,7 +372,8 @@ class _ProfileEditScreenState extends ConsumerState { ); } - Widget _buildTextFieldSection({ + Widget _buildTextFieldSection( + AppColorsExtension appColors, { required String label, required TextEditingController controller, required int maxLength, @@ -377,7 +381,10 @@ class _ProfileEditScreenState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: AppTypography.b3.copyWith(color: AppColors.black)), + Text( + label, + style: AppTypography.b3.copyWith(color: appColors.blackToWhite), + ), const SizedBox(height: 8), TextField( controller: controller, @@ -387,11 +394,11 @@ class _ProfileEditScreenState extends ConsumerState { contentPadding: const EdgeInsets.all(12), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: AppColors.gray4), + borderSide: BorderSide(color: appColors.gray4), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: AppColors.primaryAble), + borderSide: BorderSide(color: appColors.primaryAble), ), ), ), @@ -400,21 +407,21 @@ class _ProfileEditScreenState extends ConsumerState { alignment: Alignment.centerRight, child: Text( '${controller.text.length}/$maxLength', - style: AppTypography.c1.copyWith(color: AppColors.gray2), + style: AppTypography.c1.copyWith(color: appColors.gray2), ), ), ], ); } - Widget _buildTagSection() { + Widget _buildTagSection(AppColorsExtension appColors) { final selectedTags = ref.watch(tagProvider).tags; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '관심 태그 (2~6개 선택)', - style: AppTypography.b3.copyWith(color: AppColors.black), + style: AppTypography.b3.copyWith(color: appColors.blackToWhite), ), const SizedBox(height: 16), ...TagMapper.categoryOrder.map((engCategory) { @@ -427,7 +434,9 @@ class _ProfileEditScreenState extends ConsumerState { children: [ Text( korCategory, - style: AppTypography.b2.copyWith(color: Colors.black), + style: AppTypography.b2.copyWith( + color: appColors.blackToWhite, + ), ), const SizedBox(height: 10), Wrap( @@ -455,23 +464,29 @@ class _ProfileEditScreenState extends ConsumerState { ref.read(tagProvider.notifier).toggleTag(tag); } - PreferredSizeWidget _buildAppBar(BuildContext context) { + PreferredSizeWidget _buildAppBar( + BuildContext context, + AppColorsExtension appColors, + ) { return AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode(AppColors.black, BlendMode.srcIn), + colorFilter: ColorFilter.mode( + appColors.blackToWhite, + BlendMode.srcIn, + ), ), onPressed: () => Navigator.pop(context), ), centerTitle: true, title: Text( '프로필 편집', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), ); } diff --git a/lib/features/user/screens/settings/push_notification_settings_screen.dart b/lib/features/user/screens/settings/push_notification_settings_screen.dart index c855bd6..a9f1fdf 100644 --- a/lib/features/user/screens/settings/push_notification_settings_screen.dart +++ b/lib/features/user/screens/settings/push_notification_settings_screen.dart @@ -37,6 +37,7 @@ class _PushNotificationSettingsScreenState // ── 공통 시간 피커 ──────────────────────────────────────── void _showTimePicker( + AppColorsExtension appColors, BuildContext context, String currentDisplayTime, void Function(String serverTime) onConfirm, @@ -49,13 +50,13 @@ class _PushNotificationSettingsScreenState showDialog( context: context, - barrierColor: Colors.black.withAlpha(100), + barrierColor: appColors.blackToWhite.withAlpha(100), builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, contentPadding: const EdgeInsets.fromLTRB(20, 20, 20, 0), content: SizedBox( width: MediaQuery.of(context).size.width * 0.8, @@ -65,6 +66,7 @@ class _PushNotificationSettingsScreenState Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: _buildAnimatedPeriodSelector( + appColors, currentPeriod, (newPeriod) => setDialogState(() => currentPeriod = newPeriod), @@ -80,7 +82,7 @@ class _PushNotificationSettingsScreenState height: 40, margin: const EdgeInsets.symmetric(horizontal: 24), decoration: BoxDecoration( - color: AppColors.gray4.withAlpha(100), + color: appColors.gray4.withAlpha(100), borderRadius: BorderRadius.circular(10), ), ), @@ -122,7 +124,7 @@ class _PushNotificationSettingsScreenState }, style: TextButton.styleFrom( backgroundColor: Colors.transparent, - foregroundColor: AppColors.primaryAble, + foregroundColor: appColors.primaryAble, minimumSize: const Size(double.infinity, 52), padding: EdgeInsets.zero, shape: RoundedRectangleBorder( @@ -132,7 +134,7 @@ class _PushNotificationSettingsScreenState child: Text( '완료', style: AppTypography.b1.copyWith( - color: AppColors.primaryAble, + color: appColors.primaryAble, fontWeight: FontWeight.bold, ), ), @@ -147,6 +149,7 @@ class _PushNotificationSettingsScreenState } Widget _buildAnimatedPeriodSelector( + AppColorsExtension appColors, String currentPeriod, Function(String) onPeriodChanged, ) { @@ -154,7 +157,7 @@ class _PushNotificationSettingsScreenState height: 48, padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: AppColors.gray4, + color: appColors.gray4, borderRadius: BorderRadius.circular(12), ), child: LayoutBuilder( @@ -172,11 +175,11 @@ class _PushNotificationSettingsScreenState width: width - 4, height: double.infinity, decoration: BoxDecoration( - color: Colors.white, + color: appColors.whiteToBlack, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( - color: Colors.black.withAlpha(20), + color: appColors.blackToWhite.withAlpha(20), blurRadius: 4, offset: const Offset(0, 2), ), @@ -195,7 +198,9 @@ class _PushNotificationSettingsScreenState child: AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 250), style: AppTypography.b2.copyWith( - color: isSelected ? Colors.black : AppColors.gray2, + color: isSelected + ? appColors.blackToWhite + : appColors.gray2, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, @@ -215,6 +220,7 @@ class _PushNotificationSettingsScreenState } Widget _buildWeeklyReminderSection( + AppColorsExtension appColors, PushNotificationSettings settings, PushNotificationSettingsNotifier notifier, ) { @@ -231,13 +237,13 @@ class _PushNotificationSettingsScreenState Text( '실패 방지 리마인더', style: AppTypography.b1.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, fontWeight: FontWeight.w500, ), ), Text( '챌린지 실패를 방지하는 주간 리마인더 알림', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ), @@ -248,6 +254,7 @@ class _PushNotificationSettingsScreenState const SizedBox(height: 4), GestureDetector( onTap: () => _showTimePicker( + appColors, context, _convertToDisplayTime(settings.weeklyReminderTime), (serverTime) => notifier.updateWeeklyReminderTime(serverTime), @@ -256,7 +263,7 @@ class _PushNotificationSettingsScreenState width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(8), ), child: Row( @@ -264,7 +271,7 @@ class _PushNotificationSettingsScreenState children: [ Text( _convertToDisplayTime(settings.weeklyReminderTime), - style: AppTypography.b2.copyWith(color: AppColors.gray3), + style: AppTypography.b2.copyWith(color: appColors.gray3), ), Opacity( opacity: 0.5, @@ -272,8 +279,8 @@ class _PushNotificationSettingsScreenState 'assets/images/icons/big_down_arrow.svg', width: 16, height: 16, - colorFilter: const ColorFilter.mode( - AppColors.gray2, + colorFilter: ColorFilter.mode( + appColors.gray2, BlendMode.srcIn, ), ), @@ -288,22 +295,24 @@ class _PushNotificationSettingsScreenState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final settings = ref.watch(pushNotificationProvider); final notifier = ref.read(pushNotificationProvider.notifier); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, - surfaceTintColor: Colors.white, + surfaceTintColor: appColors.whiteToBlack, leading: IconButton( icon: SvgPicture.asset( 'assets/images/icons/arrow_left.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -311,7 +320,7 @@ class _PushNotificationSettingsScreenState ), title: Text( '푸시 알림 설정', - style: AppTypography.h3.copyWith(color: AppColors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), centerTitle: true, ), @@ -324,6 +333,7 @@ class _PushNotificationSettingsScreenState // ── 전체 알림 ─────────────────────────────── const SizedBox(height: 16), _buildSwitchRow( + appColors, title: '전체 알림', subtitle: '모든 알림 받기', value: settings.allNotifications, @@ -331,34 +341,38 @@ class _PushNotificationSettingsScreenState isMain: true, ), const SizedBox(height: 20), - const Divider(color: AppColors.gray4, height: 1), + Divider(color: appColors.gray4, height: 1), const SizedBox(height: 20), // ── 소셜 섹션 ──────────────────────────────── - _buildSectionHeader('소셜'), + _buildSectionHeader('소셜', appColors), const SizedBox(height: 10), Column( spacing: 20, children: [ _buildSwitchRow( + appColors, title: '내 글 좋아요', subtitle: "내 게시물에 '좋아요' 반응이 올 때 알림", value: settings.likeNotifications, onChanged: (val) => notifier.toggleLikes(val), ), _buildSwitchRow( + appColors, title: '댓글', subtitle: '내 글에 새로운 댓글이 달릴 때 알림', value: settings.commentNotifications, onChanged: (val) => notifier.toggleComments(val), ), _buildSwitchRow( + appColors, title: '친구 신청', subtitle: '나에게 새로운 친구 요청이 도착할 때 알림', value: settings.friendRequestNotifications, onChanged: (val) => notifier.toggleFriend(val), ), _buildSwitchRow( + appColors, title: '멤버 인증 소식 (전체)', subtitle: '멤버 인증 알림 통합 관리', value: settings.memberAuthNotifications, @@ -369,18 +383,20 @@ class _PushNotificationSettingsScreenState const SizedBox(height: 42), // ── 챌린지 섹션 ────────────────────────────── - _buildSectionHeader('챌린지'), + _buildSectionHeader('챌린지', appColors), const SizedBox(height: 10), Column( spacing: 20, children: [ _buildSwitchRow( + appColors, title: '챌린지 초대', subtitle: '새로운 챌린지 참여 제안을 받았을 때 알림', value: settings.challengeInviteNotifications, onChanged: (val) => notifier.toggleChallengeInvite(val), ), _buildSwitchRow( + appColors, title: '동기부여 메시지', subtitle: '꾸준한 챌린지 참여를 돕는 응원 푸시 알림', value: settings.motivationNotifications, @@ -388,13 +404,14 @@ class _PushNotificationSettingsScreenState ), // ── 일일 리마인더 (시간 피커 없음) ────────────── _buildSwitchRow( + appColors, title: '일일 리마인더 (전체)', subtitle: '리마인더 알림 통합 관리', value: settings.dailyReminder, onChanged: (val) => notifier.toggleDailyReminder(val), ), // ── 실패 방지 리마인더 ───────────────────── - _buildWeeklyReminderSection(settings, notifier), + _buildWeeklyReminderSection(appColors, settings, notifier), ], ), const SizedBox(height: 100), @@ -406,15 +423,16 @@ class _PushNotificationSettingsScreenState } // ── 섹션 헤더 ──────────────────────────────────────────── - Widget _buildSectionHeader(String title) { + Widget _buildSectionHeader(String title, AppColorsExtension appColors) { return Text( title, - style: AppTypography.b1.copyWith(color: AppColors.gray1), + style: AppTypography.b1.copyWith(color: appColors.gray1), ); } // ── 스위치 행 ──────────────────────────────────────────── - Widget _buildSwitchRow({ + Widget _buildSwitchRow( + AppColorsExtension appColors, { required String title, required String subtitle, required bool value, @@ -432,13 +450,13 @@ class _PushNotificationSettingsScreenState Text( title, style: AppTypography.b1.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, fontWeight: isMain ? FontWeight.w600 : FontWeight.w500, ), ), Text( subtitle, - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), ], ), diff --git a/lib/features/user/screens/settings/withdrawal_screen.dart b/lib/features/user/screens/settings/withdrawal_screen.dart index bb984b1..dbabede 100644 --- a/lib/features/user/screens/settings/withdrawal_screen.dart +++ b/lib/features/user/screens/settings/withdrawal_screen.dart @@ -27,20 +27,22 @@ class _WithdrawalScreenState extends State { /// 반환 값: Widget @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: appColors.whiteToBlack, elevation: 0, leading: IconButton( // 뒤로가기 버튼 (SVG 사용 권장) - icon: const Icon(Icons.arrow_back, color: AppColors.black), + icon: Icon(Icons.arrow_back, color: appColors.blackToWhite), onPressed: () => Navigator.pop(context), ), centerTitle: true, title: Text( '회원 탈퇴', - style: AppTypography.h2.copyWith(color: AppColors.black), // + style: AppTypography.h2.copyWith(color: appColors.blackToWhite), // ), ), body: SafeArea( @@ -54,13 +56,13 @@ class _WithdrawalScreenState extends State { children: [ const SizedBox(height: 60), // --- 경고 아이콘 영역 --- - buildWarningIcon(), + buildWarningIcon(appColors), const SizedBox(height: 20), // --- 안내 문구 영역 --- Text( '정말 탈퇴하시겠습니까?', style: AppTypography.h1.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), // textAlign: TextAlign.center, ), @@ -68,16 +70,16 @@ class _WithdrawalScreenState extends State { Text( '계정을 삭제하면 모든 데이터가 영구적으로\n삭제되며 복구할 수 없습니다.', style: AppTypography.b1.copyWith( - color: AppColors.gray2, + color: appColors.gray2, ), // textAlign: TextAlign.center, ), const SizedBox(height: 40), // --- 동의 체크박스 영역 --- - buildAgreementBox(), + buildAgreementBox(appColors), const SizedBox(height: 10), // --- 추가 참고 사항 영역 --- - buildNoticeBox(), + buildNoticeBox(appColors), ], ), ), @@ -93,13 +95,13 @@ class _WithdrawalScreenState extends State { /// 함수의 용도: 경고 아이콘(SVG) 영역 위젯 생성 /// 매개 변수: 없음 /// 반환 값: Widget - Widget buildWarningIcon() { + Widget buildWarningIcon(AppColorsExtension appColors) { return Container( width: 80, height: 80, decoration: BoxDecoration( // 피그마 mainlist-warning 컬러 적용 (투명도 포함) - color: const Color(0xFFFFD5C8).withAlpha(127), + color: appColors.warning.withAlpha(127), shape: BoxShape.circle, ), child: Center( @@ -111,14 +113,14 @@ class _WithdrawalScreenState extends State { /// 함수의 용도: 탈퇴 동의 여부를 확인하는 체크박스 박스 생성 /// 매개 변수: 없음 /// 반환 값: Widget - Widget buildAgreementBox() { + Widget buildAgreementBox(AppColorsExtension appColors) { return InkWell( onTap: () => setState(() => isAgreed = !isAgreed), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFFDFE1DC).withAlpha(127), // gray5 + color: appColors.gray5.withAlpha(127), // gray5 borderRadius: BorderRadius.circular(12), ), child: Row( @@ -128,13 +130,15 @@ class _WithdrawalScreenState extends State { isAgreed ? Icons.check_box : Icons.check_box_outline_blank, size: 24, // 체크 여부에 따라 색상 변경 (검정 / 회색3) - color: isAgreed ? AppColors.black : AppColors.gray3, // + color: isAgreed ? appColors.blackToWhite : appColors.gray3, // ), const SizedBox(width: 12), Expanded( child: Text( '위 내용을 확인했으며, 계정 삭제에 동의합니다.', - style: AppTypography.b2.copyWith(color: AppColors.black), // + style: AppTypography.b2.copyWith( + color: appColors.blackToWhite, + ), // ), ), ], @@ -146,18 +150,18 @@ class _WithdrawalScreenState extends State { /// 함수의 용도: 탈퇴 관련 추가 참고 사항을 안내하는 박스 생성 /// 매개 변수: 없음 /// 반환 값: Widget - Widget buildNoticeBox() { + Widget buildNoticeBox(AppColorsExtension appColors) { return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFFDFE1DC).withAlpha(127), + color: appColors.gray5.withAlpha(127), borderRadius: BorderRadius.circular(12), ), child: Text( '참고: 탈퇴 후 30일 이내에는 계정 복구가 가능합니다. ' '30일이 지나면 모든 데이터가 완전히 삭제됩니다.', - style: AppTypography.c1.copyWith(color: AppColors.gray2), // + style: AppTypography.c1.copyWith(color: appColors.gray2), // ), ); } @@ -166,6 +170,8 @@ class _WithdrawalScreenState extends State { /// 매개 변수: BuildContext context /// 반환 값: Widget Widget buildBottomButtons(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( @@ -176,7 +182,7 @@ class _WithdrawalScreenState extends State { text: '탈퇴하기', // 동의 여부에 따라 배경색 변경 color: isAgreed - ? AppColors.notification + ? appColors.notification : const Color(0xFFDBADAD), // textColor: Colors.white, onTap: isAgreed @@ -214,8 +220,8 @@ class _WithdrawalScreenState extends State { Expanded( child: buildActionButton( text: '취소', - color: const Color(0xFFDFE1DC).withAlpha(127), - textColor: AppColors.gray2, + color: appColors.gray5.withAlpha(127), + textColor: appColors.gray2, onTap: () => Navigator.pop(context), ), ), diff --git a/lib/features/user/views/my_challenge_section_view.dart b/lib/features/user/views/my_challenge_section_view.dart index 9abd24f..78fc19a 100644 --- a/lib/features/user/views/my_challenge_section_view.dart +++ b/lib/features/user/views/my_challenge_section_view.dart @@ -29,29 +29,31 @@ class _MyChallengeSectionViewState @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColors.gray4, width: 1), + border: Border.all(color: appColors.gray4, width: 1), ), clipBehavior: Clip.antiAlias, child: Column( mainAxisSize: MainAxisSize.min, children: [ - _buildHeader(), - const Divider(height: 1, color: AppColors.gray4), - _buildChallengeListArea(), + _buildHeader(appColors), + Divider(height: 1, color: appColors.gray4), + _buildChallengeListArea(appColors), ], ), ); } // --- 헤더 영역 (제목 + 더보기 + 탭) --- - Widget _buildHeader() { + Widget _buildHeader(AppColorsExtension appColors) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - color: AppColors.gray5, + color: appColors.gray5, child: Column( children: [ Row( @@ -59,19 +61,19 @@ class _MyChallengeSectionViewState children: [ Text( '나의 챌린지', - style: AppTypography.h3.copyWith(color: Colors.black), + style: AppTypography.h3.copyWith(color: appColors.blackToWhite), ), - _buildMoreButton(), + _buildMoreButton(appColors), ], ), const SizedBox(height: 8), - _buildStatusTabs(), + _buildStatusTabs(appColors), ], ), ); } - Widget _buildMoreButton() { + Widget _buildMoreButton(AppColorsExtension appColors) { return InkWell( onTap: () => Navigator.push( context, @@ -80,7 +82,7 @@ class _MyChallengeSectionViewState child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text('더보기', style: AppTypography.b1.copyWith(color: AppColors.gray3)), + Text('더보기', style: AppTypography.b1.copyWith(color: appColors.gray3)), SvgPicture.asset( 'assets/images/icons/right_arrow_icon.svg', width: 20, @@ -92,33 +94,41 @@ class _MyChallengeSectionViewState } // --- 탭 버튼 영역 --- - Widget _buildStatusTabs() { + Widget _buildStatusTabs(AppColorsExtension appColors) { return Row( children: [ _buildTabButton( '진행중', MyPageTab.inProgress, 'assets/images/icons/inprogress.svg', + appColors, ), const SizedBox(width: 8), _buildTabButton( '완료', MyPageTab.success, 'assets/images/icons/success_check.svg', + appColors, ), const SizedBox(width: 8), _buildTabButton( '실패', MyPageTab.fail, 'assets/images/icons/fail_circle.svg', + appColors, ), ], ); } - Widget _buildTabButton(String label, MyPageTab tab, String svgPath) { + Widget _buildTabButton( + String label, + MyPageTab tab, + String svgPath, + AppColorsExtension appColors, + ) { final bool isSelected = selectedTab == tab; - final Color activeColor = _getTabColor(tab); + final Color activeColor = _getTabColor(tab, appColors); return Expanded( child: InkWell( @@ -126,9 +136,9 @@ class _MyChallengeSectionViewState child: Container( padding: const EdgeInsets.symmetric(vertical: 6), decoration: BoxDecoration( - color: isSelected ? activeColor : Colors.white, + color: isSelected ? activeColor : appColors.whiteToBlack, borderRadius: BorderRadius.circular(9), - border: isSelected ? null : Border.all(color: AppColors.gray4), + border: isSelected ? null : Border.all(color: appColors.gray4), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -138,7 +148,7 @@ class _MyChallengeSectionViewState width: 16, height: 16, colorFilter: ColorFilter.mode( - isSelected ? Colors.white : AppColors.gray3, + isSelected ? appColors.whiteToBlack : appColors.gray3, BlendMode.srcIn, ), ), @@ -146,7 +156,7 @@ class _MyChallengeSectionViewState Text( label, style: AppTypography.b2.copyWith( - color: isSelected ? Colors.white : AppColors.gray3, + color: isSelected ? appColors.whiteToBlack : appColors.gray3, ), ), ], @@ -156,19 +166,19 @@ class _MyChallengeSectionViewState ); } - Color _getTabColor(MyPageTab tab) { + Color _getTabColor(MyPageTab tab, AppColorsExtension appColors) { switch (tab) { case MyPageTab.inProgress: return AppColors.blue; case MyPageTab.success: - return AppColors.primaryAble; + return appColors.primaryAble; case MyPageTab.fail: - return AppColors.notification; + return appColors.notification; } } // --- 챌린지 리스트 데이터 처리 영역 --- - Widget _buildChallengeListArea() { + Widget _buildChallengeListArea(AppColorsExtension appColors) { final challengesAsync = _getChallengesProvider(); return challengesAsync.when( @@ -185,9 +195,9 @@ class _MyChallengeSectionViewState return Container( height: 100, alignment: Alignment.center, - child: const Text( + child: Text( '해당하는 챌린지가 없습니다.', - style: TextStyle(color: AppColors.gray2), + style: TextStyle(color: appColors.gray2), ), ); } @@ -198,7 +208,7 @@ class _MyChallengeSectionViewState return Column( children: [ MyChallengeCard(item: challenge), - if (!isLast) Divider(height: 1, color: AppColors.gray5), + if (!isLast) Divider(height: 1, color: appColors.gray5), ], ); }).toList(), diff --git a/lib/features/user/views/my_page_menu_view.dart b/lib/features/user/views/my_page_menu_view.dart index d01b122..8081544 100644 --- a/lib/features/user/views/my_page_menu_view.dart +++ b/lib/features/user/views/my_page_menu_view.dart @@ -17,6 +17,8 @@ class MyPageMenuView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final appColors = Theme.of(context).extension()!; + return Column( children: [ MyPageMenuItem( @@ -76,7 +78,7 @@ class MyPageMenuView extends ConsumerWidget { const SizedBox(height: 10), MyPageMenuItem( title: '회원 탈퇴', - textColor: AppColors.notification, + textColor: appColors.notification, showArrow: true, onTap: () { Navigator.push( diff --git a/lib/features/user/views/profile_header_view.dart b/lib/features/user/views/profile_header_view.dart index daccb2d..279b570 100644 --- a/lib/features/user/views/profile_header_view.dart +++ b/lib/features/user/views/profile_header_view.dart @@ -24,6 +24,8 @@ class ProfileHeaderView extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + final bool hasIntro = introduction.trim().isNotEmpty; // 한줄소개 있없 확인 return Column( @@ -35,7 +37,7 @@ class ProfileHeaderView extends StatelessWidget { const SizedBox(height: 2), Text( introduction, - style: AppTypography.b1.copyWith(color: AppColors.gray3), + style: AppTypography.b1.copyWith(color: appColors.gray3), ), ], SizedBox(height: hasIntro ? 17 : 14), // 한줄소개 없으면 닉네임과 태그 사이 간격 좁힘 diff --git a/lib/features/user/widgets/my_challenge_card.dart b/lib/features/user/widgets/my_challenge_card.dart index 5f2968c..9a902b6 100644 --- a/lib/features/user/widgets/my_challenge_card.dart +++ b/lib/features/user/widgets/my_challenge_card.dart @@ -15,6 +15,8 @@ class MyChallengeCard extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + // 상태별 테마 설정 Color themeColor; String statusText; @@ -22,11 +24,11 @@ class MyChallengeCard extends StatelessWidget { switch (item.status) { case ChallengeStatus.success: - themeColor = AppColors.primaryAble; + themeColor = appColors.primaryAble; statusText = '완료'; break; case ChallengeStatus.fail: - themeColor = AppColors.notification; + themeColor = appColors.notification; statusText = '실패'; break; case ChallengeStatus.inProgress: @@ -39,10 +41,10 @@ class MyChallengeCard extends StatelessWidget { width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: Colors.white, + color: appColors.whiteToBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), - side: BorderSide(width: 0.69, color: AppColors.gray5), + side: BorderSide(width: 0.69, color: appColors.gray5), ), ), child: Column( @@ -63,7 +65,7 @@ class MyChallengeCard extends StatelessWidget { child: Text( item.challengeInfo.challengeBase.title, style: AppTypography.b3.copyWith( - color: AppColors.black, + color: appColors.blackToWhite, ), overflow: TextOverflow.ellipsis, maxLines: 1, @@ -78,25 +80,25 @@ class MyChallengeCard extends StatelessWidget { item.status == ChallengeStatus.inProgress ? '주 ${item.challengeInfo.weeklyFrequency}회, 완료까지 D-${item.challengeInfo.dDay}' : '$statusText일 ${item.failedDate?.toString().substring(0, 10).replaceAll('-', '/') ?? ''}', - style: AppTypography.b2.copyWith(color: AppColors.gray2), + style: AppTypography.b2.copyWith(color: appColors.gray2), ), // ✅ 스트리크/멤버 정보를 Column 안으로 이동 - _buildDetailInfoRow(themeColor), + _buildDetailInfoRow(themeColor, appColors), ], ), ), - _buildProgressText(item.rate, themeColor), + _buildProgressText(item.rate, themeColor, appColors), ], ), const SizedBox(height: 8), - _buildGaugeBar(item.rate, themeColor), + _buildGaugeBar(item.rate, themeColor, appColors), ], ), ); } // --- 하단 상세 정보 (스트리크 + 멤버 참여도) --- - Widget _buildDetailInfoRow(Color themeColor) { + Widget _buildDetailInfoRow(Color themeColor, AppColorsExtension appColors) { final info = item.challengeInfo; return Row( children: [ @@ -111,7 +113,7 @@ class MyChallengeCard extends StatelessWidget { ), Text( '${info.streakCount}일째', // ✅ currentStreak 매핑 - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith(color: appColors.blackToWhite), ), const SizedBox(width: 12), SvgPicture.asset( @@ -122,7 +124,7 @@ class MyChallengeCard extends StatelessWidget { // 예: 3/5명 인증 완료 Text( '${info.successParticipantCount}/${info.participantCount}명', - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith(color: appColors.blackToWhite), ), ], ); @@ -139,7 +141,11 @@ class MyChallengeCard extends StatelessWidget { ); } - Widget _buildProgressText(double progress, Color color) { + Widget _buildProgressText( + double progress, + Color color, + AppColorsExtension appColors, + ) { return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -147,18 +153,22 @@ class MyChallengeCard extends StatelessWidget { '${progress.toInt()}%', style: AppTypography.h2.copyWith(color: color), ), - Text('달성률', style: AppTypography.c1.copyWith(color: AppColors.gray2)), + Text('달성률', style: AppTypography.c1.copyWith(color: appColors.gray2)), ], ); } - Widget _buildGaugeBar(double progress, Color color) { + Widget _buildGaugeBar( + double progress, + Color color, + AppColorsExtension appColors, + ) { return ClipRRect( borderRadius: BorderRadius.circular(23), child: LinearProgressIndicator( value: (progress / 100).clamp(0.0, 1.0), minHeight: 4, - backgroundColor: AppColors.gray5, + backgroundColor: appColors.gray5, valueColor: AlwaysStoppedAnimation(color), ), ); diff --git a/lib/features/user/widgets/my_page_menu_item.dart b/lib/features/user/widgets/my_page_menu_item.dart index a06a260..711dec6 100644 --- a/lib/features/user/widgets/my_page_menu_item.dart +++ b/lib/features/user/widgets/my_page_menu_item.dart @@ -6,20 +6,22 @@ import '../../../core/theme/app_typography.dart'; // 반복되는 메뉴 버튼들을 독립적인 위젯으로 분리 class MyPageMenuItem extends StatelessWidget { final String title; - final Color textColor; + final Color? textColor; final VoidCallback onTap; final bool showArrow; const MyPageMenuItem({ super.key, required this.title, - this.textColor = AppColors.black, + this.textColor, required this.onTap, this.showArrow = false, }); @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(10), @@ -27,13 +29,18 @@ class MyPageMenuItem extends StatelessWidget { width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( - color: AppColors.gray5, + color: appColors.gray5, borderRadius: BorderRadius.circular(10), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(title, style: AppTypography.b1.copyWith(color: textColor)), + Text( + title, + style: AppTypography.b1.copyWith( + color: textColor ?? appColors.blackToWhite, + ), + ), ], ), ), diff --git a/lib/features/user/widgets/profile_image_menu.dart b/lib/features/user/widgets/profile_image_menu.dart index 292deea..31be1b1 100644 --- a/lib/features/user/widgets/profile_image_menu.dart +++ b/lib/features/user/widgets/profile_image_menu.dart @@ -17,12 +17,14 @@ class ProfileImageMenu extends StatelessWidget { @override Widget build(BuildContext context) { + final appColors = Theme.of(context).extension()!; + return Container( clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: Colors.white, + color: appColors.whiteToBlack, shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.gray4), + side: BorderSide(width: 1, color: appColors.gray4), borderRadius: BorderRadius.circular(10), ), shadows: const [ @@ -45,6 +47,7 @@ class ProfileImageMenu extends StatelessWidget { children: [ // 1. 프로필 사진 변경 버튼 _buildMenuOption( + appColors, iconPath: 'assets/images/icons/edit_icon.svg', // 예시 아이콘 경로 label: '프로필 사진 변경', onTap: onChangePressed, @@ -52,6 +55,7 @@ class ProfileImageMenu extends StatelessWidget { ), // 2. 프로필 사진 삭제 버튼 _buildMenuOption( + appColors, iconPath: 'assets/images/icons/small_trash_icon.svg', // 예시 아이콘 경로 label: '프로필 사진 삭제', onTap: onDeletePressed, @@ -62,7 +66,8 @@ class ProfileImageMenu extends StatelessWidget { ); } - Widget _buildMenuOption({ + Widget _buildMenuOption( + AppColorsExtension appColors, { required String iconPath, required String label, required VoidCallback onTap, @@ -75,9 +80,7 @@ class ProfileImageMenu extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( border: showBorder - ? const Border( - bottom: BorderSide(width: 1, color: AppColors.gray4), - ) + ? Border(bottom: BorderSide(width: 1, color: appColors.gray4)) : null, ), child: Row( @@ -89,8 +92,8 @@ class ProfileImageMenu extends StatelessWidget { height: 16, child: SvgPicture.asset( iconPath, - colorFilter: const ColorFilter.mode( - AppColors.black, + colorFilter: ColorFilter.mode( + appColors.blackToWhite, BlendMode.srcIn, ), ), @@ -99,7 +102,7 @@ class ProfileImageMenu extends StatelessWidget { // 텍스트 영역 Text( label, - style: AppTypography.b2.copyWith(color: AppColors.black), + style: AppTypography.b2.copyWith(color: appColors.blackToWhite), ), ], ), From 5d2e6ec575ab24091b3c2cdf77acb5c23d71b12d Mon Sep 17 00:00:00 2001 From: Seungvin Date: Thu, 6 Aug 2026 19:18:36 +0900 Subject: [PATCH 18/18] =?UTF-8?q?chore:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=95=8C=EB=A6=BC=20=ED=83=AD=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD=EC=83=89=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/challenge_invites_view.dart | 158 +++++++++--------- 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/lib/features/notification/views/challenge_invites_view.dart b/lib/features/notification/views/challenge_invites_view.dart index 145c7be..1099956 100644 --- a/lib/features/notification/views/challenge_invites_view.dart +++ b/lib/features/notification/views/challenge_invites_view.dart @@ -18,103 +18,105 @@ class ChallengeInvitesView extends ConsumerWidget { final state = ref.watch(challengeInviteProvider); final notifier = ref.read(challengeInviteProvider.notifier); + Widget content; + if (state.isLoading) { return Center( child: CircularProgressIndicator(color: appColors.primaryAble), ); - } - - if (state.invites.isEmpty) { + } else if (state.invites.isEmpty) { return Center( child: Text( '받은 챌린지 초대가 없습니다.', style: TextStyle(color: appColors.gray2), ), ); - } - - return RefreshIndicator( - onRefresh: () => notifier.fetchInvites(), - color: appColors.primaryAble, - child: ListView.builder( - padding: const EdgeInsets.only(top: 10, bottom: 40), - itemCount: state.invites.length, - itemBuilder: (context, index) { - final invite = state.invites[index]; + } else { + content = RefreshIndicator( + onRefresh: () => notifier.fetchInvites(), + color: appColors.primaryAble, + child: ListView.builder( + padding: const EdgeInsets.only(top: 10, bottom: 40), + itemCount: state.invites.length, + itemBuilder: (context, index) { + final invite = state.invites[index]; - return ChallengeInviteCard( - inviteChallenge: invite, - // 수락 콜백 연결 - onAccept: () async { - try { - await notifier.acceptInvite(invite.challengeInfo.base.id); + return ChallengeInviteCard( + inviteChallenge: invite, + // 수락 콜백 연결 + onAccept: () async { + try { + await notifier.acceptInvite(invite.challengeInfo.base.id); - ref.read(needsHomeRefreshProvider.notifier).state = - true; // 홈 화면 새로고침 필요 플래그 켜기 + ref.read(needsHomeRefreshProvider.notifier).state = + true; // 홈 화면 새로고침 필요 플래그 켜기 - if (context.mounted) { - // 위젯이 살아있을 때 안전한 Navigator를 미리 변수로 캡처해 둡니다. - final safeNavigator = Navigator.of(context); + if (context.mounted) { + // 위젯이 살아있을 때 안전한 Navigator를 미리 변수로 캡처해 둡니다. + final safeNavigator = Navigator.of(context); - // 다이얼로그(showDialog) 대신 스낵바를 띄웁니다. - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - '${invite.challengeInfo.base.title} 초대를 수락했습니다.', - ), - behavior: SnackBarBehavior.floating, // 화면 아래에 살짝 떠 있는 스타일 - duration: const Duration(seconds: 3), - // ✨ 꿀팁: 스낵바 우측에 '이동' 버튼 추가 - action: SnackBarAction( - label: '이동', - textColor: AppColors.primaryAble, // 앱 테마색 (초록) - onPressed: () { - // '이동'을 누르면 해당 챌린지 방으로 라우팅 - safeNavigator.push( - MaterialPageRoute( - builder: (context) => ChallengeMainScreen( - challengeId: invite.challengeInfo.base.id, + // 다이얼로그(showDialog) 대신 스낵바를 띄웁니다. + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '${invite.challengeInfo.base.title} 초대를 수락했습니다.', + ), + behavior: + SnackBarBehavior.floating, // 화면 아래에 살짝 떠 있는 스타일 + duration: const Duration(seconds: 3), + // 스낵바 우측에 '이동' 버튼 추가 + action: SnackBarAction( + label: '이동', + textColor: appColors.primaryAble, // 앱 테마색 (초록) + onPressed: () { + // '이동'을 누르면 해당 챌린지 방으로 라우팅 + safeNavigator.push( + MaterialPageRoute( + builder: (context) => ChallengeMainScreen( + challengeId: invite.challengeInfo.base.id, + ), ), - ), - ); - }, + ); + }, + ), ), - ), - ); + ); + } + } catch (e) { + // 수락 실패 시 에러 메시지 띄우기 + if (context.mounted) { + final errorMsg = e.toString().replaceAll('Exception: ', ''); + displayToast(context, errorMsg); + } } - } catch (e) { - // 수락 실패 시 에러 메시지 띄우기 - if (context.mounted) { - final errorMsg = e.toString().replaceAll('Exception: ', ''); - displayToast(context, errorMsg); - } - } - }, - // 거절 콜백 연결 - onReject: () async { - try { - await notifier.rejectInvite(invite.challengeInfo.base.id); + }, + // 거절 콜백 연결 + onReject: () async { + try { + await notifier.rejectInvite(invite.challengeInfo.base.id); - ref.read(needsHomeRefreshProvider.notifier).state = - true; // 홈 화면 새로고침 필요 플래그 켜기 + ref.read(needsHomeRefreshProvider.notifier).state = + true; // 홈 화면 새로고침 필요 플래그 켜기 - if (context.mounted) { - displayToast( - context, - '${invite.challengeInfo.base.title} 초대를 거절했습니다.', - ); - } - } catch (e) { - // 거절 실패 시 에러 메시지 띄우기 - if (context.mounted) { - final errorMsg = e.toString().replaceAll('Exception: ', ''); - displayToast(context, errorMsg); + if (context.mounted) { + displayToast( + context, + '${invite.challengeInfo.base.title} 초대를 거절했습니다.', + ); + } + } catch (e) { + // 거절 실패 시 에러 메시지 띄우기 + if (context.mounted) { + final errorMsg = e.toString().replaceAll('Exception: ', ''); + displayToast(context, errorMsg); + } } - } - }, - ); - }, - ), - ); + }, + ); + }, + ), + ); + } + return Container(color: appColors.gray5, child: content); } }