diff --git a/OPeace/Plugins/DependencyPackagePlugin/ProjectDescriptionHelpers/DependencyPackage/Extension+TargetDependencySPM.swift b/OPeace/Plugins/DependencyPackagePlugin/ProjectDescriptionHelpers/DependencyPackage/Extension+TargetDependencySPM.swift index a9d2798..05ff7f5 100644 --- a/OPeace/Plugins/DependencyPackagePlugin/ProjectDescriptionHelpers/DependencyPackage/Extension+TargetDependencySPM.swift +++ b/OPeace/Plugins/DependencyPackagePlugin/ProjectDescriptionHelpers/DependencyPackage/Extension+TargetDependencySPM.swift @@ -23,6 +23,7 @@ public extension TargetDependency.SPM { static let firebaseAuth = TargetDependency.external(name: "FirebaseAuth", condition: .none) static let firebaseAnalytics = TargetDependency.external(name: "FirebaseAnalytics", condition: .none) + static let firebaseRemoteConfig = TargetDependency.external(name: "FirebaseRemoteConfig", condition: .none) static let firebaseCrashlytics = TargetDependency.external(name: "FirebaseCrashlytics", condition: .none) static let swiftUIIntrospect = TargetDependency.external(name: "SwiftUIIntrospect", condition: .none) static let isEmojiView = TargetDependency.external(name: "ISEmojiView", condition: .none) diff --git a/OPeace/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift b/OPeace/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift index c80a160..4238c4a 100644 --- a/OPeace/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift +++ b/OPeace/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift @@ -9,7 +9,7 @@ import Foundation import ProjectDescription extension String { - public static func appVersion(version: String = "1.0.0") -> String { + public static func appVersion(version: String = "1.0.1") -> String { return version } diff --git a/OPeace/Projects/App/OPeace.xcodeproj/project.pbxproj b/OPeace/Projects/App/OPeace.xcodeproj/project.pbxproj index 068c246..3550162 100644 --- a/OPeace/Projects/App/OPeace.xcodeproj/project.pbxproj +++ b/OPeace/Projects/App/OPeace.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 01BC504ADDF32EDA601532F3 /* FirebaseCoreInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 569483D266B244AF8EE72A21 /* FirebaseCoreInternal.framework */; }; 01E0D1B3A4995E27E9036F9B /* UIKitNavigationShim.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 132FCFE59D7B9C7D00F4362A /* UIKitNavigationShim.framework */; }; 0213510E485216819148A1E4 /* DesignSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1949B50C00C8126136F9F239 /* DesignSystem.framework */; }; + 05EFBC81FEDC4C32D198AF6F /* libFirebaseRemoteConfig.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D38F2A8008EA5187ABE8447D /* libFirebaseRemoteConfig.a */; }; 064F0BAF98A31B08AA696D7A /* GoogleUtilities_MethodSwizzler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 924356B9EA4A57FBE1A8AA1B /* GoogleUtilities_MethodSwizzler.framework */; }; 06503497B42F4EB1099A8705 /* FirebaseAnalyticsWrapper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3039CE8A6EC8DD9ABE7DDC9F /* FirebaseAnalyticsWrapper.framework */; }; 06BE1F30EA1F2B51F27B6F87 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2ECC4C295DAC332701BD3A2E /* Preview Assets.xcassets */; }; @@ -18,6 +19,7 @@ 0A35E184B4D921E869B4E3E4 /* nanopb_nanopb.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 14CFCDCA19362B310799DC88 /* nanopb_nanopb.bundle */; }; 0A3F075832E9DDEEFAF5EC37 /* KituraContracts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BFDA6B6D2DD45A951461166 /* KituraContracts.framework */; }; 0B8157C4CD6AD45ABF2DD45F /* SwiftUIIntrospect.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16CFD0EF89905B6D9CCAB1F7 /* SwiftUIIntrospect.framework */; }; + 0C767DA7566F23124C128B17 /* Firebase_FirebaseABTesting.bundle in Resources */ = {isa = PBXBuildFile; fileRef = A00F775EFA9AE73D1FA4ECCE /* Firebase_FirebaseABTesting.bundle */; }; 0E0AC4E91A0AE44DE2B9E92F /* RxSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AC6EF06F272AE35807624AEF /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 0E8BD2FB4ECD02ECF8D79369 /* SwiftJWT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6F1F7E47ED2FCC1DC73C1C4 /* SwiftJWT.framework */; }; 0E9A12C52FC4AA815F0E69D0 /* TuistPlists+OPeace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5653B1ABE9B64C4B994BCBA4 /* TuistPlists+OPeace.swift */; }; @@ -55,6 +57,7 @@ 2552F6C9A88E89E2AACDF60F /* CustomDump.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCD20FDB83BC2E8595770794 /* CustomDump.framework */; }; 25A4BE4E05AAF915CBF4D1FA /* Cryptor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E1CBB94F98F3F822EBDA8A /* Cryptor.framework */; }; 25B61F7FA82FB97E73C24616 /* FirebaseRemoteConfigInterop.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A7FC6B2494718DB316EC5C /* FirebaseRemoteConfigInterop.framework */; }; + 26D0B213A3258364A15A13F8 /* FirebaseRemoteConfigInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6754F2B1CAC63F1037921F35 /* FirebaseRemoteConfigInternal.framework */; }; 27F51C0CD77C5D6D32550CF5 /* swift-composable-architecture_ComposableArchitecture.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 59918D10B5B47AE0CA5D556B /* swift-composable-architecture_ComposableArchitecture.bundle */; }; 2A4713217600EBC8975BADBB /* GoogleUtilities_GoogleUtilities_Logger.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5F2C47689F3448B3E5816627 /* GoogleUtilities_GoogleUtilities_Logger.bundle */; }; 2BC888B02B057B81295A4270 /* AuthKey_UGLKH5D2RL.p8 in Resources */ = {isa = PBXBuildFile; fileRef = 7FC5EC7FD02866F02C866F0F /* AuthKey_UGLKH5D2RL.p8 */; }; @@ -80,6 +83,7 @@ 364D87122FEF7058849B4C85 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7EA694C62C96F3AAEEC3ED0A /* StoreKit.framework */; }; 377FC8BBE81197F4329C36EA /* GoogleDataTransport_GoogleDataTransport.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 00234366CA750A795D33D3B5 /* GoogleDataTransport_GoogleDataTransport.bundle */; }; 38B853A83DEED546CC4C1BA5 /* FirebaseCoreExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F951859C16300D718D790CE /* FirebaseCoreExtension.framework */; }; + 39062F9399FFBF849EAFFB51 /* libFirebaseRemoteConfig.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D38F2A8008EA5187ABE8447D /* libFirebaseRemoteConfig.a */; }; 39CBE340BA91C90279F2DF66 /* TuistAssets+OPeaceQA.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B570466FE6B49D7EE86039 /* TuistAssets+OPeaceQA.swift */; }; 3A809BD1BF48112F63B4DD61 /* FirebaseRemoteConfigInterop.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A7FC6B2494718DB316EC5C /* FirebaseRemoteConfigInterop.framework */; }; 3A8191C3C538D2102118852A /* Service.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD35C1953A546211001D7C5E /* Service.framework */; }; @@ -98,6 +102,7 @@ 4621F1A5733C664596BDD9A7 /* FirebaseInstallations.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5204BCC7C59BED83E132DBCF /* FirebaseInstallations.framework */; }; 48E948C858BA12B1A2428C61 /* Firebase_FirebaseCore.bundle in Resources */ = {isa = PBXBuildFile; fileRef = EB3A506691B1B12EE559399F /* Firebase_FirebaseCore.bundle */; }; 4973189A6AAEE69DC82055E4 /* XCTestDynamicOverlay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DE957440EFEAB5E2C301F1D /* XCTestDynamicOverlay.framework */; }; + 49D44B9BC7B4CE649547F726 /* FirebaseABTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CFFC4E50ED5490CA2520E59 /* FirebaseABTesting.framework */; }; 49FC83940C92BDC9877CC1FA /* RxMoya.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C70AFFD360E78EA24D24D4A /* RxMoya.framework */; }; 4B3F683EEAF99EE42C0CF223 /* LoggerAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4980D13C864ABA2570E48F74 /* LoggerAPI.framework */; }; 4BF0A1855E50A5FB99B39401 /* GoogleUtilities_Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40F3693C5BE5CC45851FFAEC /* GoogleUtilities_Reachability.framework */; }; @@ -111,6 +116,7 @@ 5114AD7142815DED41592275 /* Logging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0D48BB7F23E463513CF2E8E /* Logging.framework */; }; 517C03290E4A5BC7EE7ABC2B /* CustomDump.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCD20FDB83BC2E8595770794 /* CustomDump.framework */; }; 52359C24C691F0C9856BBC63 /* GoogleUtilities_GoogleUtilities_Network.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = 9FE851A49FEDC5B110848E6A /* GoogleUtilities_GoogleUtilities_Network.bundle */; }; + 5285720668F9DF9419B5DD95 /* Firebase_FirebaseABTesting.bundle in Resources */ = {isa = PBXBuildFile; fileRef = A00F775EFA9AE73D1FA4ECCE /* Firebase_FirebaseABTesting.bundle */; }; 52EBB38248733D11F6F7ACAB /* GoogleUtilities_GoogleUtilities_Environment.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0B5781E71E34027E1F8A1DFA /* GoogleUtilities_GoogleUtilities_Environment.bundle */; }; 533B7B90E7B0B79C8250B265 /* FirebaseCoreExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F951859C16300D718D790CE /* FirebaseCoreExtension.framework */; }; 54455E0731975F8944EBDD25 /* GoogleUtilities_Logger.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9F4225125638960D3B5AF8F /* GoogleUtilities_Logger.framework */; }; @@ -142,6 +148,7 @@ 6B43BA1F3F58F63FB91F5046 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 33064E17F46C2F885FAB85E7 /* PrivacyInfo.xcprivacy */; }; 6C7535A0158B38F1B67455FC /* GoogleUtilities_AppDelegateSwizzler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 142EF8D5CC1D8D1601B6F3E2 /* GoogleUtilities_AppDelegateSwizzler.framework */; }; 6C768B8BB5FC312F3CFD0D25 /* OpeaceTestPlan.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 3A5201F617FBA5B358B12ABA /* OpeaceTestPlan.xctestplan */; }; + 6C9648CFB1E558687CB63C47 /* Firebase_FirebaseABTesting.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = A00F775EFA9AE73D1FA4ECCE /* Firebase_FirebaseABTesting.bundle */; }; 6CB5D792C0264ED2EEA9B4B2 /* TCACoordinators.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 751E50873D027887AE55D5D2 /* TCACoordinators.framework */; }; 6CC7444FB030B6D8AD18DA94 /* Firebase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D854C9173A511BF9726AD10B /* Firebase.framework */; }; 6CE1529FEB37637A164F0363 /* Utill.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F793A1AE8366CD0940432DC /* Utill.framework */; }; @@ -152,6 +159,7 @@ 6EE5A8A57FB3DB33328CFC65 /* Firebase_FirebaseInstallations.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B5F6E282BB29A8D7670468A4 /* Firebase_FirebaseInstallations.bundle */; }; 6F09C7505EF80B438534DD41 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BB50DDFBE8463CDFCC02C7F5 /* libz.tbd */; }; 6F165E92B749FB2DF2F94A63 /* Perception.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE0631089E46A99964F1F75E /* Perception.framework */; }; + 7034AD513DB70CB6477E3F11 /* Firebase_FirebaseABTesting.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = A00F775EFA9AE73D1FA4ECCE /* Firebase_FirebaseABTesting.bundle */; }; 70F9577142F8E18F2CFFA95D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26E05419CB93F2B2B23A14FC /* UIKit.framework */; }; 710FA4C5C4C3465B18359305 /* PopupView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5686D52821B9A80E66958A3 /* PopupView.framework */; }; 725E92C201C7E49714F94F47 /* ThirdPartys.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70CEE66DE71E36764B066F7F /* ThirdPartys.framework */; }; @@ -182,6 +190,7 @@ 7EBDA3ED64FC9D894E7033AE /* GoogleAppMeasurementIdentitySupport.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64FC3934D7F31766E2859437 /* GoogleAppMeasurementIdentitySupport.xcframework */; }; 7EFAACCA5850E992A9A194D7 /* UIKitNavigation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6363B03307A8F67F9AB4DE6 /* UIKitNavigation.framework */; }; 7F17ECEDDD0AB97E29B63835 /* UseCase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AA78626F61B7A844F78DC9C /* UseCase.framework */; }; + 82182C1E2D4F1FF6DEF8AC25 /* Firebase_FirebaseRemoteConfig.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = E890E1E2E06463D2D8AC00A3 /* Firebase_FirebaseRemoteConfig.bundle */; }; 82D45CA455EF9B4B195ED262 /* InternalCollectionsUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 005318C81558C9BF226FA9BE /* InternalCollectionsUtilities.framework */; }; 839861321E71E42D18B5B53F /* ThirdPartys.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70CEE66DE71E36764B066F7F /* ThirdPartys.framework */; }; 83FD415F10EF29EDAC89347B /* Clocks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3DC95C364244F938C7DFCCA /* Clocks.framework */; }; @@ -196,6 +205,7 @@ 8E07F1619598615C6736F044 /* GoogleUtilities_UserDefaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32257DB7570012C256C3F7C3 /* GoogleUtilities_UserDefaults.framework */; }; 8E17BE3A922B72154286A801 /* ConcurrencyExtras.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7CD16D4BE579298838632D /* ConcurrencyExtras.framework */; }; 8FAA6055E1C01F7EE6D4C8C4 /* DependenciesMacros.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54F7EF819168A470F989D372 /* DependenciesMacros.framework */; }; + 8FB0AA8471CF9432808AE431 /* Firebase_FirebaseRemoteConfig.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = E890E1E2E06463D2D8AC00A3 /* Firebase_FirebaseRemoteConfig.bundle */; }; 906D4BA4F47C951B91EB957D /* Firebase_FirebaseCrashlytics.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = 9E449D7F0BCB1EBCA64659E1 /* Firebase_FirebaseCrashlytics.bundle */; }; 90A8567380AD78E56709285E /* GoogleDataTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6031D9519EC6FC01AAA5886A /* GoogleDataTransport.framework */; }; 91D4CAB3A03C653EC2FFDD3D /* GoogleAppMeasurementTarget.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60B0C1283CB30AAF071C5C53 /* GoogleAppMeasurementTarget.framework */; }; @@ -206,6 +216,7 @@ 93AEF0CA0400F21761297155 /* KakaoSDKAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8A8DE4B98B94ABFDFBE12D4 /* KakaoSDKAuth.framework */; }; 93BE67F58BC1AA53AC238D61 /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA1E4FC25D9C7F95A96380 /* AppReducer.swift */; }; 943E2AE0460C86195DE04CFC /* FirebaseSessionsObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0301551DA340BF108E0D226D /* FirebaseSessionsObjC.framework */; }; + 948C6151C603685E484180F2 /* Firebase_FirebaseRemoteConfig.bundle in Resources */ = {isa = PBXBuildFile; fileRef = E890E1E2E06463D2D8AC00A3 /* Firebase_FirebaseRemoteConfig.bundle */; }; 96C3EBD31C031956CCB2284B /* Cryptor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E1CBB94F98F3F822EBDA8A /* Cryptor.framework */; }; 97F9E20380EAFD5D0FED0E99 /* GoogleUtilities_GoogleUtilities_UserDefaults.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 19EACC688ECB7E7FB0323B5A /* GoogleUtilities_GoogleUtilities_UserDefaults.bundle */; }; 9A80DBEEC842AF7059B33C16 /* OSLog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D12E17A75CA23D981ACEC5F /* OSLog.framework */; }; @@ -235,6 +246,7 @@ A69D8ECABBA6464AF0C5E16C /* CryptorRSA.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20FF3753DF0F0436C2B9F83A /* CryptorRSA.framework */; }; A6E704845C23931FBF7231B9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 697B8FCF31717CABCDD5C879 /* ContentView.swift */; }; A727BAFE8677A640C4EFED5D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66E408F6FE139A5449BB799B /* Security.framework */; }; + A75797D0B89878EC9936B1F4 /* Firebase_FirebaseRemoteConfig.bundle in Resources */ = {isa = PBXBuildFile; fileRef = E890E1E2E06463D2D8AC00A3 /* Firebase_FirebaseRemoteConfig.bundle */; }; A7822C03275E31F6BA34461D /* GoogleUtilities_GoogleUtilities_Environment.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = 0B5781E71E34027E1F8A1DFA /* GoogleUtilities_GoogleUtilities_Environment.bundle */; }; A82BB99ADB24664EC664FCCB /* PopupView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5686D52821B9A80E66958A3 /* PopupView.framework */; }; A946DEF0FBAD65BF9106B0B8 /* LoggerAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4980D13C864ABA2570E48F74 /* LoggerAPI.framework */; }; @@ -246,6 +258,7 @@ B1B8A01C6BCC3B6D1E13F998 /* CombineMoya.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D9BE4AB849FDC214806C108 /* CombineMoya.framework */; }; B1DAA02DA98B03DFED7F46A4 /* GoogleUtilities_GoogleUtilities_Reachability.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = 7C030D1B5F8FC2D48A45F7E6 /* GoogleUtilities_GoogleUtilities_Reachability.bundle */; }; B3A247D998E1E73D9417B92B /* FlowStacks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7E7806E201F690FB4F91817 /* FlowStacks.framework */; }; + B5A3C734B6CDA3D491EA9CE4 /* FirebaseRemoteConfigInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6754F2B1CAC63F1037921F35 /* FirebaseRemoteConfigInternal.framework */; }; B5BE8517EDD79872E3222EF7 /* DesignSystem_DesignSystem.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 7FD3567291A1386FF6591C0A /* DesignSystem_DesignSystem.bundle */; }; B695AC6BE4A4870A3FBA7F1C /* Promises_Promises.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5A04370E3537A6E7879DA378 /* Promises_Promises.bundle */; }; B7FA98CBA34619C96BB2BB39 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0C7F7074430E076D9A8F093 /* OrderedCollections.framework */; }; @@ -302,6 +315,7 @@ DE6FBA0741A4702D8E7ECBA9 /* KakaoOpenSDK_KakaoSDKCommon.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = 47C809FF8501FA41CDDB53F4 /* KakaoOpenSDK_KakaoSDKCommon.bundle */; }; DFD08F7C9AFF0DC7AAFB235C /* SwiftNavigation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCC4F4A7FE171CDC02417DC9 /* SwiftNavigation.framework */; }; E1ACDF3A6C04A42F6E4E2786 /* Firebase_FirebaseCoreInternal.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3950F0B63D4AE14552B6DDB9 /* Firebase_FirebaseCoreInternal.bundle */; }; + E21760578B9C1640C29BE3B3 /* FirebaseSharedSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEA1991A5985EDA360A7C40F /* FirebaseSharedSwift.framework */; }; E3966D8124FAB3CE251C8649 /* GoogleUtilities_GoogleUtilities_Reachability.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = 7C030D1B5F8FC2D48A45F7E6 /* GoogleUtilities_GoogleUtilities_Reachability.bundle */; }; E3EEC42AF9EABB8E517E17ED /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D317BF12399D712D8ED8697 /* AppView.swift */; }; E44A109BE0CAE50682280568 /* GoogleUtilities_GoogleUtilities_Network.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 9FE851A49FEDC5B110848E6A /* GoogleUtilities_GoogleUtilities_Network.bundle */; }; @@ -321,6 +335,8 @@ F1D15ABA6EA579227C29EB6E /* third_party_IsAppEncrypted.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC35EDBAB9D33C27F4D370E1 /* third_party_IsAppEncrypted.framework */; }; F25669293A0EC1821294C384 /* GoogleUtilities_GoogleUtilities_UserDefaults.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 19EACC688ECB7E7FB0323B5A /* GoogleUtilities_GoogleUtilities_UserDefaults.bundle */; }; F35893BD170A109EBFEE5574 /* CryptorECC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20A64602EEEF583F213B60F2 /* CryptorECC.framework */; }; + F45BDA75461B67CBD6BD29E1 /* FirebaseSharedSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEA1991A5985EDA360A7C40F /* FirebaseSharedSwift.framework */; }; + F499B69ED02CB22DD6780EB1 /* FirebaseABTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CFFC4E50ED5490CA2520E59 /* FirebaseABTesting.framework */; }; F4E3EDBD1817F9579BC56156 /* SortedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D95B9E0679952090A721AC1 /* SortedCollections.framework */; }; F534F08CACCA7C5B3771F26D /* DiContainer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 588A8A67B23F6F2DF15EB9FF /* DiContainer.framework */; }; F6341EAF42D0D9C37304AC4F /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BADB783234D3C27BCB2CEE5 /* libc++.tbd */; }; @@ -374,11 +390,13 @@ files = ( 4E3AD5D940177356986F407E /* Alamofire_Alamofire.bundle in Dependencies */, 188385B73570EF9A0F5DF15C /* DesignSystem_DesignSystem.bundle in Dependencies */, + 6C9648CFB1E558687CB63C47 /* Firebase_FirebaseABTesting.bundle in Dependencies */, 6094D6EA0721D7226E8D7022 /* Firebase_FirebaseCore.bundle in Dependencies */, C1C798446807E8CAF88291C8 /* Firebase_FirebaseCoreExtension.bundle in Dependencies */, 62265A4DB79A0DE55F2F1911 /* Firebase_FirebaseCoreInternal.bundle in Dependencies */, 906D4BA4F47C951B91EB957D /* Firebase_FirebaseCrashlytics.bundle in Dependencies */, 123CCCE5389578EB2645B50B /* Firebase_FirebaseInstallations.bundle in Dependencies */, + 82182C1E2D4F1FF6DEF8AC25 /* Firebase_FirebaseRemoteConfig.bundle in Dependencies */, 16EC2E9692A336F1349FCB81 /* GoogleDataTransport_GoogleDataTransport.bundle in Dependencies */, 4435A60D329891C752B554FF /* GoogleUtilities_GoogleUtilities_AppDelegateSwizzler.bundle in Dependencies */, 34FC0DBECCAC996C700B2CB5 /* GoogleUtilities_GoogleUtilities_Environment.bundle in Dependencies */, @@ -404,11 +422,13 @@ files = ( 0FAAF4A22EBF607C49E6F14B /* Alamofire_Alamofire.bundle in Dependencies */, EDFC6B9C3EA569A0C9A4DDD3 /* DesignSystem_DesignSystem.bundle in Dependencies */, + 7034AD513DB70CB6477E3F11 /* Firebase_FirebaseABTesting.bundle in Dependencies */, BDC7C548BB7401D724640BB8 /* Firebase_FirebaseCore.bundle in Dependencies */, DD7FF029AF5D76F309488FBD /* Firebase_FirebaseCoreExtension.bundle in Dependencies */, 1B869B7A503BB24473D77376 /* Firebase_FirebaseCoreInternal.bundle in Dependencies */, 6E526A42AE68FF3149B348CB /* Firebase_FirebaseCrashlytics.bundle in Dependencies */, 4EBEA7F00FBDAACCC31FCC3D /* Firebase_FirebaseInstallations.bundle in Dependencies */, + 8FB0AA8471CF9432808AE431 /* Firebase_FirebaseRemoteConfig.bundle in Dependencies */, C5A01D022DDB25BCCB050C00 /* GoogleDataTransport_GoogleDataTransport.bundle in Dependencies */, BC6F85225E81AD97FFBB3A00 /* GoogleUtilities_GoogleUtilities_AppDelegateSwizzler.bundle in Dependencies */, A7822C03275E31F6BA34461D /* GoogleUtilities_GoogleUtilities_Environment.bundle in Dependencies */, @@ -532,7 +552,9 @@ 65E1CBB94F98F3F822EBDA8A /* Cryptor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cryptor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 666517ACAEAFE67DE9DE1F99 /* ThirdParty.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ThirdParty.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 66E408F6FE139A5449BB799B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + 6754F2B1CAC63F1037921F35 /* FirebaseRemoteConfigInternal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FirebaseRemoteConfigInternal.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 697B8FCF31717CABCDD5C879 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 6CFFC4E50ED5490CA2520E59 /* FirebaseABTesting.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FirebaseABTesting.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6D9BE4AB849FDC214806C108 /* CombineMoya.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CombineMoya.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6FA69F98539B6C93DFF8B85B /* FirebaseCrashlyticsSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FirebaseCrashlyticsSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7045A18C58DE5AB0F9204357 /* FirebaseAnalytics.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:EQHXZ8M8AV:Google LLC"; lastKnownFileType = wrapper.xcframework; path = FirebaseAnalytics.xcframework; sourceTree = ""; }; @@ -561,6 +583,7 @@ 9C70AFFD360E78EA24D24D4A /* RxMoya.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxMoya.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9E449D7F0BCB1EBCA64659E1 /* Firebase_FirebaseCrashlytics.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firebase_FirebaseCrashlytics.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 9FE851A49FEDC5B110848E6A /* GoogleUtilities_GoogleUtilities_Network.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GoogleUtilities_GoogleUtilities_Network.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + A00F775EFA9AE73D1FA4ECCE /* Firebase_FirebaseABTesting.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firebase_FirebaseABTesting.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; A3B570466FE6B49D7EE86039 /* TuistAssets+OPeaceQA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistAssets+OPeaceQA.swift"; sourceTree = ""; }; A445C66880DC63E9958A59AA /* OPeace.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OPeace.app; sourceTree = BUILT_PRODUCTS_DIR; }; AC35EDBAB9D33C27F4D370E1 /* third_party_IsAppEncrypted.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = third_party_IsAppEncrypted.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -577,6 +600,7 @@ C0D48BB7F23E463513CF2E8E /* Logging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Logging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C8C4D636798393C69B96A513 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/CoreTelephony.framework; sourceTree = DEVELOPER_DIR; }; CCC4F4A7FE171CDC02417DC9 /* SwiftNavigation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftNavigation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D38F2A8008EA5187ABE8447D /* libFirebaseRemoteConfig.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFirebaseRemoteConfig.a; sourceTree = BUILT_PRODUCTS_DIR; }; D527E4719932A5E1B98FC9B0 /* OPeace_QA.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OPeace_QA.app; sourceTree = BUILT_PRODUCTS_DIR; }; D55C1F4027FA10007AED142B /* Firebase_FirebaseCoreExtension.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firebase_FirebaseCoreExtension.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; D854C9173A511BF9726AD10B /* Firebase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Firebase.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -585,10 +609,12 @@ DCD4C24D21BADD2F06CF052A /* EventLimiter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = EventLimiter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DD35C1953A546211001D7C5E /* Service.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Service.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DE77E1B0D4071D61EFEBAD16 /* OPeaceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OPeaceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DEA1991A5985EDA360A7C40F /* FirebaseSharedSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FirebaseSharedSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E0C7F7074430E076D9A8F093 /* OrderedCollections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OrderedCollections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E3022953F3DC2156F43F5302 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E3123F5221E4AF12459D7F8B /* GoogleUtilities_Network.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GoogleUtilities_Network.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E7A5934BE7249FFF11EF99CA /* GoogleUtilities_GoogleUtilities_NSData.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GoogleUtilities_GoogleUtilities_NSData.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + E890E1E2E06463D2D8AC00A3 /* Firebase_FirebaseRemoteConfig.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firebase_FirebaseRemoteConfig.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; E9AF3BE6FAD546CD1A68EE13 /* TuistAssets+OPeace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistAssets+OPeace.swift"; sourceTree = ""; }; E9F4225125638960D3B5AF8F /* GoogleUtilities_Logger.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GoogleUtilities_Logger.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EB3A506691B1B12EE559399F /* Firebase_FirebaseCore.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firebase_FirebaseCore.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -639,6 +665,7 @@ A56552E5B9FBFB9D87823313 /* EventLimiter.framework in Frameworks */, E98304EFFF93519F9F6378A8 /* FBLPromises.framework in Frameworks */, 6CC7444FB030B6D8AD18DA94 /* Firebase.framework in Frameworks */, + 49D44B9BC7B4CE649547F726 /* FirebaseABTesting.framework in Frameworks */, B9EB366E3F5EF68AA04F5F0E /* FirebaseAnalyticsTarget.framework in Frameworks */, 06503497B42F4EB1099A8705 /* FirebaseAnalyticsWrapper.framework in Frameworks */, 24673324622F9DAF2AA1CB8F /* libFirebaseCore.a in Frameworks */, @@ -647,9 +674,12 @@ 193C80816BF22EA61CDDC008 /* libFirebaseCrashlytics.a in Frameworks */, 4D900A69979E2316B79F1D1E /* FirebaseCrashlyticsSwift.framework in Frameworks */, 4621F1A5733C664596BDD9A7 /* FirebaseInstallations.framework in Frameworks */, + 39062F9399FFBF849EAFFB51 /* libFirebaseRemoteConfig.a in Frameworks */, + 26D0B213A3258364A15A13F8 /* FirebaseRemoteConfigInternal.framework in Frameworks */, 25B61F7FA82FB97E73C24616 /* FirebaseRemoteConfigInterop.framework in Frameworks */, D4539B0E9DFB74BC4D2B6E78 /* FirebaseSessions.framework in Frameworks */, 336C0C47F67F86F1A13EBE0C /* FirebaseSessionsObjC.framework in Frameworks */, + E21760578B9C1640C29BE3B3 /* FirebaseSharedSwift.framework in Frameworks */, B3A247D998E1E73D9417B92B /* FlowStacks.framework in Frameworks */, EBF24824A2B0B45D92D69CB2 /* Foundations.framework in Frameworks */, 151AD4C8D38450B1B0527189 /* GoogleAppMeasurementTarget.framework in Frameworks */, @@ -754,6 +784,7 @@ 5D93069BA3F3A84F021E47EC /* EventLimiter.framework in Frameworks */, 12CC918D01E562BC925E26E2 /* FBLPromises.framework in Frameworks */, 79587001CFBAA3AEDF4AC34E /* Firebase.framework in Frameworks */, + F499B69ED02CB22DD6780EB1 /* FirebaseABTesting.framework in Frameworks */, FFA401452E5CA82B5908B8C1 /* FirebaseAnalyticsTarget.framework in Frameworks */, 456C1360B5CD42406052C1AA /* FirebaseAnalyticsWrapper.framework in Frameworks */, 734B03DA826BF318DEF9206F /* libFirebaseCore.a in Frameworks */, @@ -762,9 +793,12 @@ 2ED7D8B9914B72C4D7FBDC68 /* libFirebaseCrashlytics.a in Frameworks */, D83AE56536763E998810C7AE /* FirebaseCrashlyticsSwift.framework in Frameworks */, 1F49154638DDC02F4D359F1E /* FirebaseInstallations.framework in Frameworks */, + 05EFBC81FEDC4C32D198AF6F /* libFirebaseRemoteConfig.a in Frameworks */, + B5A3C734B6CDA3D491EA9CE4 /* FirebaseRemoteConfigInternal.framework in Frameworks */, 3A809BD1BF48112F63B4DD61 /* FirebaseRemoteConfigInterop.framework in Frameworks */, D51C32F0ADF080046D06A517 /* FirebaseSessions.framework in Frameworks */, 943E2AE0460C86195DE04CFC /* FirebaseSessionsObjC.framework in Frameworks */, + F45BDA75461B67CBD6BD29E1 /* FirebaseSharedSwift.framework in Frameworks */, 58A5C542AC2076F4F1145534 /* FlowStacks.framework in Frameworks */, CA61519B52C68608ED8339CA /* Foundations.framework in Frameworks */, 91D4CAB3A03C653EC2FFDD3D /* GoogleAppMeasurementTarget.framework in Frameworks */, @@ -898,21 +932,26 @@ 588A8A67B23F6F2DF15EB9FF /* DiContainer.framework */, DCD4C24D21BADD2F06CF052A /* EventLimiter.framework */, 0B06E9281676A52997687620 /* FBLPromises.framework */, + A00F775EFA9AE73D1FA4ECCE /* Firebase_FirebaseABTesting.bundle */, EB3A506691B1B12EE559399F /* Firebase_FirebaseCore.bundle */, D55C1F4027FA10007AED142B /* Firebase_FirebaseCoreExtension.bundle */, 3950F0B63D4AE14552B6DDB9 /* Firebase_FirebaseCoreInternal.bundle */, 9E449D7F0BCB1EBCA64659E1 /* Firebase_FirebaseCrashlytics.bundle */, B5F6E282BB29A8D7670468A4 /* Firebase_FirebaseInstallations.bundle */, + E890E1E2E06463D2D8AC00A3 /* Firebase_FirebaseRemoteConfig.bundle */, D854C9173A511BF9726AD10B /* Firebase.framework */, + 6CFFC4E50ED5490CA2520E59 /* FirebaseABTesting.framework */, 50193CBD9B3B1878F0A57488 /* FirebaseAnalyticsTarget.framework */, 3039CE8A6EC8DD9ABE7DDC9F /* FirebaseAnalyticsWrapper.framework */, 8F951859C16300D718D790CE /* FirebaseCoreExtension.framework */, 569483D266B244AF8EE72A21 /* FirebaseCoreInternal.framework */, 6FA69F98539B6C93DFF8B85B /* FirebaseCrashlyticsSwift.framework */, 5204BCC7C59BED83E132DBCF /* FirebaseInstallations.framework */, + 6754F2B1CAC63F1037921F35 /* FirebaseRemoteConfigInternal.framework */, B5A7FC6B2494718DB316EC5C /* FirebaseRemoteConfigInterop.framework */, B11694366D59709B9C38785B /* FirebaseSessions.framework */, 0301551DA340BF108E0D226D /* FirebaseSessionsObjC.framework */, + DEA1991A5985EDA360A7C40F /* FirebaseSharedSwift.framework */, B7E7806E201F690FB4F91817 /* FlowStacks.framework */, 2DA43DBB964683DE5164F153 /* Foundations.framework */, 60B0C1283CB30AAF071C5C53 /* GoogleAppMeasurementTarget.framework */, @@ -946,6 +985,7 @@ 7BFDA6B6D2DD45A951461166 /* KituraContracts.framework */, 394610A0A5E087C4F2157343 /* libFirebaseCore.a */, 4A57EAC1B95DE8643BA97FF1 /* libFirebaseCrashlytics.a */, + D38F2A8008EA5187ABE8447D /* libFirebaseRemoteConfig.a */, 4980D13C864ABA2570E48F74 /* LoggerAPI.framework */, C0D48BB7F23E463513CF2E8E /* Logging.framework */, 4DC839EB875B512EDF1EA911 /* LogMacro.framework */, @@ -1307,11 +1347,13 @@ 6B43BA1F3F58F63FB91F5046 /* PrivacyInfo.xcprivacy in Resources */, F8F2A067E0BDD5FAEC2FED0A /* Alamofire_Alamofire.bundle in Resources */, 2E2B831094AC55C4FEAAB77D /* DesignSystem_DesignSystem.bundle in Resources */, + 0C767DA7566F23124C128B17 /* Firebase_FirebaseABTesting.bundle in Resources */, 161D833D68549B3685321944 /* Firebase_FirebaseCore.bundle in Resources */, D024ADF830D17C2A6EEBB11F /* Firebase_FirebaseCoreExtension.bundle in Resources */, E1ACDF3A6C04A42F6E4E2786 /* Firebase_FirebaseCoreInternal.bundle in Resources */, D154AACD63A56D498E6BED8B /* Firebase_FirebaseCrashlytics.bundle in Resources */, 6EE5A8A57FB3DB33328CFC65 /* Firebase_FirebaseInstallations.bundle in Resources */, + A75797D0B89878EC9936B1F4 /* Firebase_FirebaseRemoteConfig.bundle in Resources */, 377FC8BBE81197F4329C36EA /* GoogleDataTransport_GoogleDataTransport.bundle in Resources */, CB32C6EBA9B003D6340BF6DF /* GoogleUtilities_GoogleUtilities_AppDelegateSwizzler.bundle in Resources */, 785962C8C1C2B365DB901362 /* GoogleUtilities_GoogleUtilities_Environment.bundle in Resources */, @@ -1349,11 +1391,13 @@ CF09C689A1E7C9FA0A7BB0C9 /* PrivacyInfo.xcprivacy in Resources */, 76ACBC0BF6722F6C8AD3CD6C /* Alamofire_Alamofire.bundle in Resources */, B5BE8517EDD79872E3222EF7 /* DesignSystem_DesignSystem.bundle in Resources */, + 5285720668F9DF9419B5DD95 /* Firebase_FirebaseABTesting.bundle in Resources */, 48E948C858BA12B1A2428C61 /* Firebase_FirebaseCore.bundle in Resources */, 636A11FA242F58830C9015E2 /* Firebase_FirebaseCoreExtension.bundle in Resources */, D4DD95DC363E093621A654E3 /* Firebase_FirebaseCoreInternal.bundle in Resources */, C644B8034F752CD543EE4BD2 /* Firebase_FirebaseCrashlytics.bundle in Resources */, A342386AB63BF655BF3B714F /* Firebase_FirebaseInstallations.bundle in Resources */, + 948C6151C603685E484180F2 /* Firebase_FirebaseRemoteConfig.bundle in Resources */, 78D579F31A8887F8CD43ACF2 /* GoogleDataTransport_GoogleDataTransport.bundle in Resources */, E7953AC4AC95A734B1764D73 /* GoogleUtilities_GoogleUtilities_AppDelegateSwizzler.bundle in Resources */, 52EBB38248733D11F6F7ACAB /* GoogleUtilities_GoogleUtilities_Environment.bundle in Resources */, @@ -1464,10 +1508,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -1485,12 +1531,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -1506,7 +1554,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace; PRODUCT_NAME = OPeace; SDKROOT = iphoneos; @@ -1553,10 +1601,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -1574,12 +1624,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -1595,7 +1647,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace; PRODUCT_NAME = OPeace_QA; SDKROOT = iphoneos; @@ -1634,10 +1686,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -1656,12 +1710,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -1677,7 +1733,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace.OPeaceTest; PRODUCT_NAME = OPeaceTests; SDKROOT = iphoneos; @@ -1796,10 +1852,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -1818,12 +1876,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -1839,7 +1899,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace.OPeaceTest; PRODUCT_NAME = OPeaceTests; SDKROOT = iphoneos; @@ -1888,10 +1948,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -1909,12 +1971,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -1930,7 +1994,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace; PRODUCT_NAME = OPeace_QA; SDKROOT = iphoneos; @@ -1968,10 +2032,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -1990,12 +2056,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -2011,7 +2079,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace.OPeaceTest; PRODUCT_NAME = OPeaceTests; SDKROOT = iphoneos; @@ -2060,10 +2128,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -2081,12 +2151,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -2102,7 +2174,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace; PRODUCT_NAME = OPeace; SDKROOT = iphoneos; @@ -2148,10 +2220,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -2169,12 +2243,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -2190,7 +2266,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace; PRODUCT_NAME = OPeace; SDKROOT = iphoneos; @@ -2388,10 +2464,12 @@ "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/Crashlytics/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseABTesting/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseAnalyticsWrapper/include", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Extension", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseCore/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseInstallations/Source/Library/Public", + "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseRemoteConfig/Sources/Public", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/FirebaseSessions/SourcesObjC", "$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/SwiftPM-PlatformExclude/FirebaseAnalyticsWrap/include", @@ -2409,12 +2487,14 @@ "$(inherited)", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap", + "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap", @@ -2430,7 +2510,7 @@ "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap", "-fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap", ); - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/firebase-ios-sdk/CoreOnly/Sources/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/checkouts/promises/Sources/FBLPromises/include/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseABTesting/FirebaseABTesting.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsTarget/FirebaseAnalyticsTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseAnalyticsWrapper/FirebaseAnalyticsWrapper.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCore/FirebaseCore.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCoreExtension/FirebaseCoreExtension.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseCrashlytics/FirebaseCrashlytics.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseInstallations/FirebaseInstallations.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseRemoteConfigInternal/FirebaseRemoteConfigInternal.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/FirebaseSessionsObjC/FirebaseSessionsObjC.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleAppMeasurementTarget/GoogleAppMeasurementTarget.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleDataTransport/GoogleDataTransport.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_AppDelegateSwizzler/GoogleUtilities_AppDelegateSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Environment/GoogleUtilities_Environment.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Logger/GoogleUtilities_Logger.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_MethodSwizzler/GoogleUtilities_MethodSwizzler.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_NSData/GoogleUtilities_NSData.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Network/GoogleUtilities_Network.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_Reachability/GoogleUtilities_Reachability.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/GoogleUtilities_UserDefaults/GoogleUtilities_UserDefaults.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/UIKitNavigationShim/UIKitNavigationShim.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/nanopb/nanopb.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/../../Tuist/.build/tuist-derived/third_party_IsAppEncrypted/third_party_IsAppEncrypted.modulemap -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/DependenciesMacrosPlugin#DependenciesMacrosPlugin -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/PerceptionMacros#PerceptionMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/LogMacroMacro#LogMacroMacro -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/ComposableArchitectureMacros#ComposableArchitectureMacros -load-plugin-executable $BUILD_DIR/Debug$EFFECTIVE_PLATFORM_NAME/CasePathsMacros#CasePathsMacros"; PRODUCT_BUNDLE_IDENTIFIER = io.Opeace.Opeace; PRODUCT_NAME = OPeace_QA; SDKROOT = iphoneos; diff --git a/OPeace/Projects/App/OpeaceTests/Sources/ProfileTest.swift b/OPeace/Projects/App/OpeaceTests/Sources/ProfileTest.swift index 1ab425b..2d0b053 100644 --- a/OPeace/Projects/App/OpeaceTests/Sources/ProfileTest.swift +++ b/OPeace/Projects/App/OpeaceTests/Sources/ProfileTest.swift @@ -18,51 +18,49 @@ import Utills @MainActor struct ProfileTest { - let store = TestStore(initialState: Profile.State()) { - Profile() - } withDependencies: { - let repository = AuthRepository() - $0.authUseCase = AuthUseCase(repository: repository) + let store = TestStore(initialState: Profile.State()) { + Profile() + } withDependencies: { + let repository = AuthRepository() + $0.authUseCase = AuthUseCase(repository: repository) + } + + @Test("유저 정보 조회") + func testUserInfo_유저정보_조회() async throws { + var mockUpdateUserInfo = UpdateUserInfoDTOModel.mockModel + + await store.send(.async(.fetchUserProfileResponse(.success(mockUpdateUserInfo)))) { state in + state.profileUserModel = mockUpdateUserInfo } - - @Test("유저 정보 조회") - func testUserInfo_유저정보_조회() async throws { - var mockUpdateUserInfo = UpdateUserInfoModel.mockModel - - await store.send(.async(.fetchUserProfileResponse(.success(mockUpdateUserInfo)))) { state in - state.profileUserModel = mockUpdateUserInfo - } - - await store.send(.async(.fetchUser)) - - store.assert { state in - state.profileUserModel = mockUpdateUserInfo - } - - let mockError = CustomError.unknownError("Failed to fetch user info") - - - await store.send(.async(.fetchUserProfileResponse(.failure(mockError)))) { - XCTAssertNil($0.profileUserModel) - } - - - - await store.finish() - store.exhaustivity = .off + + await store.send(.async(.fetchUser)) + + store.assert { state in + state.profileUserModel = mockUpdateUserInfo } - @Test("유저 정보 업데이트") - func testUserInfo_유저정보_업데이트() async throws { - let testEditStore = TestStore(initialState: EditProfile.State()) { - EditProfile() - } withDependencies: { - let repository = AuthRepository() - $0.authUseCase = AuthUseCase(repository: repository) - let signupRepository = SingUpRepository() - $0.signUpUseCase = SignUpUseCase(repository: signupRepository) - } - - await testEditStore.send(.async(.updateUserInfo(nickName: "로이", year: 1998, job: "개발", generation: "Z 세대"))) + let mockError = CustomError.unknownError("Failed to fetch user info") + + + await store.send(.async(.fetchUserProfileResponse(.failure(mockError)))) { + XCTAssertNil($0.profileUserModel) + } + + await store.finish() + store.exhaustivity = .off + } + + @Test("유저 정보 업데이트") + func testUserInfo_유저정보_업데이트() async throws { + let testEditStore = TestStore(initialState: EditProfile.State()) { + EditProfile() + } withDependencies: { + let repository = AuthRepository() + $0.authUseCase = AuthUseCase(repository: repository) + let signupRepository = SingUpRepository() + $0.signUpUseCase = SignUpUseCase(repository: signupRepository) } + + await testEditStore.send(.async(.updateUserInfo(nickName: "로이", year: 1998, job: "개발", generation: "Z 세대"))) + } } diff --git a/OPeace/Projects/App/Sources/Application/AppDelegate.swift b/OPeace/Projects/App/Sources/Application/AppDelegate.swift index ff63b4b..3d38d17 100644 --- a/OPeace/Projects/App/Sources/Application/AppDelegate.swift +++ b/OPeace/Projects/App/Sources/Application/AppDelegate.swift @@ -6,16 +6,26 @@ // import UIKit import Firebase +import FirebaseRemoteConfig class AppDelegate: UIResponder, UIApplicationDelegate { + var remoteConfig: RemoteConfig! func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { FirebaseApp.configure() + FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.min) + remoteConfig = RemoteConfig.remoteConfig() + let settings = RemoteConfigSettings() + settings.minimumFetchInterval = 0 + remoteConfig.configSettings = settings + remoteConfig.setDefaults(fromPlist: "RemoteConfigDefaults") return true } + + func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/CheckUserVerify.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/CheckUserVerify.swift deleted file mode 100644 index 3a0db73..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Auth/CheckUserVerify.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// CheckUserVerify.swift -// Model -// -// Created by 서원지 on 8/6/24. -// -import Foundation - -public struct CheckUserVerifyModel: Codable, Equatable { - public let detail, code: String? - public let data: CheckUserResponse? - public let message: [Message]? - - public init( - data: CheckUserResponse?, - detail: String?, - code: String?, - message: [Message]? - ) { - self.data = data - self.detail = detail - self.code = code - self.message = message - } -} - -// MARK: - DataClass -public struct CheckUserResponse: Codable, Equatable { - public let status: Bool? - public let message: String? - public let user: User? - - public init( - status: Bool?, - message: String?, - user: User? - ) { - self.status = status - self.message = message - self.user = user - } -} - -// MARK: - User -public struct User: Codable, Equatable { - public let socialID, socialType, email: String? - public let phone: String? - public let createdAt, lastLogin, nickname: String? - public let year: Int? - public let job, generation: String? - public let isFirstLogin: Bool? - - public enum CodingKeys: String, CodingKey { - case socialID = "social_id" - case socialType = "social_type" - case email, phone - case createdAt = "created_at" - case lastLogin = "last_login" - case nickname, year, job, generation - case isFirstLogin = "is_first_login" - } - - public init( - socialID: String?, - socialType: String?, - email: String?, - phone: String?, - createdAt: String?, - lastLogin: String?, - nickname: String?, - year: Int?, - job: String?, - generation: String?, - isFirstLogin: Bool? - ) { - self.socialID = socialID - self.socialType = socialType - self.email = email - self.phone = phone - self.createdAt = createdAt - self.lastLogin = lastLogin - self.nickname = nickname - self.year = year - self.job = job - self.generation = generation - self.isFirstLogin = isFirstLogin - } -} - -// MARK: - Message -public struct Message: Codable, Equatable { - public let tokenClass, tokenType, message: String? - - public enum CodingKeys: String, CodingKey { - case tokenClass = "token_class" - case tokenType = "token_type" - case message - } - - public init( - tokenClass: String?, - tokenType: String?, - message: String? - ) { - self.tokenClass = tokenClass - self.tokenType = tokenType - self.message = message - } -} - diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/DeleteUser.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/DeleteUser.swift deleted file mode 100644 index 759d73d..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Auth/DeleteUser.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// DeleteUser.swift -// Model -// -// Created by 서원지 on 8/4/24. -// - -import Foundation - -public struct DeleteUserModel: Equatable, Codable { - public let data: DeleteUserModelResponse? - - public init( - data: DeleteUserModelResponse? = nil - ) { - self.data = data - } -} - -public struct DeleteUserModelResponse: Codable, Equatable { - public let status: Bool? - public let message: String? - - public init(status: Bool?, message: String?) { - self.status = status - self.message = message - } -} - diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/DTO/OAuthDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/DTO/OAuthDTOModel.swift new file mode 100644 index 0000000..99b0e9c --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/DTO/OAuthDTOModel.swift @@ -0,0 +1,29 @@ +// +// OAuthDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public struct OAuthDTOModel: Codable, Equatable { + public let data: OAuthReponseDTOModel + + public init( + data: OAuthReponseDTOModel + ) { + self.data = data + } +} + +public struct OAuthReponseDTOModel: Codable, Equatable { + public let accessToken: String + public let refreshToken: String + + + public init(accessToken: String, refreshToken: String) { + self.accessToken = accessToken + self.refreshToken = refreshToken + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Extension/Extension+AppleTokenResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Extension/Extension+AppleTokenResponse.swift new file mode 100644 index 0000000..4e07fc3 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Extension/Extension+AppleTokenResponse.swift @@ -0,0 +1,18 @@ +// +// Extension+AppleTokenResponse.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public extension AppleTokenResponse { + func toOAuthDTOToModel() -> OAuthDTOModel { + let data: OAuthReponseDTOModel = .init( + accessToken: self.access_token ?? "", + refreshToken: self.refresh_token ?? "") + + return OAuthDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Extension/Extension+KakaoResponseModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Extension/Extension+KakaoResponseModel.swift new file mode 100644 index 0000000..2b0d85c --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Extension/Extension+KakaoResponseModel.swift @@ -0,0 +1,18 @@ +// +// Extension+KakaoResponseModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +public extension UserLoginModel { + func toOAuthDTOModel() -> OAuthDTOModel { + let data: OAuthReponseDTOModel = .init( + accessToken: self.data?.accessToken ?? "", + refreshToken: self.data?.refreshToken ?? "" + ) + + return OAuthDTOModel(data: data) + } + +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/AppleTokenResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Model/AppleTokenResponse.swift similarity index 100% rename from OPeace/Projects/Core/Networking/Model/Sources/Auth/AppleTokenResponse.swift rename to OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Model/AppleTokenResponse.swift diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/KakaoResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Model/KakaoResponse.swift similarity index 75% rename from OPeace/Projects/Core/Networking/Model/Sources/Auth/KakaoResponse.swift rename to OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Model/KakaoResponse.swift index 746d8e7..84c17a1 100644 --- a/OPeace/Projects/Core/Networking/Model/Sources/Auth/KakaoResponse.swift +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/OAuth/Model/KakaoResponse.swift @@ -8,18 +8,18 @@ import Foundation // MARK: - Welcome -public struct UserLoginModel: Codable, Equatable { - public let data: UserLoginResponse? +public struct UserLoginModel: Decodable { + let data: UserLoginResponse? - public init(data: UserLoginResponse?) { + init(data: UserLoginResponse?) { self.data = data } } -public struct UserLoginResponse: Codable, Equatable { - public let socialID, accessToken, refreshToken: String? - public let expiresIn, refreshTokenExpiresIn: Int? - public let isExpires, isRefreshTokenExpires: Bool? +struct UserLoginResponse: Decodable { + let socialID, accessToken, refreshToken: String? + let expiresIn, refreshTokenExpiresIn: Int? + let isExpires, isRefreshTokenExpires: Bool? enum CodingKeys: String, CodingKey { diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/RefreshModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/RefreshModel.swift deleted file mode 100644 index c60b4af..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Auth/RefreshModel.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// RefreshModel.swift -// Model -// -// Created by 서원지 on 8/1/24. -// - -// MARK: - Welcome -public struct RefreshModel: Codable, Equatable { - public let data: RefreshModelResponse? - - public init( - data: RefreshModelResponse? - ) { - self.data = data - } -} - -// MARK: - DataClass -public struct RefreshModelResponse: Codable, Equatable { - public let accessToken, refreshToken: String? - - public enum CodingKeys: String, CodingKey { - case accessToken = "access_token" - case refreshToken = "refresh_token" - } - - public init( - accessToken: String?, - refreshToken: String? - ) { - self.accessToken = accessToken - self.refreshToken = refreshToken - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/UseLogin.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/UseLogin.swift deleted file mode 100644 index 56613af..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Auth/UseLogin.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// UseLogin.swift -// Model -// -// Created by 서원지 on 8/4/24. -// - -import Foundation - -public struct UseLoginModel: Equatable, Codable { - public let data: UseLoginResponse? - - public init( - data: UseLoginResponse? - ) { - self.data = data - } -} - - -// MARK: - DataClass -public struct UseLoginResponse: Codable, Equatable { - public let socialID, socialType, email: String? - public let phone: String? - public let createdAt, lastLogin, nickname: String? - public let year: Int? - public let job, generation: String? - public let isFirstLogin: Bool? - - public enum CodingKeys: String, CodingKey { - case socialID = "social_id" - case socialType = "social_type" - case email, phone - case createdAt = "created_at" - case lastLogin = "last_login" - case nickname, year, job, generation - case isFirstLogin = "is_first_login" - } - - public init( - socialID: String?, - socialType: String?, - email: String?, phone: String?, - createdAt: String?, - lastLogin: String?, - nickname: String?, - year: Int?, - job: String?, - generation: String?, - isFirstLogin: Bool? - ) { - self.socialID = socialID - self.socialType = socialType - self.email = email - self.phone = phone - self.createdAt = createdAt - self.lastLogin = lastLogin - self.nickname = nickname - self.year = year - self.job = job - self.generation = generation - self.isFirstLogin = isFirstLogin - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/CheckUserDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/CheckUserDTOModel.swift new file mode 100644 index 0000000..a5eb9d9 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/CheckUserDTOModel.swift @@ -0,0 +1,42 @@ +// +// CheckUserDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public struct CheckUserDTOModel: Codable, Equatable { + public let data: CheckUserDTOResponseModel + + public init(data: CheckUserDTOResponseModel) { + self.data = data + } +} + + +public struct CheckUserDTOResponseModel: Codable, Equatable { + public let job, generation, nickname, email: String + public let isFirstLogin: Bool + public let year: Int + public let status: Bool + + public init( + job: String, + generation: String, + nickname: String, + email: String, + isFirstLogin: Bool, + year: Int, + status: Bool + ) { + self.job = job + self.generation = generation + self.nickname = nickname + self.email = email + self.isFirstLogin = isFirstLogin + self.year = year + self.status = status + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/DeletUserDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/DeletUserDTOModel.swift new file mode 100644 index 0000000..a49a40d --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/DeletUserDTOModel.swift @@ -0,0 +1,30 @@ +// +// DeletUserDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// +import Foundation + +public struct DeletUserDTOModel: Codable, Equatable { + public let data: DeletUserResponseDTOModel + + public init( + data: DeletUserResponseDTOModel + ) { + self.data = data + } +} + +public struct DeletUserResponseDTOModel: Codable, Equatable { + public let status: Bool + public let message: String + + public init( + status: Bool, + message: String + ) { + self.status = status + self.message = message + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/RefreshDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/RefreshDTOModel.swift new file mode 100644 index 0000000..ba85d12 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/RefreshDTOModel.swift @@ -0,0 +1,28 @@ +// +// RefreshDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public struct RefreshDTOModel: Codable, Equatable { + public let data: RefreshResponseDTOModel + + public init(data: RefreshResponseDTOModel) { + self.data = data + } +} + +public struct RefreshResponseDTOModel: Codable, Equatable { + public let accessToken, refreshToken: String + + public init( + accessToken: String, + refreshToken: String + ) { + self.accessToken = accessToken + self.refreshToken = refreshToken + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/UserDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/UserDTOModel.swift new file mode 100644 index 0000000..355da96 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/DTO/UserDTOModel.swift @@ -0,0 +1,38 @@ +// +// UserDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public struct UserDTOModel: Codable, Equatable { + public let data: UserResponseDTOModel + + public init(data: UserResponseDTOModel) { + self.data = data + } +} + +public struct UserResponseDTOModel: Codable, Equatable { + public let job, generation, nickname, email: String + public let isFirstLogin: Bool + public let year: Int + + public init( + job: String, + generation: String, + nickname: String, + email: String, + isFirstLogin: Bool, + year: Int + ) { + self.job = job + self.generation = generation + self.nickname = nickname + self.email = email + self.isFirstLogin = isFirstLogin + self.year = year + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+CheckUserVerifyModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+CheckUserVerifyModel.swift new file mode 100644 index 0000000..631bc6e --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+CheckUserVerifyModel.swift @@ -0,0 +1,23 @@ +// +// Extension+CheckUserVerifyModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +public extension CheckUserVerifyModel { + func toCheckUserDTOModel() -> CheckUserDTOModel { + let data: CheckUserDTOResponseModel = .init( + job: self.data?.user?.job ?? "", + generation: self.data?.user?.generation ?? "", + nickname: self.data?.user?.nickname ?? "", + email: self.data?.user?.email ?? "", + isFirstLogin: self.data?.user?.isFirstLogin ?? false, + year: self.data?.user?.year ?? .zero, + status: self.data?.status ?? false + ) + + return CheckUserDTOModel(data: data) + } + +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+DeleteUserModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+DeleteUserModel.swift new file mode 100644 index 0000000..0dd2149 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+DeleteUserModel.swift @@ -0,0 +1,16 @@ +// +// Extension+DeleteUserModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +public extension DeleteUserModel { + func toDeleteUserDTOToModel() -> DeletUserDTOModel { + let data: DeletUserResponseDTOModel = .init( + status: self.data?.status ?? false, + message: self.data?.message ?? "" + ) + return DeletUserDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+RefreshModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+RefreshModel.swift new file mode 100644 index 0000000..b26ce41 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+RefreshModel.swift @@ -0,0 +1,17 @@ +// +// Extension+RefreshModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +public extension RefreshModel { + func toRefreshDTOToModel() -> RefreshDTOModel { + let data: RefreshResponseDTOModel = .init( + accessToken: self.data?.accessToken ?? "", + refreshToken: self.data?.refreshToken ?? "") + + return RefreshDTOModel(data: data) + + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+UseLoginModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+UseLoginModel.swift new file mode 100644 index 0000000..04deb31 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+UseLoginModel.swift @@ -0,0 +1,23 @@ +// +// Extension+UseLoginModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public extension UseLoginModel { + func userDTOToModel() -> UserDTOModel { + let data: UserResponseDTOModel = .init( + job: self.data?.job ?? "", + generation: self.data?.generation ?? "", + nickname: self.data?.nickname ?? "", + email: self.data?.email ?? "", + isFirstLogin: self.data?.isFirstLogin ?? false, + year: self.data?.year ?? .zero + ) + return UserDTOModel(data: data) + } + +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+UserLogOutModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+UserLogOutModel.swift new file mode 100644 index 0000000..0d5dd78 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Extension/Extension+UserLogOutModel.swift @@ -0,0 +1,22 @@ +// +// Extension+UserLogOutModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public extension UserLogOutModel { + func toUserDTOToModel() -> UserDTOModel { + let data: UserResponseDTOModel = .init( + job: self.data?.job ?? "", + generation: self.data?.generation ?? "", + nickname: self.data?.nickname ?? "", + email: self.data?.email ?? "", + isFirstLogin: self.data?.isFirstLogin ?? false, + year: self.data?.year ?? .zero + ) + return UserDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/CheckUserVerify.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/CheckUserVerify.swift new file mode 100644 index 0000000..28c49b4 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/CheckUserVerify.swift @@ -0,0 +1,55 @@ +// +// CheckUserVerify.swift +// Model +// +// Created by 서원지 on 8/6/24. +// +import Foundation + +public struct CheckUserVerifyModel: Decodable { + let detail, code: String? + let data: CheckUserResponse? + let message: [Message]? + +} + +// MARK: - DataClass +public struct CheckUserResponse: Decodable { + let status: Bool? + let message: String? + let user: User? + +} + +// MARK: - User +public struct User: Decodable { + let socialID, socialType, email: String? + let phone: String? + let createdAt, lastLogin, nickname: String? + let year: Int? + let job, generation: String? + let isFirstLogin: Bool? + + enum CodingKeys: String, CodingKey { + case socialID = "social_id" + case socialType = "social_type" + case email, phone + case createdAt = "created_at" + case lastLogin = "last_login" + case nickname, year, job, generation + case isFirstLogin = "is_first_login" + } + +} + +// MARK: - Message +struct Message: Decodable { + let tokenClass, tokenType, message: String? + + enum CodingKeys: String, CodingKey { + case tokenClass = "token_class" + case tokenType = "token_type" + case message + } +} + diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/DeleteUser.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/DeleteUser.swift new file mode 100644 index 0000000..c9df828 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/DeleteUser.swift @@ -0,0 +1,20 @@ +// +// DeleteUser.swift +// Model +// +// Created by 서원지 on 8/4/24. +// + +import Foundation + +public struct DeleteUserModel: Decodable { + let data: DeleteUserModelResponse? + +} + +struct DeleteUserModelResponse: Decodable { + let status: Bool? + let message: String? + +} + diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/RefreshModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/RefreshModel.swift new file mode 100644 index 0000000..8dc07cb --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/RefreshModel.swift @@ -0,0 +1,22 @@ +// +// RefreshModel.swift +// Model +// +// Created by 서원지 on 8/1/24. +// + +// MARK: - Welcome +public struct RefreshModel: Decodable { + let data: RefreshModelResponse? + +} + +// MARK: - DataClass +struct RefreshModelResponse: Decodable { + let accessToken, refreshToken: String? + + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case refreshToken = "refresh_token" + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/UseLogin.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/UseLogin.swift new file mode 100644 index 0000000..bca0867 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/UseLogin.swift @@ -0,0 +1,33 @@ +// +// UseLogin.swift +// Model +// +// Created by 서원지 on 8/4/24. +// + +import Foundation + +public struct UseLoginModel: Decodable { + let data: UseLoginResponse? + +} + +// MARK: - DataClass +struct UseLoginResponse: Decodable { + let socialID, socialType, email: String? + let phone: String? + let createdAt, lastLogin, nickname: String? + let year: Int? + let job, generation: String? + let isFirstLogin: Bool? + + enum CodingKeys: String, CodingKey { + case socialID = "social_id" + case socialType = "social_type" + case email, phone + case createdAt = "created_at" + case lastLogin = "last_login" + case nickname, year, job, generation + case isFirstLogin = "is_first_login" + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/UserLogOut.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/UserLogOut.swift new file mode 100644 index 0000000..85ed4ad --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Auth/User/Model/UserLogOut.swift @@ -0,0 +1,34 @@ +// +// UserLogOut.swift +// Model +// +// Created by 서원지 on 8/3/24. +// + +import Foundation + +public struct UserLogOutModel: Decodable { + let data: UserLogOutResponse? + +} + + +// MARK: - DataClass +struct UserLogOutResponse: Decodable { + let socialID, socialType, email: String? + let phone: String? + let createdAt, lastLogin, nickname: String? + let year: Int? + let job, generation: String? + let isFirstLogin: Bool? + + enum CodingKeys: String, CodingKey { + case socialID = "social_id" + case socialType = "social_type" + case email, phone + case createdAt = "created_at" + case lastLogin = "last_login" + case nickname, year, job, generation + case isFirstLogin = "is_first_login" + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Auth/UserLogOut.swift b/OPeace/Projects/Core/Networking/Model/Sources/Auth/UserLogOut.swift deleted file mode 100644 index ec086bc..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Auth/UserLogOut.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// UserLogOut.swift -// Model -// -// Created by 서원지 on 8/3/24. -// - -import Foundation - -public struct UserLogOutModel: Equatable, Codable { - public let data: UserLogOutResponse? - - public init( - data: UserLogOutResponse? - ) { - self.data = data - } -} - - -// MARK: - DataClass -public struct UserLogOutResponse: Codable, Equatable { - public let socialID, socialType, email: String? - public let phone: String? - public let createdAt, lastLogin, nickname: String? - public let year: Int? - public let job, generation: String? - public let isFirstLogin: Bool? - - public enum CodingKeys: String, CodingKey { - case socialID = "social_id" - case socialType = "social_type" - case email, phone - case createdAt = "created_at" - case lastLogin = "last_login" - case nickname, year, job, generation - case isFirstLogin = "is_first_login" - } - - public init( - socialID: String?, - socialType: String?, - email: String?, phone: String?, - createdAt: String?, - lastLogin: String?, - nickname: String?, - year: Int?, - job: String?, - generation: String?, - isFirstLogin: Bool? - ) { - self.socialID = socialID - self.socialType = socialType - self.email = email - self.phone = phone - self.createdAt = createdAt - self.lastLogin = lastLogin - self.nickname = nickname - self.year = year - self.job = job - self.generation = generation - self.isFirstLogin = isFirstLogin - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/CreateQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/CreateQuestionModel.swift deleted file mode 100644 index fc9d146..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Questions/CreateQuestionModel.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// CreateQuestionModel.swift -// Model -// -// Created by 서원지 on 8/15/24. -// - -import Foundation - -public struct CreateQuestionModel: Codable, Equatable { - public let data: CreateQuestionResponse? - - public init( - data: CreateQuestionResponse? - ) { - self.data = data - } -} - -// MARK: - DataClass -public struct CreateQuestionResponse: Codable, Equatable { - public let id: Int? - public let userID: String? - public let emoji: String? - public let title, choiceA, choiceB, createAt: String? - public let updateAt: String? - - public enum CodingKeys: String, CodingKey { - case id - case userID = "user_id" - case emoji, title - case choiceA = "choice_a" - case choiceB = "choice_b" - case createAt = "create_at" - case updateAt = "update_at" - } - - public init( - id: Int?, - userID: String?, - emoji: String?, - title: String?, - choiceA: String?, - choiceB: String?, - createAt: String?, - updateAt: String? - ) { - self.id = id - self.userID = userID - self.emoji = emoji - self.title = title - self.choiceA = choiceA - self.choiceB = choiceB - self.createAt = createAt - self.updateAt = updateAt - } -} - diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/CreateQuestionDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/CreateQuestionDTOModel.swift new file mode 100644 index 0000000..e04dc17 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/CreateQuestionDTOModel.swift @@ -0,0 +1,42 @@ +// +// CreateQuestionDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public struct CreateQuestionDTOModel: Codable, Equatable { + public let data: CreateQuestionResponseDTOModel + + public init(data: CreateQuestionResponseDTOModel) { + self.data = data + } +} + +public struct CreateQuestionResponseDTOModel: Codable, Equatable { + public let id: Int + public let userID: String + public let emoji: String + public let title, choiceA, choiceB, createAt: String + + public init( + id: Int = .zero, + userID: String = "", + emoji: String = "", + title: String = "", + choiceA: String = "", + choiceB: String = "", + createAt: String = "" + ) { + self.id = id + self.userID = userID + self.emoji = emoji + self.title = title + self.choiceA = choiceA + self.choiceB = choiceB + self.createAt = createAt + } +} + diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/FlagQuestionDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/FlagQuestionDTOModel.swift new file mode 100644 index 0000000..ac5a544 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/FlagQuestionDTOModel.swift @@ -0,0 +1,43 @@ +// +// FlagQuestionDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public struct FlagQuestionDTOModel: Codable, Equatable { + public let data: FlagQuestionResponseDTOModel + + public init(data: FlagQuestionResponseDTOModel) { + self.data = data + } +} + +public struct FlagQuestionResponseDTOModel: Codable, Equatable { + public let id, question: Int + public let userID, reason, createAt, userChoice: String + public let status: Bool + public let message: String + + public init( + id: Int = .zero, + question: Int = .zero, + userID: String = "", + reason: String = "", + createAt: String = "", + userChoice: String = "", + status: Bool = false, + message: String = "" + ) { + self.id = id + self.question = question + self.userID = userID + self.reason = reason + self.createAt = createAt + self.userChoice = userChoice + self.status = status + self.message = message + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/QuestionDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/QuestionDTOModel.swift new file mode 100644 index 0000000..4b25ec3 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/QuestionDTOModel.swift @@ -0,0 +1,122 @@ +// +// QuestionDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public struct QuestionDTOModel: Codable, Equatable { + public var data: QuestionResponseDTOModel? + + public init( + data: QuestionResponseDTOModel? + ) { + self.data = data + } +} + +public struct QuestionResponseDTOModel: Codable, Equatable { + public let error: String + public let count: Int + public var content: [QuestionContentData]? + + public init( + error: String, + count: Int, + content: [QuestionContentData]? = nil + ) { + self.error = error + self.count = count + self.content = content + } + +} + +public struct QuestionContentData: Equatable, Codable { + public let id: Int + public let error: String + public let userInfo: UserInfoDTO? + public let emoji, title: String? + public let choiceA, choiceB: String + public let answerRatio: AnswerRatioDTO? + public let answerCount: Int + public let likeCount, reportCount: Int + public let metadata: MetadataDTO? + public let createdAt: String + + public init( + id: Int, + error: String, + userInfo: UserInfoDTO?, + emoji: String?, + title: String?, + choiceA: String, + choiceB: String, + answerRatio: AnswerRatioDTO?, + answerCount: Int, + likeCount: Int, + reportCount: Int, + metadata: MetadataDTO?, + createdAt: String + ) { + self.id = id + self.error = error + self.userInfo = userInfo + self.emoji = emoji + self.title = title + self.choiceA = choiceA + self.choiceB = choiceB + self.answerRatio = answerRatio + self.answerCount = answerCount + self.likeCount = likeCount + self.reportCount = reportCount + self.metadata = metadata + self.createdAt = createdAt + } +} + +public struct AnswerRatioDTO: Codable, Equatable { + public let answerRatioA, answerRatioB: Double + + public init( + answerRatioA: Double, + answerRatioB: Double + ) { + self.answerRatioA = answerRatioA + self.answerRatioB = answerRatioB + } +} + +public struct MetadataDTO: Codable, Equatable { + public let liked, voted: Bool + public let votedTo: String + + public init( + liked: Bool, + voted: Bool, + votedTo: String + ) { + self.liked = liked + self.voted = voted + self.votedTo = votedTo + } +} + + +public struct UserInfoDTO: Codable, Equatable { + public let userID, userNickname, userJob, userGeneration: String + + public init( + userID: String, + userNickname: String, + userJob: String, + userGeneration: String + ) { + self.userID = userID + self.userNickname = userNickname + self.userJob = userJob + self.userGeneration = userGeneration + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/StatusQuestionDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/StatusQuestionDTOModel.swift new file mode 100644 index 0000000..e4ea5e7 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DTO/StatusQuestionDTOModel.swift @@ -0,0 +1,49 @@ +// +// StatusQuestionDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public struct StatusQuestionDTOModel: Codable, Equatable { + public let data: StatusQuestionResponseDTOModel + + public init( + data: StatusQuestionResponseDTOModel + ) { + self.data = data + } +} + +public struct StatusQuestionResponseDTOModel: Codable, Equatable { + public let error: String + public let id: Int + public let status: StatusDTO? + public let overallRatioA, overallRatioB: Double + + public init( + error: String, + id: Int, + status: StatusDTO?, + overallRatioA: Double, + overallRatioB: Double + ) { + self.error = error + self.id = id + self.status = status + self.overallRatioA = overallRatioA + self.overallRatioB = overallRatioB + } +} + + +public struct StatusDTO: Codable, Equatable { + public let statusA, statusB: [String: Double]? + + public init(statusA: [String : Double], statusB: [String : Double]?) { + self.statusA = statusA + self.statusB = statusB + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/DeleteQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/DeleteQuestionModel.swift deleted file mode 100644 index 5b91144..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Questions/DeleteQuestionModel.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// DeleteQuestionModel.swift -// Model -// -// Created by 서원지 on 8/26/24. -// - -import Foundation - -public struct DeleteQuestionModel: Codable, Equatable { - public let data: DeleteQuestionResponseModel? - - public init( - data: DeleteQuestionResponseModel? - ) { - self.data = data - } -} - -// MARK: - DataClass -public struct DeleteQuestionResponseModel: Codable, Equatable { - public let status: Bool? - public let message: String? - - public init( - status: Bool?, - message: String? - ) { - self.status = status - self.message = message - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+CreateQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+CreateQuestionModel.swift new file mode 100644 index 0000000..75696ba --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+CreateQuestionModel.swift @@ -0,0 +1,24 @@ +// +// Extension+CreateQuestionModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension CreateQuestionModel { + func toCreateQuestionDTOToModel() -> CreateQuestionDTOModel { + let data: CreateQuestionResponseDTOModel = .init( + id: self.data?.id ?? .zero, + userID: self.data?.userID ?? "", + emoji: self.data?.emoji ?? "", + title: self.data?.title ?? "", + choiceA: self.data?.choiceA ?? "", + choiceB: self.data?.choiceB ?? "", + createAt: self.data?.createAt ?? "" + ) + + return CreateQuestionDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+DeleteQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+DeleteQuestionModel.swift new file mode 100644 index 0000000..ed25096 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+DeleteQuestionModel.swift @@ -0,0 +1,17 @@ +// +// Extension+DeleteQuestionModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +public extension DeleteQuestionModel { + func toFlagQuestionDTOToModel() -> FlagQuestionDTOModel { + let data: FlagQuestionResponseDTOModel = .init( + status: self.data?.status ?? false, + message: self.data?.message ?? "" + ) + + return FlagQuestionDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+QuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+QuestionModel.swift new file mode 100644 index 0000000..9eaa537 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+QuestionModel.swift @@ -0,0 +1,75 @@ +// +// Extension+QuestionModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension QuestionModel { + func toQuestionDTOToModel() -> QuestionDTOModel { + + let content: [QuestionContentData] = self.data?.results?.compactMap( { data in + return QuestionContentData( + id: data.id ?? .zero, + error: data.error ?? "", + userInfo: data.userInfo?.toDTO() , + emoji: data.emoji ?? "", + title: data.title ?? "", + choiceA: data.choiceA ?? "", + choiceB: data.choiceB ?? "", + answerRatio: data.answerRatio?.toDTO(), + answerCount: data.answerCount ?? .zero, + likeCount: data.likeCount ?? .zero, + reportCount: data.reportCount ?? .zero, + metadata: data.metadata?.toDTO(), + createdAt: data.createdAt ?? "" + ) + }) ?? [] + + let data: QuestionResponseDTOModel = .init( + error: self.data?.error ?? "" , + count: self.data?.count ?? .zero, + content: content + ) + return QuestionDTOModel(data: data) + } +} + +extension UserInfo{ + func toDTO() -> UserInfoDTO { + let data: UserInfoDTO = .init( + userID: self.userID ?? "", + userNickname: self.userNickname ?? "", + userJob: self.userJob ?? "", + userGeneration: self.userGeneration ?? "" + ) + + return data + } + +} + +extension Metadata { + func toDTO() -> MetadataDTO { + let data: MetadataDTO = .init( + liked: self.liked ?? false, + voted: self.voted ?? false, + votedTo: self.votedTo ?? "" + ) + + return data + } +} + +extension AnswerRatio { + func toDTO() -> AnswerRatioDTO { + let data: AnswerRatioDTO = .init( + answerRatioA: self.a ?? .zero, + answerRatioB: self.b ?? .zero + ) + + return data + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+QuestionVoteModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+QuestionVoteModel.swift new file mode 100644 index 0000000..2298375 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+QuestionVoteModel.swift @@ -0,0 +1,21 @@ +// +// Extension+QuestionVoteModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// +//let question: Int? +//let userID, createAt: String? +import Foundation + +public extension QuestionVoteModel { + func toFlagQuestionDTOToModel() -> FlagQuestionDTOModel { + let data: FlagQuestionResponseDTOModel = .init( + id: self.data?.id ?? .zero, + question: self.data?.question ?? .zero, + userID: self.data?.userID ?? "", + userChoice: self.data?.userChoice ?? "" + ) + return FlagQuestionDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+ReportQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+ReportQuestionModel.swift new file mode 100644 index 0000000..c0fef93 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+ReportQuestionModel.swift @@ -0,0 +1,22 @@ +// +// Extension+ReportQuestionModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension ReportQuestionModel { + func toFlagQuestionDTOToModel() -> FlagQuestionDTOModel { + let data: FlagQuestionResponseDTOModel = .init( + id: self.data?.id ?? .zero, + question: self.data?.question ?? .zero, + userID: self.data?.userID ?? "", + reason: self.data?.reason ?? "", + createAt: self.data?.createAt ?? "" + ) + + return FlagQuestionDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+StatusQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+StatusQuestionModel.swift new file mode 100644 index 0000000..25d7adf --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+StatusQuestionModel.swift @@ -0,0 +1,35 @@ +// +// Extension+StatusQuestionModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension StatusQuestionModel { + func toStatusDTOToModel() -> StatusQuestionDTOModel { + let error = self.data?.error ?? "" + let id = self.data?.id ?? .zero + let status = self.data?.stats?.toDTO() + let overallRatioA = self.data?.overallRatio?.a ?? .zero + let overallRatioB = self.data?.overallRatio?.b ?? .zero + + let data = StatusQuestionResponseDTOModel( + error: error, + id: id, + status: status, + overallRatioA: overallRatioA, + overallRatioB: overallRatioB + ) + + return StatusQuestionDTOModel(data: data) + } +} + +extension Stats { + func toDTO() -> StatusDTO { + return StatusDTO(statusA: self.a ?? [:], statusB: self.b ?? [:]) + } +} + diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+VoteQuestionLikeModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+VoteQuestionLikeModel.swift new file mode 100644 index 0000000..6836b88 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Extension/Extension+VoteQuestionLikeModel.swift @@ -0,0 +1,20 @@ +// +// Extension+VoteQuestionLikeModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension VoteQuestionLikeModel { + func toFlagQuestionDTOToModel() -> FlagQuestionDTOModel { + let data: FlagQuestionResponseDTOModel = .init( + question: self.data?.question ?? .zero, + userID: self.data?.userID ?? "", + createAt: self.data?.createAt ?? "" + ) + + return FlagQuestionDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/CreateQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/CreateQuestionModel.swift new file mode 100644 index 0000000..15594d5 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/CreateQuestionModel.swift @@ -0,0 +1,33 @@ +// +// CreateQuestionModel.swift +// Model +// +// Created by 서원지 on 8/15/24. +// + +import Foundation + +public struct CreateQuestionModel: Decodable { + let data: CreateQuestionResponse? + +} + +// MARK: - DataClass +struct CreateQuestionResponse: Decodable { + let id: Int? + let userID: String? + let emoji: String? + let title, choiceA, choiceB, createAt: String? + let updateAt: String? + + enum CodingKeys: String, CodingKey { + case id + case userID = "user_id" + case emoji, title + case choiceA = "choice_a" + case choiceB = "choice_b" + case createAt = "create_at" + case updateAt = "update_at" + } +} + diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/DeleteQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/DeleteQuestionModel.swift new file mode 100644 index 0000000..9ae4e85 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/DeleteQuestionModel.swift @@ -0,0 +1,20 @@ +// +// DeleteQuestionModel.swift +// Model +// +// Created by 서원지 on 8/26/24. +// + +import Foundation + +public struct DeleteQuestionModel: Decodable { + let data: DeleteQuestionResponseModel? + +} + +// MARK: - DataClass +struct DeleteQuestionResponseModel: Decodable { + let status: Bool? + let message: String? + +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/QuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/QuestionModel.swift similarity index 100% rename from OPeace/Projects/Core/Networking/Model/Sources/Questions/QuestionModel.swift rename to OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/QuestionModel.swift diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/QuestionVoteModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/QuestionVoteModel.swift similarity index 97% rename from OPeace/Projects/Core/Networking/Model/Sources/Questions/QuestionVoteModel.swift rename to OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/QuestionVoteModel.swift index fddb352..b922e67 100644 --- a/OPeace/Projects/Core/Networking/Model/Sources/Questions/QuestionVoteModel.swift +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/QuestionVoteModel.swift @@ -5,6 +5,7 @@ // Created by 서원지 on 8/26/24. // +import Foundation // MARK: - Welcome public struct QuestionVoteModel: Codable, Equatable { public let data: QuestionVoteResponseModel? diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/ReportQuestion.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/ReportQuestion.swift new file mode 100644 index 0000000..695d467 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/ReportQuestion.swift @@ -0,0 +1,27 @@ +// +// ReportQuestion.swift +// Model +// +// Created by 서원지 on 8/26/24. +// + +import Foundation +// MARK: - Welcome +public struct ReportQuestionModel: Decodable { + let data: ReportQuestionResponseModel? + +} + +// MARK: - DataClass +struct ReportQuestionResponseModel: Decodable { + let id, question: Int? + let userID, reason, createAt: String? + + enum CodingKeys: String, CodingKey { + case id, question + case userID = "user_id" + case reason + case createAt = "create_at" + } +} + diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/StatusQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/StatusQuestionModel.swift new file mode 100644 index 0000000..f865b7a --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/StatusQuestionModel.swift @@ -0,0 +1,41 @@ +// +// StatusQuestionModel.swift +// Model +// +// Created by 서원지 on 8/31/24. +// + +import Foundation + +// MARK: - Welcome +public struct StatusQuestionModel: Decodable { + let data: QuestionStatusResponseModel? + +} + +// MARK: - DataClass +struct QuestionStatusResponseModel: Decodable { + let error: String? + let id: Int? + let stats: Stats? + let overallRatio: OverallRatio? + + enum CodingKeys: String, CodingKey { + case id, stats + case overallRatio = "overall_ratio" + case error + } + +} + +// MARK: - OverallRatio +struct OverallRatio: Decodable { + let a, b: Double? + +} + +// MARK: - Stats +struct Stats: Decodable { + let a, b: [String: Double]? + +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/VoteQuestionLikeModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/VoteQuestionLikeModel.swift new file mode 100644 index 0000000..386d69b --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/Questions/Model/VoteQuestionLikeModel.swift @@ -0,0 +1,25 @@ +// +// VoteQuestionLike.swift +// Model +// +// Created by 서원지 on 8/25/24. +// + +import Foundation + +public struct VoteQuestionLikeModel: Decodable { + let data: VoteQuestionLikeResponse? + +} + +// MARK: - DataClass +struct VoteQuestionLikeResponse: Decodable{ + let question: Int? + let userID, createAt: String? + + enum CodingKeys: String, CodingKey { + case question + case userID = "user_id" + case createAt = "create_at" + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/ReportQuestion.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/ReportQuestion.swift deleted file mode 100644 index 7e8b658..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Questions/ReportQuestion.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ReportQuestion.swift -// Model -// -// Created by 서원지 on 8/26/24. -// - -import Foundation -// MARK: - Welcome -public struct ReportQuestionModel: Codable, Equatable { - public let data: ReportQuestionResponseModel? - - public init( - data: ReportQuestionResponseModel? - ) { - self.data = data - } -} - -// MARK: - DataClass -public struct ReportQuestionResponseModel: Codable, Equatable { - public let id, question: Int? - public let userID, reason, createAt: String? - - public enum CodingKeys: String, CodingKey { - case id, question - case userID = "user_id" - case reason - case createAt = "create_at" - } - - public init( - id: Int?, - question: Int?, - userID: String?, - reason: String?, - createAt: String? - ) { - self.id = id - self.question = question - self.userID = userID - self.reason = reason - self.createAt = createAt - } -} - diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/StatusQuestionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/StatusQuestionModel.swift deleted file mode 100644 index a88e2d1..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Questions/StatusQuestionModel.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// StatusQuestionModel.swift -// Model -// -// Created by 서원지 on 8/31/24. -// - -import Foundation - -// MARK: - Welcome -public struct StatusQuestionModel: Codable, Equatable { - public let data: QuestionStatusResponseModel? - - public init( - data: QuestionStatusResponseModel? - ) { - self.data = data - } -} - -// MARK: - DataClass -public struct QuestionStatusResponseModel: Codable, Equatable { - public let error: String? - public let id: Int? - public let stats: Stats? - public let overallRatio: OverallRatio? - - public enum CodingKeys: String, CodingKey { - case id, stats - case overallRatio = "overall_ratio" - case error - } - - public init( - id: Int?, - stats: Stats?, - overallRatio: OverallRatio?, - error: String? - ) { - self.id = id - self.stats = stats - self.overallRatio = overallRatio - self.error = error - } -} - -// MARK: - OverallRatio -public struct OverallRatio: Codable, Equatable { - public let a, b: Double? - - public init( - a: Double?, - b: Double? - ) { - self.a = a - self.b = b - } -} - -// MARK: - Stats -public struct Stats: Codable, Equatable { - public let a, b: [String: Double]? - - public init( - a: [String : Double]?, - b: [String : Double]? - ) { - self.a = a - self.b = b - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/QuestionSort.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/View/QuestionSort.swift similarity index 100% rename from OPeace/Projects/Core/Networking/Model/Sources/Questions/QuestionSort.swift rename to OPeace/Projects/Core/Networking/Model/Sources/Questions/View/QuestionSort.swift diff --git a/OPeace/Projects/Core/Networking/Model/Sources/Questions/VoteQuestionLikeModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/Questions/VoteQuestionLikeModel.swift deleted file mode 100644 index bb52927..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/Questions/VoteQuestionLikeModel.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// VoteQuestionLike.swift -// Model -// -// Created by 서원지 on 8/25/24. -// - -public struct VoteQuestionLikeModel: Codable, Equatable { - public let data: VoteQuestionLikeResponse? - - public init( - data: VoteQuestionLikeResponse? - ) { - self.data = data - } -} - -// MARK: - DataClass -public struct VoteQuestionLikeResponse: Codable, Equatable { - public let question: Int? - public let userID, createAt: String? - - public enum CodingKeys: String, CodingKey { - case question - case userID = "user_id" - case createAt = "create_at" - } - - public init( - question: Int?, - userID: String?, - createAt: String? - ) { - self.question = question - self.userID = userID - self.createAt = createAt - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/CheckGeneraion.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/CheckGeneraion.swift deleted file mode 100644 index 1981604..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/CheckGeneraion.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// SignUpGeneraion.swift -// Model -// -// Created by 서원지 on 8/28/24. -// -import Foundation - -// MARK: - Welcome -public struct CheckGeneraionModel: Codable, Equatable { - public let data: CheckGeneraionResponseModel? - - public init( - data: CheckGeneraionResponseModel? - ) { - self.data = data - } -} - -// MARK: - DataClass -public struct CheckGeneraionResponseModel: Codable, Equatable { - public let data: String? - - public init( - data: String? - ) { - self.data = data - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/CheckNickNameResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/CheckNickNameResponse.swift deleted file mode 100644 index ac089fa..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/CheckNickNameResponse.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// CheckNickName.swift -// Model -// -// Created by 서원지 on 7/25/24. -// - -import Foundation - -public struct CheckNickNameModel: Codable, Equatable { - public var data: CheckNickNameResponse? - - public init( - data: CheckNickNameResponse? - ) { - self.data = data - } -} - - -public struct CheckNickNameResponse: Codable, Equatable { - public var exists: Bool? - public var message: String? - - public enum CodingKeys: String, CodingKey { - case exists, message - } - - public init( - exists: Bool?, - message: String? - ) { - self.exists = exists - self.message = message - } -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+CheckGeneraionModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+CheckGeneraionModel.swift new file mode 100644 index 0000000..f471370 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+CheckGeneraionModel.swift @@ -0,0 +1,18 @@ +// +// Extension+CheckGeneraionModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension CheckGeneraionModel { + func toSignUpInfoDTOToModel() -> SignUpCheckInfoDTOModel { + let data: SignUpCheckInfoResponseDTOModel = .init( + message: self.data?.data ?? "" , + exists: false + ) + return SignUpCheckInfoDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+CheckNickNameModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+CheckNickNameModel.swift new file mode 100644 index 0000000..6928e9e --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+CheckNickNameModel.swift @@ -0,0 +1,18 @@ +// +// Extension+CheckNickNameModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// +import Foundation + +public extension CheckNickNameModel { + func toSignUPInfoDTOToModel() -> SignUpCheckInfoDTOModel { + let data: SignUpCheckInfoResponseDTOModel = .init( + message: self.data?.message ?? "", + exists: self.data?.exists ?? false + ) + + return SignUpCheckInfoDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+GenerationListResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+GenerationListResponse.swift new file mode 100644 index 0000000..f50efac --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+GenerationListResponse.swift @@ -0,0 +1,15 @@ +// +// Extension+GenerationListResponse.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension GenerationListResponse { + func toGenerationListDTOToModel() -> SignUpListDTOModel { + let data: SignUpListResponseDTOModel = .init(content: self.data?.data ?? []) + return SignUpListDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+SignUpJobModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+SignUpJobModel.swift new file mode 100644 index 0000000..857fc32 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+SignUpJobModel.swift @@ -0,0 +1,15 @@ +// +// Extension+SignUpJobModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public extension SignUpJobModel { + func toSignUPListDTOToModel() -> SignUpListDTOModel { + let data: SignUpListResponseDTOModel = .init(content: self.data?.data ?? []) + return SignUpListDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+UpdateUserInfoDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+UpdateUserInfoDTOModel.swift new file mode 100644 index 0000000..5632530 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Extension/Extension+UpdateUserInfoDTOModel.swift @@ -0,0 +1,40 @@ +// +// Extension+UpdateUserInfoDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +public extension UpdateUserInfoModel { + func toUpdateUserInfoDTOToModel() -> UpdateUserInfoDTOModel { + let data: UpdateUserInfoResponseDTOModel = .init( + socialID: self.data?.socialID ?? "", + socialType: self.data?.socialType ?? "", + email: self.data?.email ?? "", + createdAt: self.data?.createdAt ?? "", + nickname: self.data?.nickname, + year: self.data?.year , + job: self.data?.job, + generation: self.data?.generation ?? "", + isFirstLogin: self.data?.isFirstLogin ?? false + ) + + return UpdateUserInfoDTOModel(data: data) + } +} + +public extension UpdateUserInfoDTOModel { + static var mockModel: UpdateUserInfoDTOModel = UpdateUserInfoDTOModel( + data: UpdateUserInfoResponseDTOModel( + socialID: "apple_001096.cf7680b2761f4de694a1d1c21ea507a6.1112", + socialType: "apple", + email: "shuwj81@daum.net", + createdAt: "2024-08-29 01:39:57", + nickname: "오피스", + year: 1998, + job: "개발", + generation: "Z 세대", + isFirstLogin: true + ) + ) +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/GenerationListResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/GenerationListResponse.swift deleted file mode 100644 index 33a0e62..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/GenerationListResponse.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// GenerationListModel.swift -// Model -// -// Created by 염성훈 on 8/31/24. -// - -import Foundation - -public struct GenerationListResponse: Codable, Equatable { - public let data: GenerationDataModel? - - public init(data: GenerationDataModel?) { - self.data = data - } -} - - -public struct GenerationDataModel: Codable, Equatable { - public let data: [String]? - - public init(data: [String]?) { - self.data = data - } - -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Model/SignUpJobModelResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Model/SignUpJobModelResponse.swift new file mode 100644 index 0000000..d8f38eb --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/Model/SignUpJobModelResponse.swift @@ -0,0 +1,21 @@ +// +// SignUpJobModel.swift +// Model +// +// Created by 서원지 on 7/27/24. +// + +public struct SignUpJobModel: Decodable { + let data: SignUpJobModelResponse? + +} + +struct SignUpJobModelResponse: Decodable { + let data: [String]? + + enum Component: String, Codable { + case data + } +} + + diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/SignUpJobModelResponse.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/SignUpJobModelResponse.swift deleted file mode 100644 index ffcd82c..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/SignUpJobModelResponse.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// SignUpJobModel.swift -// Model -// -// Created by 서원지 on 7/27/24. -// - -public struct SignUpJobModel: Codable, Equatable { - public let data: SignUpJobModelResponse? - - public init(data: SignUpJobModelResponse?) { - self.data = data - } -} - -public struct SignUpJobModelResponse: Codable, Equatable { - public let data: [String]? - - public init(data: [String]?) { - self.data = data - - } - public enum Component: String, Codable { - case data - } -} - - diff --git a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/UpdateUserInfoModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/SignUp/UpdateUserInfoModel.swift deleted file mode 100644 index 1d22871..0000000 --- a/OPeace/Projects/Core/Networking/Model/Sources/SignUp/UpdateUserInfoModel.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// UpdateUserInfoModel.swift -// Model -// -// Created by 서원지 on 7/30/24. -// - -import Foundation - -// MARK: - Welcome -public struct UpdateUserInfoModel: Codable, Equatable { - public var data: UpdateUserInfoResponse? - - public init(data: UpdateUserInfoResponse?) { - self.data = data - } - -} - -// MARK: - DataClass -public struct UpdateUserInfoResponse: Codable , Equatable { - public let socialID, socialType, email: String? - public let phone: String? - public let createdAt, lastLogin, nickname: String? - public let year: Int? - public let job, generation: String? - public let isFirstLogin: Bool? - - enum CodingKeys: String, CodingKey { - case socialID = "social_id" - case socialType = "social_type" - case email, phone - case createdAt = "created_at" - case lastLogin = "last_login" - case nickname, year, job, generation - case isFirstLogin = "is_first_login" - } -} - -public extension UpdateUserInfoModel { - static var mockModel: UpdateUserInfoModel = UpdateUserInfoModel( - data: UpdateUserInfoResponse( - socialID: "apple_001096.cf7680b2761f4de694a1d1c21ea507a6.1112", - socialType: "apple", - email: "shuwj81@daum.net", - phone: nil, - createdAt: "2024-08-29 01:39:57", - lastLogin: "2024-08-30 18:25:17", - nickname: "오피스", - year: 1998, - job: "개발", - generation: "Z 세대", - isFirstLogin: true - ) - ) -} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/DTO/UserBlockDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/DTO/UserBlockDTOModel.swift new file mode 100644 index 0000000..fb61685 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/DTO/UserBlockDTOModel.swift @@ -0,0 +1,32 @@ +// +// UserBlockDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public struct UserBlockDTOModel: Codable, Equatable { + let data: UserBlockResponseDTOModel + + public init( + data: UserBlockResponseDTOModel + ) { + self.data = data + } +} + + +public struct UserBlockResponseDTOModel: Codable, Equatable { + public let status: Bool + public let message: String + + public init( + status: Bool, + message: String + ) { + self.status = status + self.message = message + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/DTO/UserBlockListDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/DTO/UserBlockListDTOModel.swift new file mode 100644 index 0000000..e2c5a18 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/DTO/UserBlockListDTOModel.swift @@ -0,0 +1,38 @@ +// +// UserBlockListDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public struct UserBlockListDTOModel: Codable, Equatable { + public let data: [UserBlockListResponseDTOModel] + + public init( + data: [UserBlockListResponseDTOModel] + ) { + self.data = data + } +} + + +public struct UserBlockListResponseDTOModel: Codable, Equatable { + public let blockID: Int + public let blockedUserID, nickname, job, generation: String + + public init( + blockID: Int, + blockedUserID: String, + nickname: String, + job: String, + generation: String + ) { + self.blockID = blockID + self.blockedUserID = blockedUserID + self.nickname = nickname + self.job = job + self.generation = generation + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Extension/Extension+UserBlockDTOModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Extension/Extension+UserBlockDTOModel.swift new file mode 100644 index 0000000..a41a80c --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Extension/Extension+UserBlockDTOModel.swift @@ -0,0 +1,18 @@ +// +// Extension+UserBlockDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +public extension UserBlockModel { + func toUserBlockDTOToModel() -> UserBlockDTOModel { + let data: UserBlockResponseDTOModel = .init( + status: self.data?.status ?? false, + message: self.data?.message ?? "" + ) + + return UserBlockDTOModel(data: data) + } + +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Extension/Extension+UserBlockListModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Extension/Extension+UserBlockListModel.swift new file mode 100644 index 0000000..fda6305 --- /dev/null +++ b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Extension/Extension+UserBlockListModel.swift @@ -0,0 +1,25 @@ +// +// Extension+UserBlockListModel.swift +// Model +// +// Created by Wonji Suh on 11/10/24. +// + +import Foundation + +public extension UserBlockListModel { + func toUserBlockLIstDTOToModel() -> UserBlockListDTOModel { + let data: [UserBlockListResponseDTOModel] = self.data?.compactMap( + { data in + return UserBlockListResponseDTOModel( + blockID: data.blockID ?? .zero, + blockedUserID: data.blockedUserID ?? "", + nickname: data.nickname ?? "", + job: data.job ?? "", + generation: data.generation ?? "" + ) + }) ?? [] + + return UserBlockListDTOModel(data: data) + } +} diff --git a/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/UserBlockListModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Model/UserBlockListModel.swift similarity index 100% rename from OPeace/Projects/Core/Networking/Model/Sources/UserBlock/UserBlockListModel.swift rename to OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Model/UserBlockListModel.swift diff --git a/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/UserBlockModel.swift b/OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Model/UserBlockModel.swift similarity index 100% rename from OPeace/Projects/Core/Networking/Model/Sources/UserBlock/UserBlockModel.swift rename to OPeace/Projects/Core/Networking/Model/Sources/UserBlock/Model/UserBlockModel.swift diff --git a/OPeace/Projects/Core/Networking/UseCase/Project.swift b/OPeace/Projects/Core/Networking/UseCase/Project.swift index 40e2521..dfdda4d 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Project.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Project.swift @@ -14,7 +14,8 @@ let project = Project.makeModule( .Networking(implements: .Service), .Networking(implements: .Model), .SPM.composableArchitecture, - .SPM.swiftJWT + .SPM.swiftJWT, + .SPM.firebaseRemoteConfig ], sources: ["Sources/**"] ) diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/AuthAPIManger/AuthAPIManger.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/AuthAPIManger/AuthAPIManger.swift index b3a948c..887f2d4 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/AuthAPIManger/AuthAPIManger.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/AuthAPIManger/AuthAPIManger.swift @@ -14,19 +14,19 @@ import AsyncMoya public final class AuthAPIManger { public static let shared = AuthAPIManger() - var loginModel: RefreshModel? = nil + var loginModel: RefreshDTOModel? = nil let repository = AuthRepository() public init() {} - public func refeshTokenRsponse(_ result: Result) -> Result { + public func refeshTokenRsponse(_ result: Result) -> Result { switch result { case .success(let refeshModel): self.loginModel = refeshModel - APIHeader.accessTokenKey = refeshModel.data?.accessToken ?? "" - APIHeader.refreshTokenKey = refeshModel.data?.refreshToken ?? "" - UserDefaults.standard.set(refeshModel.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - UserDefaults.standard.set(refeshModel.data?.refreshToken, forKey: "REFRESH_TOKEN") + APIHeader.accessTokenKey = refeshModel.data.accessToken + APIHeader.refreshTokenKey = refeshModel.data.refreshToken + UserDefaults.standard.set(refeshModel.data.accessToken, forKey: "ACCESS_TOKEN") + UserDefaults.standard.set(refeshModel.data.refreshToken, forKey: "REFRESH_TOKEN") return .success(refeshModel) case .failure(let error): #logError("리프레쉬 에러", error.localizedDescription) diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepository.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepository.swift index d162f34..c002d43 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepository.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepository.swift @@ -63,9 +63,10 @@ import SwiftJWT } //MARK: - 애플로그인 API - public func appleLogin() async throws -> UserLoginModel? { + public func appleLogin() async throws -> OAuthDTOModel? { let appleAcessToken = UserDefaults.standard.string(forKey: "APPLE_ACCESS_TOKEN") ?? "" - return try await provider.requestAsync(.appleLogin(accessToken: appleAcessToken), decodeTo: UserLoginModel.self) + let userLoginModel = try await provider.requestAsync(.appleLogin(accessToken: appleAcessToken), decodeTo: UserLoginModel.self) + return userLoginModel.toOAuthDTOModel() } //MARK: - 애플로그인 JWT @@ -104,16 +105,18 @@ import SwiftJWT } //MARK: - 애플로그인 토큰 발급 - public func getAppleRefreshToken(code: String) async throws -> AppleTokenResponse? { + public func getAppleRefreshToken(code: String) async throws -> OAuthDTOModel? { let clientSecret = try await makeJWT() - return try await appleProvider.requestAsync(.getRefreshToken(code: code, clientSecret: clientSecret), decodeTo: AppleTokenResponse.self) + let appleResponseModel = try await appleProvider.requestAsync(.getRefreshToken(code: code, clientSecret: clientSecret), decodeTo: AppleTokenResponse.self) + return appleResponseModel.toOAuthDTOToModel() } //MARK: - 애플 탈퇴 - public func revokeAppleToken() async throws -> AppleTokenResponse? { + public func revokeAppleToken() async throws -> OAuthDTOModel? { let clientSecret = UserDefaults.standard.string(forKey: "AppleClientSecret") ?? "" let token = UserDefaults.standard.string(forKey: "APPLE_REFRESH_TOKEN") ?? "" - return try await appleProvider.requestAsync(.revokeToken(clientSecret: clientSecret, token: token), decodeTo: AppleTokenResponse.self) + let appleResponseModel = try await appleProvider.requestAsync(.revokeToken(clientSecret: clientSecret, token: token), decodeTo: AppleTokenResponse.self) + return appleResponseModel.toOAuthDTOToModel() } //MARK: - kakaoLoigin @@ -238,62 +241,73 @@ import SwiftJWT //MARK: - 카카오 로그인 API - public func reauestKakaoLogin() async throws -> UserLoginModel? { + public func reauestKakaoLogin() async throws -> OAuthDTOModel? { let kakaoAcessToken = UserDefaults.standard.string(forKey: "KAKAO_ACCESS_TOKEN") - return try await provider.requestAsync(.kakaoLogin(accessToken: kakaoAcessToken ?? ""), decodeTo: UserLoginModel.self) + let userLoginModel = try await provider.requestAsync(.kakaoLogin(accessToken: kakaoAcessToken ?? ""), decodeTo: UserLoginModel.self) + return userLoginModel.toOAuthDTOModel() + } //MARK: - 토큰 재발급 API - public func requestRefreshToken(refreshToken: String) async throws -> RefreshModel? { - return try await provider.requestAsync(.refreshToken(refreshToken: refreshToken), decodeTo: RefreshModel.self) + public func requestRefreshToken(refreshToken: String) async throws -> RefreshDTOModel? { + let refershModel = try await provider.requestAsync(.refreshToken(refreshToken: refreshToken), decodeTo: RefreshModel.self) + return refershModel.toRefreshDTOToModel() } //MARK: - 유저정보 조회 API - public func fetchUserInfo() async throws -> UpdateUserInfoModel? { - return try await authProvider.requestAsync(.fetchUserInfo, decodeTo: UpdateUserInfoModel.self) + public func fetchUserInfo() async throws -> UpdateUserInfoDTOModel? { + let updateUserInfoModel = try await authProvider.requestAsync(.fetchUserInfo, decodeTo: UpdateUserInfoModel.self) + return updateUserInfoModel.toUpdateUserInfoDTOToModel() } //MARK: - 유저 로그아웃 API - public func logoutUser(refreshToken: String) async throws -> UserLogOutModel? { + public func logoutUser(refreshToken: String) async throws -> UserDTOModel? { let refreshToken = UserDefaults.standard.string(forKey: "REFRESH_TOKEN") ?? "" - return try await authProvider.requestAsync( + let userLogOutModel = try await authProvider.requestAsync( .logoutUser(refreshToken: refreshToken), decodeTo: UserLogOutModel.self) + return userLogOutModel.toUserDTOToModel() } //MARK: - 자동 로그인 API - public func autoLogin() async throws -> UseLoginModel? { - return try await authProvider.requestAsync(.autoLogin, decodeTo: UseLoginModel.self) + public func autoLogin() async throws -> UserDTOModel? { + let userLoginModel = try await authProvider.requestAsync(.autoLogin, decodeTo: UseLoginModel.self) + return userLoginModel.userDTOToModel() } //MARK: - 회원 탈퇴 API - public func deleteUser(reason: String) async throws -> DeleteUserModel? { - return try await authProvider.requestAsync(.deleteUser(reason: reason), decodeTo: DeleteUserModel.self) + public func deleteUser(reason: String) async throws -> DeletUserDTOModel? { + let deleteUserModel = try await authProvider.requestAsync(.deleteUser(reason: reason), decodeTo: DeleteUserModel.self) + return deleteUserModel.toDeleteUserDTOToModel() } //MARK: - 유저 토큰 확인 API - public func checkUserVerify() async throws -> CheckUserVerifyModel? { - return try await provider.requestAsync(.userVerify, decodeTo: CheckUserVerifyModel.self) + public func checkUserVerify() async throws -> CheckUserDTOModel? { + let chekcUserVerfiyModel = try await provider.requestAsync(.userVerify, decodeTo: CheckUserVerifyModel.self) + return chekcUserVerfiyModel.toCheckUserDTOModel() } //MARK: - 유저 차단 API public func userBlock( questioniD: Int, userID: String - ) async throws -> UserBlockModel? { - return try await authProvider.requestAsync(.userBlock( + ) async throws -> UserBlockDTOModel? { + let userBlockModel = try await authProvider.requestAsync(.userBlock( questioniD: questioniD, userID: userID), decodeTo: UserBlockModel.self) + return userBlockModel.toUserBlockDTOToModel() } //MARK: - 유저 차단 리스트 조회 API - public func fetchUserBlockList() async throws -> UserBlockListModel? { - return try await authProvider.requestAsync(.fectchUserBlock, decodeTo: UserBlockListModel.self) + public func fetchUserBlockList() async throws -> UserBlockListDTOModel? { + let userBlockListModel = try await authProvider.requestAsync(.fectchUserBlock, decodeTo: UserBlockListModel.self) + return userBlockListModel.toUserBlockLIstDTOToModel() } //MARK: - 유저 차단 해제 API - public func realseUserBlock(userID: String) async throws -> UserBlockModel? { - return try await authProvider.requestAsync(.realseUserBlock(userID: userID), decodeTo: UserBlockModel.self) + public func realseUserBlock(userID: String) async throws -> UserBlockDTOModel? { + let userBlockModel = try await authProvider.requestAsync(.realseUserBlock(userID: userID), decodeTo: UserBlockModel.self) + return userBlockModel.toUserBlockDTOToModel() } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepositoryProtocol.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepositoryProtocol.swift index 6773342..470ea73 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepositoryProtocol.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/AuthRepositoryProtocol.swift @@ -11,21 +11,21 @@ import Utills import Model public protocol AuthRepositoryProtocol { - func handleAppleLogin(_ request: Result) async throws -> ASAuthorization - func appleLogin() async throws -> UserLoginModel? - func requestKakaoTokenAsync() async throws -> (String?, String?) - func reauestKakaoLogin() async throws -> UserLoginModel? - func requestRefreshToken(refreshToken : String) async throws -> RefreshModel? - func fetchUserInfo() async throws -> UpdateUserInfoModel? - func logoutUser(refreshToken : String) async throws -> UserLogOutModel? - func autoLogin() async throws -> UseLoginModel? - func deleteUser(reason: String) async throws -> DeleteUserModel? - func checkUserVerify() async throws -> CheckUserVerifyModel? - func userBlock(questioniD: Int, userID: String) async throws -> UserBlockModel? - func fetchUserBlockList() async throws -> UserBlockListModel? - func realseUserBlock(userID: String) async throws -> UserBlockModel? - func makeJWT() async throws -> String - func getAppleRefreshToken(code: String) async throws -> AppleTokenResponse? - func revokeAppleToken() async throws -> AppleTokenResponse? - + func handleAppleLogin(_ request: Result) async throws -> ASAuthorization + func appleLogin() async throws -> OAuthDTOModel? + func requestKakaoTokenAsync() async throws -> (String?, String?) + func reauestKakaoLogin() async throws -> OAuthDTOModel? + func requestRefreshToken(refreshToken : String) async throws -> RefreshDTOModel? + func fetchUserInfo() async throws -> UpdateUserInfoDTOModel? + func logoutUser(refreshToken : String) async throws -> UserDTOModel? + func autoLogin() async throws -> UserDTOModel? + func deleteUser(reason: String) async throws -> DeletUserDTOModel? + func checkUserVerify() async throws -> CheckUserDTOModel? + func userBlock(questioniD: Int, userID: String) async throws -> UserBlockDTOModel? + func fetchUserBlockList() async throws -> UserBlockListDTOModel? + func realseUserBlock(userID: String) async throws -> UserBlockDTOModel? + func makeJWT() async throws -> String + func getAppleRefreshToken(code: String) async throws -> OAuthDTOModel? + func revokeAppleToken() async throws -> OAuthDTOModel? + } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/DefaultAuthRepository.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/DefaultAuthRepository.swift index a9c1847..3b32b2e 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/DefaultAuthRepository.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/Repository/DefaultAuthRepository.swift @@ -12,82 +12,82 @@ import Model import Utills public final class DefaultAuthRepository: AuthRepositoryProtocol { - - - public init() {} - - public func handleAppleLogin(_ requestResult: Result) async throws -> ASAuthorization { - return try await withCheckedThrowingContinuation { continuation in - switch requestResult { - case .success(let request): - break - case .failure(let error): - continuation.resume(throwing: error) - } - } - } - - public func appleLogin() async throws -> UserLoginModel? { - return nil - } - - - public func requestKakaoTokenAsync() async throws -> (String?, String?) { - return (nil, nil) - } - - public func reauestKakaoLogin() async throws -> UserLoginModel? { - return nil - } - - public func requestRefreshToken( - refreshToken: String - ) async throws -> RefreshModel? { - return nil - } - - public func fetchUserInfo() async throws -> UpdateUserInfoModel? { - return nil - } - - - public func logoutUser(refreshToken: String) async throws -> UserLogOutModel? { - return nil - } - - public func autoLogin() async throws -> UseLoginModel? { - return nil - } - - public func deleteUser(reason: String) async throws -> DeleteUserModel?{ - return nil - } - - public func checkUserVerify() async throws -> CheckUserVerifyModel? { - return nil - } - - public func userBlock(questioniD: Int, userID: String) async throws -> UserBlockModel? { - return nil - } - - public func fetchUserBlockList() async throws -> UserBlockListModel? { - return nil - } - - public func realseUserBlock(userID: String) async throws -> UserBlockModel? { - return nil - } - - public func makeJWT() async throws -> String { - return "" - } - - public func getAppleRefreshToken(code: String) async throws -> AppleTokenResponse? { - return nil - } - - public func revokeAppleToken() async throws -> AppleTokenResponse? { - return nil - } + + + public init() {} + + public func handleAppleLogin(_ requestResult: Result) async throws -> ASAuthorization { + return try await withCheckedThrowingContinuation { continuation in + switch requestResult { + case .success(let request): + break + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + + public func appleLogin() async throws -> OAuthDTOModel? { + return nil + } + + + public func requestKakaoTokenAsync() async throws -> (String?, String?) { + return (nil, nil) + } + + public func reauestKakaoLogin() async throws -> OAuthDTOModel? { + return nil + } + + public func requestRefreshToken( + refreshToken: String + ) async throws -> RefreshDTOModel? { + return nil + } + + public func fetchUserInfo() async throws -> UpdateUserInfoDTOModel? { + return nil + } + + + public func logoutUser(refreshToken: String) async throws -> UserDTOModel? { + return nil + } + + public func autoLogin() async throws -> UserDTOModel? { + return nil + } + + public func deleteUser(reason: String) async throws -> DeletUserDTOModel?{ + return nil + } + + public func checkUserVerify() async throws -> CheckUserDTOModel? { + return nil + } + + public func userBlock(questioniD: Int, userID: String) async throws -> UserBlockDTOModel? { + return nil + } + + public func fetchUserBlockList() async throws -> UserBlockListDTOModel? { + return nil + } + + public func realseUserBlock(userID: String) async throws -> UserBlockDTOModel? { + return nil + } + + public func makeJWT() async throws -> String { + return "" + } + + public func getAppleRefreshToken(code: String) async throws -> OAuthDTOModel? { + return nil + } + + public func revokeAppleToken() async throws -> OAuthDTOModel? { + return nil + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/ AuthUseCaseProtocol.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/ AuthUseCaseProtocol.swift index 45297a7..6349299 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/ AuthUseCaseProtocol.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/ AuthUseCaseProtocol.swift @@ -11,20 +11,20 @@ import Utills import Model public protocol AuthUseCaseProtocol { - func handleAppleLogin(_ request: Result) async throws -> ASAuthorization - func requestKakaoTokenAsync() async throws -> (String?, String?) - func appleLogin() async throws -> UserLoginModel? - func reauestKakaoLogin() async throws -> UserLoginModel? - func requestRefreshToken(refreshToken : String) async throws -> RefreshModel? - func fetchUserInfo() async throws -> UpdateUserInfoModel? - func logoutUser(refreshToken : String) async throws -> UserLogOutModel? - func autoLogin() async throws -> UseLoginModel? - func deleteUser(reason: String) async throws -> DeleteUserModel? - func checkUserVerify() async throws -> CheckUserVerifyModel? - func userBlock(questioniD: Int, userID: String) async throws -> UserBlockModel? - func fetchUserBlockList() async throws -> UserBlockListModel? - func realseUserBlock(userID: String) async throws -> UserBlockModel? - func makeJWT() async throws -> String - func getAppleRefreshToken(code: String) async throws -> AppleTokenResponse? - func revokeAppleToken() async throws -> AppleTokenResponse? + func handleAppleLogin(_ request: Result) async throws -> ASAuthorization + func requestKakaoTokenAsync() async throws -> (String?, String?) + func appleLogin() async throws -> OAuthDTOModel? + func reauestKakaoLogin() async throws -> OAuthDTOModel? + func requestRefreshToken(refreshToken : String) async throws -> RefreshDTOModel? + func fetchUserInfo() async throws -> UpdateUserInfoDTOModel? + func logoutUser(refreshToken : String) async throws -> UserDTOModel? + func autoLogin() async throws -> UserDTOModel? + func deleteUser(reason: String) async throws -> DeletUserDTOModel? + func checkUserVerify() async throws -> CheckUserDTOModel? + func userBlock(questioniD: Int, userID: String) async throws -> UserBlockDTOModel? + func fetchUserBlockList() async throws -> UserBlockListDTOModel? + func realseUserBlock(userID: String) async throws -> UserBlockDTOModel? + func makeJWT() async throws -> String + func getAppleRefreshToken(code: String) async throws -> OAuthDTOModel? + func revokeAppleToken() async throws -> OAuthDTOModel? } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/AuthUseCase.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/AuthUseCase.swift index d4fe1fe..934b206 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/AuthUseCase.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Auth/UseCase/AuthUseCase.swift @@ -14,117 +14,117 @@ import Model public struct AuthUseCase: AuthUseCaseProtocol { - private let repository: AuthRepositoryProtocol - - - public init( - repository: AuthRepositoryProtocol - ) { - self.repository = repository - } - - - //MARK: - 애플 로그인 토큰 - public func handleAppleLogin( - _ request: Result - ) async throws -> ASAuthorization { - try await repository.handleAppleLogin(request) - } - - //MARK: - 애플 로그인 - public func appleLogin() async throws -> UserLoginModel? { - try await repository.appleLogin() - } - - //MARK: - 애플 JWT 만들기 - public func makeJWT() async throws -> String { - try await repository.makeJWT() - } - - //MARK: - 애플 토큰 발급 - public func getAppleRefreshToken( - code: String - ) async throws -> AppleTokenResponse? { - try await repository.getAppleRefreshToken(code: code) - } - - //MARK: - 애플 회원 탈퇴 - public func revokeAppleToken() async throws -> AppleTokenResponse? { - try await repository.revokeAppleToken() - } - - //MARK: - 카카오 로그인 토근 - public func requestKakaoTokenAsync() async throws -> (String?, String?) { - try await repository.requestKakaoTokenAsync() - } - - //MARK: - 카카오 로그인 - public func reauestKakaoLogin() async throws -> UserLoginModel? { - try await repository.reauestKakaoLogin() - } - - //MARK: - 리프레쉬토큰 재발급 - public func requestRefreshToken( - refreshToken: String - ) async throws -> RefreshModel? { - try await repository.requestRefreshToken(refreshToken: refreshToken) - } - - //MARK: - 유저 정보 조회 - public func fetchUserInfo() async throws -> UpdateUserInfoModel? { - try await repository.fetchUserInfo() - } - - //MARK: - 유저 로그아웃 - public func logoutUser(refreshToken: String) async throws -> UserLogOutModel? { - try await repository.logoutUser(refreshToken: refreshToken) - } - - //MARK: - 자동 로그인 - public func autoLogin() async throws -> UseLoginModel? { - try await repository.autoLogin() - } - - //MARK: - 회원탈퇴 - public func deleteUser(reason: String) async throws -> DeleteUserModel? { - try await repository.deleteUser(reason: reason) - } - - //MARK: - 유저 토큰 확인 - public func checkUserVerify() async throws -> CheckUserVerifyModel? { - try await repository.checkUserVerify() - } - - //MARK: - 유저 차단 - public func userBlock( - questioniD: Int, - userID: String - ) async throws -> UserBlockModel? { - try await repository.userBlock(questioniD: questioniD, userID: userID) - } - - //MARK: - 유저차단 목록 - public func fetchUserBlockList() async throws -> UserBlockListModel? { - try await repository.fetchUserBlockList() - } - - //MARK: - 유저 차단 해제 - public func realseUserBlock(userID: String) async throws -> UserBlockModel? { - try await repository.realseUserBlock(userID: userID) - } + private let repository: AuthRepositoryProtocol + + + public init( + repository: AuthRepositoryProtocol + ) { + self.repository = repository + } + + + //MARK: - 애플 로그인 토큰 + public func handleAppleLogin( + _ request: Result + ) async throws -> ASAuthorization { + try await repository.handleAppleLogin(request) + } + + //MARK: - 애플 로그인 + public func appleLogin() async throws -> OAuthDTOModel? { + try await repository.appleLogin() + } + + //MARK: - 애플 JWT 만들기 + public func makeJWT() async throws -> String { + try await repository.makeJWT() + } + + //MARK: - 애플 토큰 발급 + public func getAppleRefreshToken( + code: String + ) async throws -> OAuthDTOModel? { + try await repository.getAppleRefreshToken(code: code) + } + + //MARK: - 애플 회원 탈퇴 + public func revokeAppleToken() async throws -> OAuthDTOModel? { + try await repository.revokeAppleToken() + } + + //MARK: - 카카오 로그인 토근 + public func requestKakaoTokenAsync() async throws -> (String?, String?) { + try await repository.requestKakaoTokenAsync() + } + + //MARK: - 카카오 로그인 + public func reauestKakaoLogin() async throws -> OAuthDTOModel? { + try await repository.reauestKakaoLogin() + } + + //MARK: - 리프레쉬토큰 재발급 + public func requestRefreshToken( + refreshToken: String + ) async throws -> RefreshDTOModel? { + try await repository.requestRefreshToken(refreshToken: refreshToken) + } + + //MARK: - 유저 정보 조회 + public func fetchUserInfo() async throws -> UpdateUserInfoDTOModel? { + try await repository.fetchUserInfo() + } + + //MARK: - 유저 로그아웃 + public func logoutUser(refreshToken: String) async throws -> UserDTOModel? { + try await repository.logoutUser(refreshToken: refreshToken) + } + + //MARK: - 자동 로그인 + public func autoLogin() async throws -> UserDTOModel? { + try await repository.autoLogin() + } + + //MARK: - 회원탈퇴 + public func deleteUser(reason: String) async throws -> DeletUserDTOModel? { + try await repository.deleteUser(reason: reason) + } + + //MARK: - 유저 토큰 확인 + public func checkUserVerify() async throws -> CheckUserDTOModel? { + try await repository.checkUserVerify() + } + + //MARK: - 유저 차단 + public func userBlock( + questioniD: Int, + userID: String + ) async throws -> UserBlockDTOModel? { + try await repository.userBlock(questioniD: questioniD, userID: userID) + } + + //MARK: - 유저차단 목록 + public func fetchUserBlockList() async throws -> UserBlockListDTOModel? { + try await repository.fetchUserBlockList() + } + + //MARK: - 유저 차단 해제 + public func realseUserBlock(userID: String) async throws -> UserBlockDTOModel? { + try await repository.realseUserBlock(userID: userID) + } } extension AuthUseCase: DependencyKey { - public static let liveValue: AuthUseCase = { - let authRepository = DependencyContainer.live.resolve(AuthRepositoryProtocol.self) ?? DefaultAuthRepository() - return AuthUseCase(repository: authRepository) - }() + public static let liveValue: AuthUseCase = { + let authRepository = DependencyContainer.live.resolve(AuthRepositoryProtocol.self) ?? DefaultAuthRepository() + return AuthUseCase(repository: authRepository) + }() } public extension DependencyValues { - var authUseCase: AuthUseCaseProtocol { - get { self[AuthUseCase.self] } - set { self[AuthUseCase.self] = newValue as! AuthUseCase} - } + var authUseCase: AuthUseCaseProtocol { + get { self[AuthUseCase.self] } + set { self[AuthUseCase.self] = newValue as! AuthUseCase} + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/DefaultQuestionRepository.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/DefaultQuestionRepository.swift index d861742..deb9538 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/DefaultQuestionRepository.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/DefaultQuestionRepository.swift @@ -9,61 +9,61 @@ import Foundation import Model public final class DefaultQuestionRepository: QuestionRepositoryProtocol { + + public init() { - public init() { - - } - - public func fetchQuestionList( - page: Int, - pageSize: Int, - job: String, - generation: String, - sortBy: QuestionSort) async throws -> QuestionModel? { - return nil - } - - public func myQuestionList( - page: Int, - pageSize: Int - ) async throws -> QuestionModel? { - return nil - } - - public func createQuestion( - emoji: String, - title: String, - choiceA: String, - choiceB: String - ) async throws -> CreateQuestionModel? { - return nil - } - - public func isVoteQuestionLike(questionID: Int) async throws -> VoteQuestionLikeModel? { - return nil - } - - public func isVoteQuestionAnswer( - questionID: Int, - choicAnswer: String - ) async throws -> QuestionVoteModel? { - return nil - } - - public func deleteQuestion(questionID: Int) async throws -> DeleteQuestionModel? { - return nil - } - - public func reportQuestion( - questionID: Int, - reason: String - ) async throws -> ReportQuestionModel? { - return nil - } - - public func statusQuestion( - questionID: Int - ) async throws -> StatusQuestionModel? { - return nil + } + + public func fetchQuestionList( + page: Int, + pageSize: Int, + job: String, + generation: String, + sortBy: QuestionSort) async throws -> QuestionDTOModel? { + return nil } + + public func myQuestionList( + page: Int, + pageSize: Int + ) async throws -> QuestionDTOModel? { + return nil + } + + public func createQuestion( + emoji: String, + title: String, + choiceA: String, + choiceB: String + ) async throws -> CreateQuestionDTOModel? { + return nil + } + + public func isVoteQuestionLike(questionID: Int) async throws -> FlagQuestionDTOModel? { + return nil + } + + public func isVoteQuestionAnswer( + questionID: Int, + choicAnswer: String + ) async throws -> FlagQuestionDTOModel? { + return nil + } + + public func deleteQuestion(questionID: Int) async throws -> FlagQuestionDTOModel? { + return nil + } + + public func reportQuestion( + questionID: Int, + reason: String + ) async throws -> FlagQuestionDTOModel? { + return nil + } + + public func statusQuestion( + questionID: Int + ) async throws -> StatusQuestionDTOModel? { + return nil + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepository.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepository.swift index a56f636..f731dbc 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepository.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepository.swift @@ -16,91 +16,99 @@ import Moya @Observable public class QuestionRepository: QuestionRepositoryProtocol { - private let provider = MoyaProvider(session: Session(interceptor: AuthInterceptor.shared), plugins: [MoyaLoggingPlugin()]) + private let provider = MoyaProvider(session: Session(interceptor: AuthInterceptor.shared), plugins: [MoyaLoggingPlugin()]) + + public init() { - public init() { - - } - - //MARK: - 피드 목록API - public func fetchQuestionList( - page: Int, - pageSize: Int, - job: String, - generation: String, - sortBy: QuestionSort - ) async throws -> QuestionModel? { - return try await provider.requestAsync(.fetchQuestionList( - page: page, - pageSize: pageSize, - job: job, - generation: generation, - sortBy: sortBy.questionSortDesc), decodeTo: QuestionModel.self) - } - - - //MARK: - 프로필 내가 쓴글 목록 조회 API - public func myQuestionList( - page: Int, - pageSize: Int - ) async throws -> QuestionModel? { - return try await provider.requestAsync(.myQuestionList( - page: page, - pageSize: pageSize - ), decodeTo: QuestionModel.self) - } - - //MARK: - 질문 생성API - public func createQuestion( - emoji: String, - title: String, - choiceA: String, - choiceB: String - ) async throws -> CreateQuestionModel? { - return try await provider.requestAsync(.createQuestion( - emoji: emoji, - title: title, - choiceA: choiceA, - choiceB: choiceB), decodeTo: CreateQuestionModel.self) - } - - //MARK: - 좋아요 API - public func isVoteQuestionLike(questionID: Int) async throws -> VoteQuestionLikeModel? { - return try await provider.requestAsync(.isVoteQustionLike( - id: questionID), decodeTo: VoteQuestionLikeModel.self) - } - - //MARK: - 유저 투표 API - public func isVoteQuestionAnswer( - questionID: Int, - choicAnswer: String - ) async throws -> QuestionVoteModel? { - return try await provider.requestAsync(.isVoteQuestionAnswer( - id: questionID, - userChoice: choicAnswer), decodeTo: QuestionVoteModel.self) - } - - //MARK: - 질문 삭제 API - public func deleteQuestion(questionID: Int) async throws -> DeleteQuestionModel? { - return try await provider.requestAsync(.deleteQuestion(id: questionID), decodeTo: DeleteQuestionModel.self) - } - - //MARK: - 유저 질문 신고 API - public func reportQuestion( - questionID: Int, - reason: String - ) async throws -> ReportQuestionModel? { - return try await provider.requestAsync(.reportQuestion( - id: questionID, - reason: reason - ), decodeTo: ReportQuestionModel.self) - } - - //MARK: - 유저 투표 결과 API - public func statusQuestion( - questionID: Int - ) async throws -> StatusQuestionModel? { - return try await provider.requestAsync(.statusQuestion( - id: questionID), decodeTo: StatusQuestionModel.self) - } + } + + //MARK: - 피드 목록API + public func fetchQuestionList( + page: Int, + pageSize: Int, + job: String, + generation: String, + sortBy: QuestionSort + ) async throws -> QuestionDTOModel? { + let questionModel = try await provider.requestAsync(.fetchQuestionList( + page: page, + pageSize: pageSize, + job: job, + generation: generation, + sortBy: sortBy.questionSortDesc), decodeTo: QuestionModel.self) + return questionModel.toQuestionDTOToModel() + } + + + //MARK: - 프로필 내가 쓴글 목록 조회 API + public func myQuestionList( + page: Int, + pageSize: Int + ) async throws -> QuestionDTOModel? { + let questionModel = try await provider.requestAsync(.myQuestionList( + page: page, + pageSize: pageSize + ), decodeTo: QuestionModel.self) + return questionModel.toQuestionDTOToModel() + } + + //MARK: - 질문 생성API + public func createQuestion( + emoji: String, + title: String, + choiceA: String, + choiceB: String + ) async throws -> CreateQuestionDTOModel? { + let createQuestionModel = try await provider.requestAsync(.createQuestion( + emoji: emoji, + title: title, + choiceA: choiceA, + choiceB: choiceB), decodeTo: CreateQuestionModel.self) + return createQuestionModel.toCreateQuestionDTOToModel() + } + + //MARK: - 좋아요 API + public func isVoteQuestionLike(questionID: Int) async throws -> FlagQuestionDTOModel? { + let voteQuestionLikeModel = try await provider.requestAsync(.isVoteQustionLike( + id: questionID), decodeTo: VoteQuestionLikeModel.self) + return voteQuestionLikeModel.toFlagQuestionDTOToModel() + } + + //MARK: - 유저 투표 API + public func isVoteQuestionAnswer( + questionID: Int, + choicAnswer: String + ) async throws -> FlagQuestionDTOModel? { + let voteQuestionAnswerModel = try await provider.requestAsync(.isVoteQuestionAnswer( + id: questionID, + userChoice: choicAnswer), decodeTo: QuestionVoteModel.self) + return voteQuestionAnswerModel.toFlagQuestionDTOToModel() + } + + //MARK: - 질문 삭제 API + public func deleteQuestion(questionID: Int) async throws -> FlagQuestionDTOModel? { + let deleteQuestionModel = try await provider.requestAsync(.deleteQuestion(id: questionID), decodeTo: DeleteQuestionModel.self) + return deleteQuestionModel.toFlagQuestionDTOToModel() + } + + //MARK: - 유저 질문 신고 API + public func reportQuestion( + questionID: Int, + reason: String + ) async throws -> FlagQuestionDTOModel? { + let reportQuestionModel = try await provider.requestAsync(.reportQuestion( + id: questionID, + reason: reason + ), decodeTo: ReportQuestionModel.self) + return reportQuestionModel.toFlagQuestionDTOToModel() + } + + //MARK: - 유저 투표 결과 API + public func statusQuestion( + questionID: Int + ) async throws -> StatusQuestionDTOModel? { + let statusQuestionModel = try await provider.requestAsync(.statusQuestion( + id: questionID), decodeTo: StatusQuestionModel.self) + return statusQuestionModel.toStatusDTOToModel() + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepositoryProtocol.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepositoryProtocol.swift index 8ae18de..ba34229 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepositoryProtocol.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/Repository/QuestionRepositoryProtocol.swift @@ -9,24 +9,24 @@ import Foundation import Model public protocol QuestionRepositoryProtocol { - func fetchQuestionList( - page: Int, - pageSize: Int, - job: String, - generation: String, - sortBy: QuestionSort) async throws -> QuestionModel? - func myQuestionList(page: Int, pageSize: Int) async throws -> QuestionModel? - func createQuestion( - emoji: String, - title: String, - choiceA: String, - choiceB: String - ) async throws -> CreateQuestionModel? - func isVoteQuestionLike(questionID: Int) async throws -> VoteQuestionLikeModel? - func isVoteQuestionAnswer(questionID: Int, choicAnswer: String) async throws -> QuestionVoteModel? - func deleteQuestion(questionID: Int) async throws -> DeleteQuestionModel? - func reportQuestion(questionID: Int, reason: String) async throws -> ReportQuestionModel? - func statusQuestion(questionID: Int) async throws -> StatusQuestionModel? - + func fetchQuestionList( + page: Int, + pageSize: Int, + job: String, + generation: String, + sortBy: QuestionSort) async throws -> QuestionDTOModel? + func myQuestionList(page: Int, pageSize: Int) async throws -> QuestionDTOModel? + func createQuestion( + emoji: String, + title: String, + choiceA: String, + choiceB: String + ) async throws -> CreateQuestionDTOModel? + func isVoteQuestionLike(questionID: Int) async throws -> FlagQuestionDTOModel? + func isVoteQuestionAnswer(questionID: Int, choicAnswer: String) async throws -> FlagQuestionDTOModel? + func deleteQuestion(questionID: Int) async throws -> FlagQuestionDTOModel? + func reportQuestion(questionID: Int, reason: String) async throws -> FlagQuestionDTOModel? + func statusQuestion(questionID: Int) async throws -> StatusQuestionDTOModel? + } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCase.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCase.swift index 778ba31..0bc898a 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCase.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCase.swift @@ -13,99 +13,99 @@ import DiContainer import ComposableArchitecture public struct QuestionUseCase: QuestionUseCaseProtocol { - private let repository: QuestionRepositoryProtocol - - public init( - repository: QuestionRepositoryProtocol - ) { - self.repository = repository - } - - //MARK: - 피드 목록 조회 - public func fetchQuestionList( - page: Int, - pageSize: Int, - job: String, - generation: String, - sortBy: QuestionSort - ) async throws -> QuestionModel? { - try await repository.fetchQuestionList( - page: page, - pageSize: pageSize, - job: job, - generation: generation, - sortBy: sortBy) - } - - - //MARK: - 프로필에서 내가 쓴글 조회 - public func myQuestionList( - page: Int, - pageSize: Int - ) async throws -> QuestionModel? { - try await repository.myQuestionList(page: page, pageSize: pageSize) - } - - //MARK: - 질문 생성 - public func createQuestion( - emoji: String, - title: String, - choiceA: String, - choiceB: String - ) async throws -> CreateQuestionModel? { - try await repository.createQuestion( - emoji: emoji, - title: title, - choiceA: choiceA, - choiceB: choiceB - ) - } - - //MARK: - 좋아요 누르기 - public func isVoteQuestionLike(questionID: Int) async throws -> VoteQuestionLikeModel? { - try await repository.isVoteQuestionLike(questionID: questionID) - } - - //MARK: - 응답 선택 - public func isVoteQuestionAnswer( - questionID: Int, - choicAnswer: String - ) async throws -> QuestionVoteModel? { - try await repository.isVoteQuestionAnswer(questionID: questionID, choicAnswer: choicAnswer) - } - - //MARK: - 내가 쓴글 삭제 - public func deleteQuestion(questionID: Int) async throws -> DeleteQuestionModel? { - try await repository.deleteQuestion(questionID: questionID) - } - - //MARK: - 유저글 신고 - public func reportQuestion( - questionID: Int, - reason: String - ) async throws -> ReportQuestionModel? { - try await repository.reportQuestion( - questionID: questionID, - reason: reason - ) - } - - //MARK: - 투표 결과 - public func statusQuestion(questionID: Int) async throws -> StatusQuestionModel? { - try await repository.statusQuestion(questionID: questionID) - } + private let repository: QuestionRepositoryProtocol + + public init( + repository: QuestionRepositoryProtocol + ) { + self.repository = repository + } + + //MARK: - 피드 목록 조회 + public func fetchQuestionList( + page: Int, + pageSize: Int, + job: String, + generation: String, + sortBy: QuestionSort + ) async throws -> QuestionDTOModel? { + try await repository.fetchQuestionList( + page: page, + pageSize: pageSize, + job: job, + generation: generation, + sortBy: sortBy) + } + + + //MARK: - 프로필에서 내가 쓴글 조회 + public func myQuestionList( + page: Int, + pageSize: Int + ) async throws -> QuestionDTOModel? { + try await repository.myQuestionList(page: page, pageSize: pageSize) + } + + //MARK: - 질문 생성 + public func createQuestion( + emoji: String, + title: String, + choiceA: String, + choiceB: String + ) async throws -> CreateQuestionDTOModel? { + try await repository.createQuestion( + emoji: emoji, + title: title, + choiceA: choiceA, + choiceB: choiceB + ) + } + + //MARK: - 좋아요 누르기 + public func isVoteQuestionLike(questionID: Int) async throws -> FlagQuestionDTOModel? { + try await repository.isVoteQuestionLike(questionID: questionID) + } + + //MARK: - 응답 선택 + public func isVoteQuestionAnswer( + questionID: Int, + choicAnswer: String + ) async throws -> FlagQuestionDTOModel? { + try await repository.isVoteQuestionAnswer(questionID: questionID, choicAnswer: choicAnswer) + } + + //MARK: - 내가 쓴글 삭제 + public func deleteQuestion(questionID: Int) async throws -> FlagQuestionDTOModel? { + try await repository.deleteQuestion(questionID: questionID) + } + + //MARK: - 유저글 신고 + public func reportQuestion( + questionID: Int, + reason: String + ) async throws -> FlagQuestionDTOModel? { + try await repository.reportQuestion( + questionID: questionID, + reason: reason + ) + } + + //MARK: - 투표 결과 + public func statusQuestion(questionID: Int) async throws -> StatusQuestionDTOModel? { + try await repository.statusQuestion(questionID: questionID) + } } extension QuestionUseCase : DependencyKey { - public static let liveValue: QuestionUseCase = { - let questionRepository: QuestionRepositoryProtocol = DependencyContainer.live.resolve(QuestionRepositoryProtocol.self) ?? DefaultQuestionRepository() - return QuestionUseCase(repository: questionRepository) - }() + public static let liveValue: QuestionUseCase = { + let questionRepository: QuestionRepositoryProtocol = DependencyContainer.live.resolve(QuestionRepositoryProtocol.self) ?? DefaultQuestionRepository() + return QuestionUseCase(repository: questionRepository) + }() } public extension DependencyValues { - var questionUseCase: QuestionUseCaseProtocol { - get { self[QuestionUseCase.self] } - set { self[QuestionUseCase.self] = newValue as! QuestionUseCase } - } + var questionUseCase: QuestionUseCaseProtocol { + get { self[QuestionUseCase.self] } + set { self[QuestionUseCase.self] = newValue as! QuestionUseCase } + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCaseProtocol.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCaseProtocol.swift index 55da732..bddc331 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCaseProtocol.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/Question/UseCase/QuestionUseCaseProtocol.swift @@ -9,22 +9,22 @@ import Foundation import Model public protocol QuestionUseCaseProtocol { - func fetchQuestionList( - page: Int, - pageSize: Int, - job: String, - generation: String, - sortBy: QuestionSort) async throws -> QuestionModel? - func myQuestionList(page: Int, pageSize: Int) async throws -> QuestionModel? - func createQuestion( - emoji: String, - title: String, - choiceA: String, - choiceB: String - ) async throws -> CreateQuestionModel? - func isVoteQuestionLike(questionID: Int) async throws -> VoteQuestionLikeModel? - func isVoteQuestionAnswer(questionID: Int, choicAnswer: String) async throws -> QuestionVoteModel? - func deleteQuestion(questionID: Int) async throws -> DeleteQuestionModel? - func reportQuestion(questionID: Int, reason: String) async throws -> ReportQuestionModel? - func statusQuestion(questionID: Int) async throws -> StatusQuestionModel? + func fetchQuestionList( + page: Int, + pageSize: Int, + job: String, + generation: String, + sortBy: QuestionSort) async throws -> QuestionDTOModel? + func myQuestionList(page: Int, pageSize: Int) async throws -> QuestionDTOModel? + func createQuestion( + emoji: String, + title: String, + choiceA: String, + choiceB: String + ) async throws -> CreateQuestionDTOModel? + func isVoteQuestionLike(questionID: Int) async throws -> FlagQuestionDTOModel? + func isVoteQuestionAnswer(questionID: Int, choicAnswer: String) async throws -> FlagQuestionDTOModel? + func deleteQuestion(questionID: Int) async throws -> FlagQuestionDTOModel? + func reportQuestion(questionID: Int, reason: String) async throws -> FlagQuestionDTOModel? + func statusQuestion(questionID: Int) async throws -> StatusQuestionDTOModel? } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/DefaultSingUpRepository.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/DefaultSingUpRepository.swift index 592c13f..6b4b448 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/DefaultSingUpRepository.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/DefaultSingUpRepository.swift @@ -10,33 +10,33 @@ import Foundation import Model public final class DefaultSignUpRepository: SignUpRepositoryProtocol { - public func fetchGenerationList() async throws -> Model.GenerationListResponse? { - nil - } + + public init() { - - public init() { - - } - - public func checkNickName(_ nickName: String) async throws -> CheckNickNameModel? { - return nil - } - - public func fetchJobList() async throws -> SignUpJobModel? { - return nil - } - - public func updateUserInfo( - nickname: String, - year: Int, - job: String, - generation: String - ) async throws -> UpdateUserInfoModel? { - return nil - } - - public func checkGeneration(year: Int) async throws -> CheckGeneraionModel? { - return nil - } + } + + public func checkNickName(_ nickName: String) async throws -> SignUpCheckInfoDTOModel? { + return nil + } + + public func fetchJobList() async throws -> SignUpListDTOModel? { + return nil + } + + public func updateUserInfo( + nickname: String, + year: Int, + job: String, + generation: String + ) async throws -> UpdateUserInfoDTOModel? { + return nil + } + + public func checkGeneration(year: Int) async throws -> SignUpCheckInfoDTOModel? { + return nil + } + + public func fetchGenerationList() async throws -> SignUpListDTOModel? { + nil + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SignUpRepositoryProtocol.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SignUpRepositoryProtocol.swift index c51d35c..32202b3 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SignUpRepositoryProtocol.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SignUpRepositoryProtocol.swift @@ -9,20 +9,14 @@ import Foundation import Model public protocol SignUpRepositoryProtocol { - func checkNickName(_ nickName: String) async throws -> CheckNickNameModel? - - func fetchJobList() async throws -> SignUpJobModel? - func fetchGenerationList() async throws -> GenerationListResponse? - - func updateUserInfo( - nickname: String, - year: Int, - job: String, - generation: String - ) async throws -> UpdateUserInfoModel? - - func checkGeneration(year: Int) async throws -> CheckGeneraionModel? - - - + func checkNickName(_ nickName: String) async throws -> SignUpCheckInfoDTOModel? + func fetchJobList() async throws -> SignUpListDTOModel? + func fetchGenerationList() async throws -> SignUpListDTOModel? + func updateUserInfo( + nickname: String, + year: Int, + job: String, + generation: String + ) async throws -> UpdateUserInfoDTOModel? + func checkGeneration(year: Int) async throws -> SignUpCheckInfoDTOModel? } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SingUpRepository.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SingUpRepository.swift index c4faea9..efc13eb 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SingUpRepository.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/Repository/SingUpRepository.swift @@ -13,45 +13,50 @@ import Service import AsyncMoya @Observable public class SingUpRepository: SignUpRepositoryProtocol { - private let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + private let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + + public init() { - public init() { - - } - - //MARK: - 이름 중복 체크 - public func checkNickName(_ nickName: String) async throws -> CheckNickNameModel? { - return try await provider.requestAsync(.nickNameCheck(nickname: nickName), decodeTo: CheckNickNameModel.self) - } - - //MARK: - 직업 정보 API - public func fetchJobList() async throws -> SignUpJobModel? { - return try await provider.requestAsync(.signUpJob, decodeTo: SignUpJobModel.self) - } - - public func fetchGenerationList() async throws -> GenerationListResponse? { - return try await provider.requestAsync(.getGenerationList, decodeTo: GenerationListResponse.self) - } - - //MARK: - 유저 정보 업데이트 - public func updateUserInfo( - nickname: String, - year: Int, - job: String, - generation: String - ) async throws -> UpdateUserInfoModel? { - return try await provider.requestAsync(.updateUserInfo( - nickname: nickname, - year: year, - job: job, - generation: generation - ), decodeTo: UpdateUserInfoModel.self) - } - - //MARK: - 년도 입력시 세대 확인 API - public func checkGeneration(year: Int) async throws -> CheckGeneraionModel? { - return try await provider.requestAsync(.checkGeneration( - year: year - ), decodeTo: CheckGeneraionModel.self) - } + } + + //MARK: - 이름 중복 체크 + public func checkNickName(_ nickName: String) async throws -> SignUpCheckInfoDTOModel? { + let checkNickNameModel = try await provider.requestAsync(.nickNameCheck(nickname: nickName), decodeTo: CheckNickNameModel.self) + return checkNickNameModel.toSignUPInfoDTOToModel() + } + + //MARK: - 직업 정보 API + public func fetchJobList() async throws -> SignUpListDTOModel? { + let jobListModel = try await provider.requestAsync(.signUpJob, decodeTo: SignUpJobModel.self) + return jobListModel.toSignUPListDTOToModel() + } + + public func fetchGenerationList() async throws -> SignUpListDTOModel? { + let generationListModel = try await provider.requestAsync(.getGenerationList, decodeTo: GenerationListResponse.self) + return generationListModel.toGenerationListDTOToModel() + } + + //MARK: - 유저 정보 업데이트 + public func updateUserInfo( + nickname: String, + year: Int, + job: String, + generation: String + ) async throws -> UpdateUserInfoDTOModel? { + let updateUserInfoModel = try await provider.requestAsync(.updateUserInfo( + nickname: nickname, + year: year, + job: job, + generation: generation + ), decodeTo: UpdateUserInfoModel.self) + return updateUserInfoModel.toUpdateUserInfoDTOToModel() + } + + //MARK: - 년도 입력시 세대 확인 API + public func checkGeneration(year: Int) async throws -> SignUpCheckInfoDTOModel? { + let chekcGenrationModel = try await provider.requestAsync(.checkGeneration( + year: year + ), decodeTo: CheckGeneraionModel.self) + return chekcGenrationModel.toSignUpInfoDTOToModel() + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCase.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCase.swift index cb6567d..4a36024 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCase.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCase.swift @@ -13,57 +13,58 @@ import Model import DiContainer public struct SignUpUseCase : SignUpUseCaseProtocol{ - private let repository: SignUpRepositoryProtocol - - - public init( - repository: SignUpRepositoryProtocol - ) { - self.repository = repository - } - - - public func checkNickName( - _ nickName: String - ) async throws -> CheckNickNameModel? { - try await repository.checkNickName(nickName) - } - - public func fetchJobList() async throws -> SignUpJobModel? { - try await repository.fetchJobList() - } - - //MARK: - 유저정보 업데이트 - public func updateUserInfo( - nickname: String, - year: Int, - job: String, - generation: String - ) async throws -> UpdateUserInfoModel? { - try await repository.updateUserInfo(nickname: nickname, year: year, job: job, generation: generation) - } - - //MARK: - 년도 입력시 세대 확인 - public func checkGeneration(year: Int) async throws -> CheckGeneraionModel? { - try await repository.checkGeneration(year: year) - } - - public func fetchGenerationList() async throws -> GenerationListResponse? { - try await repository.fetchGenerationList() - } + private let repository: SignUpRepositoryProtocol + + public init( + repository: SignUpRepositoryProtocol + ) { + self.repository = repository + } + + //MARK: -이름 체크 + public func checkNickName( + _ nickName: String + ) async throws -> SignUpCheckInfoDTOModel? { + try await repository.checkNickName(nickName) + } + + //MARK: - 직업에 대한 리스트 + public func fetchJobList() async throws -> SignUpListDTOModel? { + try await repository.fetchJobList() + } + + //MARK: - 유저정보 업데이트 + public func updateUserInfo( + nickname: String, + year: Int, + job: String, + generation: String + ) async throws -> UpdateUserInfoDTOModel? { + try await repository.updateUserInfo(nickname: nickname, year: year, job: job, generation: generation) + } + + //MARK: - 년도 입력시 세대 확인 + public func checkGeneration(year: Int) async throws -> SignUpCheckInfoDTOModel? { + try await repository.checkGeneration(year: year) + } + + //MARK: - 세대 리스트 + public func fetchGenerationList() async throws -> SignUpListDTOModel? { + try await repository.fetchGenerationList() + } } extension SignUpUseCase: DependencyKey { - public static let liveValue: SignUpUseCase = { - let authRepository = DependencyContainer.live.resolve(SignUpRepositoryProtocol.self) ?? DefaultSignUpRepository() - return SignUpUseCase(repository: authRepository) - }() + public static let liveValue: SignUpUseCase = { + let authRepository = DependencyContainer.live.resolve(SignUpRepositoryProtocol.self) ?? DefaultSignUpRepository() + return SignUpUseCase(repository: authRepository) + }() } public extension DependencyValues { - var signUpUseCase: SignUpUseCaseProtocol { - get { self[SignUpUseCase.self] } - set { self[SignUpUseCase.self] = newValue as! SignUpUseCase} - } + var signUpUseCase: SignUpUseCaseProtocol { + get { self[SignUpUseCase.self] } + set { self[SignUpUseCase.self] = newValue as! SignUpUseCase} + } } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCaseProtocol.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCaseProtocol.swift index d7caee6..7cd1dd5 100644 --- a/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCaseProtocol.swift +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/SignUp/UseCase/SingUpUseCaseProtocol.swift @@ -10,14 +10,14 @@ import Foundation import Model public protocol SignUpUseCaseProtocol { - func checkNickName(_ nickName: String) async throws -> CheckNickNameModel? - func fetchJobList () async throws -> SignUpJobModel? - func updateUserInfo( - nickname: String, - year: Int, - job: String, - generation: String - ) async throws -> UpdateUserInfoModel? - func checkGeneration(year: Int) async throws -> CheckGeneraionModel? - + func checkNickName(_ nickName: String) async throws -> SignUpCheckInfoDTOModel? + func fetchJobList () async throws -> SignUpListDTOModel? + func updateUserInfo( + nickname: String, + year: Int, + job: String, + generation: String + ) async throws -> UpdateUserInfoDTOModel? + func checkGeneration(year: Int) async throws -> SignUpCheckInfoDTOModel? + func fetchGenerationList() async throws -> SignUpListDTOModel? } diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/AppStoreVersionFetcher.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/AppStoreVersionFetcher.swift new file mode 100644 index 0000000..e8fb271 --- /dev/null +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/AppStoreVersionFetcher.swift @@ -0,0 +1,34 @@ +// +// AppStoreVersionFetcher.swift +// UseCase +// +// Created by Wonji Suh on 11/12/24. +// + +import Foundation +import FirebaseRemoteConfig + +public actor AppStoreVersionFetcher { + public init() {} + + public static func fetchAppStoreVersion(bundleID: String) async throws -> String { + guard let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleID)") else { + throw URLError(.badURL) + } + + let (data, _) = try await URLSession.shared.data(from: url) + + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let results = json["results"] as? [[String: Any]], + let appInfo = results.first, + let appStoreVersion = appInfo["version"] as? String { + return appStoreVersion + } else { + throw NSError( + domain: "AppStoreError", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Failed to parse App Store response."] + ) + } + } +} diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/Extension+RemoteConfig.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/Extension+RemoteConfig.swift new file mode 100644 index 0000000..3140d6d --- /dev/null +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/Extension+RemoteConfig.swift @@ -0,0 +1,29 @@ +// +// Extension+RemoteConfig.swift +// UseCase +// +// Created by Wonji Suh on 11/12/24. +// + +import FirebaseRemoteConfig + +public extension RemoteConfig { + func fetchAsync(withExpirationDuration duration: TimeInterval) async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + self.fetch(withExpirationDuration: duration) { status, error in + if let error = error { + continuation.resume(throwing: error) + } else if status == .success { + continuation.resume(returning: ()) + } else { + let fetchError = NSError( + domain: "RemoteConfigError", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Fetch failed with status: \(status)"] + ) + continuation.resume(throwing: fetchError) + } + } + } + } +} diff --git a/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/VersionComparer.swift b/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/VersionComparer.swift new file mode 100644 index 0000000..26e830a --- /dev/null +++ b/OPeace/Projects/Core/Networking/UseCase/Sources/VersionCheck/VersionComparer.swift @@ -0,0 +1,17 @@ +// +// VersionComparer.swift +// UseCase +// +// Created by Wonji Suh on 11/12/24. +// + +import Foundation +import Foundation + +public struct VersionComparer { + public init() {} + + public static func shouldShowUpdatePopup(minimumVersion: String, appStoreVersion: String) -> Bool { + return minimumVersion.compare(appStoreVersion, options: .numeric) == .orderedDescending + } +} diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/Login/Reducer/Login.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/Login/Reducer/Login.swift index 1bc309e..200dabb 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/Login/Reducer/Login.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/Login/Reducer/Login.swift @@ -14,7 +14,6 @@ import DesignSystem import Utill import Networkings - @Reducer public struct Login { public init() {} @@ -33,11 +32,11 @@ public struct Login { var accessToken: String? var idToken: String? - var userLoginModel : UserLoginModel? = nil + var userLoginModel : OAuthDTOModel? = nil var socialType: SocialType? = nil - var profileUserModel: UpdateUserInfoModel? = nil - var refreshTokenModel: RefreshModel? - var appleRefreshTokenMode: AppleTokenResponse? = nil + var profileUserModel: UpdateUserInfoDTOModel? = nil + var refreshTokenModel: RefreshDTOModel? + var appleRefreshTokenMode: OAuthDTOModel? = nil @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() @Shared(.inMemory("isLookAround")) var isLookAround: Bool = false @@ -67,16 +66,16 @@ public struct Login { case fetchAppleRespose(Result) case appleLogin(Result) case loginWithApple(token: String) - case loginWithAppleResponse(Result) + case loginWithAppleResponse(Result) case appleGetRefreshToken - case appleResponseRefreshToken(Result) + case appleResponseRefreshToken(Result) case kakaoLogin case kakaoLoginResponse(Result<(String?, String?), CustomError>) case loginWIthKakao - case kakaoLoginApiResponse(Result) - case fetchUserProfileResponse(Result) + case kakaoLoginApiResponse(Result) + case fetchUserProfileResponse(Result) case fetchUser - case refreshTokenResponse(Result) + case refreshTokenResponse(Result) case refreshTokenRequest(refreshToken: String) } @@ -98,307 +97,334 @@ public struct Login { public var body: some ReducerOf { Reduce { state, action in - switch action { - case .view(let View): - switch View { - - } + switch action { + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) - case .async(let AsyncAction): - switch AsyncAction { - - case .appleLogin(let authData): - nonisolated(unsafe) var appleAccessToken = state.appleAccessToken - return .run { send in - do { - let result = try await authUseCase.handleAppleLogin(authData) - await send(.async(.fetchAppleRespose(.success(result)))) - await send(.async(.loginWithApple(token: appleAccessToken))) - } catch { - #logDebug("애플 로그인 에러", error.localizedDescription) - } - } - - case .fetchAppleRespose(let data): - switch data { - case .success(let authResult): - switch authResult.credential { - case let appleIDCredential as ASAuthorizationAppleIDCredential: - guard let tokenData = appleIDCredential.identityToken, - let identityToken = String(data: tokenData, encoding: .utf8) else { - #logError("Identity token is missing") - return .none - - } - state.appleAccessToken = identityToken - state.socialType = .apple - default: - break - } - case .failure(let error): - #logError("애플로그인 에러", error) - } - return .none - - case .loginWithApple(let token): - return .run { @MainActor send in - let appleLoginResult = await Result { - try await authUseCase.appleLogin() - } + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } + } + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + + case .appleLogin(let authData): + nonisolated(unsafe) var appleAccessToken = state.appleAccessToken + return .run { send in + do { + let result = try await authUseCase.handleAppleLogin(authData) + await send(.async(.fetchAppleRespose(.success(result)))) + await send(.async(.loginWithApple(token: appleAccessToken))) + } catch { + #logDebug("애플 로그인 에러", error.localizedDescription) + } + } + + case .fetchAppleRespose(let data): + switch data { + case .success(let authResult): + switch authResult.credential { + case let appleIDCredential as ASAuthorizationAppleIDCredential: + guard let tokenData = appleIDCredential.identityToken, + let identityToken = String(data: tokenData, encoding: .utf8) else { + #logError("Identity token is missing") + return .none - switch appleLoginResult { - case .success(let appleLoginResult): - if let appleLoginResult = appleLoginResult { - send(.async(.loginWithAppleResponse(.success(appleLoginResult)))) - try await clock.sleep(for: .seconds(0.1)) - send(.async(.fetchUser)) - send(.async(.appleGetRefreshToken)) - } - case .failure(let error): - send(.async(.loginWithAppleResponse(.failure(CustomError.kakaoTokenError(error.localizedDescription))))) - } } - - case .loginWithAppleResponse(let result): - switch result { - case .success(let ResponseData): - state.userLoginModel = ResponseData - UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") - UserDefaults.standard.removeObject(forKey: "REFRESH_TOKEN") - UserDefaults.standard.set(state.userLoginModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - state.loginSocialType = .apple - state.socialType = .apple - let socialTypeValue = state.socialType?.rawValue ?? SocialType.apple.rawValue - UserDefaults.standard.set(socialTypeValue, forKey: "LoginSocialType") - if state.userLoginModel?.data?.accessToken != "" { - UserDefaults.standard.set(state.userLoginModel?.data?.refreshToken ?? "", forKey: "REFRESH_TOKEN") - UserDefaults.standard.set(state.userLoginModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - state.userInfoModel?.isLogOut = false - state.userInfoModel?.isLookAround = false - - state.userInfoModel?.isDeleteUser = false - state.userInfoModel?.isChangeProfile = false - } else { - UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") - UserDefaults.standard.removeObject(forKey: "REFRESH_TOKEN") - UserDefaults.standard.set(state.userLoginModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - UserDefaults.standard.set(state.userLoginModel?.data?.refreshToken ?? "", forKey: "REFRESH_TOKEN") - state.userInfoModel?.isLogOut = false - state.userInfoModel?.isLookAround = false - state.userInfoModel?.isDeleteUser = false - state.userInfoModel?.isChangeProfile = false - } - case .failure(let error): - #logNetwork("애플 로그인 에러", error.localizedDescription) - state.socialType = .apple - state.loginSocialType = .apple + state.appleAccessToken = identityToken + state.socialType = .apple + default: + break + } + case .failure(let error): + #logError("애플로그인 에러", error) + } + return .none + + case .loginWithApple(let token): + return .run { @MainActor send in + let appleLoginResult = await Result { + try await authUseCase.appleLogin() + } + + switch appleLoginResult { + case .success(let appleLoginResult): + if let appleLoginResult = appleLoginResult { + send(.async(.loginWithAppleResponse(.success(appleLoginResult)))) + try await clock.sleep(for: .seconds(0.1)) + send(.async(.fetchUser)) + send(.async(.appleGetRefreshToken)) } - return .none + case .failure(let error): + send(.async(.loginWithAppleResponse(.failure(CustomError.kakaoTokenError(error.localizedDescription))))) + } + } + + case .loginWithAppleResponse(let result): + switch result { + case .success(let ResponseData): + state.userLoginModel = ResponseData + UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") + UserDefaults.standard.removeObject(forKey: "REFRESH_TOKEN") + UserDefaults.standard.set(state.userLoginModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") + state.loginSocialType = .apple + state.socialType = .apple + let socialTypeValue = state.socialType?.rawValue ?? SocialType.apple.rawValue + UserDefaults.standard.set(socialTypeValue, forKey: "LoginSocialType") + if state.userLoginModel?.data.accessToken != "" { + UserDefaults.standard.set(state.userLoginModel?.data.refreshToken ?? "", forKey: "REFRESH_TOKEN") + UserDefaults.standard.set(state.userLoginModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") + state.userInfoModel?.isLogOut = false + state.userInfoModel?.isLookAround = false - case .appleGetRefreshToken: - nonisolated(unsafe) var appleToken = UserDefaults.standard.string(forKey: "APPLE_ACCESS_CODE") ?? "" - return .run { send in - let appleGetRefreshTokenResult = await Result { - try await authUseCase.getAppleRefreshToken(code: appleToken) - } - - switch appleGetRefreshTokenResult { - - case .success(let appleGetRefreshTokenData): - if let appleGetRefreshTokenData = appleGetRefreshTokenData { - await send(.async(.appleResponseRefreshToken(.success(appleGetRefreshTokenData)))) - } - case .failure(let error): - await send(.async(.appleResponseRefreshToken(.failure(CustomError.tokenError(error.localizedDescription))))) - } - } + state.userInfoModel?.isDeleteUser = false + state.userInfoModel?.isChangeProfile = false + } else { + UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") + UserDefaults.standard.removeObject(forKey: "REFRESH_TOKEN") + UserDefaults.standard.set(state.userLoginModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") + UserDefaults.standard.set(state.userLoginModel?.data.refreshToken ?? "", forKey: "REFRESH_TOKEN") + state.userInfoModel?.isLogOut = false + state.userInfoModel?.isLookAround = false + state.userInfoModel?.isDeleteUser = false + state.userInfoModel?.isChangeProfile = false + } + case .failure(let error): + #logNetwork("애플 로그인 에러", error.localizedDescription) + state.socialType = .apple + state.loginSocialType = .apple + } + return .none + + case .appleGetRefreshToken: + nonisolated(unsafe) var appleToken = UserDefaults.standard.string(forKey: "APPLE_ACCESS_CODE") ?? "" + return .run { send in + let appleGetRefreshTokenResult = await Result { + try await authUseCase.getAppleRefreshToken(code: appleToken) + } + + switch appleGetRefreshTokenResult { - case .appleResponseRefreshToken(let result): - switch result { - case .success(let appleResponseData): - state.appleRefreshTokenMode = appleResponseData - UserDefaults.standard.set(appleResponseData.refresh_token ?? "", forKey: "APPLE_REFRESH_TOKEN") - case .failure(let error): - #logError("애플 토큰 발급 실패", error.localizedDescription) + case .success(let appleGetRefreshTokenData): + if let appleGetRefreshTokenData = appleGetRefreshTokenData { + await send(.async(.appleResponseRefreshToken(.success(appleGetRefreshTokenData)))) } - return .none - - case .kakaoLogin: + case .failure(let error): + await send(.async(.appleResponseRefreshToken(.failure(CustomError.tokenError(error.localizedDescription))))) + } + } + + case .appleResponseRefreshToken(let result): + switch result { + case .success(let appleResponseData): + state.appleRefreshTokenMode = appleResponseData + UserDefaults.standard.set(appleResponseData.data.refreshToken, forKey: "APPLE_REFRESH_TOKEN") + case .failure(let error): + #logError("애플 토큰 발급 실패", error.localizedDescription) + } + return .none + + case .kakaoLogin: #if swift(>=6.0) - nonisolated(unsafe) var socialType = state.socialType + nonisolated(unsafe) var socialType = state.socialType #else - var socialType = state.socialType + var socialType = state.socialType #endif + + return .run { send in + let requset = await Result { + try await authUseCase.requestKakaoTokenAsync() + } + + switch requset { - return .run { send in - let requset = await Result { - try await authUseCase.requestKakaoTokenAsync() - } - - switch requset { - - case .success(let (accessToken, idToken)): - await send(.async(.kakaoLoginResponse(.success((accessToken, idToken))))) - - try await clock.sleep(for: .seconds(0.8)) - await send(.async(.loginWIthKakao)) - - case let .failure(error): - await send(.async(.kakaoLoginResponse(.failure(CustomError.map(error))))) - } - } - - case .kakaoLoginResponse(let result): - switch result { - case .success(let (accessToken, idToken)): - state.accessToken = accessToken - state.idToken = accessToken - state.idToken = idToken - #logDebug("카카오 idToken ", state.idToken, state.accessToken) - case let .failure(error): - Log.error("카카오 리턴 에러", error) - - } - return .none - - case .loginWIthKakao: - return .run { @MainActor send in - let kakaoRequest = await Result { - try await authUseCase.reauestKakaoLogin() - } - - switch kakaoRequest { - case .success(let resopnse): - if let responseData = resopnse { - send(.async(.kakaoLoginApiResponse(.success(responseData)))) - send(.async(.fetchUser)) - } - - case .failure(let error): - send(.async(.kakaoLoginApiResponse(.failure(CustomError.kakaoTokenError(error.localizedDescription))))) - } - } + case .success(let (accessToken, idToken)): + await send(.async(.kakaoLoginResponse(.success((accessToken, idToken))))) - case .kakaoLoginApiResponse(let data): - switch data { - case .success(let ResponseData): - state.userLoginModel = ResponseData - UserDefaults.standard.set(state.userLoginModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - state.socialType = .kakao - state.loginSocialType = .kakao - let socialTypeValue = state.socialType?.rawValue ?? SocialType.kakao.rawValue - UserDefaults.standard.set(socialTypeValue, forKey: "LoginSocialType") - if state.userLoginModel?.data?.accessToken != "" { - UserDefaults.standard.set(state.userLoginModel?.data?.refreshToken ?? "", forKey: "REFRESH_TOKEN") - UserDefaults.standard.set(state.userLoginModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - state.userInfoModel?.isLogOut = false - state.userInfoModel?.isLookAround = false - state.userInfoModel?.isDeleteUser = false - state.userInfoModel?.isChangeProfile = false - } else { - UserDefaults.standard.set(state.userLoginModel?.data?.refreshToken ?? "", forKey: "REFRESH_TOKEN") - UserDefaults.standard.set(state.userLoginModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - - state.userInfoModel?.isLogOut = false - state.userInfoModel?.isLookAround = false - state.userInfoModel?.isDeleteUser = false - state.userInfoModel?.isChangeProfile = false - } - case .failure(let error): - #logNetwork("카카오 로그인 에러", error.localizedDescription) - state.socialType = .kakao - state.loginSocialType = .kakao - } - return .none + try await clock.sleep(for: .seconds(0.8)) + await send(.async(.loginWIthKakao)) - case .fetchUser: - return .run { send in - let fetchUserData = await Result { - try await authUseCase.fetchUserInfo() - } - - switch fetchUserData { - case .success(let fetchUserResult): - if let fetchUserResult = fetchUserResult { - await send(.async(.fetchUserProfileResponse(.success(fetchUserResult)))) - UserDefaults.standard.set(true, forKey: "isFirstTimeUser") - - if fetchUserResult.data?.nickname != nil && fetchUserResult.data?.year != nil && fetchUserResult.data?.job != nil { - await send(.navigation(.presentMain)) - } else { - await send(.navigation(.presnetAgreement)) - } - - } - case .failure(let error): - await send(.async(.fetchUserProfileResponse(.failure(CustomError.map(error))))) - - } + case let .failure(error): + await send(.async(.kakaoLoginResponse(.failure(CustomError.map(error))))) + } + } + + case .kakaoLoginResponse(let result): + switch result { + case .success(let (accessToken, idToken)): + state.accessToken = accessToken + state.idToken = accessToken + state.idToken = idToken + #logDebug("카카오 idToken ", state.idToken, state.accessToken) + case let .failure(error): + Log.error("카카오 리턴 에러", error) + + } + return .none + + case .loginWIthKakao: + return .run { @MainActor send in + let kakaoRequest = await Result { + try await authUseCase.reauestKakaoLogin() + } + + switch kakaoRequest { + case .success(let resopnse): + if let responseData = resopnse { + send(.async(.kakaoLoginApiResponse(.success(responseData)))) + send(.async(.fetchUser)) } - case .fetchUserProfileResponse(let result): - switch result { - case .success(let resultData): - state.profileUserModel = resultData - case let .failure(error): - #logNetwork("프로필 오류", error.localizedDescription) - } - return .none + case .failure(let error): + send(.async(.kakaoLoginApiResponse(.failure(CustomError.kakaoTokenError(error.localizedDescription))))) + } + } + + case .kakaoLoginApiResponse(let data): + switch data { + case .success(let ResponseData): + state.userLoginModel = ResponseData + UserDefaults.standard.set(state.userLoginModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") + state.socialType = .kakao + state.loginSocialType = .kakao + let socialTypeValue = state.socialType?.rawValue ?? SocialType.kakao.rawValue + UserDefaults.standard.set(socialTypeValue, forKey: "LoginSocialType") + if state.userLoginModel?.data.accessToken != "" { + UserDefaults.standard.set(state.userLoginModel?.data.refreshToken ?? "", forKey: "REFRESH_TOKEN") + UserDefaults.standard.set(state.userLoginModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") + state.userInfoModel?.isLogOut = false + state.userInfoModel?.isLookAround = false + state.userInfoModel?.isDeleteUser = false + state.userInfoModel?.isChangeProfile = false + } else { + UserDefaults.standard.set(state.userLoginModel?.data.refreshToken ?? "", forKey: "REFRESH_TOKEN") + UserDefaults.standard.set(state.userLoginModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") - case .refreshTokenRequest(let refreshToken): - return .run { send in - let refreshTokenRequest = await Result { - try await authUseCase.requestRefreshToken(refreshToken: refreshToken) - } + state.userInfoModel?.isLogOut = false + state.userInfoModel?.isLookAround = false + state.userInfoModel?.isDeleteUser = false + state.userInfoModel?.isChangeProfile = false + } + case .failure(let error): + #logNetwork("카카오 로그인 에러", error.localizedDescription) + state.socialType = .kakao + state.loginSocialType = .kakao + } + return .none + + case .fetchUser: + return .run { send in + let fetchUserData = await Result { + try await authUseCase.fetchUserInfo() + } + + switch fetchUserData { + case .success(let fetchUserResult): + if let fetchUserResult = fetchUserResult { + await send(.async(.fetchUserProfileResponse(.success(fetchUserResult)))) + UserDefaults.standard.set(true, forKey: "isFirstTimeUser") - switch refreshTokenRequest { - case .success(let refreshTokenData): - if let refreshTokenData = refreshTokenData { - await send(.async(.refreshTokenResponse(.success(refreshTokenData)))) - } - case .failure(let error): - await send(.async(.refreshTokenResponse(.failure(CustomError.map(error))))) + if fetchUserResult.data.nickname != nil && fetchUserResult.data.year != nil && fetchUserResult.data.job != nil { + await send(.navigation(.presentMain)) + } else { + await send(.navigation(.presnetAgreement)) } } - - case .refreshTokenResponse(let result): - switch result { - case .success(let refreshTokenData): - state.refreshTokenModel = refreshTokenData - #logNetwork("리프레쉬 토큰 발급", refreshTokenData) - #logNetwork("refershToken", refreshTokenData) - UserDefaults.standard.set(state.refreshTokenModel?.data?.refreshToken ?? "", forKey: "REFRESH_TOKEN") - UserDefaults.standard.set(state.refreshTokenModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - - case .failure(let error): - #logNetwork("리프레쉬 토큰 발급 에러", error.localizedDescription) - } - return .none + case .failure(let error): + await send(.async(.fetchUserProfileResponse(.failure(CustomError.map(error))))) } - - case .inner(let InnerAction): - switch InnerAction { - + } + + case .fetchUserProfileResponse(let result): + switch result { + case .success(let resultData): + state.profileUserModel = resultData + case let .failure(error): + #logNetwork("프로필 오류", error.localizedDescription) + } + return .none + + case .refreshTokenRequest(let refreshToken): + return .run { send in + let refreshTokenRequest = await Result { + try await authUseCase.requestRefreshToken(refreshToken: refreshToken) } - case .navigation(let NavigationAction): - switch NavigationAction { - case .presnetAgreement: - return .none - - case .presentMain: - return .none - - case .presntLookAround: - state.userInfoModel?.isLogOut = false - state.userInfoModel?.isLookAround = true - state.lastViewedPage = 0 - return .none + switch refreshTokenRequest { + case .success(let refreshTokenData): + if let refreshTokenData = refreshTokenData { + await send(.async(.refreshTokenResponse(.success(refreshTokenData)))) + } + case .failure(let error): + await send(.async(.refreshTokenResponse(.failure(CustomError.map(error))))) } + + } + + case .refreshTokenResponse(let result): + switch result { + case .success(let refreshTokenData): + state.refreshTokenModel = refreshTokenData + #logNetwork("리프레쉬 토큰 발급", refreshTokenData) + #logNetwork("refershToken", refreshTokenData) + UserDefaults.standard.set(state.refreshTokenModel?.data.refreshToken ?? "", forKey: "REFRESH_TOKEN") + UserDefaults.standard.set(state.refreshTokenModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") + + case .failure(let error): + #logNetwork("리프레쉬 토큰 발급 에러", error.localizedDescription) } + return .none + + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presnetAgreement: + return .none + + case .presentMain: + return .none + case .presntLookAround: + state.userInfoModel?.isLogOut = false + state.userInfoModel?.isLookAround = true + state.lastViewedPage = 0 + return .none } } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/Reducer/SignUpAge.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/Reducer/SignUpAge.swift index c3ad84e..17e8fc4 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/Reducer/SignUpAge.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/Reducer/SignUpAge.swift @@ -16,190 +16,230 @@ import Networkings @Reducer public struct SignUpAge { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { - @ObservableState - public struct State: Equatable { - - - var signUpAgeTitle: String = "나이 입력" - var signUpAgeSubTitle: String = "출생 연도를 알려주세요" - var signUpAgeDisplay = "" - var signUpAgeDisplayColor: Color = Color.gray500 - var checkGenerationTextColor: Color = Color.gray500 - var checkGenerationText: String = "기타세대" - var isErrorGenerationText: String = "" - var presntNextViewButtonTitle = "다음" - var enableButton: Bool = false - var signUpName: String? = nil - var signUpNames: String = "" - - var chekcGenerationModel: CheckGeneraionModel? = nil - @Presents var destination: Destination.State? - var activeMenu: SignUpTab = .signUpName - var signUpJob = SignUpJob.State() - - public init( - signUpName: String? = nil - ) { - self.signUpName = signUpName - } - } - - public enum Action: ViewAction, FeatureAction , BindableAction, FeatureScopeAction { - case destination(PresentationAction) - case binding(BindingAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - case switchTabs -// case apperName - case scope(ScopeAction) - case signUpJob(SignUpJob.Action) - } - - @CasePathable - public enum View { - case apperName - case updateGeneration(generation: String) - } - + var signUpAgeTitle: String = "나이 입력" + var signUpAgeSubTitle: String = "출생 연도를 알려주세요" + var signUpAgeDisplay = "" + var signUpAgeDisplayColor: Color = Color.gray500 + var checkGenerationTextColor: Color = Color.gray500 + var checkGenerationText: String = "기타세대" + var isErrorGenerationText: String = "" + var presntNextViewButtonTitle = "다음" + var enableButton: Bool = false + var signUpName: String? = nil + var signUpNames: String = "" - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case fetchJobList - case updateName - case checkGeneration(year: Int) - case chekcGenerationResponse(Result) - } + var chekcGenerationModel: SignUpCheckInfoDTOModel? = nil + @Presents var destination: Destination.State? + var activeMenu: SignUpTab = .signUpName + var signUpJob = SignUpJob.State() - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - + public init( + signUpName: String? = nil + ) { + self.signUpName = signUpName } + } + + public enum Action: ViewAction, FeatureAction , BindableAction, FeatureScopeAction { + case destination(PresentationAction) + case binding(BindingAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + case switchTabs + // case apperName + case scope(ScopeAction) + case signUpJob(SignUpJob.Action) + } + + @CasePathable + public enum View { + case apperName + case updateGeneration(generation: String) + } + + + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case fetchJobList + case updateName + case checkGeneration(year: Int) + case chekcGenerationResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - //MARK: - NavigationAction - public enum NavigationAction: Equatable { + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { - } + } + + public enum ScopeAction: Equatable { + case fetchJobList - public enum ScopeAction: Equatable { - case fetchJobList + } + + @Reducer(state: .equatable) + public enum Destination { + case signUpJob(SignUpJob) + } + + struct SignUpAgeCancel: Hashable {} + + @Dependency(SignUpUseCase.self) var signUpUseCase + @Dependency(\.mainQueue) var mainQueue + @Dependency(\.continuousClock) var clock + + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .switchTabs: + state.destination = .signUpJob(.init()) + return .none + + case .binding(\.signUpAgeDisplay): + return .none + case .destination(_): + return .none + + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + + case .scope(let scopeAction): + return handleScopeAction(state: &state, action: scopeAction) + + default: + return .none + } } - - @Reducer(state: .equatable) - public enum Destination { - case signUpJob(SignUpJob) + .ifLet(\.$destination, action: \.destination) + Scope(state: \.signUpJob, action: \.signUpJob) { + SignUpJob() } - - @Dependency(SignUpUseCase.self) var signUpUseCase - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .switchTabs: - state.destination = .signUpJob(.init()) - return .none - - case .binding(\.signUpAgeDisplay): - return .none - - case .destination(_): - return .none - - case .view(let View): - switch View { - - case .apperName: - return .none - - case .updateGeneration(generation: let generation): - let (color, textColor) = CheckRegister.getGenerationSignUp( - generation: generation, - color: state.signUpAgeDisplayColor, - textColor: state.checkGenerationTextColor) -// store.checkGenerationText = generation - state.signUpAgeDisplayColor = color - state.checkGenerationTextColor = textColor - return .none - } - - case .async(let AsyncAction): - switch AsyncAction { - case .fetchJobList: - return .run { @MainActor send in - send(.signUpJob(.fetchJob)) - } - - case .updateName: - let signUpName = state.signUpName - return .run { @MainActor send in - send(.signUpJob(.appearName(signUpName ?? ""))) - } - - case .checkGeneration(year: let year): - var checkGenerationText = state.checkGenerationText - var signUpAgeDisplayColor = state.signUpAgeDisplayColor - var checkGenerationTextColor = state.checkGenerationTextColor - return .run { @MainActor send in - let checkGenerationResult = await Result { - try await signUpUseCase.checkGeneration(year: year) - } - - switch checkGenerationResult { - case .success(let checkGenerationData): - if let checkGenerationData = checkGenerationData { - send(.async(.chekcGenerationResponse(.success(checkGenerationData)))) - - send(.view(.updateGeneration(generation: checkGenerationData.data?.data ?? ""))) - - } - case .failure(let error): - send(.async(.chekcGenerationResponse(.failure(CustomError.encodingError(error.localizedDescription))))) - } - } - - case .chekcGenerationResponse(let result): - switch result { - case .success(let checkGenrationResult): - state.chekcGenerationModel = checkGenrationResult - state.checkGenerationText = checkGenrationResult.data?.data ?? "" -// state.checkGenerationText = checkGenrationResult.data?.data - case .failure(let error): - Log.error("세대 확인 에러", error.localizedDescription) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - - } - - case .scope(let ScopeAction): - switch ScopeAction { - case .fetchJobList: - return .run { @MainActor send in - send(.signUpJob(.fetchJob)) - } - - } - - default: - return .none - } + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .apperName: + return .none + + case .updateGeneration(generation: let generation): + let (color, textColor) = CheckRegister.getGenerationSignUp( + generation: generation, + color: state.signUpAgeDisplayColor, + textColor: state.checkGenerationTextColor) + // store.checkGenerationText = generation + state.signUpAgeDisplayColor = color + state.checkGenerationTextColor = textColor + return .none + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .fetchJobList: + return .run { send in + await send(.signUpJob(.fetchJob)) + } + + case .updateName: + let signUpName = state.signUpName + return .run { send in + await send(.signUpJob(.appearName(signUpName ?? ""))) + } + + case .checkGeneration(year: let year): + var checkGenerationText = state.checkGenerationText + var signUpAgeDisplayColor = state.signUpAgeDisplayColor + var checkGenerationTextColor = state.checkGenerationTextColor + return .run { send in + let checkGenerationResult = await Result { + try await signUpUseCase.checkGeneration(year: year) } - .ifLet(\.$destination, action: \.destination) - Scope(state: \.signUpJob, action: \.signUpJob) { - SignUpJob() + + switch checkGenerationResult { + case .success(let checkGenerationData): + if let checkGenerationData = checkGenerationData { + await send(.async(.chekcGenerationResponse(.success(checkGenerationData)))) + + await send(.view(.updateGeneration(generation: checkGenerationData.data.message))) + + } + case .failure(let error): + await send(.async(.chekcGenerationResponse(.failure(CustomError.encodingError(error.localizedDescription))))) } + } + .debounce(id: SignUpAgeCancel(), for: 0.3, scheduler: mainQueue) + + case .chekcGenerationResponse(let result): + switch result { + case .success(let checkGenrationResult): + state.chekcGenerationModel = checkGenrationResult + state.checkGenerationText = checkGenrationResult.data.message + // state.checkGenerationText = checkGenrationResult.data?.data + case .failure(let error): + Log.error("세대 확인 에러", error.localizedDescription) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + + } + } + + private func handleScopeAction( + state: inout State, + action: ScopeAction + ) -> Effect { + switch action { + case .fetchJobList: + return .run { send in + await send(.signUpJob(.fetchJob)) + } } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/View/SignUpAgeView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/View/SignUpAgeView.swift index 9267264..ad3fad9 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/View/SignUpAgeView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpAge/View/SignUpAgeView.swift @@ -13,186 +13,184 @@ import Utill public struct SignUpAgeView: View { - @Bindable var store: StoreOf + @Bindable var store: StoreOf + + public init( + store: StoreOf + ) { + self.store = store - public init( - store: StoreOf - ) { - self.store = store + } + + public var body: some View { + ZStack { + Color.gray600 + .edgesIgnoringSafeArea(.all) + + VStack { - } - - public var body: some View { - ZStack { - Color.gray600 - .edgesIgnoringSafeArea(.all) - - VStack { - - ScrollView(.vertical, showsIndicators: false) { - signUpAgeTitle() - - signUpAgeTextField() - - chekcAgeErrorText() - - Spacer() - .frame(height: UIScreen.screenHeight * 0.352) - - CustomButton( - action: { - store.send(.async(.fetchJobList)) - Task { - try await Task.sleep(nanoseconds: UInt64(5)) - store.send(.switchTabs) - } - - }, title: store.presntNextViewButtonTitle, - config: CustomButtonConfig.create() - ,isEnable: store.enableButton - ) - .padding(.horizontal, 20) - - Spacer() - .frame(height: 16) - } - .bounce(false) - - } - .onAppear { - store.send(.view(.apperName)) - } - .onChange(of: store.signUpAgeDisplay, { oldValue, newValue in - store.send(.async(.checkGeneration(year: Int(newValue) ?? .zero))) - - }) - .onTapGesture { - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - - if !CheckRegister.containsInvalidAge(store.signUpAgeDisplay) { - store.enableButton = false - store.isErrorGenerationText = "정확한 연도를 입력해 주세요" - } else if store.signUpAgeDisplay.isEmpty || store.signUpAgeDisplay.count != 4 || Int(store.signUpAgeDisplay) == nil { - store.isErrorGenerationText = "정확한 연도를 입력해 주세요" - store.enableButton = false - } else if let year = Int(store.signUpAgeDisplay), year < 1900 { - store.isErrorGenerationText = "연도는 1900년 이상이어야 합니다" - store.enableButton = false - } else { - store.enableButton = true - store.isErrorGenerationText = "" - } - } - + ScrollView(.vertical, showsIndicators: false) { + signUpAgeTitle() + + signUpAgeTextField() + + chekcAgeErrorText() + + Spacer() + .frame(height: UIScreen.screenHeight * 0.352) + + CustomButton( + action: { + store.send(.async(.fetchJobList)) + Task { + try await Task.sleep(nanoseconds: UInt64(5)) + store.send(.switchTabs) + } + + }, title: store.presntNextViewButtonTitle, + config: CustomButtonConfig.create() + ,isEnable: store.enableButton + ) + .padding(.horizontal, 20) + + Spacer() + .frame(height: 16) + } + .bounce(false) + + } + .onAppear { + store.send(.view(.apperName)) + } + .onChange(of: store.signUpAgeDisplay, { oldValue, newValue in + store.send(.async(.checkGeneration(year: Int(newValue) ?? .zero))) + + }) + .onTapGesture { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + + if !CheckRegister.containsInvalidAge(store.signUpAgeDisplay) { + store.enableButton = false + store.isErrorGenerationText = "정확한 연도를 입력해 주세요" + } else if store.signUpAgeDisplay.isEmpty || store.signUpAgeDisplay.count != 4 || Int(store.signUpAgeDisplay) == nil { + store.isErrorGenerationText = "정확한 연도를 입력해 주세요" + store.enableButton = false + } else if let year = Int(store.signUpAgeDisplay), year < 1900 { + store.isErrorGenerationText = "연도는 1900년 이상이어야 합니다" + store.enableButton = false + } else { + store.enableButton = true + store.isErrorGenerationText = "" } + } + } + } } extension SignUpAgeView { - - @ViewBuilder - private func signUpAgeTitle() -> some View { - VStack { - Spacer() - .frame(height: UIScreen.screenHeight * 0.02) - - Text(store.signUpAgeTitle) - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.basicWhite) - - Spacer() - .frame(height: 16) - - Text(store.signUpAgeSubTitle) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(Color.gray300) - - - } + + @ViewBuilder + private func signUpAgeTitle() -> some View { + VStack { + Spacer() + .frame(height: UIScreen.screenHeight * 0.02) + + Text(store.signUpAgeTitle) + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.basicWhite) + + Spacer() + .frame(height: 16) + + Text(store.signUpAgeSubTitle) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(Color.gray300) + + } - - @ViewBuilder - private func signUpAgeTextField() -> some View { - VStack { - Spacer() - .frame(height: UIScreen.screenHeight * 0.08) - - TextField("YYYY", text: $store.signUpAgeDisplay) - .pretendardFont(family: .SemiBold, size: 48) - .foregroundStyle((Int(store.signUpAgeDisplay) == nil) ? Color.gray300 : Color.basicWhite) - .multilineTextAlignment(.center) - .submitLabel(.return) - .keyboardType(.numberPad) - .onChange(of: store.signUpAgeDisplay) { newValue in - if newValue.count > 4 { - store.signUpAgeDisplay = String(newValue.prefix(4)) - } - store.signUpAgeDisplay = store.signUpAgeDisplay.filter { $0.isNumber } - store.send(.async(.checkGeneration(year: Int(newValue) ?? .zero))) - } - .onSubmit { - // Check if the input is exactly 4 digits and can be converted to an integer - if store.signUpAgeDisplay.count != 4 || Int(store.signUpAgeDisplay) == nil { - store.isErrorGenerationText = "정확한 연도를 입력해 주세요" - } else if let year = Int(store.signUpAgeDisplay), year < 1900 { - store.isErrorGenerationText = "연도는 1900년 이상이어야 합니다" - store.enableButton = false - } else { - store.isErrorGenerationText = "" - store.send(.async(.checkGeneration(year: Int(store.signUpAgeDisplay) ?? .zero))) - } - } - - - HStack { - Spacer() - - if store.signUpAgeDisplay.isEmpty { - RoundedRectangle(cornerRadius: 18) - .fill(Color.gray500) - .frame(width: "기타 세대".calculateWidthAge(for: "기타 세대"), height: 32) - .overlay { - Text("기타 세대") - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(Color.gray200) - } - } else { - RoundedRectangle(cornerRadius: 18) - .fill(store.signUpAgeDisplayColor) - .frame(width: store.checkGenerationText.calculateWidthAge(for: store.checkGenerationText), height: 32) - .overlay { - Text(store.checkGenerationText) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(store.checkGenerationTextColor) - } - } - - Spacer() - } + } + + @ViewBuilder + private func signUpAgeTextField() -> some View { + VStack { + Spacer() + .frame(height: UIScreen.screenHeight * 0.08) + + TextField("YYYY", text: $store.signUpAgeDisplay) + .pretendardFont(family: .SemiBold, size: 48) + .foregroundStyle((Int(store.signUpAgeDisplay) == nil) ? Color.gray300 : Color.basicWhite) + .multilineTextAlignment(.center) + .submitLabel(.return) + .keyboardType(.numberPad) + .onChange(of: store.signUpAgeDisplay) { newValue in + if newValue.count > 4 { + store.signUpAgeDisplay = String(newValue.prefix(4)) + } + store.signUpAgeDisplay = store.signUpAgeDisplay.filter { $0.isNumber } + store.send(.async(.checkGeneration(year: Int(newValue) ?? .zero))) } - } - - @ViewBuilder - private func chekcAgeErrorText() -> some View { - VStack { - Spacer() - .frame(height: 20) - - if store.signUpAgeDisplay.isEmpty { - Text(store.isErrorGenerationText) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) - } else if let year = Int(store.signUpAgeDisplay), year < 1900 || store.signUpAgeDisplay.count != 4 { - Text(store.isErrorGenerationText) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) - } else { - Text(store.isErrorGenerationText) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) + .onSubmit { + // Check if the input is exactly 4 digits and can be converted to an integer + if store.signUpAgeDisplay.count != 4 || Int(store.signUpAgeDisplay) == nil { + store.isErrorGenerationText = "정확한 연도를 입력해 주세요" + } else if let year = Int(store.signUpAgeDisplay), year < 1900 { + store.isErrorGenerationText = "연도는 1900년 이상이어야 합니다" + store.enableButton = false + } else { + store.isErrorGenerationText = "" + store.send(.async(.checkGeneration(year: Int(store.signUpAgeDisplay) ?? .zero))) + } + } + + + HStack { + Spacer() + + if store.signUpAgeDisplay.isEmpty { + RoundedRectangle(cornerRadius: 18) + .fill(Color.gray500) + .frame(width: "기타 세대".calculateWidthAge(for: "기타 세대"), height: 32) + .overlay { + Text("기타 세대") + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(Color.gray200) + } + } else { + RoundedRectangle(cornerRadius: 18) + .fill(store.signUpAgeDisplayColor) + .frame(width: store.checkGenerationText.calculateWidthAge(for: store.checkGenerationText), height: 32) + .overlay { + Text(store.checkGenerationText) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(store.checkGenerationTextColor) } - - } + + Spacer() + } + } + } + + @ViewBuilder + private func chekcAgeErrorText() -> some View { + VStack { + Spacer() + .frame(height: 20) + + if store.signUpAgeDisplay.isEmpty { + Text(store.isErrorGenerationText) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) + } else if let year = Int(store.signUpAgeDisplay), year < 1900 || store.signUpAgeDisplay.count != 4 { + Text(store.isErrorGenerationText) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) + } else { + Text(store.isErrorGenerationText) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) + } } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/Reducer/SignUpJob.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/Reducer/SignUpJob.swift index c9cad0e..d50de7c 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/Reducer/SignUpJob.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/Reducer/SignUpJob.swift @@ -17,168 +17,194 @@ import Networkings @Reducer public struct SignUpJob { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { - @ObservableState - public struct State: Equatable { - - var signUpJobTitle: String = "직무 선택" - var signUpJobSubTitle: String = "직무 계열을 알려주세요" - var signUpName: String? = nil - var signUpGeneration: String? = nil - var presntNextViewButtonTitle = "다음" - var signUpJobModel: SignUpJobModel? = nil - var enableButton: Bool = false - var selectedJob: String? - let paddings: [CGFloat] = [48, 25, 32, 48, 24, 0] - var backGroudColor = Color.gray600 - - @Presents var destination: Destination.State? - - public init( - signUpName: String? = nil, - signUpGeneration: String? = nil - ) { - self.signUpName = signUpName - self.signUpGeneration = signUpGeneration - } - } + var signUpJobTitle: String = "직무 선택" + var signUpJobSubTitle: String = "직무 계열을 알려주세요" + var signUpName: String? = nil + var signUpGeneration: String? = nil + var presntNextViewButtonTitle = "다음" - public enum Action: ViewAction, FeatureAction , BindableAction { - case binding(BindingAction) - case destination(PresentationAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - case fetchJob - case appearName(String) - } + var enableButton: Bool = false + var selectedJob: String? + let paddings: [CGFloat] = [48, 25, 32, 48, 24, 0] + var backGroudColor = Color.gray600 - @Reducer(state: .equatable) - public enum Destination { - case customPopUp(CustomPopUp) - } + var signUpJobModel: SignUpListDTOModel? = nil - @CasePathable - public enum View { - case selectJob(String) - case appearPopUp - case closePopUp - } + @Presents var destination: Destination.State? - //MARK: - AsyncAction 비동기 처리 액션 - @CasePathable - public enum AsyncAction: Equatable { - case signUpJobResponse(Result) - case fetchSignUpJobList + public init( + signUpName: String? = nil, + signUpGeneration: String? = nil + ) { + self.signUpName = signUpName + self.signUpGeneration = signUpGeneration } + } + + public enum Action: ViewAction, FeatureAction , BindableAction { + case binding(BindingAction) + case destination(PresentationAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + case fetchJob + case appearName(String) + } + + @Reducer(state: .equatable) + public enum Destination { + case customPopUp(CustomPopUp) + } + + + @CasePathable + public enum View { + case selectJob(String) + case appearPopUp + case closePopUp + } + + //MARK: - AsyncAction 비동기 처리 액션 + @CasePathable + public enum AsyncAction: Equatable { + case signUpJobResponse(Result) + case fetchSignUpJobList + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - - } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + } + + @Dependency(SignUpUseCase.self) var signUpUseCase + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + + case .binding(_): + return .none + + case .destination(_): + return .none + + case .fetchJob: + return .run { @MainActor send in + send(.async(.fetchSignUpJobList)) + } + + case .appearName(let name): + state.signUpName = name + return .none + + + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } } - - @Dependency(SignUpUseCase.self) var signUpUseCase - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - - case .binding(_): - return .none - - case .destination(_): - return .none - - case .fetchJob: - return .run { @MainActor send in - send(.async(.fetchSignUpJobList)) - } - - case .appearName(let name): - state.signUpName = name - return .none - - - case .view(let View): - switch View { - case let .selectJob(job): - if state.selectedJob == job { - state.selectedJob = nil - state.enableButton = false - } else { - state.selectedJob = job - state.enableButton = true - } - return .none - - case .appearPopUp: - state.destination = .customPopUp(.init()) - state.backGroudColor = Color.basicBlack.opacity(0.3) - return .none - - case .closePopUp: - state.destination = nil - state.backGroudColor = Color.gray600 - return .none - } - - - - case .async(let AsyncAction): - switch AsyncAction { - case .signUpJobResponse(let result): - switch result { - case .success(let data): - state.signUpJobModel = data - - case .failure(let error): - Log.network("닉네임 에러", error.localizedDescription) - } - return .none - - case .fetchSignUpJobList: - return .run { @MainActor send in - let request = await Result { - try await self.signUpUseCase.fetchJobList() - } - - switch request { - case .success(let data): - if let data = data { - send(.async(.signUpJobResponse(.success(data)))) - } - - case .failure(let error): - send(.async(.signUpJobResponse(.failure(CustomError.map(error))))) - } - } - - - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - - } - - + .ifLet(\.$destination, action: \.destination) + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case let .selectJob(job): + if state.selectedJob == job { + state.selectedJob = nil + state.enableButton = false + } else { + state.selectedJob = job + state.enableButton = true + } + return .none + + case .appearPopUp: + state.destination = .customPopUp(.init()) + state.backGroudColor = Color.basicBlack.opacity(0.3) + return .none + + case .closePopUp: + state.destination = nil + state.backGroudColor = Color.gray600 + return .none + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .signUpJobResponse(let result): + switch result { + case .success(let data): + state.signUpJobModel = data + + case .failure(let error): + #logNetwork("닉네임 에러", error.localizedDescription) + } + return .none + + case .fetchSignUpJobList: + return .run { send in + let request = await Result { + try await self.signUpUseCase.fetchJobList() + } + + switch request { + case .success(let data): + if let data = data { + await send(.async(.signUpJobResponse(.success(data)))) } + + case .failure(let error): + await send(.async(.signUpJobResponse(.failure(CustomError.map(error))))) + } } - .ifLet(\.$destination, action: \.destination) } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/View/SignUpJobView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/View/SignUpJobView.swift index 60099ce..11b70e9 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/View/SignUpJobView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpJob/View/SignUpJobView.swift @@ -13,144 +13,144 @@ import DesignSystem import PopupView public struct SignUpJobView: View { - @Bindable var store: StoreOf - private var confirmAction: () -> Void + @Bindable var store: StoreOf + private var confirmAction: () -> Void + + public init( + store: StoreOf, + confirmAction: @escaping() -> Void - public init( - store: StoreOf, - confirmAction: @escaping() -> Void + ) { + self.store = store + self.confirmAction = confirmAction + } + + + public var body: some View { + ZStack { + store.backGroudColor + .edgesIgnoringSafeArea(.all) + + VStack { - ) { - self.store = store - self.confirmAction = confirmAction - } - - - public var body: some View { - ZStack { - store.backGroudColor - .edgesIgnoringSafeArea(.all) - - VStack { - - ScrollView(.vertical, showsIndicators: false) { - signUpJobTitle() - - - signUpJobSelect() - - Spacer() - .frame(height: UIScreen.screenHeight * 0.145) - - CustomButton( - action: { - confirmAction() - }, title: store.presntNextViewButtonTitle, - config: CustomButtonConfig.create() - ,isEnable: store.enableButton - ) - .padding(.horizontal, 20) - .offset(y: 4) - - Spacer() - .frame(height: 16) - } - .bounce(false) - } - - } - .onAppear { - store.send(.async(.fetchSignUpJobList)) - } - .task { - store.send(.async(.fetchSignUpJobList)) - store.send(.appearName(store.signUpName ?? "")) + ScrollView(.vertical, showsIndicators: false) { + signUpJobTitle() + + + signUpJobSelect() + + Spacer() + .frame(height: UIScreen.screenHeight * 0.145) + + CustomButton( + action: { + confirmAction() + }, title: store.presntNextViewButtonTitle, + config: CustomButtonConfig.create() + ,isEnable: store.enableButton + ) + .padding(.horizontal, 20) + .offset(y: 4) + + Spacer() + .frame(height: 16) } - .onTapGesture { - store.send(.view(.closePopUp)) - } - + .bounce(false) + } + + } + .onAppear { + store.send(.async(.fetchSignUpJobList)) + } + .task { + store.send(.async(.fetchSignUpJobList)) + store.send(.appearName(store.signUpName ?? "")) + } + .onTapGesture { + store.send(.view(.closePopUp)) } + + } } extension SignUpJobView { - - @ViewBuilder - private func signUpJobTitle() -> some View { - VStack { - Spacer() - .frame(height: UIScreen.screenHeight * 0.015) - - Text(store.signUpJobTitle) - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.basicWhite) - - Spacer() - .frame(height: 16) - - Text(store.signUpJobSubTitle) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(Color.gray300) - - - } + + @ViewBuilder + private func signUpJobTitle() -> some View { + VStack { + Spacer() + .frame(height: UIScreen.screenHeight * 0.015) + + Text(store.signUpJobTitle) + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.basicWhite) + + Spacer() + .frame(height: 16) + + Text(store.signUpJobSubTitle) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(Color.gray300) + + } - - @ViewBuilder - private func signUpJobSelect() -> some View { - VStack { - Spacer() - .frame(height: 40) - - ScrollView { - VStack(spacing: 12) { - if let categories = store.signUpJobModel?.data?.data { - ForEach(0..<(categories.count / 4 + (categories.count % 4 > 0 ? 1 : 0)), id: \.self) { rowIndex in - let startIndex = rowIndex * 4 - let endIndex = min(startIndex + 4, categories.count) - let padding = rowIndex < store.paddings.count ? store.paddings[rowIndex] : store.paddings.last ?? 0 - createRow(startIndex: startIndex, endIndex: endIndex, padding: padding, categories: categories) - } - } - } - .padding(.horizontal) + } + + @ViewBuilder + private func signUpJobSelect() -> some View { + VStack { + Spacer() + .frame(height: 40) + + ScrollView { + VStack(spacing: 12) { + if let categories = store.signUpJobModel?.data.content { + ForEach(0..<(categories.count / 4 + (categories.count % 4 > 0 ? 1 : 0)), id: \.self) { rowIndex in + let startIndex = rowIndex * 4 + let endIndex = min(startIndex + 4, categories.count) + let padding = rowIndex < store.paddings.count ? store.paddings[rowIndex] : store.paddings.last ?? 0 + createRow(startIndex: startIndex, endIndex: endIndex, padding: padding, categories: categories) } + } } + .padding(.horizontal) + } } - - @ViewBuilder - func createRow( - startIndex: Int, - endIndex: Int, - padding: CGFloat, - categories: [String] - ) -> some View { - HStack { - Spacer(minLength: padding) - ForEach(startIndex.. some View { - RoundedRectangle(cornerRadius: 20) - .fill(store.selectedJob == jobTitle ? Color.basicPrimary : Color.gray500) - .frame(width: width, height: 38) - .overlay { - Text(jobTitle) - .pretendardFont(family: .Regular, size: 18) - .foregroundStyle(store.selectedJob == jobTitle ? Color.gray600 : Color.gray100) - .foregroundStyle(Color.gray100) - } - .onTapGesture { - store.send(.view(.selectJob(jobTitle))) - } + } + + @ViewBuilder + func createRow( + startIndex: Int, + endIndex: Int, + padding: CGFloat, + categories: [String] + ) -> some View { + HStack { + Spacer(minLength: padding) + ForEach(startIndex.. some View { + RoundedRectangle(cornerRadius: 20) + .fill(store.selectedJob == jobTitle ? Color.basicPrimary : Color.gray500) + .frame(width: width, height: 38) + .overlay { + Text(jobTitle) + .pretendardFont(family: .Regular, size: 18) + .foregroundStyle(store.selectedJob == jobTitle ? Color.gray600 : Color.gray100) + .foregroundStyle(Color.gray100) + } + .onTapGesture { + store.send(.view(.selectJob(jobTitle))) + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/Reducer/SignUpName.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/Reducer/SignUpName.swift index 56f744a..a800f1c 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/Reducer/SignUpName.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/Reducer/SignUpName.swift @@ -13,142 +13,167 @@ import Networkings @Reducer public struct SignUpName { + public init() {} + + @ObservableState + public struct State: Equatable { public init() {} + var signUpNameTitle: String = "닉네임 입력" + var signUpNameSubTitle: String = "2~5자까지 입력할 수 있어요" + var signUpNameDisplay = "" + var presntNextViewButtonTitle = "다음" + var nickNameModel: SignUpCheckInfoDTOModel? = nil + var checkNickNameMessage: String = "" + var enableButton: Bool = false - @ObservableState - public struct State: Equatable { - public init() {} - var signUpNameTitle: String = "닉네임 입력" - var signUpNameSubTitle: String = "2~5자까지 입력할 수 있어요" - var signUpNameDisplay = "" - var presntNextViewButtonTitle = "다음" - var nickNameModel: CheckNickNameModel? = nil - var checkNickNameMessage: String = "" - var enableButton: Bool = false + @Presents var destination: Destination.State? + var activeMenu: SignUpTab = .signUpName + } + + public enum Action: ViewAction, FeatureAction , BindableAction { + case destination(PresentationAction) + case binding(BindingAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + case switchSettingTab + } + + //MARK: - ViewAction + @CasePathable + public enum View { + case signUpNameDisplay(text: String) - @Presents var destination: Destination.State? - var activeMenu: SignUpTab = .signUpName - } + } + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case checkNickName(nickName: String) + case checkNIckNameResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - public enum Action: ViewAction, FeatureAction , BindableAction { - case destination(PresentationAction) - case binding(BindingAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - case switchSettingTab - } + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { - //MARK: - ViewAction - @CasePathable - public enum View { - case signUpNameDisplay(text: String) - - } - - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case checkNickName(nickName: String) - case checkNIckNameResponse(Result) + } + + @Reducer(state: .equatable) + public enum Destination { + case signUpAge(SignUpAge) + case signUpJob(SignUpJob) + } + + @Dependency(SignUpUseCase.self) var signUpUseCase + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(\.signUpNameDisplay): + return .none + + case .switchSettingTab: + state.activeMenu = .signUpGeneration + state.destination = .signUpAge(.init(signUpName: state.signUpNameDisplay)) + state.destination = .signUpJob(.init(signUpName: state.signUpNameDisplay)) + return .none + + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + + default: + return .none + + } } - - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - + .ifLet(\.$destination, action: \.destination) + // .onChange(of: \.signUpNameDisplay) { oldValue, newValue in + // Reduce { state, action in + // state.signUpNameDisplay = newValue + // return .none + // } + // } + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .signUpNameDisplay(text: let text): + state.signUpNameDisplay = text + return .none } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .checkNickName(nickName: let nickName): + return .run { send in + let requset = await Result { + try await signUpUseCase.checkNickName(nickName) + } + + switch requset { + case .success(let result): + if let result = result { + await send(.async(.checkNIckNameResponse(.success(result)))) + } + + case .failure(let error): + await send(.async(.checkNIckNameResponse(.failure(CustomError.map(error))))) + } + } + + + case .checkNIckNameResponse(let result): + switch result { + case .success(let data): + state.nickNameModel = data + state.checkNickNameMessage = state.nickNameModel?.data.message ?? "" + state.enableButton = state.nickNameModel?.data.exists ?? false + case .failure(let error): + Log.network("닉네임 에러", error.localizedDescription) + } + return .none } - - @Reducer(state: .equatable) - public enum Destination { - case signUpAge(SignUpAge) - case signUpJob(SignUpJob) + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + } - - @Dependency(SignUpUseCase.self) var signUpUseCase - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .binding(\.signUpNameDisplay): - return .none - - case .switchSettingTab: - state.activeMenu = .signUpGeneration - state.destination = .signUpAge(.init(signUpName: state.signUpNameDisplay)) - state.destination = .signUpJob(.init(signUpName: state.signUpNameDisplay)) - return .none - - case .view(let View): - switch View { - - - case .signUpNameDisplay(text: let text): - state.signUpNameDisplay = text - return .none - } - - case .async(let AsyncAction): - switch AsyncAction { - - case .checkNickName(nickName: let nickName): - return .run { @MainActor send in - let requset = await Result { - try await signUpUseCase.checkNickName(nickName) - } - - switch requset { - case .success(let result): - if let result = result { - send(.async(.checkNIckNameResponse(.success(result)))) - } - - case .failure(let error): - send(.async(.checkNIckNameResponse(.failure(CustomError.map(error))))) - } - } - - - case .checkNIckNameResponse(let result): - switch result { - case .success(let data): - state.nickNameModel = data - state.checkNickNameMessage = state.nickNameModel?.data?.message ?? "" - state.enableButton = state.nickNameModel?.data?.exists ?? false - - case .failure(let error): - Log.network("닉네임 에러", error.localizedDescription) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - - } - - default: - return .none - - } - } - .ifLet(\.$destination, action: \.destination) -// .onChange(of: \.signUpNameDisplay) { oldValue, newValue in -// Reduce { state, action in -// state.signUpNameDisplay = newValue -// return .none -// } -// } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/View/SignUpNameView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/View/SignUpNameView.swift index d6d9f3a..2b5aad8 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/View/SignUpNameView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpName/View/SignUpNameView.swift @@ -13,127 +13,125 @@ import Utill import DesignSystem public struct SignUpNameView: View { - @Bindable var store: StoreOf + @Bindable var store: StoreOf + + public init( + store: StoreOf - public init( - store: StoreOf - - ) { - self.store = store - } - - public var body: some View { - ZStack { - Color.gray600 - .edgesIgnoringSafeArea(.all) - - VStack { - ScrollView(.vertical, showsIndicators: false) { - signUpNameTitle() - - checkNickNameTextField() - - erorNIckCheckText() - - Spacer() - .frame(height: UIScreen.screenHeight * 0.4) - - } - .bounce(false) - - CustomButton( - action: { - store.send(.switchSettingTab) - }, title: store.presntNextViewButtonTitle, - config: CustomButtonConfig.create() - ,isEnable: store.enableButton - ) - .padding(.horizontal, 20) - - Spacer() - .frame(height: 16) - } + ) { + self.store = store + } + + public var body: some View { + ZStack { + Color.gray600 + .edgesIgnoringSafeArea(.all) + + VStack { + ScrollView(.vertical, showsIndicators: false) { + signUpNameTitle() + + checkNickNameTextField() + + erorNIckCheckText() + + Spacer() + .frame(height: UIScreen.screenHeight * 0.4) + } + .bounce(false) + + CustomButton( + action: { + store.send(.switchSettingTab) + }, title: store.presntNextViewButtonTitle, + config: CustomButtonConfig.create() + ,isEnable: store.enableButton + ) + .padding(.horizontal, 20) + + Spacer() + .frame(height: 16) + } } - + } + } extension SignUpNameView { - - @ViewBuilder - private func signUpNameTitle() -> some View { - VStack { - Spacer() - .frame(height: UIScreen.screenHeight * 0.02) - - Text(store.signUpNameTitle) - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.basicWhite) - - Spacer() - .frame(height: 16) - - Text(store.signUpNameSubTitle) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(Color.gray300) - - - } + + @ViewBuilder + private func signUpNameTitle() -> some View { + VStack { + Spacer() + .frame(height: UIScreen.screenHeight * 0.02) + + Text(store.signUpNameTitle) + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.basicWhite) + + Spacer() + .frame(height: 16) + + Text(store.signUpNameSubTitle) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(Color.gray300) + + } - - @ViewBuilder - private func checkNickNameTextField() -> some View { - VStack { - Spacer() - .frame(height: UIScreen.screenHeight * 0.08) - - TextField("닉네임", text: $store.signUpNameDisplay) - .pretendardFont(family: .SemiBold, size: store.signUpNameDisplay.isEmpty ? 48 : 48) - .foregroundStyle(store.signUpNameDisplay.isEmpty ? Color.gray300 : Color.basicWhite) - .multilineTextAlignment(.center) - .submitLabel(.done) - .onSubmit { - if CheckRegister.isValidNickName(store.signUpNameDisplay) { - store.checkNickNameMessage = "사용 가능한 닉네임이에요" - store.send(.async(.checkNickName(nickName: store.signUpNameDisplay))) - } else if CheckRegister.containsInvalidCharacters(store.signUpNameDisplay) { - store.checkNickNameMessage = "띄어쓰기와 특수문자는 사용할 수 없어요" - } else if store.signUpNameDisplay.isEmpty { - store.checkNickNameMessage = "닉네임은 5글자 이하까지 입력 가능해요" - store.enableButton = false - } else if CheckRegister.containsInvalidCharacters(store.signUpNameDisplay) { - store.enableButton = false - } else { - store.checkNickNameMessage = "닉네임은 5글자 이하까지 입력 가능해요" - store.enableButton = false - } - } - - .onTapGesture { - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - } - - + } + + @ViewBuilder + private func checkNickNameTextField() -> some View { + VStack { + Spacer() + .frame(height: UIScreen.screenHeight * 0.08) + + TextField("닉네임", text: $store.signUpNameDisplay) + .pretendardFont(family: .SemiBold, size: store.signUpNameDisplay.isEmpty ? 48 : 48) + .foregroundStyle(store.signUpNameDisplay.isEmpty ? Color.gray300 : Color.basicWhite) + .multilineTextAlignment(.center) + .submitLabel(.done) + .onSubmit { + if CheckRegister.isValidNickName(store.signUpNameDisplay) { + store.checkNickNameMessage = "사용 가능한 닉네임이에요" + store.send(.async(.checkNickName(nickName: store.signUpNameDisplay))) + } else if CheckRegister.containsInvalidCharacters(store.signUpNameDisplay) { + store.checkNickNameMessage = "띄어쓰기와 특수문자는 사용할 수 없어요" + } else if store.signUpNameDisplay.isEmpty { + store.checkNickNameMessage = "닉네임은 5글자 이하까지 입력 가능해요" + store.enableButton = false + } else if CheckRegister.containsInvalidCharacters(store.signUpNameDisplay) { + store.enableButton = false + } else { + store.checkNickNameMessage = "닉네임은 5글자 이하까지 입력 가능해요" + store.enableButton = false + } } - } - - @ViewBuilder - private func erorNIckCheckText() -> some View { - VStack { - Spacer() - .frame(height: 20) - - if store.signUpNameDisplay.isEmpty { - Text(store.checkNickNameMessage) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) - } else { - Text(store.checkNickNameMessage) - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) - } + + .onTapGesture { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } + + } - - + } + + @ViewBuilder + private func erorNIckCheckText() -> some View { + VStack { + Spacer() + .frame(height: 20) + + if store.signUpNameDisplay.isEmpty { + Text(store.checkNickNameMessage) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) + } else { + Text(store.checkNickNameMessage) + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(store.enableButton ? Color.basicPrimary : Color.alertError) + } + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/Reducer/SignUpPaging.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/Reducer/SignUpPaging.swift index 83ffa9b..3c34328 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/Reducer/SignUpPaging.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/Reducer/SignUpPaging.swift @@ -14,197 +14,220 @@ import Networkings @Reducer public struct SignUpPaging { + public init() {} + + @ObservableState + public struct State: Equatable { public init() {} - @ObservableState - public struct State: Equatable { - public init() {} + var signUpName = SignUpName.State() + var signUpAge = SignUpAge.State() + var signUpAges = SignUpAge.State() + var signUpJob = SignUpJob.State() + var activeMenu: SignUpTab = .signUpName + + var updateUserinfoModel: UpdateUserInfoDTOModel? = nil + @Presents var destination: Destination.State? + + + } + + public enum Action: ViewAction, FeatureAction, BindableAction { + case binding(BindingAction) + case destination(PresentationAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + case signUpName(SignUpName.Action) + case signUpAge(SignUpAge.Action) + case signUpJob(SignUpJob.Action) + case activeTabChanged(SignUpTab) + + } + + @Reducer(state: .equatable) + public enum Destination { + case customPopUp(CustomPopUp) + } + + //MARK: - ViewAction + @CasePathable + public enum View { + case backSelectTab + case appearPopUp + case closePopUp + case presntOnboarding + } + + + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case updateUserInfoResponse(Result) + case updateUserInfo( + nickName: String, + year: Int, + job: String, + generation: String) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { + + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntOnboarding + case presntMainHome + + } + + + @Dependency(SignUpUseCase.self) var signUpUseCase + @Dependency(\.continuousClock) var clock + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(_): + return .none - var signUpName = SignUpName.State() - var signUpAge = SignUpAge.State() - var signUpAges = SignUpAge.State() - var signUpJob = SignUpJob.State() - var activeMenu: SignUpTab = .signUpName + case .destination(_): + return .none - var updateUserinfoModel: UpdateUserInfoModel? - @Presents var destination: Destination.State? + case .activeTabChanged(let changeTab): + state.activeMenu = changeTab + return .none + case .signUpName(.switchSettingTab): + state.activeMenu = .signUpGeneration + // state.selectedTab = 1 + return .none - } - - public enum Action: ViewAction, FeatureAction, BindableAction { - case binding(BindingAction) - case destination(PresentationAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - case signUpName(SignUpName.Action) - case signUpAge(SignUpAge.Action) - case signUpJob(SignUpJob.Action) - case activeTabChanged(SignUpTab) + case .signUpAge(.switchTabs): + state.activeMenu = .signUpJob + // state.selectedTab = 1 + return .none + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + + default: + return .none + } } - - @Reducer(state: .equatable) - public enum Destination { - case customPopUp(CustomPopUp) - } - - //MARK: - ViewAction - @CasePathable - public enum View { - case backSelectTab - case appearPopUp - case closePopUp - case presntOnboarding + .ifLet(\.$destination, action: \.destination) + Scope(state: \.signUpName, action: \.signUpName) { + SignUpName() } - - - - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case updateUserInfoResponse(Result) - case updateUserInfo( - nickName: String, - year: Int, - job: String, - generation: String) + Scope(state: \.signUpAge, action: \.signUpAge) { + SignUpAge() } - - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - + Scope(state: \.signUpJob, action: \.signUpJob) { + SignUpJob() } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntOnboarding - case presntMainHome - + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .backSelectTab: + if state.activeMenu == .signUpGeneration { + state.activeMenu = .signUpGeneration + } else if state.activeMenu == .signUpGeneration { + state.activeMenu = .signUpName + } + return .none + + case .appearPopUp: + state.destination = .customPopUp(.init()) + return .none + + case .closePopUp: + state.destination = nil + return .none + + + case .presntOnboarding: + return .none } - - - @Dependency(SignUpUseCase.self) var signUpUseCase - @Dependency(\.continuousClock) var clock - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .binding(_): - return .none - - case .destination(_): - return .none - - case .activeTabChanged(let changeTab): - state.activeMenu = changeTab - return .none - - case .signUpName(.switchSettingTab): - state.activeMenu = .signUpGeneration -// state.selectedTab = 1 - return .none - - case .signUpAge(.switchTabs): - state.activeMenu = .signUpJob -// state.selectedTab = 1 - return .none - - case .view(let View): - switch View { - - - case .backSelectTab: - if state.activeMenu == .signUpGeneration { - state.activeMenu = .signUpGeneration - } else if state.activeMenu == .signUpGeneration { - state.activeMenu = .signUpName - } - return .none - - case .appearPopUp: - state.destination = .customPopUp(.init()) - return .none - - case .closePopUp: - state.destination = nil - return .none - - - case .presntOnboarding: - return .none - } - - case .async(let AsyncAction): - switch AsyncAction { - - case .updateUserInfo(let nickname, let year, let job, let generation): - return .run { send in - let userInfoResult = await Result { - try await signUpUseCase.updateUserInfo(nickname: nickname, year: year, job: job, generation: generation) - } - - switch userInfoResult { - case .success(let updateUserInfoData): - if let updateUserInfoData = updateUserInfoData { - await send(.async(.updateUserInfoResponse(.success(updateUserInfoData)))) - - await send(.view(.closePopUp)) - - try await clock.sleep(for: .seconds(1)) - if updateUserInfoData.data?.isFirstLogin == false { - await send(.navigation(.presntOnboarding)) - } else { - await send(.navigation(.presntMainHome)) - } - } - - case .failure(let error): - await send(.async(.updateUserInfoResponse(.failure(CustomError.map(error))))) - } - } - - case .updateUserInfoResponse(let result): - switch result { - case .success(let userInfoData): - state.updateUserinfoModel = userInfoData - case let .failure(error): - Log.error("update user info 리턴 에러", error) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - case .presntOnboarding: - return .none - - case .presntMainHome: - return .none - } - - - - default: - return .none + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .updateUserInfo(let nickname, let year, let job, let generation): + return .run { send in + let userInfoResult = await Result { + try await signUpUseCase.updateUserInfo(nickname: nickname, year: year, job: job, generation: generation) + } + + switch userInfoResult { + case .success(let updateUserInfoData): + if let updateUserInfoData = updateUserInfoData { + await send(.async(.updateUserInfoResponse(.success(updateUserInfoData)))) + + await send(.view(.closePopUp)) + + try await clock.sleep(for: .seconds(1)) + if updateUserInfoData.data.isFirstLogin == false { + await send(.navigation(.presntOnboarding)) + } else { + await send(.navigation(.presntMainHome)) + } } + + case .failure(let error): + await send(.async(.updateUserInfoResponse(.failure(CustomError.map(error))))) + } } - .ifLet(\.$destination, action: \.destination) - Scope(state: \.signUpName, action: \.signUpName) { - SignUpName() - } - Scope(state: \.signUpAge, action: \.signUpAge) { - SignUpAge() - } - Scope(state: \.signUpJob, action: \.signUpJob) { - SignUpJob() + + case .updateUserInfoResponse(let result): + switch result { + case .success(let userInfoData): + state.updateUserinfoModel = userInfoData + case let .failure(error): + #logError("update user info 리턴 에러", error) } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntOnboarding: + return .none + + case .presntMainHome: + return .none } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/View/SignUpPagingView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/View/SignUpPagingView.swift index 325dbef..c4dd661 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/View/SignUpPagingView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Auth/SignUpPaging/View/SignUpPagingView.swift @@ -14,94 +14,94 @@ import Model import PopupView public struct SignUpPagingView: View { - @Bindable var store: StoreOf - var backAction: () -> Void = { } + @Bindable var store: StoreOf + var backAction: () -> Void = { } + + + public init( + store: StoreOf, + backAction: @escaping () -> Void + ) { + self.store = store + self.backAction = backAction - - public init( - store: StoreOf, - backAction: @escaping () -> Void - ) { - self.store = store - self.backAction = backAction + } + + + public var body: some View { + ZStack { + Color.gray600 + .edgesIgnoringSafeArea(.all) - } - - - public var body: some View { - ZStack { - Color.gray600 - .edgesIgnoringSafeArea(.all) - - VStack { - Spacer() - .frame(height: 14) - - NavigationBackButton { - if store.activeMenu == .signUpName { - backAction() - } else if store.activeMenu == .signUpGeneration { - store.activeMenu = .signUpName - } else { - store.activeMenu = .signUpGeneration - } - - } - - Spacer() - .frame(height: 20) - - DotBarView(activeIndex: $store.activeMenu) - - TabView(selection: $store.activeMenu) { - SignUpNameView(store: self.store.scope(state: \.signUpName, action: \.signUpName)) - .tag(SignUpTab.signUpName) - - - SignUpAgeView(store: self.store.scope(state: \.signUpAge, action: \.signUpAge)) - .tag(SignUpTab.signUpGeneration) - - SignUpJobView(store: self.store.scope(state: \.signUpJob, action: \.signUpJob), confirmAction: { - store.send(.view(.appearPopUp)) - }) - .tag(SignUpTab.signUpJob) - } - .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) - .animation(.none, value: store.activeMenu) - - - Spacer() - } - + VStack { + Spacer() + .frame(height: 14) + + NavigationBackButton { + if store.activeMenu == .signUpName { + backAction() + } else if store.activeMenu == .signUpGeneration { + store.activeMenu = .signUpName + } else { + store.activeMenu = .signUpGeneration + } + } - .popup(item: $store.scope(state: \.destination?.customPopUp, action: \.destination.customPopUp), itemView: { customPopUpStore in - CheckSignUpPopUp( - store: customPopUpStore, - nickName: store.signUpName.signUpNameDisplay, - yearOfBirth: store.signUpAge.signUpAgeDisplay, - job: store.signUpJob.selectedJob ?? "", - confirmButtonText: "가입하기", - cancelAction: { - store.send(.view(.closePopUp)) - }, confirmAction: { - store.send(.async(.updateUserInfo( - nickName: store.signUpName.signUpNameDisplay, - year: Int(store.signUpAge.signUpAgeDisplay) ?? 0 , - job: store.signUpJob.selectedJob ?? "", - generation: store.signUpAge.checkGenerationText))) - } - - ) - }, customize: { popup in - popup - .type(.floater(verticalPadding: UIScreen.screenHeight * 0.2)) - .position(.bottom) - .animation(.spring) - .closeOnTap(true) - .closeOnTapOutside(true) - .backgroundColor(Color.basicBlack.opacity(0.8)) - }) + Spacer() + .frame(height: 20) + + DotBarView(activeIndex: $store.activeMenu) + + TabView(selection: $store.activeMenu) { + SignUpNameView(store: self.store.scope(state: \.signUpName, action: \.signUpName)) + .tag(SignUpTab.signUpName) + + + SignUpAgeView(store: self.store.scope(state: \.signUpAge, action: \.signUpAge)) + .tag(SignUpTab.signUpGeneration) + + SignUpJobView(store: self.store.scope(state: \.signUpJob, action: \.signUpJob), confirmAction: { + store.send(.view(.appearPopUp)) + }) + .tag(SignUpTab.signUpJob) + } + .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) + .animation(.none, value: store.activeMenu) + + + Spacer() + } + } + .popup(item: $store.scope(state: \.destination?.customPopUp, action: \.destination.customPopUp), itemView: { customPopUpStore in + CheckSignUpPopUp( + store: customPopUpStore, + nickName: store.signUpName.signUpNameDisplay, + yearOfBirth: store.signUpAge.signUpAgeDisplay, + job: store.signUpJob.selectedJob ?? "", + confirmButtonText: "가입하기", + cancelAction: { + store.send(.view(.closePopUp)) + }, confirmAction: { + store.send(.async(.updateUserInfo( + nickName: store.signUpName.signUpNameDisplay, + year: Int(store.signUpAge.signUpAgeDisplay) ?? 0 , + job: store.signUpJob.selectedJob ?? "", + generation: store.signUpAge.checkGenerationText))) + } + + ) + }, customize: { popup in + popup + .type(.floater(verticalPadding: UIScreen.screenHeight * 0.2)) + .position(.bottom) + .animation(.spring) + .closeOnTap(true) + .closeOnTapOutside(true) + .backgroundColor(Color.basicBlack.opacity(0.8)) + }) + + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/CreateQuestion/WirteAnswer/Reducer/WriteAnswer.swift b/OPeace/Projects/Presentation/Presentation/Sources/CreateQuestion/WirteAnswer/Reducer/WriteAnswer.swift index 7324c62..8363dfc 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/CreateQuestion/WirteAnswer/Reducer/WriteAnswer.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/CreateQuestion/WirteAnswer/Reducer/WriteAnswer.swift @@ -16,178 +16,206 @@ import Networkings @Reducer public struct WriteAnswer { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { + // @Shared var createQuestionEmoji: String + // @Shared var createQuestionTitle: String - @ObservableState - public struct State: Equatable { -// @Shared var createQuestionEmoji: String -// @Shared var createQuestionTitle: String - - @Shared var createQuestionUserModel: CreateQuestionUserModel - - @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - - @Presents var destination: Destination.State? - - var presntWriteUploadViewButtonTitle: String = "고민 올리기" - var enableButton: Bool = false - var choiceAtext: String = "" - var choiceBtext: String = "" - var floatinPopUpText: String = "" - var isErrorEnableAnswerAStroke: Bool = false - var isErrorEnableAnswerBStroke: Bool = false - - var createQuesionModel: CreateQuestionModel? = nil - - - public init( - createQuestionUserModel: CreateQuestionUserModel = .init() - ) { - self._createQuestionUserModel = Shared(wrappedValue: createQuestionUserModel, .inMemory("createQuestionUserModel")) - - } - } + @Shared var createQuestionUserModel: CreateQuestionUserModel - public enum Action: ViewAction, BindableAction, FeatureAction { - case binding(BindingAction) - case destination(PresentationAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - } + @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - @Reducer(state: .equatable) - public enum Destination { - case floatingPopUP(FloatingPopUp) - - } + @Presents var destination: Destination.State? - //MARK: - ViewAction - @CasePathable - public enum View { - case presntFloatintPopUp - case closePopUp - case timeToCloseFloatingPopUp - } + var presntWriteUploadViewButtonTitle: String = "고민 올리기" + var enableButton: Bool = false + var choiceAtext: String = "" + var choiceBtext: String = "" + var floatinPopUpText: String = "" + var isErrorEnableAnswerAStroke: Bool = false + var isErrorEnableAnswerBStroke: Bool = false + var createQuesionModel: CreateQuestionDTOModel? = nil - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case createQuestion(emoji: String, title: String, choiceA: String, choiceB: String) - case createQuestionResponse(Result) + public init( + createQuestionUserModel: CreateQuestionUserModel = .init() + ) { + self._createQuestionUserModel = Shared(wrappedValue: createQuestionUserModel, .inMemory("createQuestionUserModel")) + } + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case binding(BindingAction) + case destination(PresentationAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + @Reducer(state: .equatable) + public enum Destination { + case floatingPopUP(FloatingPopUp) - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - - } + } + + //MARK: - ViewAction + @CasePathable + public enum View { + case presntFloatintPopUp + case closePopUp + case timeToCloseFloatingPopUp + } + + + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case createQuestion(emoji: String, title: String, choiceA: String, choiceB: String) + case createQuestionResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntMainHome + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntMainHome + + } + + @Dependency(\.continuousClock) var clock + @Dependency(QuestionUseCase.self) var questionUseCase + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + + case .binding(\.choiceAtext): + return .none + case .binding(\.choiceBtext): + return .none + + case .binding(\.isErrorEnableAnswerAStroke): + return .none + + case .binding(\.isErrorEnableAnswerBStroke): + return .none + + case .destination(_): + return .none + + case .binding(_): + return .none + + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } } - - @Dependency(\.continuousClock) var clock - @Dependency(QuestionUseCase.self) var questionUseCase - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - - case .binding(\.choiceAtext): - return .none - - case .binding(\.choiceBtext): - return .none - - case .binding(\.isErrorEnableAnswerAStroke): - return .none - - case .binding(\.isErrorEnableAnswerBStroke): - return .none - - case .destination(_): - return .none - - case .binding(_): - return .none - - case .view(let View): - switch View { - case .presntFloatintPopUp: - state.destination = .floatingPopUP(.init()) - return .none - - case .closePopUp: - state.destination = nil - return .none - - case .timeToCloseFloatingPopUp: - return .run { send in - try await clock.sleep(for: .seconds(1.5)) - await send(.view(.closePopUp)) - } - } - case .async(let AsyncAction): - switch AsyncAction { - - case .createQuestion( - let emoji, - let title, - let choiceA, - let choiceB): - return .run { @MainActor send in - let createResult = await Result { - try await questionUseCase.createQuestion( - emoji: emoji, - title: title, - choiceA: choiceA, - choiceB: choiceB - ) - } - - switch createResult { - case .success(let createResultData): - if let createResultData = createResultData { - send(.async(.createQuestionResponse(.success(createResultData)))) - - if createResultData.data?.id ?? .zero != 0 { - send(.navigation(.presntMainHome)) - } - } - case .failure(let error): - send(.async(.createQuestionResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) - } - - } - case .createQuestionResponse(let result): - switch result { - case .success(let createResultData): - state.createQuesionModel = createResultData - state.createQuestionUserModel.createQuestionEmoji = "" - state.createQuestionUserModel.createQuestionTitle = "" - case .failure(let error): - Log.error("질문 생성 실패", error.localizedDescription) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - case .presntMainHome: - return .none - } + .ifLet(\.$destination, action: \.destination) + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .presntFloatintPopUp: + state.destination = .floatingPopUP(.init()) + return .none + + case .closePopUp: + state.destination = nil + return .none + + case .timeToCloseFloatingPopUp: + return .run { send in + try await clock.sleep(for: .seconds(1.5)) + await send(.view(.closePopUp)) + } + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .createQuestion( + let emoji, + let title, + let choiceA, + let choiceB): + return .run { send in + let createResult = await Result { + try await questionUseCase.createQuestion( + emoji: emoji, + title: title, + choiceA: choiceA, + choiceB: choiceB + ) + } + + switch createResult { + case .success(let createResultData): + if let createResultData = createResultData { + await send(.async(.createQuestionResponse(.success(createResultData)))) + + if createResultData.data.id != .zero { + await send(.navigation(.presntMainHome)) } + } + case .failure(let error): + await send(.async(.createQuestionResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) } - .ifLet(\.$destination, action: \.destination) + + } + case .createQuestionResponse(let result): + switch result { + case .success(let createResultData): + state.createQuesionModel = createResultData + state.createQuestionUserModel.createQuestionEmoji = "" + state.createQuestionUserModel.createQuestionTitle = "" + case .failure(let error): + #logError("질문 생성 실패", error.localizedDescription) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntMainHome: + return .none } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/Reducer/HomeCoordinator.swift b/OPeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/Reducer/HomeCoordinator.swift index fa96206..9791e88 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/Reducer/HomeCoordinator.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/Reducer/HomeCoordinator.swift @@ -15,210 +15,234 @@ import API @Reducer public struct HomeCoordinator { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { + var routes: [Route] - @ObservableState - public struct State: Equatable { - var routes: [Route] + var home = Home.State() + var profile = Profile.State() + var report = Report.State() + var createQuestion = CreateQuestionCoordinator.State() + + @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = nil + @Shared(.inMemory("questionID")) var questionID: Int = 0 + + + public init() { + @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() + self.routes = [.root(.home(.init(userInfoModel: userInfoModel)), embedInNavigationView: true)] + } + + } + + public enum Action : ViewAction, FeatureAction { + case router(IndexedRouterActionOf) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + case home(Home.Action) + case profile(Profile.Action) + case createQuestion(CreateQuestionCoordinator.Action) + case report(Report.Action) + } + + + //MARK: - ViewAction + public enum View { + + } + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { + case removePath + case removeAllPath + case removeToHome + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntAuth + } + + + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .router(let routeAction): + return routerAction(state: &state, action: routeAction) - var home = Home.State() - var profile = Profile.State() - var report = Report.State() - var createQuestion = CreateQuestionCoordinator.State() + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) - @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = nil - @Shared(.inMemory("questionID")) var questionID: Int = 0 + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) - public init() { - @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - self.routes = [.root(.home(.init(userInfoModel: userInfoModel)), embedInNavigationView: true)] - } + case .home(.navigation(.presntProfile)): + return .send(.profile(.async(.fetchUser))) + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + + default: + return .none + } } - - public enum Action : ViewAction, FeatureAction { - case router(IndexedRouterActionOf) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - case home(Home.Action) - case profile(Profile.Action) - case createQuestion(CreateQuestionCoordinator.Action) - case report(Report.Action) + .forEachRoute(\.routes, action: \.router) + Scope(state: \.home, action: \.home) { + Home() } - - - //MARK: - ViewAction - public enum View { - + Scope(state: \.profile, action: \.profile) { + Profile() } - - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - + Scope(state: \.createQuestion, action: \.createQuestion) { + CreateQuestionCoordinator() } - - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - case removePath - case removeAllPath - case removeToHome + Scope(state: \.report, action: \.report) { + Report() } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntAuth + } + + private func routerAction( + state: inout State, + action: IndexedRouterActionOf + ) -> Effect { + switch action { + //MARK: - Home + case .routeAction(id: _, action: .home(.navigation(.presntProfile))): + state.routes.push(.profile(.init())) + return .none + + //MARK: - 로그인 + case .routeAction(id: _, action: .home(.navigation(.presntLogin))): + return .send(.navigation(.presntAuth)) + + //MARK: - 질문 등록 하기 + case .routeAction(id: _, action: .home(.navigation(.presntWriteQuestion))): + state.routes.push(.createQuestion(.init())) + return .none + + //MARK: - 신고하기 + case .routeAction(id: _, action: .home(.navigation(.presntReport))): + state.routes.push(.report(.init(questionID: state.questionID))) + return .none + + case .routeAction(id: _, action: .report(.navigation(.presntMainHome))): + return .send(.inner(.removeToHome)) + + //MARK: - 프로필 화면 + //MARK: - logout + case .routeAction(id: _, action: .profile(.navigation(.presntLogout))): + state.routes.goBackToRoot() + return .none + + //MARK: - 프로필 수정 + case .routeAction(id: _, action: .profile(.navigation(.presntEditProfile))): + state.routes.push(.editProfile(.init())) + return .none + + //MARK: - withDraw 회원 탈퇴 + case .routeAction(id: _, action: .profile(.navigation(.presntWithDraw))): + state.routes.push(.withDraw(.init())) + return .none + + case .routeAction(id: _, action: .withDraw(.navigation(.presntDeleteUser))): + return .send(.inner(.removeToHome)) + + case .routeAction(id: _, action: .profile(.navigation(.presntUserBlock))): + state.routes.push(.blockUser(.init())) + return .none + + //MARK: - 내가 작성한 글 삭제 + case .routeAction(id: _, action: .profile(.navigation(.presntDeleteQuestion))): + return .send(.inner(.removeToHome)) + + //MARK: - 작성한 글이 없을때 + case .routeAction(id: _, action: .profile(.navigation(.presnetCreateQuestionList))): + state.routes.push(.createQuestion(.init())) + return .none + + case .routeAction(id: _, action: .createQuestion(.navigation(.presntHome))): + return .send(.inner(.removeToHome)) + + default: + return .none } - - - public var body: some ReducerOf { - Reduce { state, action in - switch action { - case .router(let routeAction): - return routerAction(state: &state, action: routeAction) - - case .view(let View): - switch View { - } - - case .async(let AsyncAction): - switch AsyncAction { - - } - - case .inner(let InnerAction): - switch InnerAction { - case .removePath: - return .routeWithDelaysIfUnsupported(state.routes, action: \.router) { - $0.goBack() - } - - case .removeAllPath: - return .none - - case .removeToHome: - state.routes.goBackToRoot() - return .none - } - - case .home(.navigation(.presntProfile)): - return .send(.profile(.async(.fetchUser))) - - case .navigation(let NavigationAction): - switch NavigationAction { - case .presntAuth: - return .none - } - - default: - return .none - } - } - .forEachRoute(\.routes, action: \.router) - Scope(state: \.home, action: \.home) { - Home() - } - Scope(state: \.profile, action: \.profile) { - Profile() - } - Scope(state: \.createQuestion, action: \.createQuestion) { - CreateQuestionCoordinator() - } - Scope(state: \.report, action: \.report) { - Report() - } + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + } - - private func routerAction( - state: inout State, - action: IndexedRouterActionOf - ) -> Effect { - switch action { - //MARK: - Home - case .routeAction(id: _, action: .home(.navigation(.presntProfile))): - return .routeWithDelaysIfUnsupported(state.routes, action: \.router) { - $0.push(.profile(.init())) - } - - //MARK: - 로그인 - case .routeAction(id: _, action: .home(.navigation(.presntLogin))): - return .send(.navigation(.presntAuth)) - - //MARK: - 질문 등록 하기 - case .routeAction(id: _, action: .home(.navigation(.presntWriteQuestion))): - state.routes.push(.createQuestion(.init())) - return .none - - //MARK: - 신고하기 - case .routeAction(id: _, action: .home(.navigation(.presntReport))): - state.routes.push(.report(.init(questionID: state.questionID))) - return .none - - case .routeAction(id: _, action: .report(.navigation(.presntMainHome))): - state.routes.goBackToRoot() - return .none - - //MARK: - 프로필 화면 - //MARK: - logout - case .routeAction(id: _, action: .profile(.navigation(.presntLogout))): - return .routeWithDelaysIfUnsupported(state.routes, action: \.router) { - $0.goBackTo(\.home) - } - - //MARK: - 프로필 수정 - case .routeAction(id: _, action: .profile(.navigation(.presntEditProfile))): - state.routes.push(.editProfile(.init())) - return .none - - //MARK: - withDraw 회원 탈퇴 - case .routeAction(id: _, action: .profile(.navigation(.presntWithDraw))): - state.routes.push(.withDraw(.init())) - return .none - - case .routeAction(id: _, action: .withDraw(.navigation(.presntDeleteUser))): - state.routes.goBackToRoot() - return .none - - case .routeAction(id: _, action: .profile(.navigation(.presntUserBlock))): - state.routes.push(.blockUser(.init())) - return .none - - //MARK: - 내가 작성한 글 삭제 - case .routeAction(id: _, action: .profile(.navigation(.presntDeleteQuestion))): - state.routes.goBackTo(\.home) - return .none - - //MARK: - 작성한 글이 없을때 - case .routeAction(id: _, action: .profile(.navigation(.presnetCreateQuestionList))): - state.routes.push(.createQuestion(.init())) - return .none - - case .routeAction(id: _, action: .createQuestion(.navigation(.presntHome))): - state.routes.goBackTo(\.home) - return .none - - default: - return .none - } - + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + case .removePath: + return .routeWithDelaysIfUnsupported(state.routes, action: \.router) { + $0.goBack() + } + + case .removeAllPath: + return .none + + case .removeToHome: + state.routes.goBackToRoot() + return .none + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntAuth: + return .none } + } + + } extension HomeCoordinator { - - @Reducer(state: .equatable) - public enum HomeScreen{ - case home(Home) - case profile(Profile) - case editProfile(EditProfile) - case withDraw(WithDraw) - case blockUser(BlockUser) - case report(Report) - case createQuestion(CreateQuestionCoordinator) - } - + + @Reducer(state: .equatable) + public enum HomeScreen{ + case home(Home) + case profile(Profile) + case editProfile(EditProfile) + case withDraw(WithDraw) + case blockUser(BlockUser) + case report(Report) + case createQuestion(CreateQuestionCoordinator) + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/Reducer/HomeFilter.swift b/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/Reducer/HomeFilter.swift index 0dd1ae8..cf56b2d 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/Reducer/HomeFilter.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/Reducer/HomeFilter.swift @@ -13,158 +13,198 @@ import Networkings @Reducer public struct HomeFilter { - public init() {} - - @ObservableState - public struct State : Equatable { - - var jsobListModel: SignUpJobModel? = nil - var generationListModel: GenerationListResponse? = nil - public var homeFilterTypeState: HomeFilterEnum? = nil - public var selectedItem: String? = nil - - public init(homeFilterEnum: HomeFilterEnum) { - homeFilterTypeState = homeFilterEnum - } - } - - public enum Action: ViewAction, BindableAction, FeatureAction { - case binding(BindingAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - case test - } + public init() {} + + @ObservableState + public struct State : Equatable { + + public var selectedItem: String? = nil - //MARK: - ViewAction - @CasePathable - public enum View { - case tapSettintitem(HomeFilterEnum) - } + var jsobListModel: SignUpListDTOModel? = nil + var generationListModel: SignUpListDTOModel? = nil + public var homeFilterTypeState: HomeFilterEnum? = nil - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case fetchListByFilterEnum(HomeFilterEnum) - case fetchJobList - case fetchJobResponse(Result) - case fetchGenerationResponse(Result) - } + - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { + public init(homeFilterEnum: HomeFilterEnum) { + homeFilterTypeState = homeFilterEnum } + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case binding(BindingAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + //MARK: - ViewAction + @CasePathable + public enum View { + case tapSettintitem(HomeFilterEnum) + } + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case fetchListByFilterEnum(HomeFilterEnum) + case fetchJobList + case fetchJobResponse(Result) + case fetchGenerationResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { - //MARK: - NavigationAction - public enum NavigationAction: Equatable { + } + + struct HomeFilterCancel: Hashable {} + + @Dependency(SignUpUseCase.self) var signUpUseCase + @Dependency(\.mainQueue) var mainQueue + + + public var body: some ReducerOf { + BindingReducer() + Reduce{ state, action in + switch action { + case .binding(\.selectedItem): + return .none + case .binding(_): + return .none + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) - } - - - @Dependency(SignUpUseCase.self) var signUpUseCase - - public var body: some ReducerOf { - BindingReducer() + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) - Reduce { state, action in - switch action { - case .binding(\.selectedItem): - return .none - case .binding(_): - return .none - case .view(let ViewAction): - switch ViewAction { - case .tapSettintitem(let homefilter): - state.homeFilterTypeState = homefilter - return .none - } - - case .async(let asyncAction): - switch asyncAction { - case .fetchListByFilterEnum(let homeFilterType): - state.homeFilterTypeState = homeFilterType - switch homeFilterType { - case .job: - return .run { @MainActor send in - let result = await Result { - try await self.signUpUseCase.fetchJobList() - } - - switch result { - case .success(let data): - if let data = data { - send(.async(.fetchJobResponse(.success(data)))) - } - case .failure(let error): - send(.async(.fetchJobResponse(.failure(CustomError.map(error))))) - } - } - case .generation: - return .run { @MainActor send in - let result = await Result { - try await self.signUpUseCase.fetchGenerationList() - } - - switch result { - case .success(let data): - if let data = data { - send(.async(.fetchGenerationResponse(.success(data)))) - } - case .failure(let error): - send(.async(.fetchGenerationResponse(.failure(CustomError.map(error))))) - } - } - default: - return .none - } - - case .fetchJobList: - return .run { @MainActor send in - let result = await Result { - try await self.signUpUseCase.fetchJobList() - } - - switch result { - case .success(let data): - if let data = data { - send(.async(.fetchJobResponse(.success(data)))) - } - case .failure(let error): - send(.async(.fetchJobResponse(.failure(CustomError.map(error))))) - } - - } - case .fetchJobResponse(let result): - switch result { - case .success(let data): - state.jsobListModel = data - case .failure(let error): - Log.network("JobList 에러", error.localizedDescription) - } - - return .none - case .fetchGenerationResponse(let result): - switch result { - case .success(let data): - state.generationListModel = data - case .failure(let error): - Log.network("generationList 에러", error.localizedDescription) - } - - return .none - } - case .test: - return .none + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } + } + .onChange(of: \.selectedItem) { oldValue, newValue in + Reduce { state, action in + state.selectedItem = newValue + return .none + } + } + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .tapSettintitem(let homefilter): + state.homeFilterTypeState = homefilter + return .none + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .fetchListByFilterEnum(let homeFilterType): + state.homeFilterTypeState = homeFilterType + switch homeFilterType { + case .job: + return .run { send in + let result = await Result { + try await self.signUpUseCase.fetchJobList() + } + + switch result { + case .success(let data): + if let data = data { + await send(.async(.fetchJobResponse(.success(data)))) } + case .failure(let error): + await send(.async(.fetchJobResponse(.failure(CustomError.map(error))))) + } } - .onChange(of: \.selectedItem) { oldValue, newValue in - Reduce { state, action in - state.selectedItem = newValue - return .none + .debounce(id: HomeFilterCancel(), for: 0.1, scheduler: mainQueue) + case .generation: + return .run { send in + let result = await Result { + try await self.signUpUseCase.fetchGenerationList() + } + + switch result { + case .success(let data): + if let data = data { + await send(.async(.fetchGenerationResponse(.success(data)))) } + case .failure(let error): + await send(.async(.fetchGenerationResponse(.failure(CustomError.map(error))))) + } + } + .debounce(id: HomeFilterCancel(), for: 0.1, scheduler: mainQueue) + default: + return .none + } + + case .fetchJobList: + return .run { send in + let result = await Result { + try await self.signUpUseCase.fetchJobList() } + + switch result { + case .success(let data): + if let data = data { + await send(.async(.fetchJobResponse(.success(data)))) + } + case .failure(let error): + await send(.async(.fetchJobResponse(.failure(CustomError.map(error))))) + } + + } + .debounce(id: HomeFilterCancel(), for: 0.1, scheduler: mainQueue) + + case .fetchJobResponse(let result): + switch result { + case .success(let data): + state.jsobListModel = data + case .failure(let error): + #logNetwork("JobList 에러", error.localizedDescription) + } + return .none + + case .fetchGenerationResponse(let result): + switch result { + case .success(let data): + state.generationListModel = data + case .failure(let error): + #logNetwork("generationList 에러", error.localizedDescription) + } + return .none } - - + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/View/HomeFilterView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/View/HomeFilterView.swift index 03d3999..1ac292a 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/View/HomeFilterView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Home/Filter/View/HomeFilterView.swift @@ -41,7 +41,7 @@ public struct HomeFilterView: View { .frame(height: 22) switch store.homeFilterTypeState { case .job: - if let jobCategories = store.jsobListModel?.data?.data { + if let jobCategories = store.jsobListModel?.data.content { ForEach(jobCategories, id: \.self) { jobCategory in filterListItem(title: jobCategory, isSelected: selectItem == jobCategory) { selectItem = jobCategory @@ -51,7 +51,7 @@ public struct HomeFilterView: View { } } case .generation: - if let generations = store.generationListModel?.data?.data { + if let generations = store.generationListModel?.data.content { ForEach(generations, id: \.self) { generation in filterListItem(title: generation, isSelected: selectItem == generation) { selectItem = generation diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/Reducer/Home.swift b/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/Reducer/Home.swift index 306bb91..3caea95 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/Reducer/Home.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/Reducer/Home.swift @@ -11,211 +11,216 @@ import Combine import ComposableArchitecture import Networkings +import FirebaseRemoteConfig @Reducer public struct Home { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { - @ObservableState - public struct State: Equatable { - - var profileImage: String = "person.fill" - var loginTiltle: String = "로그인을 해야 다른 기능을 사용하실 수 있습니다. " - var floatingText: String = "" - var customPopUpText: String = "" - var floatingImage: ImageAsset = .succesLogout - var cancellable: AnyCancellable? - - var questionModel: QuestionModel? = nil - var isVoteLikeQuestionModel: VoteQuestionLikeModel? = nil - var userBlockModel: UserBlockModel? = nil - var isVoteAnswerQuestionModel: QuestionVoteModel? = nil - var profileUserModel: UpdateUserInfoModel? = nil - var statusQuestionModel: StatusQuestionModel? = nil - var userBlockListModel: UserBlockListModel? = nil - - var cardGenerationColor: Color = .basicBlack - var isLikeTap: Bool = false - var isRoatinCard: Bool = false - - var likedItemId: String? - var questionID: Int? - var userID: String? - var isSelectAnswerA: String = "a" - var isSelectAnswerB: String = "b" - var questionDetailId: Int? = nil - var isTapAVote: Bool = false - var isTapBVote: Bool = false - -// var profile = Profile.State() - var pageSize: Int = 20 - - var isTapBlockUser: Bool = false - var isShowSelectEditModal: Bool = false - var isBlockQuestionPopUp: Bool = false - var isReportQuestionPopUp: Bool = false - - var selectedJobButtonTitle: String = "계열" - var selectedJob: String = "" - var isActivateJobButton: Bool = false - - var selectedGenerationButtonTitle: String = "세대" - var selectedGeneration: String = "" - var isActivateGenerationButton: Bool = false - - var selectedSortedButtonTitle: QuestionSort = .recent - var selectedSorted: QuestionSort = .recent - var isActivateSortedButton: Bool = true - - var selectedSortDesc: String = "" - var isFilterQuestion: Bool = false - var selectedItem: String? = "" - - @Shared var userInfoModel: UserInfoModel? - - @Shared(.inMemory("questionID")) var reportQuestionID: Int = 0 - - @Presents var destination: Destination.State? - - - public init( + var profileImage: String = "person.fill" + var loginTiltle: String = "로그인을 해야 다른 기능을 사용하실 수 있습니다. " + var floatingText: String = "" + var customPopUpText: String = "" + var floatingImage: ImageAsset = .succesLogout + var cancellable: AnyCancellable? + + var questionModel: QuestionDTOModel? = nil + var isVoteLikeQuestionModel: FlagQuestionDTOModel? = nil + var userBlockModel: UserBlockDTOModel? = nil + var isVoteAnswerQuestionModel: FlagQuestionDTOModel? = nil + var profileUserModel: UpdateUserInfoDTOModel? = nil + var statusQuestionModel: StatusQuestionDTOModel? = nil + var userBlockListModel: UserBlockListDTOModel? = nil + var remoteConfig: RemoteConfig? + + var cardGenerationColor: Color = .basicBlack + var isLikeTap: Bool = false + var isRoatinCard: Bool = false + + var likedItemId: String? + var questionID: Int? + var userID: String? + var isSelectAnswerA: String = "a" + var isSelectAnswerB: String = "b" + var questionDetailId: Int? = nil + var isTapAVote: Bool = false + var isTapBVote: Bool = false + + // var profile = Profile.State() + var pageSize: Int = 20 + + var isTapBlockUser: Bool = false + var isShowSelectEditModal: Bool = false + var isBlockQuestionPopUp: Bool = false + var isReportQuestionPopUp: Bool = false + + var selectedJobButtonTitle: String = "계열" + var selectedJob: String = "" + var isActivateJobButton: Bool = false + + var selectedGenerationButtonTitle: String = "세대" + var selectedGeneration: String = "" + var isActivateGenerationButton: Bool = false + + var selectedSortedButtonTitle: QuestionSort = .recent + var selectedSorted: QuestionSort = .recent + var isActivateSortedButton: Bool = true + + var selectedSortDesc: String = "" + var isFilterQuestion: Bool = false + var selectedItem: String? = "" + + @Shared var userInfoModel: UserInfoModel? + + @Shared(.inMemory("questionID")) var reportQuestionID: Int = 0 + @Shared(.appStorage("lastViewedPage")) var lastViewedPage: Int = .zero - userInfoModel: UserInfoModel? = .init() - ) { - self._userInfoModel = Shared(wrappedValue: userInfoModel, .inMemory("userInfoModel")) - } - - } + @Presents var destination: Destination.State? - public enum Action: ViewAction, BindableAction, FeatureAction { - case binding(BindingAction) - case destination(PresentationAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) -// case profile(Profile.Action) - - - } - @Reducer(state: .equatable) - public enum Destination { - case homeFilter(HomeFilter) - case customPopUp(CustomPopUp) - case floatingPopUP(FloatingPopUp) - case editQuestion(EditQuestion) - + public init( + + userInfoModel: UserInfoModel? = .init() + ) { + self._userInfoModel = Shared(wrappedValue: userInfoModel, .inMemory("userInfoModel")) } - //MARK: - ViewAction - public enum View { - case appaerProfiluserData - case prsentCustomPopUp - case presntFloatintPopUp - case presntEditQuestion - case closeEditQuestionModal - case closePopUp - case timeToCloseFloatingPopUp - case switchModalAction(EditQuestionType) - case filterViewTappd(HomeFilterEnum) - case closeFilterModal - } + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case binding(BindingAction) + case destination(PresentationAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + // case profile(Profile.Action) + + } + @Reducer(state: .equatable) + public enum Destination { + case homeFilter(HomeFilter) + case customPopUp(CustomPopUp) + case updatePopUp(CustomPopUp) + case floatingPopUP(FloatingPopUp) + case editQuestion(EditQuestion) + } + + //MARK: - ViewAction + public enum View { + case appaerProfiluserData + case prsentCustomPopUp + case presntFloatintPopUp + case presntEditQuestion + case closeEditQuestionModal + case closePopUp + case timeToCloseFloatingPopUp + case switchModalAction(EditQuestionType) + case filterViewTappd(HomeFilterEnum) + case closeFilterModal + case checkVersion(completion: () async -> Void) + case forceUpate + case forceUpdateResponse(Bool) + case appearCheckUpdatePopUp + } + + + //MARK: - AsyncAction 비동기 처리 액션 public enum AsyncAction: Equatable { case fetchQuestionList - case qusetsionListResponse(Result) + case qusetsionListResponse(Result) case isVoteQuestionLike(questioniD: Int) - case isVoteQuestionLikeResponse(Result) + case isVoteQuestionLikeResponse(Result) case blockUser(qusetionID: Int, userID: String) - case blockUserResponse(Result) + case blockUserResponse(Result) case isVoteQuestionAnswer(questionID: Int, choiceAnswer: String) - case isVoteQuestionAnsweResponse(Result) + case isVoteQuestionAnsweResponse(Result) case fetchUserProfile - case userProfileResponse(Result) + case userProfileResponse(Result) case jobFilterSelected(job: String) case generationFilterSelected(generation: String) case sortedFilterSelected(sortedEnum:QuestionSort) case filterQuestionList(job: String , generation: String, sortBy: QuestionSort) case statusQuestion(id: Int) - case statusQuestionResponse(Result) + case statusQuestionResponse(Result) case clearFilter case fetchUserBlockList - case fetchUserBlockResponse(Result) + case fetchUserBlockResponse(Result) case appearData } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - - } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntProfile - case presntLogin - case presntWriteQuestion - case presntReport + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntProfile + case presntLogin + case presntWriteQuestion + case presntReport + } + + @Dependency(\.continuousClock) var clock + @Dependency(QuestionUseCase.self) var questionUseCase + @Dependency(AuthUseCase.self) var authUseCase + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(_): + return .none + + case .binding(\.questionID): + return .none + + case .view(let View): + return handleViewAction(state: &state, action: View) + + case .async(let AsyncAction): + return handleAsyncAction(state: &state, action: AsyncAction) + + case .inner(let InnerAction): + return handleInnerAction(state: &state, action: InnerAction) + + case .navigation(let NavigationAction): + return handleNavigationAction(state: &state, action: NavigationAction) + + default: + return .none + } } - - @Dependency(\.continuousClock) var clock - @Dependency(QuestionUseCase.self) var questionUseCase - @Dependency(AuthUseCase.self) var authUseCase - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .binding(_): - return .none - - case .destination(.presented(.homeFilter(.test))): - return .none - - case .binding(\.questionID): - return .none - - case .view(let View): - return handleViewAction(state: &state, action: View) - - case .async(let AsyncAction): - return handleAsyncAction(state: &state, action: AsyncAction) - - case .inner(let InnerAction): - return handleInnerAction(state: &state, action: InnerAction) - - case .navigation(let NavigationAction): - return handleNavigationAction(state: &state, action: NavigationAction) - - default: - return .none - } - } - .ifLet(\.$destination, action: \.destination) - .onChange(of: \.questionModel) { oldValue, newValue in - Reduce { state, action in - state.questionModel = newValue - return .none - } - } - .onChange(of: \.statusQuestionModel) { oldValue, newValue in - Reduce { state, action in - state.statusQuestionModel = newValue - return .none - } - } - .onChange(of: \.userInfoModel) { oldValue, newValue in - Reduce { state, action in - state.userInfoModel = newValue - return .none - } - } + .ifLet(\.$destination, action: \.destination) + .onChange(of: \.questionModel) { oldValue, newValue in + Reduce { state, action in + state.questionModel = newValue + return .none + } + } + .onChange(of: \.statusQuestionModel) { oldValue, newValue in + Reduce { state, action in + state.statusQuestionModel = newValue + return .none + } } + .onChange(of: \.userInfoModel) { oldValue, newValue in + Reduce { state, action in + state.userInfoModel = newValue + return .none + } + } + } private func handleViewAction( state: inout State, @@ -223,75 +228,123 @@ public struct Home { ) -> Effect { switch action { case .appaerProfiluserData: - return .run { send in -// await send(.profile(.scopeFetchUser)) - } - + return .run { send in + // await send(.profile(.scopeFetchUser)) + } + case .prsentCustomPopUp: - state.destination = .customPopUp(.init()) - return .none - + state.destination = .customPopUp(.init()) + return .none + case .closePopUp: - state.destination = nil - return .none + state.destination = nil + return .none case .closeFilterModal: - state.destination = nil - return .none - + state.destination = nil + return .none + case .presntFloatintPopUp: - state.destination = .floatingPopUP(.init()) - return .none - + state.destination = .floatingPopUP(.init()) + return .none + case .timeToCloseFloatingPopUp: - return .run { send in - try await clock.sleep(for: .seconds(1.5)) - await send(.view(.closePopUp)) - } + return .run { send in + try await clock.sleep(for: .seconds(1.5)) + await send(.view(.closePopUp)) + } case .filterViewTappd(let filterEnum): - state.destination = .homeFilter(.init(homeFilterEnum: filterEnum)) - switch filterEnum { - case .job: - state.selectedItem = state.selectedJob - case .generation: - state.selectedItem = state.selectedGeneration - case .sorted(let sortedOrderEnum): - state.selectedItem = state.selectedSorted.sortedKoreanString - } - return .run { send in - await send(.destination(.presented(.homeFilter(.async(.fetchListByFilterEnum(filterEnum)))))) - } - + state.destination = .homeFilter(.init(homeFilterEnum: filterEnum)) + switch filterEnum { + case .job: + state.selectedItem = state.selectedJob + case .generation: + state.selectedItem = state.selectedGeneration + case .sorted(let sortedOrderEnum): + state.selectedItem = state.selectedSorted.sortedKoreanString + } + return .run { send in + await send(.destination(.presented(.homeFilter(.async(.fetchListByFilterEnum(filterEnum)))))) + } + case .presntEditQuestion: - state.destination = .editQuestion(.init()) - return .none - + state.destination = .editQuestion(.init()) + return .none + case .closeEditQuestionModal: - state.destination = nil - return .none - + state.destination = nil + return .none + case .switchModalAction(let editQuestion): - nonisolated(unsafe) let editQuestion = editQuestion + nonisolated(unsafe) let editQuestion = editQuestion + switch editQuestion { + case .reportUser: + state.customPopUpText = "정말 신고하시겠어요?" + state.isReportQuestionPopUp = true + case .blockUser: + state.customPopUpText = "정말 차단하시겠어요?" + state.isTapBlockUser = true + } + + return .run { send in switch editQuestion { case .reportUser: - Log.debug("신고하기") - state.customPopUpText = "정말 신고하시겠어요?" - state.isReportQuestionPopUp = true + Log.debug("신고하기") + await send(.view(.prsentCustomPopUp)) case .blockUser: - Log.debug("차단하기") - state.customPopUpText = "정말 차단하시겠어요?" - state.isTapBlockUser = true + Log.debug("차단하기") + await send(.view(.prsentCustomPopUp)) } - - return .run { send in - switch editQuestion { - case .reportUser: - Log.debug("신고하기") - await send(.view(.prsentCustomPopUp)) - case .blockUser: - Log.debug("차단하기") - await send(.view(.prsentCustomPopUp)) + } + + case .checkVersion(let completion): + return .run { send in + do { + let remoteConfig = RemoteConfig.remoteConfig() + try await remoteConfig.fetchAsync(withExpirationDuration: 0) + guard let minVersionString = remoteConfig["minimum_version"].stringValue else { + return + } + #logDebug("Firebase minimum_version: \(minVersionString)") + guard let bundleID = Bundle.main.bundleIdentifier else { + return + } + + let minVesrsion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + #logDebug("Firebase minimum_version: \(minVesrsion)") + let appStoreVersion = try await AppStoreVersionFetcher.fetchAppStoreVersion(bundleID: bundleID) + + if VersionComparer.shouldShowUpdatePopup(minimumVersion: appStoreVersion, appStoreVersion: minVesrsion) { + #logDebug("Firebase minimum_version is higher than App Store version.") + await completion() + } else { + #logDebug("App Store version is up-to-date.") + } + } catch { + #logDebug("Error during version check: \(error.localizedDescription)") } } + + case .forceUpate: + return .run { send in + let remoteConfig = RemoteConfig.remoteConfig() + try await remoteConfig.fetchAsync(withExpirationDuration: 0) + let forceUpdate = remoteConfig["force_update"].boolValue + print(forceUpdate) + await send(.view(.forceUpdateResponse(forceUpdate))) + } + + case let .forceUpdateResponse(forceUpdate): + return .run { @MainActor send in + if forceUpdate { + if let url = URL(string: "itms-apps://itunes.apple.com/app/6587550388") { + UIApplication.shared.open(url) + } + } + } + + case .appearCheckUpdatePopUp: + state.destination = .updatePopUp(.init()) + return .none } } @@ -301,18 +354,18 @@ public struct Home { ) -> Effect { switch action { case .presntProfile: - return .run { send in -// await send(.profile(.scopeFetchUser)) - } - + return .run { send in + // await send(.profile(.scopeFetchUser)) + } + case .presntLogin: - return .none - + return .none + case .presntWriteQuestion: - return .none - + return .none + case .presntReport: - return .none + return .none } } @@ -322,311 +375,306 @@ public struct Home { ) -> Effect { switch action { case .fetchQuestionList: - let pageSize = state.pageSize - return .run { send in - let questionResult = await Result { - try await questionUseCase.fetchQuestionList( - page: 1, - pageSize: pageSize, - job: "", - generation: "", - sortBy: .empty) - } - - switch questionResult { - case .success(let questionModel): - if let questionModel = questionModel { - await send(.async(.qusetsionListResponse(.success(questionModel)))) - } - case .failure(let error): - await send(.async(.qusetsionListResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) - } - + let pageSize = state.pageSize + return .run { send in + let questionResult = await Result { + try await questionUseCase.fetchQuestionList( + page: 1, + pageSize: pageSize, + job: "", + generation: "", + sortBy: .empty) } - case .jobFilterSelected(let job): - let currentSelectedGeneration = state.selectedGeneration - nonisolated(unsafe) let currentSortBy = state.selectedSorted - if state.selectedJob == job { - state.selectedJobButtonTitle = "계열" - state.selectedJob = "" - state.selectedItem = "" - state.isActivateJobButton = false - - return .send(.async(.filterQuestionList(job: "", generation: currentSelectedGeneration, sortBy: currentSortBy))) - } else { - state.selectedJobButtonTitle = job - state.selectedJob = job - state.selectedItem = "" - state.isActivateJobButton = true - return .send(.async(.filterQuestionList(job: job, generation: currentSelectedGeneration, sortBy: currentSortBy))) - } - case .generationFilterSelected(let generation): - let currentSelectedJob = state.selectedJob - nonisolated(unsafe) let currentSortBy = state.selectedSorted - if state.selectedGeneration == generation { - state.selectedGenerationButtonTitle = "세대" - state.selectedGeneration = "" - state.selectedItem = "" - state.isActivateGenerationButton = false - return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: "", sortBy: currentSortBy))) - } else { - state.isActivateGenerationButton = true - state.selectedGenerationButtonTitle = generation - state.selectedItem = generation - state.selectedGeneration = generation - return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: generation, sortBy: currentSortBy))) - } - case .sortedFilterSelected(let sorted): - let currentSelectedGeneration = state.selectedGeneration - let currentSelectedJob = state.selectedJob - - - if state.selectedSorted == sorted { - state.selectedSorted = .empty - return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: currentSelectedGeneration, sortBy: .empty))) - } else { - state.selectedSorted = sorted - state.selectedSortedButtonTitle = sorted - return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: currentSelectedGeneration, sortBy: sorted))) - } - - case .filterQuestionList(let job, let generation, let sortBy): - let pageSize = state.pageSize - - return .run { send in - let questionResult = await Result { - try await questionUseCase.fetchQuestionList( - page: 1, - pageSize: pageSize, - job: job, - generation: generation, - sortBy: sortBy) - } - - switch questionResult { - case .success(let questionModel): - if let questionModel = questionModel { - await send(.async(.qusetsionListResponse(.success(questionModel)))) - } - case .failure(let error): - await send(.async(.qusetsionListResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) - } + switch questionResult { + case .success(let questionModel): + if let questionModel = questionModel { + await send(.async(.qusetsionListResponse(.success(questionModel)))) + } + case .failure(let error): + await send(.async(.qusetsionListResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) } - case .clearFilter: + } + + case .jobFilterSelected(let job): + let currentSelectedGeneration = state.selectedGeneration + nonisolated(unsafe) let currentSortBy = state.selectedSorted + if state.selectedJob == job { state.selectedJobButtonTitle = "계열" state.selectedJob = "" state.selectedItem = "" state.isActivateJobButton = false + + return .send(.async(.filterQuestionList(job: "", generation: currentSelectedGeneration, sortBy: currentSortBy))) + } else { + state.selectedJobButtonTitle = job + state.selectedJob = job + state.selectedItem = "" + state.isActivateJobButton = true + return .send(.async(.filterQuestionList(job: job, generation: currentSelectedGeneration, sortBy: currentSortBy))) + } + case .generationFilterSelected(let generation): + let currentSelectedJob = state.selectedJob + nonisolated(unsafe) let currentSortBy = state.selectedSorted + if state.selectedGeneration == generation { state.selectedGenerationButtonTitle = "세대" state.selectedGeneration = "" + state.selectedItem = "" state.isActivateGenerationButton = false - state.selectedSorted = .recent - return .none + return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: "", sortBy: currentSortBy))) + } else { + state.isActivateGenerationButton = true + state.selectedGenerationButtonTitle = generation + state.selectedItem = generation + state.selectedGeneration = generation + return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: generation, sortBy: currentSortBy))) + } + case .sortedFilterSelected(let sorted): + let currentSelectedGeneration = state.selectedGeneration + let currentSelectedJob = state.selectedJob + + + if state.selectedSorted == sorted { + state.selectedSorted = .empty + return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: currentSelectedGeneration, sortBy: .empty))) + } else { + state.selectedSorted = sorted + state.selectedSortedButtonTitle = sorted + return .send(.async(.filterQuestionList(job: currentSelectedJob, generation: currentSelectedGeneration, sortBy: sorted))) + } + + case .filterQuestionList(let job, let generation, let sortBy): + let pageSize = state.pageSize + + return .run { send in + let questionResult = await Result { + try await questionUseCase.fetchQuestionList( + page: 1, + pageSize: pageSize, + job: job, + generation: generation, + sortBy: sortBy) + } + switch questionResult { + case .success(let questionModel): + if let questionModel = questionModel { + await send(.async(.qusetsionListResponse(.success(questionModel)))) + } + case .failure(let error): + await send(.async(.qusetsionListResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) + } + } + + case .clearFilter: + state.selectedJobButtonTitle = "계열" + state.selectedJob = "" + state.selectedItem = "" + state.isActivateJobButton = false + state.selectedGenerationButtonTitle = "세대" + state.selectedGeneration = "" + state.isActivateGenerationButton = false + state.selectedSorted = .recent + return .none + case .qusetsionListResponse(let result): switch result { case .success(let qusetsionListData): - // userInfoModel.isLogOut 확인 if state.userInfoModel?.isLogOut == true { // 로그아웃 상태에서는 필터링하지 않고 원래 데이터를 그대로 사용 state.questionModel = qusetsionListData } else { // 차단된 닉네임 리스트 가져오기 - let blockedNicknames = state.userBlockListModel?.data?.compactMap { $0.nickname } ?? [] - + let blockedNicknames = state.userBlockListModel?.data.compactMap { $0.nickname } ?? [] + // 차단된 닉네임 제외한 결과 필터링 - let filteredResults = qusetsionListData.data?.results?.filter { item in + let filteredResults = qusetsionListData.data?.content?.filter { item in guard let nickname = item.userInfo?.userNickname else { return true } return !blockedNicknames.contains(nickname) } - + // 새로운 데이터를 생성하여 필터링된 결과를 저장 var updatedQuestionListData = qusetsionListData - updatedQuestionListData.data = updatedQuestionListData.data.map { data in - var modifiedData = data - modifiedData.results = filteredResults - return modifiedData - } + updatedQuestionListData.data?.content = filteredResults state.questionModel = updatedQuestionListData } - + // 페이지 크기 증가 state.pageSize += 20 case .failure(let error): Log.error("QuestionList 에러", error.localizedDescription) } return .none - + case .isVoteQuestionLike(questioniD: let questioniD): - return .run { send in - let voteQuestionLikeResult = await Result { - try await questionUseCase.isVoteQuestionLike(questionID: questioniD) - } - - switch voteQuestionLikeResult { - case .success(let voteQuestionLikeResult): - if let voteQuestionLikeResult = voteQuestionLikeResult { - await send(.async(.isVoteQuestionLikeResponse( - .success(voteQuestionLikeResult)))) - - await send(.async(.fetchQuestionList)) - } - case .failure(let error): - await send(.async(.isVoteQuestionLikeResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) - } + return .run { send in + let voteQuestionLikeResult = await Result { + try await questionUseCase.isVoteQuestionLike(questionID: questioniD) } - case .isVoteQuestionLikeResponse(let voteQuestionResult): - switch voteQuestionResult { - case .success(let voteQuestionResult): - state.isVoteLikeQuestionModel = voteQuestionResult - state.isLikeTap.toggle() + switch voteQuestionLikeResult { + case .success(let voteQuestionLikeResult): + if let voteQuestionLikeResult = voteQuestionLikeResult { + await send(.async(.isVoteQuestionLikeResponse( + .success(voteQuestionLikeResult)))) + + await send(.async(.fetchQuestionList)) + } case .failure(let error): - Log.error("좋아요 누르기 에러", error.localizedDescription) + await send(.async(.isVoteQuestionLikeResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) } - return .none - + } + + case .isVoteQuestionLikeResponse(let voteQuestionResult): + switch voteQuestionResult { + case .success(let voteQuestionResult): + state.isVoteLikeQuestionModel = voteQuestionResult + state.isLikeTap.toggle() + case .failure(let error): + Log.error("좋아요 누르기 에러", error.localizedDescription) + } + return .none + case .blockUser(let qusetionID, let userID): - return .run { send in - let blockUserResult = await Result { - try await authUseCase.userBlock(questioniD: qusetionID, userID: userID) - } - switch blockUserResult { - case .success(let blockUserResult): - if let blockUserResult = blockUserResult { - await send(.async(.blockUserResponse(.success(blockUserResult)))) - - try await clock.sleep(for: .seconds(0.4)) - await send(.view(.presntFloatintPopUp)) - - await send(.view(.timeToCloseFloatingPopUp)) - } - case .failure(let error): - await send(.async(.blockUserResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) - } + return .run { send in + let blockUserResult = await Result { + try await authUseCase.userBlock(questioniD: qusetionID, userID: userID) } - - case .blockUserResponse(let result): - switch result { + switch blockUserResult { case .success(let blockUserResult): - state.userBlockModel = blockUserResult - state.floatingText = "차단이 완료 되었어요!" - state.floatingImage = .succesLogout + if let blockUserResult = blockUserResult { + await send(.async(.blockUserResponse(.success(blockUserResult)))) + + try await clock.sleep(for: .seconds(0.4)) + await send(.view(.presntFloatintPopUp)) + + await send(.view(.timeToCloseFloatingPopUp)) + } case .failure(let error): - Log.error("유저 차단 에러", error.localizedDescription) + await send(.async(.blockUserResponse(.failure(CustomError.createQuestionError(error.localizedDescription))))) } - return .none - + } + + case .blockUserResponse(let result): + switch result { + case .success(let blockUserResult): + state.userBlockModel = blockUserResult + state.floatingText = "차단이 완료 되었어요!" + state.floatingImage = .succesLogout + case .failure(let error): + Log.error("유저 차단 에러", error.localizedDescription) + } + return .none + case .isVoteQuestionAnswer(let questionID, let choiceAnswer): - return .run { send in - let voteQuestionAnswerResult = await Result { - try await questionUseCase.isVoteQuestionAnswer(questionID: questionID, choicAnswer: choiceAnswer) - } - - switch voteQuestionAnswerResult { - case .success(let voteQuestionResult): - if let voteQuestionResult = voteQuestionResult { - await send(.async(.isVoteQuestionAnsweResponse( - .success(voteQuestionResult)))) - - } - case .failure(let error): - await send(.async(.isVoteQuestionAnsweResponse(.failure( - CustomError.createQuestionError(error.localizedDescription))))) - } + return .run { send in + let voteQuestionAnswerResult = await Result { + try await questionUseCase.isVoteQuestionAnswer(questionID: questionID, choicAnswer: choiceAnswer) } - case .isVoteQuestionAnsweResponse(let reuslt): - switch reuslt { - case .success(let isVoteQuestionAnswer): - state.isVoteAnswerQuestionModel = isVoteQuestionAnswer - state.isRoatinCard = true + switch voteQuestionAnswerResult { + case .success(let voteQuestionResult): + if let voteQuestionResult = voteQuestionResult { + await send(.async(.isVoteQuestionAnsweResponse( + .success(voteQuestionResult)))) + + } case .failure(let error): - Log.error("유저 투표 에러", error.localizedDescription) + await send(.async(.isVoteQuestionAnsweResponse(.failure( + CustomError.createQuestionError(error.localizedDescription))))) } - return .none - - + } + + case .isVoteQuestionAnsweResponse(let reuslt): + switch reuslt { + case .success(let isVoteQuestionAnswer): + state.isVoteAnswerQuestionModel = isVoteQuestionAnswer + state.isRoatinCard = true + case .failure(let error): + Log.error("유저 투표 에러", error.localizedDescription) + } + return .none + + case .fetchUserProfile: - return .run { send in - let fetchUserData = await Result { - try await authUseCase.fetchUserInfo() - } - - switch fetchUserData { - case .success(let fetchUserResult): - if let fetchUserResult = fetchUserResult { - await send(.async(.userProfileResponse(.success(fetchUserResult)))) - } - case .failure(let error): - await send(.async(.userProfileResponse(.failure( - CustomError.userError(error.localizedDescription))))) - - } + return .run { send in + let fetchUserData = await Result { + try await authUseCase.fetchUserInfo() } - case .userProfileResponse(let result): - switch result { - case .success(let resultData): - state.profileUserModel = resultData - case let .failure(error): - Log.network("프로필 오류", error.localizedDescription) + switch fetchUserData { + case .success(let fetchUserResult): + if let fetchUserResult = fetchUserResult { + await send(.async(.userProfileResponse(.success(fetchUserResult)))) + } + case .failure(let error): + await send(.async(.userProfileResponse(.failure( + CustomError.userError(error.localizedDescription))))) + } - return .none - + } + + case .userProfileResponse(let result): + switch result { + case .success(let resultData): + state.profileUserModel = resultData + case let .failure(error): + Log.network("프로필 오류", error.localizedDescription) + } + return .none + case .statusQuestion(id: let id): - return .run { send in - let questionResult = await Result { - try await questionUseCase.statusQuestion(questionID: id) - } - - switch questionResult { - case .success(let questionStatusData): - if let questionStatusData = questionStatusData { - await send(.async(.statusQuestionResponse(.success(questionStatusData)))) - } - case .failure(let error): - await send(.async(.statusQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) - } + return .run { send in + let questionResult = await Result { + try await questionUseCase.statusQuestion(questionID: id) } - case .statusQuestionResponse(let result): - switch result { - case .success(let statusQuestionData): - state.statusQuestionModel = statusQuestionData + switch questionResult { + case .success(let questionStatusData): + if let questionStatusData = questionStatusData { + await send(.async(.statusQuestionResponse(.success(questionStatusData)))) + } case .failure(let error): - Log.error("질문 에대한 결과 보여주기 실패", error.localizedDescription) + await send(.async(.statusQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) } - return .none + } + + case .statusQuestionResponse(let result): + switch result { + case .success(let statusQuestionData): + state.statusQuestionModel = statusQuestionData + case .failure(let error): + Log.error("질문 에대한 결과 보여주기 실패", error.localizedDescription) + } + return .none case .fetchUserBlockList: - return .run { send in - let userBlockResult = await Result { - try await authUseCase.fetchUserBlockList() - } - - switch userBlockResult { - case .success(let userBlockLIstData): - if let userBlockListData = userBlockLIstData { - await send(.async(.fetchUserBlockResponse(.success(userBlockListData)))) - } - - case .failure(let error): - await send(.async(.fetchUserBlockResponse(.failure(CustomError.userError(error.localizedDescription))))) - } + return .run { send in + let userBlockResult = await Result { + try await authUseCase.fetchUserBlockList() } - case .fetchUserBlockResponse(let result): - switch result { - case .success(let userBlockListData): - state.userBlockListModel = userBlockListData + switch userBlockResult { + case .success(let userBlockLIstData): + if let userBlockListData = userBlockLIstData { + await send(.async(.fetchUserBlockResponse(.success(userBlockListData)))) + } + case .failure(let error): - Log.error("차단 유저 가져오기 실패", error.localizedDescription) + await send(.async(.fetchUserBlockResponse(.failure(CustomError.userError(error.localizedDescription))))) } - return .none + } + + case .fetchUserBlockResponse(let result): + switch result { + case .success(let userBlockListData): + state.userBlockListModel = userBlockListData + case .failure(let error): + Log.error("차단 유저 가져오기 실패", error.localizedDescription) + } + return .none case .appearData: return .concatenate( @@ -636,10 +684,9 @@ public struct Home { Effect.run(operation: { send in await send (.async(.fetchUserProfile)) }), - Effect.run(operation: { [userInfoModel = state.userInfoModel] send in + Effect.run(operation: { send in await send(.async(.fetchUserBlockList)) }) - ) } } @@ -649,7 +696,7 @@ public struct Home { action: InnerAction ) -> Effect { switch action { - + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/View/HomeView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/View/HomeView.swift index 68b26d8..bd9f8e5 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/View/HomeView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Home/Main/View/HomeView.swift @@ -14,315 +14,340 @@ import PopupView import DesignSystem public struct HomeView: View { - @Bindable var store: StoreOf - - @State private var refreshTimer: Timer? - - public init(store: StoreOf) { - self.store = store - } - - public var body: some View { - ZStack { - Color.gray600 - .edgesIgnoringSafeArea(.all) - - VStack { - navigationBaritem() - - questionLIstView() - } - .onAppear { - store.send(.async(.appearData)) - startRefreshData() - appearFloatingPopUp() - } - .onDisappear { - store.send(.async(.clearFilter)) - refreshTimer?.invalidate() - } - if store.questionModel?.data?.results == [] || - ((store.questionModel?.data?.results?.isEmpty) == nil) { - - } else { - VStack { - Spacer() - - writeQuestionButton() - .padding(.bottom, 32) - } - .edgesIgnoringSafeArea(.bottom) - } - } - .sheet(item: $store.scope(state: \.destination?.editQuestion, action: \.destination.editQuestion)) { editQuestionStore in - EditQuestionView(store: editQuestionStore) { - guard let edititem = editQuestionStore.editQuestionitem else {return} - store.send(.view(.switchModalAction(edititem ))) - } closeModalAction: { - store.send(.view(.closeEditQuestionModal)) - } - .presentationDetents([.height(UIScreen.screenHeight * 0.2)]) - .presentationCornerRadius(20) - .presentationDragIndicator(.hidden) - } - - .sheet(item: $store.scope(state: \.destination?.homeFilter, action: \.destination.homeFilter)) { homeFilterStore in - HomeFilterView( - store: homeFilterStore, - selectItem: $store.selectedItem, - closeModalAction: { data in - guard let homeFilter = homeFilterStore.homeFilterTypeState else { return } - store.isFilterQuestion = true - - switch homeFilter { - case .job: - store.send(.async(.jobFilterSelected(job: data))) - store.send(.view(.closeFilterModal)) - case .generation: - store.send(.async(.generationFilterSelected(generation:data))) - store.send(.view(.closeFilterModal)) - case .sorted(let sortedEnum): - let sortedEnumFromData = QuestionSort.fromKoreanString(data) - store.send(.async(.sortedFilterSelected(sortedEnum: sortedEnumFromData))) - store.send(.view(.closeFilterModal)) - } - - }) - .presentationDetents([.fraction(homeFilterStore.homeFilterTypeState == .job ? 0.7 : homeFilterStore.homeFilterTypeState == .generation ? 0.42: 0.2 )]) - .presentationDragIndicator(.visible) - .presentationCornerRadius(20) - } + @Bindable var store: StoreOf + + @State private var refreshTimer: Timer? + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + ZStack { + Color.gray600 + .edgesIgnoringSafeArea(.all) + + VStack { + navigationBaritem() - .popup(item: $store.scope(state: \.destination?.customPopUp, action: \.destination.customPopUp)) { customPopUp in - if store.userInfoModel?.isLogOut == true || store.userInfoModel?.isLookAround == true || store.userInfoModel?.isDeleteUser == true { - CustomBasicPopUpView( - store: customPopUp, - title: "로그인 하시겠어요?") { - store.send(.view(.closePopUp)) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - store.send(.navigation(.presntLogin)) - } - - } cancelAction: { - store.send(.view(.closePopUp)) - } - } - else if store.isTapBlockUser == true { - CustomBasicPopUpView( - store: customPopUp, - title: store.customPopUpText) { - store.send(.view(.closePopUp)) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - store.send(.async(.blockUser(qusetionID: store.questionID ?? .zero, userID: store.userID ?? ""))) - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - store.send(.async(.appearData)) - } - } - } cancelAction: { - store.send(.view(.closePopUp)) - } - } - else if store.isReportQuestionPopUp == true { - CustomBasicPopUpView( - store: customPopUp, - title: store.customPopUpText) { - store.send(.view(.closePopUp)) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - - store.send(.navigation(.presntReport)) - } - } cancelAction: { - store.send(.view(.closePopUp)) - } - } - } customize: { popup in - popup - .type(.floater(verticalPadding: UIScreen.screenHeight * 0.35)) - .position(.bottom) - .animation(.spring) - .closeOnTap(true) - .closeOnTapOutside(true) - .backgroundColor(Color.basicBlack.opacity(0.8)) - } + questionLIstView() + } + .onAppear { + store.send(.async(.appearData)) +// startRefreshData() + appearFloatingPopUp() + store.send(.view(.checkVersion { + store.send(.view(.appearCheckUpdatePopUp)) + })) + } + .onDisappear { + store.send(.async(.clearFilter)) + refreshTimer?.invalidate() + } + if store.questionModel?.data?.content == [] || + ((store.questionModel?.data?.content?.isEmpty) == nil) { - .popup(item: $store.scope(state: \.destination?.floatingPopUP, action: \.destination.floatingPopUP)) { floatingPopUpStore in - FloatingPopUpView(store: floatingPopUpStore, title: store.floatingText, image: store.floatingImage) - } customize: { popup in - popup - .type(.floater(verticalPadding: UIScreen.screenHeight * 0.02)) - .position(.bottom) - .animation(.spring) - .closeOnTap(true) - .closeOnTapOutside(true) + } else { + VStack { + Spacer() + + writeQuestionButton() + .padding(.bottom, 32) } - + .edgesIgnoringSafeArea(.bottom) + } + } + .sheet(item: $store.scope(state: \.destination?.editQuestion, action: \.destination.editQuestion)) { editQuestionStore in + EditQuestionView(store: editQuestionStore) { + guard let edititem = editQuestionStore.editQuestionitem else {return} + store.send(.view(.switchModalAction(edititem ))) + } closeModalAction: { + store.send(.view(.closeEditQuestionModal)) + } + .presentationDetents([.height(UIScreen.screenHeight * 0.2)]) + .presentationCornerRadius(20) + .presentationDragIndicator(.hidden) } - private func startRefreshData() { - refreshTimer?.invalidate() - - if !store.isFilterQuestion { - refreshTimer = Timer.scheduledTimer(withTimeInterval: 300, repeats: true) { _ in - store.send(.async(.fetchQuestionList)) - } - } + .sheet(item: $store.scope(state: \.destination?.homeFilter, action: \.destination.homeFilter)) { homeFilterStore in + HomeFilterView( + store: homeFilterStore, + selectItem: $store.selectedItem, + closeModalAction: { data in + guard let homeFilter = homeFilterStore.homeFilterTypeState else { return } + store.isFilterQuestion = true + + switch homeFilter { + case .job: + store.send(.async(.jobFilterSelected(job: data))) + store.send(.view(.closeFilterModal)) + case .generation: + store.send(.async(.generationFilterSelected(generation:data))) + store.send(.view(.closeFilterModal)) + case .sorted(let sortedEnum): + let sortedEnumFromData = QuestionSort.fromKoreanString(data) + store.send(.async(.sortedFilterSelected(sortedEnum: sortedEnumFromData))) + store.send(.view(.closeFilterModal)) + } + + }) + .presentationDetents([.fraction(homeFilterStore.homeFilterTypeState == .job ? 0.7 : homeFilterStore.homeFilterTypeState == .generation ? 0.42: 0.2 )]) + .presentationDragIndicator(.visible) + .presentationCornerRadius(20) } -} - - -extension HomeView { - @ViewBuilder - private func navigationBaritem() -> some View { - LazyVStack { - Spacer() - .frame(height: 22) + .popup(item: $store.scope(state: \.destination?.customPopUp, action: \.destination.customPopUp)) { customPopUp in + if store.userInfoModel?.isLogOut == true || store.userInfoModel?.isLookAround == true || store.userInfoModel?.isDeleteUser == true { + CustomBasicPopUpView( + store: customPopUp, + title: "로그인 하시겠어요?") { + store.send(.view(.closePopUp)) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + store.send(.navigation(.presntLogin)) + } - HStack(spacing: 8) { - Spacer() - - RightImageButton(isActive: $store.isActivateJobButton, action: { - store.send(.view(.filterViewTappd(.job))) - }, title: store.selectedJobButtonTitle) - .frame(maxHeight: .infinity) - - RightImageButton(isActive: $store.isActivateGenerationButton, action: { - store.send(.view(.filterViewTappd(.generation))) - }, title: store.selectedGenerationButtonTitle) - .frame(maxHeight: .infinity) - - RightImageButton(isActive: $store.isActivateSortedButton, action: { - store.send(.view(.filterViewTappd(.sorted(.popular)))) - }, title: store.selectedSortedButtonTitle.sortedKoreanString) - .frame(maxHeight: .infinity) - - if store.userInfoModel?.isLogOut == true || - store.userInfoModel?.isLookAround == true || - store.userInfoModel?.isDeleteUser == true { - Circle() - .fill(Color.gray500) - .frame(width: 40, height: 40) - .overlay { - VStack{ - Image(systemName: store.profileImage) - .resizable() - .scaledToFit() - .frame(width: 15, height: 16) - .foregroundStyle(Color.gray200) - } - } - .onTapGesture { - store.send(.view(.prsentCustomPopUp)) - } - } else { - Circle() - .fill(Color.gray500) - .frame(width: 40, height: 40) - .overlay { - VStack{ - Image(systemName: store.profileImage) - .resizable() - .scaledToFit() - .frame(width: 15, height: 16) - .foregroundStyle(Color.gray200) - - } - } - .onTapGesture { - store.send(.navigation(.presntProfile)) - } - } + } cancelAction: { + store.send(.view(.closePopUp)) + } + } + else if store.isTapBlockUser == true { + CustomBasicPopUpView( + store: customPopUp, + title: store.customPopUpText) { + store.send(.view(.closePopUp)) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + store.send(.async(.blockUser(qusetionID: store.questionID ?? .zero, userID: store.userID ?? ""))) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + store.send(.async(.appearData)) + } } - .padding(.horizontal, 16) - .fixedSize(horizontal: false, vertical: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) - } + } cancelAction: { + store.send(.view(.closePopUp)) + } + } + else if store.isReportQuestionPopUp == true { + CustomBasicPopUpView( + store: customPopUp, + title: store.customPopUpText) { + store.send(.view(.closePopUp)) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + + store.send(.navigation(.presntReport)) + } + } cancelAction: { + store.send(.view(.closePopUp)) + } + } + } customize: { popup in + popup + .type(.floater(verticalPadding: UIScreen.screenHeight * 0.35)) + .position(.bottom) + .animation(.spring) + .closeOnTap(true) + .closeOnTapOutside(true) + .backgroundColor(Color.basicBlack.opacity(0.8)) } - private func appearFloatingPopUp() { - if store.userInfoModel?.isLogOut == true { - store.send(.view(.presntFloatintPopUp)) - store.floatingText = "로그아웃 되었어요" - store.send(.view(.timeToCloseFloatingPopUp)) - } else if store.userInfoModel?.isLookAround == true { - store.floatingText = "로그인 하시겠어요?" - } else if store.userInfoModel?.isDeleteUser == true { - store.send(.view(.presntFloatintPopUp)) - store.floatingText = "탈퇴 완료! 언젠가 다시 만나요" - store.send(.view(.timeToCloseFloatingPopUp)) - } else if store.userInfoModel?.isChangeProfile == true { - store.send(.view(.presntFloatintPopUp)) - store.floatingText = "수정이 완료되었어요!" - store.send(.view(.timeToCloseFloatingPopUp)) - store.userInfoModel?.isChangeProfile = false - } else if store.userInfoModel?.isCreateQuestion == true { - store.send(.view(.presntFloatintPopUp)) - store.floatingText = "고민 등록이 완료 되었어요!" - store.send(.view(.timeToCloseFloatingPopUp)) - store.userInfoModel?.isCreateQuestion = false - } else if store.userInfoModel?.isDeleteQuestion == true { - store.send(.view(.presntFloatintPopUp)) - store.floatingText = "고민이 삭제되었어요!" - store.send(.view(.timeToCloseFloatingPopUp)) - store.userInfoModel?.isDeleteQuestion = false - } else if store.userInfoModel?.isReportQuestion == true { - store.send(.view(.presntFloatintPopUp)) - store.floatingText = "신고가 완료 되었어요" - store.floatingImage = .warning - store.send(.view(.timeToCloseFloatingPopUp)) - store.userInfoModel?.isReportQuestion = false - } else { - store.floatingText = "로그인 하시겠어요?" + .popup(item: $store.scope(state: \.destination?.floatingPopUP, action: \.destination.floatingPopUP)) { floatingPopUpStore in + FloatingPopUpView(store: floatingPopUpStore, title: store.floatingText, image: store.floatingImage) + } customize: { popup in + popup + .type(.floater(verticalPadding: UIScreen.screenHeight * 0.02)) + .position(.bottom) + .animation(.spring) + .closeOnTap(true) + .closeOnTapOutside(true) + } + + .popup(item: $store.scope(state: \.destination?.updatePopUp, action: \.destination.updatePopUp)) { updatePopUpStore in + CustomBasicPopUpView( + store: updatePopUpStore, + title: "업데이트 해주세요", + confirmAction: { + store.send(.view(.forceUpate)) + }, + cancelAction: { + store.send(.view(.closePopUp)) } + ) + } customize: { popup in + popup + .type(.floater(verticalPadding: UIScreen.screenHeight * 0.35)) + .position(.bottom) + .animation(.spring) + .closeOnTap(true) + .closeOnTapOutside(true) + .backgroundColor(Color.basicBlack.opacity(0.8)) } - @ViewBuilder - private func writeQuestionButton() -> some View { - RoundedRectangle(cornerRadius: 20) - .fill(Color.basicWhite) - .frame(width: 120, height: 56) - .clipShape(Capsule()) + } + + private func startRefreshData() { + refreshTimer?.invalidate() + + if !store.isFilterQuestion { + refreshTimer = Timer.scheduledTimer(withTimeInterval: 300, repeats: true) { _ in + store.send(.async(.fetchQuestionList)) + store.lastViewedPage = store.lastViewedPage + } + } + } +} + + +extension HomeView { + + @ViewBuilder + private func navigationBaritem() -> some View { + LazyVStack { + Spacer() + .frame(height: 22) + + HStack(spacing: 8) { + Spacer() + + RightImageButton(isActive: $store.isActivateJobButton, action: { + store.send(.view(.filterViewTappd(.job))) + }, title: store.selectedJobButtonTitle) + .frame(maxHeight: .infinity) + + RightImageButton(isActive: $store.isActivateGenerationButton, action: { + store.send(.view(.filterViewTappd(.generation))) + }, title: store.selectedGenerationButtonTitle) + .frame(maxHeight: .infinity) + + RightImageButton(isActive: $store.isActivateSortedButton, action: { + store.send(.view(.filterViewTappd(.sorted(.popular)))) + }, title: store.selectedSortedButtonTitle.sortedKoreanString) + .frame(maxHeight: .infinity) + + if store.userInfoModel?.isLogOut == true || + store.userInfoModel?.isLookAround == true || + store.userInfoModel?.isDeleteUser == true { + Circle() + .fill(Color.gray500) + .frame(width: 40, height: 40) .overlay { - Text("글쓰기") - .pretendardFont(family: .Bold, size: 20) - .foregroundStyle(Color.textColor100) + VStack{ + Image(systemName: store.profileImage) + .resizable() + .scaledToFit() + .frame(width: 15, height: 16) + .foregroundStyle(Color.gray200) + } } .onTapGesture { - if store.userInfoModel?.isLogOut == false && - store.userInfoModel?.isLookAround == false && - store.userInfoModel?.isDeleteUser == false { - store.send(.navigation(.presntWriteQuestion)) - } else { - store.send(.view(.prsentCustomPopUp)) - } + store.send(.view(.prsentCustomPopUp)) } - .padding(.bottom, 16) - - } - - @ViewBuilder - private func questionLIstView() -> some View { - VStack { - Spacer() - .frame(height: 15) - - if store.questionModel?.data?.results == [] || ((store.questionModel?.data?.results?.isEmpty) == nil) { - Spacer() - .frame(height: 16) - - noQuestionCardView() - - } else { - Spacer() - .frame(height: 16) - - qustionCardView() + } else { + Circle() + .fill(Color.gray500) + .frame(width: 40, height: 40) + .overlay { + VStack{ + Image(systemName: store.profileImage) + .resizable() + .scaledToFit() + .frame(width: 15, height: 16) + .foregroundStyle(Color.gray200) + } + } + .onTapGesture { + store.send(.navigation(.presntProfile)) } } + } + .padding(.horizontal, 16) + .fixedSize(horizontal: false, vertical: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) } + } + + private func appearFloatingPopUp() { + if store.userInfoModel?.isLogOut == true { + store.send(.view(.presntFloatintPopUp)) + store.floatingText = "로그아웃 되었어요" + store.send(.view(.timeToCloseFloatingPopUp)) + } else if store.userInfoModel?.isLookAround == true { + store.floatingText = "로그인 하시겠어요?" + } else if store.userInfoModel?.isDeleteUser == true { + store.send(.view(.presntFloatintPopUp)) + store.floatingText = "탈퇴 완료! 언젠가 다시 만나요" + store.send(.view(.timeToCloseFloatingPopUp)) + } else if store.userInfoModel?.isChangeProfile == true { + store.send(.view(.presntFloatintPopUp)) + store.floatingText = "수정이 완료되었어요!" + store.send(.view(.timeToCloseFloatingPopUp)) + store.userInfoModel?.isChangeProfile = false + } else if store.userInfoModel?.isCreateQuestion == true { + store.send(.view(.presntFloatintPopUp)) + store.floatingText = "고민 등록이 완료 되었어요!" + store.send(.view(.timeToCloseFloatingPopUp)) + store.userInfoModel?.isCreateQuestion = false + } else if store.userInfoModel?.isDeleteQuestion == true { + store.send(.view(.presntFloatintPopUp)) + store.floatingText = "고민이 삭제되었어요!" + store.send(.view(.timeToCloseFloatingPopUp)) + store.userInfoModel?.isDeleteQuestion = false + } else if store.userInfoModel?.isReportQuestion == true { + store.send(.view(.presntFloatintPopUp)) + store.floatingText = "신고가 완료 되었어요" + store.floatingImage = .warning + store.send(.view(.timeToCloseFloatingPopUp)) + store.userInfoModel?.isReportQuestion = false + } else { + store.floatingText = "로그인 하시겠어요?" + } + } + + @ViewBuilder + private func writeQuestionButton() -> some View { + RoundedRectangle(cornerRadius: 20) + .fill(Color.basicWhite) + .frame(width: 120, height: 56) + .clipShape(Capsule()) + .overlay { + Text("글쓰기") + .pretendardFont(family: .Bold, size: 20) + .foregroundStyle(Color.textColor100) + } + .onTapGesture { + if store.userInfoModel?.isLogOut == false && + store.userInfoModel?.isLookAround == false && + store.userInfoModel?.isDeleteUser == false { + store.send(.navigation(.presntWriteQuestion)) + } else { + store.send(.view(.prsentCustomPopUp)) + } + } + .padding(.bottom, 16) - + } + + @ViewBuilder + private func questionLIstView() -> some View { + VStack { + Spacer() + .frame(height: 15) + + if store.questionModel?.data?.content == [] || ((store.questionModel?.data?.content?.isEmpty) == nil) { + Spacer() + .frame(height: 16) + + noQuestionCardView() + + } else { + Spacer() + .frame(height: 16) + + qustionCardView() + + } + } + } + + @ViewBuilder private func qustionCardView() -> some View { if store.userInfoModel?.isLogOut == true { @@ -334,217 +359,213 @@ extension HomeView { @ViewBuilder private func filterQuestionList() -> some View { - if let resultData = store.questionModel?.data?.results?.filter({ item in - let blockedNicknames = store.userBlockListModel?.data?.compactMap { $0.nickname } ?? [] - guard let nickname = item.userInfo?.userNickname else { return true } - return !blockedNicknames.contains(nickname) + if let resultData = store.questionModel?.data?.content?.filter({ item in + let blockedNicknames = store.userBlockListModel?.data.compactMap { $0.nickname } ?? [] + guard let nickname = item.userInfo?.userNickname else { return true } + return !blockedNicknames.contains(nickname) }) { - FlippableCardView( - data: resultData, - shouldSaveState: true - ) { - if !store.isFilterQuestion { - store.send(.async(.fetchQuestionList)) - } - } onItemAppear: { item in - if let resultItem = item as? ResultData, resultItem.id != store.questionID { - store.questionID = resultItem.id ?? 0 - } - } content: { item in - CardItemView( - resultData: item, - statsData: store.statusQuestionModel?.data, - isProfile: false, - userLoginID: store.profileUserModel?.data?.socialID ?? "", - generationColor: store.cardGenerationColor, - isTapAVote: $store.isTapAVote, - isTapBVote: $store.isTapBVote, - isLogOut: store.userInfoModel?.isLogOut ?? false, - isLookAround: store.userInfoModel?.isLookAround ?? false, - isDeleteUser: store.userInfoModel?.isDeleteUser ?? false, - answerRatio: (A: Int(item.answerRatio?.a ?? 0), B: Int(item.answerRatio?.b ?? 0)), - editTapAction: { - handleEditTap(item: item) - }, - likeTapAction: { userID in - handleLikeTap(userID: userID) - }, - appearStatusAction: { - }, - choiceTapAction: { - handleChoiceTap(item: item) - } - ) - .onChange(of: store.questionID ?? .zero) { oldValue, newValue in - guard let id = item.id, id == newValue else { return } - store.send(.async(.statusQuestion(id: newValue))) - } + FlippableCardView( + data: resultData, + shouldSaveState: true + ) { + if !store.isFilterQuestion { + store.send(.async(.fetchQuestionList)) + } + } onItemAppear: { item in + if let resultItem = item as? QuestionContentData, resultItem.id != store.questionID { + store.questionID = resultItem.id + } + } content: { item in + CardItemView( + resultData: item, + statsData: store.statusQuestionModel?.data, + isProfile: false, + userLoginID: store.profileUserModel?.data.socialID ?? "", + generationColor: store.cardGenerationColor, + isTapAVote: $store.isTapAVote, + isTapBVote: $store.isTapBVote, + isLogOut: store.userInfoModel?.isLogOut ?? false, + isLookAround: store.userInfoModel?.isLookAround ?? false, + isDeleteUser: store.userInfoModel?.isDeleteUser ?? false, + answerRatio: (A: Int(item.answerRatio?.answerRatioA ?? 0), B: Int(item.answerRatio?.answerRatioB ?? 0)), + editTapAction: { + handleEditTap(item: item) + }, + likeTapAction: { userID in + handleLikeTap(userID: userID) + }, + appearStatusAction: { + }, + choiceTapAction: { + handleChoiceTap(item: item) + } + ) + .onChange(of: store.questionID ?? .zero) { oldValue, newValue in + store.send(.async(.statusQuestion(id: newValue))) } + } } } @ViewBuilder private func noFilterQuestionList() -> some View { - if let resultData = store.questionModel?.data?.results { - FlippableCardView( - data: resultData, - shouldSaveState: true) { - if !store.isFilterQuestion { - store.send(.async(.fetchQuestionList)) - } - } onItemAppear: { item in - if let resultItem = item as? ResultData, resultItem.id != store.questionID { - store.questionID = resultItem.id ?? 0 - } - } content: { item in - CardItemView( - resultData: item, - statsData: store.statusQuestionModel?.data, - isProfile: false, - userLoginID: store.profileUserModel?.data?.socialID ?? "", - generationColor: store.cardGenerationColor, - isTapAVote: $store.isTapAVote, - isTapBVote: $store.isTapBVote, - isLogOut: store.userInfoModel?.isLogOut ?? false, - isLookAround: store.userInfoModel?.isLookAround ?? false, - isDeleteUser: store.userInfoModel?.isDeleteUser ?? false, - answerRatio: (A: Int(item.answerRatio?.a ?? 0), B: Int(item.answerRatio?.b ?? 0)), - editTapAction: { - handleEditTap(item: item) - }, - likeTapAction: { userID in - handleLikeTap(userID: userID) - }, - appearStatusAction: { - - }, - choiceTapAction: { - handleChoiceTap(item: item) - } - ) -// .onAppear { -// store.send(.async(.statusQuestion(id: item.id ?? .zero))) -// } - - .onChange(of: store.questionID ?? .zero) { oldValue, newValue in - guard let id = item.id, id == newValue else { return } - store.send(.async(.statusQuestion(id: newValue))) - } + if let resultData = store.questionModel?.data?.content { + FlippableCardView( + data: resultData, + shouldSaveState: true + ) { + if !store.isFilterQuestion { + store.send(.async(.fetchQuestionList)) + } + } onItemAppear: { item in + if let resultItem = item as? QuestionContentData, resultItem.id != store.questionID { + store.questionID = resultItem.id + } + } content: { item in + CardItemView( + resultData: item, + statsData: store.statusQuestionModel?.data, + isProfile: false, + userLoginID: store.profileUserModel?.data.socialID ?? "", + generationColor: store.cardGenerationColor, + isTapAVote: $store.isTapAVote, + isTapBVote: $store.isTapBVote, + isLogOut: store.userInfoModel?.isLogOut ?? false, + isLookAround: store.userInfoModel?.isLookAround ?? false, + isDeleteUser: store.userInfoModel?.isDeleteUser ?? false, + answerRatio: ( + A: Int(item.answerRatio?.answerRatioA ?? .zero), + B: Int(item.answerRatio?.answerRatioB ?? .zero) + ), + editTapAction: { + handleEditTap(item: item) + }, + likeTapAction: { userID in + handleLikeTap(userID: userID) + }, + appearStatusAction: {}, + choiceTapAction: { + handleChoiceTap(item: item) } + ) + .onChange(of: store.questionID ?? .zero) { oldValue, newValue in + store.send(.async(.statusQuestion(id: newValue))) + } } + } } - - - - private func handleChoiceTap(item: ResultData) { - if store.isTapAVote { - if item.metadata?.voted == true { - store.questionID = item.id ?? .zero - store.send(.async(.statusQuestion(id: item.id ?? .zero))) - } else if store.profileUserModel?.data?.socialID == item.userInfo?.userID { - store.questionID = item.id ?? .zero - store.isTapAVote = false - } else if store.profileUserModel?.data?.socialID != item.userInfo?.userID { - store.send(.async(.isVoteQuestionAnswer(questionID: item.id ?? .zero, choiceAnswer: store.isSelectAnswerA))) - store.send(.async(.fetchQuestionList)) - store.questionID = item.id ?? .zero - store.send(.async(.statusQuestion(id: item.id ?? .zero))) - } - } else if store.isTapBVote { - if item.metadata?.voted == true { - store.questionID = item.id ?? .zero - store.send(.async(.statusQuestion(id: store.questionID ?? .zero))) - } else if store.profileUserModel?.data?.socialID == item.userInfo?.userID { - store.questionID = item.id ?? .zero - store.isTapBVote = false - } else if store.profileUserModel?.data?.socialID != item.userInfo?.userID{ - store.send(.async(.isVoteQuestionAnswer(questionID: item.id ?? .zero, choiceAnswer: store.isSelectAnswerB))) - store.send(.async(.fetchQuestionList)) - store.questionID = item.id ?? .zero - store.send(.async(.statusQuestion(id: item.id ?? .zero))) - } - } else { - store.send(.view(.prsentCustomPopUp)) - } + + + + private func handleChoiceTap(item: QuestionContentData) { + if store.isTapAVote { + if item.metadata?.voted == true { + store.questionID = item.id + store.send(.async(.statusQuestion(id: item.id))) + } else if store.profileUserModel?.data.socialID == item.userInfo?.userID { + store.questionID = item.id + store.isTapAVote = false + } else if store.profileUserModel?.data.socialID != item.userInfo?.userID { + store.send(.async(.isVoteQuestionAnswer(questionID: item.id, choiceAnswer: store.isSelectAnswerA))) + store.send(.async(.fetchQuestionList)) + store.questionID = item.id + store.send(.async(.statusQuestion(id: item.id))) + } + } else if store.isTapBVote { + if item.metadata?.voted == true { + store.questionID = item.id + store.send(.async(.statusQuestion(id: store.questionID ?? .zero))) + } else if store.profileUserModel?.data.socialID == item.userInfo?.userID { + store.questionID = item.id + store.isTapBVote = false + } else if store.profileUserModel?.data.socialID != item.userInfo?.userID{ + store.send(.async(.isVoteQuestionAnswer(questionID: item.id, choiceAnswer: store.isSelectAnswerB))) + store.send(.async(.fetchQuestionList)) + store.questionID = item.id + store.send(.async(.statusQuestion(id: item.id))) + } + } else { + store.send(.view(.prsentCustomPopUp)) } - - private func handleEditTap(item: ResultData) { - if store.userInfoModel?.isLogOut == true || - store.userInfoModel?.isLookAround == true || - store.userInfoModel?.isDeleteUser == true { - store.send(.view(.prsentCustomPopUp)) - } else { - store.userID = item.userInfo?.userID ?? "" - store.reportQuestionID = item.id ?? .zero - store.questionID = item.id ?? .zero - store.send(.view(.presntEditQuestion)) - } + } + + private func handleEditTap(item: QuestionContentData) { + if store.userInfoModel?.isLogOut == true || + store.userInfoModel?.isLookAround == true || + store.userInfoModel?.isDeleteUser == true { + store.send(.view(.prsentCustomPopUp)) + } else { + store.userID = item.userInfo?.userID ?? "" + store.reportQuestionID = item.id + store.questionID = item.id + store.send(.view(.presntEditQuestion)) } - - private func handleLikeTap(userID: String) { - if store.userInfoModel?.isLogOut == true || - store.userInfoModel?.isLookAround == true || - store.userInfoModel?.isDeleteUser == true { - store.send(.view(.prsentCustomPopUp)) - } else { - store.send(.async(.isVoteQuestionLike(questioniD: Int(userID) ?? .zero))) - store.send(.async(.fetchQuestionList)) - } + } + + private func handleLikeTap(userID: String) { + if store.userInfoModel?.isLogOut == true || + store.userInfoModel?.isLookAround == true || + store.userInfoModel?.isDeleteUser == true { + store.send(.view(.prsentCustomPopUp)) + } else { + store.send(.async(.isVoteQuestionLike(questioniD: Int(userID) ?? .zero))) + store.send(.async(.fetchQuestionList)) } - - private func appearStatusActionIfNeeded(item: ResultData) { - if store.questionID != item.id { - store.questionID = item.id ?? .zero - } + } + + private func appearStatusActionIfNeeded(item: QuestionContentData) { + if store.questionID != item.id { + store.questionID = item.id } - - @ViewBuilder - private func noQuestionCardView() -> some View { - VStack { - Spacer() - - Image(asset: .questonSmail) - .resizable() - .scaledToFit() - .frame(width: 80, height: 80) - - Spacer() - .frame(height: 16) - - Text("조건에 맞는 고민이 없어요") - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.gray200) - - Spacer() - .frame(height: 16) - - Text("직접 고민을 등록해보세요") - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(Color.gray300) - - Spacer() - .frame(height: 32) - - RoundedRectangle(cornerRadius: 20) - .fill(Color.basicWhite) - .frame(width: 120, height: 56) - .clipShape(Capsule()) - .overlay { - Text("글쓰기") - .pretendardFont(family: .Medium, size: 20) - .foregroundStyle(Color.textColor100) - } - .onTapGesture { - if store.userInfoModel?.isLogOut == false && - store.userInfoModel?.isLookAround == false && - store.userInfoModel?.isDeleteUser == false { - store.send(.navigation(.presntWriteQuestion)) - } else { - store.send(.view(.prsentCustomPopUp)) - } - } - - Spacer() + } + + @ViewBuilder + private func noQuestionCardView() -> some View { + VStack { + Spacer() + + Image(asset: .questonSmail) + .resizable() + .scaledToFit() + .frame(width: 80, height: 80) + + Spacer() + .frame(height: 16) + + Text("조건에 맞는 고민이 없어요") + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.gray200) + + Spacer() + .frame(height: 16) + + Text("직접 고민을 등록해보세요") + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(Color.gray300) + + Spacer() + .frame(height: 32) + + RoundedRectangle(cornerRadius: 20) + .fill(Color.basicWhite) + .frame(width: 120, height: 56) + .clipShape(Capsule()) + .overlay { + Text("글쓰기") + .pretendardFont(family: .Medium, size: 20) + .foregroundStyle(Color.textColor100) + } + .onTapGesture { + if store.userInfoModel?.isLogOut == false && + store.userInfoModel?.isLookAround == false && + store.userInfoModel?.isDeleteUser == false { + store.send(.navigation(.presntWriteQuestion)) + } else { + store.send(.view(.prsentCustomPopUp)) + } } + + Spacer() } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Home/Report/Reducer/Report.swift b/OPeace/Projects/Presentation/Presentation/Sources/Home/Report/Reducer/Report.swift index 633d691..4adae2b 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Home/Report/Reducer/Report.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Home/Report/Reducer/Report.swift @@ -13,122 +13,148 @@ import Networkings @Reducer public struct Report { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { - @ObservableState - public struct State: Equatable { - - var reportReasonText: String = "" - var reportButtonComplete: String = "완료" - var reportQuestionModel: ReportQuestionModel? = nil - - @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - @Shared var questionID: Int - - public init( - questionID: Int = 0 - ) { - self._questionID = Shared(wrappedValue: questionID, .inMemory("questionID")) - } - } + var reportReasonText: String = "" + var reportButtonComplete: String = "완료" + var reportQuestionModel: FlagQuestionDTOModel? = nil - public enum Action: ViewAction, BindableAction, FeatureAction { - case binding(BindingAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - } + @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() + @Shared var questionID: Int - //MARK: - ViewAction - @CasePathable - public enum View { - + public init( + questionID: Int = 0 + ) { + self._questionID = Shared(wrappedValue: questionID, .inMemory("questionID")) } + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case binding(BindingAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + //MARK: - ViewAction + @CasePathable + public enum View { - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case reportQuestion(questionID: Int, reason: String) - case reportQuestionResponse(Result) - } + } + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case reportQuestion(questionID: Int, reason: String) + case reportQuestionResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - - } + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntMainHome - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntMainHome + } + + @Dependency(QuestionUseCase.self) var questionUseCase + @Dependency(\.continuousClock) var clock + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(\.reportReasonText): + return .none + + case .binding(_): + return .none + + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } } - - @Dependency(QuestionUseCase.self) var questionUseCase - @Dependency(\.continuousClock) var clock - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .binding(\.reportReasonText): - return .none - - case .binding(_): - return .none - - - case .view(let View): - switch View { - - } - - case .async(let AsyncAction): - switch AsyncAction { - - case .reportQuestion(questionID: let questionID, reason: let reason): - return .run { send in - let reportQuestionResult = await Result { - try await questionUseCase.reportQuestion(questionID: questionID, reason: reason) - } - - switch reportQuestionResult { - case .success(let reportQuestionResultData): - if let reportQuestionResultData = reportQuestionResultData { - await send(.async(.reportQuestionResponse(.success(reportQuestionResultData)))) - - try await clock.sleep(for: .seconds(1)) - - await send(.navigation(.presntMainHome)) - } - case .failure(let error): - await send(.async(.reportQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) - } - - } - - case .reportQuestionResponse(let result): - switch result { - case .success(let reportQuestionResult): - state.reportQuestionModel = reportQuestionResult - state.userInfoModel?.isReportQuestion = true - case .failure(let error): - Log.error("질문 신고 실패", error.localizedDescription) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - - case .presntMainHome: - return .none - } - } + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .reportQuestion(questionID: let questionID, reason: let reason): + return .run { send in + let reportQuestionResult = await Result { + try await questionUseCase.reportQuestion(questionID: questionID, reason: reason) } + + switch reportQuestionResult { + case .success(let reportQuestionResultData): + if let reportQuestionResultData = reportQuestionResultData { + await send(.async(.reportQuestionResponse(.success(reportQuestionResultData)))) + + try await clock.sleep(for: .seconds(1)) + + await send(.navigation(.presntMainHome)) + } + case .failure(let error): + await send(.async(.reportQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) + } + + } + + case .reportQuestionResponse(let result): + switch result { + case .success(let reportQuestionResult): + state.reportQuestionModel = reportQuestionResult + state.userInfoModel?.isReportQuestion = true + case .failure(let error): + Log.error("질문 신고 실패", error.localizedDescription) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntMainHome: + return .none } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/Reducer/BlockUser.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/Reducer/BlockUser.swift index e3f6a48..762d9d6 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/Reducer/BlockUser.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/Reducer/BlockUser.swift @@ -16,166 +16,193 @@ import Networkings @Reducer public struct BlockUser { + public init() {} + + @ObservableState + public struct State: Equatable { public init() {} + var userBlockListModel: UserBlockListDTOModel? = nil + var realseUserBlocModel: UserBlockDTOModel? = nil - @ObservableState - public struct State: Equatable { - public init() {} - var userBlockListModel: UserBlockListModel? = nil - var realseUserBlocModel: UserBlockModel? = nil - - @Presents var destination: Destination.State? - } - - public enum Action: ViewAction, BindableAction, FeatureAction { - case binding(BindingAction) - case destination(PresentationAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - } + @Presents var destination: Destination.State? + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case binding(BindingAction) + case destination(PresentationAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + @Reducer(state: .equatable) + public enum Destination { + case floatingPopUP(FloatingPopUp) + } + + //MARK: - ViewAction + @CasePathable + public enum View { + case presntFloatintPopUp + case closePopUp + case timeToCloseFloatingPopUp + } + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case fetchUserBlockList + case fetchUserBlockResponse(Result) + case realseUserBlock(blockUserID: String) + case realseUserBlockResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - @Reducer(state: .equatable) - public enum Destination { - case floatingPopUP(FloatingPopUp) - } + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntMainHome - //MARK: - ViewAction - @CasePathable - public enum View { - case presntFloatintPopUp - case closePopUp - case timeToCloseFloatingPopUp + } + + @Dependency(AuthUseCase.self) var authUseCase + @Dependency(\.continuousClock) var clock + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(_): + return .none + + case .destination(_): + return .none + + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } } - - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case fetchUserBlockList - case fetchUserBlockResponse(Result) - case realseUserBlock(blockUserID: String) - case realseUserBlockResponse(Result) + .ifLet(\.$destination, action: \.destination) + .onChange(of: \.userBlockListModel) { oldValue, newValue in + Reduce { state, action in + state.userBlockListModel = newValue + return .none + } } - - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .presntFloatintPopUp: + state.destination = .floatingPopUP(.init()) + return .none + + case .closePopUp: + state.destination = nil + return .none + + case .timeToCloseFloatingPopUp: + return .run { send in + try await clock.sleep(for: .seconds(1.5)) + await send(.view(.closePopUp)) + } } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntMainHome + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .fetchUserBlockList: + return .run { @MainActor send in + let userBlockResult = await Result { + try await authUseCase.fetchUserBlockList() + } - } - - @Dependency(AuthUseCase.self) var authUseCase - @Dependency(\.continuousClock) var clock - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .binding(_): - return .none - - case .destination(_): - return .none - - case .view(let View): - switch View { - case .presntFloatintPopUp: - state.destination = .floatingPopUP(.init()) - return .none - - case .closePopUp: - state.destination = nil - return .none - - case .timeToCloseFloatingPopUp: - return .run { send in - try await clock.sleep(for: .seconds(1.5)) - await send(.view(.closePopUp)) - } - } - - case .async(let AsyncAction): - switch AsyncAction { - - case .fetchUserBlockList: - return .run { @MainActor send in - let userBlockResult = await Result { - try await authUseCase.fetchUserBlockList() - } - - switch userBlockResult { - case .success(let userBlockLIstData): - if let userBlockListData = userBlockLIstData { - send(.async(.fetchUserBlockResponse(.success(userBlockListData)))) - } - - case .failure(let error): - send(.async(.fetchUserBlockResponse(.failure(CustomError.userError(error.localizedDescription))))) - } - } - - case .fetchUserBlockResponse(let result): - switch result { - case .success(let userBlockListData): - state.userBlockListModel = userBlockListData - case .failure(let error): - Log.error("차단 유저 가져오기 실패", error.localizedDescription) - } - return .none - - case .realseUserBlock(let blockUserID): - return .run { @MainActor send in - let realseUserBlockResult = await Result { - try await authUseCase.realseUserBlock(userID: blockUserID) - } - - switch realseUserBlockResult { - case .success(let realseUserBlockResultData): - if let realseUserBlockResultData = realseUserBlockResultData { - send(.async(.realseUserBlockResponse(.success(realseUserBlockResultData)))) - - try await clock.sleep(for: .seconds(0.3)) - send(.view(.presntFloatintPopUp)) - - send(.async(.fetchUserBlockList)) - } - case .failure(let error): - send(.async(.realseUserBlockResponse(.failure(CustomError.userError(error.localizedDescription))))) - } - } - - case .realseUserBlockResponse(let result): - switch result { - case .success(let realseUserBlockResult): - state.realseUserBlocModel = realseUserBlockResult - case .failure(let error): - Log.error("유저 차단 해제 에러", error.localizedDescription) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - case .presntMainHome: - return .none - } - } + switch userBlockResult { + case .success(let userBlockLIstData): + if let userBlockListData = userBlockLIstData { + send(.async(.fetchUserBlockResponse(.success(userBlockListData)))) + } + + case .failure(let error): + send(.async(.fetchUserBlockResponse(.failure(CustomError.userError(error.localizedDescription))))) + } + } + + case .fetchUserBlockResponse(let result): + switch result { + case .success(let userBlockListData): + state.userBlockListModel = userBlockListData + case .failure(let error): + Log.error("차단 유저 가져오기 실패", error.localizedDescription) + } + return .none + + case .realseUserBlock(let blockUserID): + return .run { @MainActor send in + let realseUserBlockResult = await Result { + try await authUseCase.realseUserBlock(userID: blockUserID) } - .ifLet(\.$destination, action: \.destination) - .onChange(of: \.userBlockListModel) { oldValue, newValue in - Reduce { state, action in - state.userBlockListModel = newValue - return .none - } + + switch realseUserBlockResult { + case .success(let realseUserBlockResultData): + if let realseUserBlockResultData = realseUserBlockResultData { + send(.async(.realseUserBlockResponse(.success(realseUserBlockResultData)))) + + try await clock.sleep(for: .seconds(0.3)) + send(.view(.presntFloatintPopUp)) + + send(.async(.fetchUserBlockList)) + } + case .failure(let error): + send(.async(.realseUserBlockResponse(.failure(CustomError.userError(error.localizedDescription))))) } + } + + case .realseUserBlockResponse(let result): + switch result { + case .success(let realseUserBlockResult): + state.realseUserBlocModel = realseUserBlockResult + case .failure(let error): + Log.error("유저 차단 해제 에러", error.localizedDescription) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntMainHome: + return .none } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/View/BlockUserView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/View/BlockUserView.swift index 2df3c61..b0c640b 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/View/BlockUserView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/BlockUser/View/BlockUserView.swift @@ -13,219 +13,219 @@ import PopupView import DesignSystem public struct BlockUserView: View { - @Bindable var store: StoreOf - var backAction: () -> Void = { } - - public init( - store: StoreOf, - backAction: @escaping () -> Void - ) { - self.store = store - self.backAction = backAction - } - - public var body: some View { - ZStack { - Color.gray600 - .edgesIgnoringSafeArea(.all) - - VStack { - Spacer() - .frame(height: 14) - - CustomTitleNaviagionBackButton(buttonAction: backAction, title: "차단 관리") - - blockUserHeader() - - ScrollView(showsIndicators: false) { - userBlockListView() - - } - - Spacer() - } - .onAppear { - store.send(.async(.fetchUserBlockList)) - } - .popup(item: $store.scope(state: \.destination?.floatingPopUP, action: \.destination.floatingPopUP)) { floatingPopUpStore in - FloatingPopUpView(store: floatingPopUpStore, title: "차단이 해제되었어요", image: .succesLogout) - .onAppear{ - store.send(.view(.timeToCloseFloatingPopUp)) - } - } customize: { popup in - popup - .type(.floater(verticalPadding: UIScreen.screenHeight * 0.02)) - .position(.bottom) - .animation(.spring) - .closeOnTap(true) - .closeOnTapOutside(true) - } + @Bindable var store: StoreOf + var backAction: () -> Void = { } + + public init( + store: StoreOf, + backAction: @escaping () -> Void + ) { + self.store = store + self.backAction = backAction + } + + public var body: some View { + ZStack { + Color.gray600 + .edgesIgnoringSafeArea(.all) + + VStack { + Spacer() + .frame(height: 14) + + CustomTitleNaviagionBackButton(buttonAction: backAction, title: "차단 관리") + + blockUserHeader() + + ScrollView(showsIndicators: false) { + userBlockListView() + } + + Spacer() + } + .onAppear { + store.send(.async(.fetchUserBlockList)) + } + .popup(item: $store.scope(state: \.destination?.floatingPopUP, action: \.destination.floatingPopUP)) { floatingPopUpStore in + FloatingPopUpView(store: floatingPopUpStore, title: "차단이 해제되었어요", image: .succesLogout) + .onAppear{ + store.send(.view(.timeToCloseFloatingPopUp)) + } + } customize: { popup in + popup + .type(.floater(verticalPadding: UIScreen.screenHeight * 0.02)) + .position(.bottom) + .animation(.spring) + .closeOnTap(true) + .closeOnTapOutside(true) + } } + } } extension BlockUserView { - - @ViewBuilder - private func blockUserHeader() -> some View { - VStack { - Spacer() - .frame(height: 16) - - HStack { - Text("차단 목록") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(Color.gray200) - - Spacer() - } - } - .padding(.horizontal, 20) + + @ViewBuilder + private func blockUserHeader() -> some View { + VStack { + Spacer() + .frame(height: 16) + + HStack { + Text("차단 목록") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(Color.gray200) + + Spacer() + } + } + .padding(.horizontal, 20) + } + + @ViewBuilder + private func userBlockListView() -> some View { + if store.userBlockListModel?.data == [] { + noBlockUserView() + } else { + blockUserListView() } - - @ViewBuilder - private func userBlockListView() -> some View { - if store.userBlockListModel?.data == [] { - noBlockUserView() - } else { - blockUserListView() + } + + @ViewBuilder + private func blockUserListView() -> some View { + VStack { + Spacer() + .frame(height: 16) + + if let userBlockData = store.userBlockListModel?.data { + ForEach(userBlockData, id: \.blockID) { item in + blockUserListItem( + nickName: item.nickname ?? "", + job: item.job ?? "", + generation: item.generation ?? "", + completion: { + store.send(.async(.realseUserBlock(blockUserID: item.blockedUserID ?? ""))) + }) } + } + } + } + + @ViewBuilder + private func noBlockUserView() -> some View { + VStack(alignment: .center) { + Spacer() + .frame(height: UIScreen.screenHeight * 0.3) + + Text("차단 목록이 비어있어요") + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.gray200) + + Spacer() + .frame(height: 16) + + HStack { + Text("고민 카드 우측 상단에") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(Color.gray200) + + Spacer() + .frame(width: 2) + + Image(asset: .questionEdit) + .resizable() + .scaledToFit() + .frame(width: 18, height: 18) + + Spacer() + .frame(width: 2) + + Text("를 누르면") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(Color.gray200) + } + + Text("차단할 수 있어요") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(Color.gray200) + + Spacer() } - - @ViewBuilder - private func blockUserListView() -> some View { + } + + @ViewBuilder + private func blockUserListItem( + nickName: String, + job: String, + generation: String, + completion: @escaping () -> Void + ) -> some View { + RoundedRectangle(cornerRadius: 16) + .fill(Color.gray500) + .padding(.horizontal, 20) + .frame(height: 72) + .overlay { VStack { + Spacer() + .frame(height: 28) + + HStack(spacing: .zero) { Spacer() - .frame(height: 16) + .frame(width: 16) + + Text(nickName) + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(Color.basicWhite) - if let userBlockData = store.userBlockListModel?.data { - ForEach(userBlockData, id: \.blockID) { item in - blockUserListItem( - nickName: item.nickname ?? "", - job: item.job ?? "", - generation: item.generation ?? "", - completion: { - store.send(.async(.realseUserBlock(blockUserID: item.blockedUserID ?? ""))) - }) - } - } - } - } - - @ViewBuilder - private func noBlockUserView() -> some View { - VStack(alignment: .center) { Spacer() - .frame(height: UIScreen.screenHeight * 0.3) + .frame(width: 8) - Text("차단 목록이 비어있어요") - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.gray200) + Text(job) + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(Color.gray200) Spacer() - .frame(height: 16) + .frame(width: 8) + + Rectangle() + .fill(Color.gray400) + .frame(width: 1, height: 10) - HStack { - Text("고민 카드 우측 상단에") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(Color.gray200) - - Spacer() - .frame(width: 2) - - Image(asset: .questionEdit) - .resizable() - .scaledToFit() - .frame(width: 18, height: 18) - - Spacer() - .frame(width: 2) - - Text("를 누르면") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(Color.gray200) - } + Spacer() + .frame(width: 8) - Text("차단할 수 있어요") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(Color.gray200) + Text(generation) + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(Color.gray200) Spacer() + + RoundedRectangle(cornerRadius: 40) + .stroke(Color.gray400, style: .init(lineWidth: 1)) + .frame(width: 72, height: 32) + .background(Color.gray500) + .clipShape(Capsule()) + .overlay { + Text("차단 해제") + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(Color.gray200) + } + .onTapGesture { + completion() + } + + Spacer() + .frame(width: 16) + + } + .padding(.horizontal, 16) + + + Spacer() + .frame(height: 28) } - } - - @ViewBuilder - private func blockUserListItem( - nickName: String, - job: String, - generation: String, - completion: @escaping () -> Void - ) -> some View { - RoundedRectangle(cornerRadius: 16) - .fill(Color.gray500) - .padding(.horizontal, 20) - .frame(height: 72) - .overlay { - VStack { - Spacer() - .frame(height: 28) - - HStack(spacing: .zero) { - Spacer() - .frame(width: 16) - - Text(nickName) - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(Color.basicWhite) - - Spacer() - .frame(width: 8) - - Text(job) - .pretendardFont(family: .Medium, size: 14) - .foregroundStyle(Color.gray200) - - Spacer() - .frame(width: 8) - - Rectangle() - .fill(Color.gray400) - .frame(width: 1, height: 10) - - Spacer() - .frame(width: 8) - - Text(generation) - .pretendardFont(family: .Medium, size: 14) - .foregroundStyle(Color.gray200) - - Spacer() - - RoundedRectangle(cornerRadius: 40) - .stroke(Color.gray400, style: .init(lineWidth: 1)) - .frame(width: 72, height: 32) - .background(Color.gray500) - .clipShape(Capsule()) - .overlay { - Text("차단 해제") - .pretendardFont(family: .Medium, size: 14) - .foregroundStyle(Color.gray200) - } - .onTapGesture { - completion() - } - - Spacer() - .frame(width: 16) - - } - .padding(.horizontal, 16) - - - Spacer() - .frame(height: 28) - } - } - } + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/Reducer/EditProfile.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/Reducer/EditProfile.swift index 100e9ec..0115197 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/Reducer/EditProfile.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/Reducer/EditProfile.swift @@ -13,257 +13,293 @@ import Utills @Reducer public struct EditProfile { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { - @ObservableState - public struct State: Equatable { - var profileUserModel: UpdateUserInfoModel? - var nickNameModel: CheckNickNameModel? - var editProfileJobModel: SignUpJobModel? - var updateUserinfoModel: UpdateUserInfoModel? - var profileSelectedJob: String? - var profileName: String = "" - var editProfileName: String = "" - var checkNickNameMessage: String = "" - var editProfileGenerationText: String = "" - var editProfileComplete = "완료" - var enableButton: Bool = false - var profileYear: Int = 0 - let paddings: [CGFloat] = [47, 25, 32, 47, 24, 32] - @Presents var destination: Destination.State? - @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - - public init() {} - } - public enum Action: ViewAction, BindableAction, FeatureAction { - case destination(PresentationAction) - case binding(BindingAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - } + var profileSelectedJob: String? + var profileName: String = "" + var editProfileName: String = "" + var checkNickNameMessage: String = "" + var editProfileGenerationText: String = "" + var editProfileComplete = "완료" + var enableButton: Bool = false + var profileYear: Int = 0 + let paddings: [CGFloat] = [47, 25, 32, 47, 24, 32] - @Reducer(state: .equatable) - public enum Destination { - case customPopUp(CustomPopUp) - case floatingPopUP(FloatingPopUp) - - } + @Presents var destination: Destination.State? + @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - //MARK: - ViewAction - @CasePathable - public enum View { - case selectJob(String) - case appearPopUp - case appearFloatingPopUp - case closePopUp - case updateGenerationInfo - } + var profileUserModel: UpdateUserInfoDTOModel? + var nickNameModel: SignUpCheckInfoDTOModel? + var editProfileJobModel: SignUpListDTOModel? + var updateUserinfoModel: UpdateUserInfoDTOModel? - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case fetchUserProfileResponse(Result) - case fetchUser - case checkNickName(nickName: String) - case checkNIckNameResponse(Result) - case editProfileResponse(Result) - case fetchEditProfileJobList - case updateUserInfoResponse(Result) - case updateUserInfo( - nickName: String, - year: Int, - job: String, - generation: String) - } + public init() {} + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case destination(PresentationAction) + case binding(BindingAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + @Reducer(state: .equatable) + public enum Destination { + case customPopUp(CustomPopUp) + case floatingPopUP(FloatingPopUp) - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { - - } + } + + //MARK: - ViewAction + @CasePathable + public enum View { + case selectJob(String) + case appearPopUp + case appearFloatingPopUp + case closePopUp + case updateGenerationInfo + } + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case fetchUserProfileResponse(Result) + case fetchUser + case checkNickName(nickName: String) + case checkNIckNameResponse(Result) + case editProfileResponse(Result) + case fetchEditProfileJobList + case updateUserInfoResponse(Result) + case updateUserInfo( + nickName: String, + year: Int, + job: String, + generation: String) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { + + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { - //MARK: - NavigationAction - public enum NavigationAction: Equatable { + + } + + struct EditProfileCancel: Hashable {} + + @Dependency(AuthUseCase.self) var authUseCase + @Dependency(SignUpUseCase.self) var signUpUseCase + @Dependency(\.continuousClock) var clock + @Dependency(\.mainQueue) var mainQueue + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(\.editProfileName): + return .none + + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + + default: + return .none + } } - - @Dependency(AuthUseCase.self) var authUseCase - @Dependency(SignUpUseCase.self) var signUpUseCase - @Dependency(\.continuousClock) var clock - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .binding(\.editProfileName): - return .none - - case .view(let View): - switch View { - case let .selectJob(job): - if state.profileSelectedJob == job { - state.profileSelectedJob = nil - state.enableButton = false - } else { - state.profileSelectedJob = job - state.enableButton = true - } - return .none - - case .appearPopUp: - state.destination = .customPopUp(.init()) - return .none - - case .appearFloatingPopUp: - state.destination = .floatingPopUP(.init()) - return .none - - case .closePopUp: - state.destination = nil - return .none - - - case .updateGenerationInfo: - let generation = CheckRegister.getGenerationText(year: state.profileYear) - state.editProfileGenerationText = generation - return .none - } - - case .async(let AsyncAction): - switch AsyncAction { - case .fetchUser: - return .run { @MainActor send in - let fetchUserData = await Result { - try await authUseCase.fetchUserInfo() - } - - switch fetchUserData { - case .success(let fetchUserResult): - if let fetchUserResult = fetchUserResult { - send(.async(.fetchUserProfileResponse(.success(fetchUserResult)))) - - send(.view(.updateGenerationInfo)) - - } - case .failure(let error): - send(.async(.fetchUserProfileResponse(.failure(CustomError.map(error))))) - - } - } - - case .fetchUserProfileResponse(let result): - switch result { - case .success(let resultData): - state.profileUserModel = resultData - state.profileName = state.profileUserModel?.data?.nickname ?? "" - state.profileSelectedJob = state.profileUserModel?.data?.job ?? "" - state.profileYear = state.profileUserModel?.data?.year ?? .zero - case let .failure(error): - Log.network("프로필 오류", error.localizedDescription) - } - return .none - - case .checkNickName(nickName: let nickName): - return .run { @MainActor send in - let requset = await Result { - try await signUpUseCase.checkNickName(nickName) - } - - switch requset { - case .success(let result): - if let result = result { - send(.async(.checkNIckNameResponse(.success(result)))) - } - - case .failure(let error): - send(.async(.checkNIckNameResponse(.failure(CustomError.map(error))))) - } - } - - case .checkNIckNameResponse(let result): - switch result { - case .success(let data): - state.nickNameModel = data - state.checkNickNameMessage = state.nickNameModel?.data?.message ?? "" - state.enableButton = state.nickNameModel?.data?.exists ?? false - - case .failure(let error): - Log.network("닉네임 에러", error.localizedDescription) - - } - return .none - - case .editProfileResponse(let result): - switch result { - case .success(let data): - state.editProfileJobModel = data - - case .failure(let error): - Log.network("닉네임 에러", error.localizedDescription) - } - return .none - - case .fetchEditProfileJobList: - return .run { @MainActor send in - let request = await Result { - try await self.signUpUseCase.fetchJobList() - } - - switch request { - case .success(let data): - if let data = data { - send(.async(.editProfileResponse(.success(data)))) - } - - case .failure(let error): - send(.async(.editProfileResponse(.failure(CustomError.map(error))))) - } - } - - case .updateUserInfo(let nickname, let year, let job, let generation): - return .run { @MainActor send in - let userInfoResult = await Result { - try await signUpUseCase.updateUserInfo(nickname: nickname, year: year, job: job, generation: generation) - } - - switch userInfoResult { - case .success(let updateUserInfoData): - if let updateUserInfoData = updateUserInfoData { - send(.async(.updateUserInfoResponse(.success(updateUserInfoData)))) - - } - - case .failure(let error): - send(.async(.updateUserInfoResponse(.failure(CustomError.map(error))))) - } - } - - case .updateUserInfoResponse(let result): - switch result { - case .success(let userInfoData): - state.updateUserinfoModel = userInfoData - case let .failure(error): - Log.error("update user info 리턴 에러", error) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - - } - - default: - return .none - } + .ifLet(\.$destination, action: \.destination) + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case let .selectJob(job): + if state.profileSelectedJob == job { + state.profileSelectedJob = nil + state.enableButton = false + } else { + state.profileSelectedJob = job + state.enableButton = true + } + return .none + + case .appearPopUp: + state.destination = .customPopUp(.init()) + return .none + + case .appearFloatingPopUp: + state.destination = .floatingPopUP(.init()) + return .none + + case .closePopUp: + state.destination = nil + return .none + + case .updateGenerationInfo: + let generation = CheckRegister.getGenerationText(year: state.profileYear) + state.editProfileGenerationText = generation + return .none + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .fetchUser: + return .run { @MainActor send in + let fetchUserData = await Result { + try await authUseCase.fetchUserInfo() + } + + switch fetchUserData { + case .success(let fetchUserResult): + if let fetchUserResult = fetchUserResult { + send(.async(.fetchUserProfileResponse(.success(fetchUserResult)))) + + send(.view(.updateGenerationInfo)) + + } + case .failure(let error): + send(.async(.fetchUserProfileResponse(.failure(CustomError.map(error))))) } - .ifLet(\.$destination, action: \.destination) + } + + case .fetchUserProfileResponse(let result): + switch result { + case .success(let resultData): + state.profileUserModel = resultData + state.profileName = state.profileUserModel?.data.nickname ?? "" + state.profileSelectedJob = state.profileUserModel?.data.job ?? "" + state.profileYear = state.profileUserModel?.data.year ?? .zero + case let .failure(error): + #logNetwork("프로필 오류", error.localizedDescription) + } + return .none + + case .checkNickName(let nickName): + return .run { send in + let requset = await Result { + try await signUpUseCase.checkNickName(nickName) + } + + switch requset { + case .success(let result): + if let result = result { + await send(.async(.checkNIckNameResponse(.success(result)))) + } + + case .failure(let error): + await send(.async(.checkNIckNameResponse(.failure(CustomError.map(error))))) + } + } + .debounce(id: EditProfileCancel(), for: 0.3, scheduler: mainQueue) + + case .checkNIckNameResponse(let result): + switch result { + case .success(let data): + state.nickNameModel = data + state.checkNickNameMessage = state.nickNameModel?.data.message ?? "" + state.enableButton = state.nickNameModel?.data.exists ?? false + + case .failure(let error): + Log.network("닉네임 에러", error.localizedDescription) + + } + return .none + + case .editProfileResponse(let result): + switch result { + case .success(let data): + state.editProfileJobModel = data + + case .failure(let error): + #logNetwork("닉네임 에러", error.localizedDescription) + } + return .none + + case .fetchEditProfileJobList: + return .run { send in + let request = await Result { + try await self.signUpUseCase.fetchJobList() + } + + switch request { + case .success(let data): + if let data = data { + await send(.async(.editProfileResponse(.success(data)))) + } + + case .failure(let error): + await send(.async(.editProfileResponse(.failure(CustomError.map(error))))) + } + } + .debounce(id: EditProfileCancel(), for: 0.05, scheduler: mainQueue) + + case .updateUserInfo(let nickname, let year, let job, let generation): + return .run { send in + let userInfoResult = await Result { + try await signUpUseCase.updateUserInfo(nickname: nickname, year: year, job: job, generation: generation) + } + + switch userInfoResult { + case .success(let updateUserInfoData): + if let updateUserInfoData = updateUserInfoData { + await send(.async(.updateUserInfoResponse(.success(updateUserInfoData)))) + + } + + case .failure(let error): + await send(.async(.updateUserInfoResponse(.failure(CustomError.map(error))))) + } + } + .debounce(id: EditProfileCancel(), for: 0.1, scheduler: mainQueue) + + case .updateUserInfoResponse(let result): + switch result { + case .success(let userInfoData): + state.updateUserinfoModel = userInfoData + case let .failure(error): + #logError("update user info 리턴 에러", error) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/View/EditProfileView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/View/EditProfileView.swift index ab2fb33..2f947cb 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/View/EditProfileView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/EditProfile/View/EditProfileView.swift @@ -138,7 +138,7 @@ extension EditProfileView { ScrollView { VStack(spacing: 12) { - if let categories = store.editProfileJobModel?.data?.data { + if let categories = store.editProfileJobModel?.data.content { ForEach(0..<(categories.count / 4 + (categories.count % 4 > 0 ? 1 : 0)), id: \.self) { rowIndex in let startIndex = rowIndex * 4 let endIndex = min(startIndex + 4, categories.count) diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/Reducer/Profile.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/Reducer/Profile.swift index ba3579c..82add4e 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/Reducer/Profile.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/Reducer/Profile.swift @@ -26,12 +26,11 @@ public struct Profile { var profileGenerationTextColor: Color = Color.gray600 var profileGenerationText: String = "" - public var profileUserModel: UpdateUserInfoModel? = nil - var userLogoutModel: UserLogOutModel? = nil - var userDeleteModel: DeleteUserModel? = nil - var myQuestionListModel: QuestionModel? = nil - var deleteQuestionModel: DeleteQuestionModel? = nil - var statusQuestionModel: StatusQuestionModel? = nil + public var profileUserModel: UpdateUserInfoDTOModel? = nil + var userLogoutModel: UserDTOModel? = nil + var myQuestionListModel: QuestionDTOModel? = nil + var deleteQuestionModel: FlagQuestionDTOModel? = nil + var statusQuestionModel: StatusQuestionDTOModel? = nil var profileGenerationYear: Int? = .zero var logoutPopUpTitle: String = "로그아웃 하시겠어요?" @@ -86,17 +85,17 @@ public struct Profile { //MARK: - AsyncAction 비동기 처리 액션 public enum AsyncAction: Equatable { - case fetchUserProfileResponse(Result) + case fetchUserProfileResponse(Result) case fetchUser - case logoutUseResponse(Result) + case logoutUseResponse(Result) case logoutUser case socilalLogOutUser - case fetchQuestionResponse(Result) + case fetchQuestionResponse(Result) case fetchQuestion case deleteQuestion(questionID: Int) - case deleteQuestionResponse(Result) + case deleteQuestionResponse(Result) case statusQuestion(id: Int) - case statusQuestionResponse(Result) + case statusQuestionResponse(Result) } @@ -136,284 +135,19 @@ public struct Profile { case .destination(_): return .none - case .view(let View): - switch View { - - case .tapPresntSettingModal: - state.destination = .setting(.init()) - return .none - - case .closeModal: - state.destination = nil - return .none - - case .presntPopUp: - state.destination = .popup(.init()) - return .none - - case .closePopUp: - state.destination = nil - return .none - - case .updateGenerationInfo: - let (generation, color, textColor) = CheckRegister.getGeneration( - year: state.profileGenerationYear ?? .zero, - color: state.profileGenerationColor, - textColor: state.profileGenerationTextColor - ) - state.profileGenerationText = generation - state.profileGenerationColor = color - state.profileGenerationTextColor = textColor - - return .none - - case .switchModalAction(let settingprofile): - nonisolated(unsafe) var settingProfile = settingprofile - switch settingProfile { - case .editProfile: - Log.debug("프로필 수정") - case .blackManagement: - Log.debug("차단") - case .logout: - state.popUpText = "로그아웃 하시겠어요?" - state.isLogOutPopUp = true - case .withDraw: - state.popUpText = "정말 탈퇴하시겠어요?" - state.isDeleteUserPopUp = true - case .contactUs: - break - } - return .run { send in - switch settingProfile { - case .editProfile: - await send(.navigation(.presntEditProfile)) - case .blackManagement: - Log.debug("차단") - await send(.navigation(.presntUserBlock)) - case .logout: - await send(.view(.presntPopUp)) - case .withDraw: - await send(.view(.presntPopUp)) - case .contactUs: - await send(.view(.contartEmail)) - } - - } - - case .contartEmail: - if let subject = "문의/신고하기".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let body = """ - 아래 내용을 적어주세요. 빠르게 답변 드리겠습니다.\n - • 이용 중인 기기/OS 버전:\n - • 닉네임: \n - • 문의 내용: - """.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let url = URL(string: "mailto:suhwj81@gmail.com?subject=\(subject)&body=\(body)") { - - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } - } - return .none - } + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) - case .async(let AsyncAction): - switch AsyncAction { - case .fetchUser: - return .run { send in - let fetchUserData = await Result { - try await authUseCase.fetchUserInfo() - } - - switch fetchUserData { - case .success(let fetchUserResult): - if let fetchUserResult = fetchUserResult { - await send(.async(.fetchUserProfileResponse(.success(fetchUserResult)))) - await send(.view(.updateGenerationInfo)) - } - case .failure(let error): - await send(.async(.fetchUserProfileResponse(.failure(CustomError.map(error))))) - - } - } - - case .fetchUserProfileResponse(let result): - switch result { - case .success(let resultData): - state.profileUserModel = resultData - state.profileGenerationYear = state.profileUserModel?.data?.year - state.userInfoModel?.isChangeProfile = false - - case let .failure(error): - #logNetwork("프로필 오류", error.localizedDescription) - } - return .none - - case .socilalLogOutUser: - let socialType = UserDefaults.standard.string(forKey: "LoginSocialType") ?? "" - return .run { send in - switch socialType { - case "kakao": - await send(.async(.logoutUser)) - - case "apple": - await send(.async(.logoutUser)) - default: - break - } - } - - case .logoutUseResponse(let result): - switch result{ - case .success(let userData): - state.userLogoutModel = userData - #logDebug("유저 로그아웃 성공", userData) - state.userInfoModel?.isLogOut = true - state.destination = .home(.init(userInfoModel: state.userInfoModel)) - case .failure(let error): - #logDebug("유저 로그아웃 에러", error.localizedDescription) - } - return .none - - case .logoutUser: - nonisolated(unsafe) var userinfoModel = state.userInfoModel - return .run { send in - let userLogOutData = await Result { - try await authUseCase.logoutUser(refreshToken: "") - } - - switch userLogOutData { - case .success(let userLogOutData): - if let userLogOutData = userLogOutData { - await send(.async(.logoutUseResponse(.success(userLogOutData)))) - UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") - let loginSocialType = UserDefaults.standard.removeObject(forKey: "LoginSocialType") - await send(.view(.closePopUp)) - userinfoModel?.isLogOut = true - try await self.clock.sleep(for: .seconds(0.4)) - await send(.navigation(.presntLogout)) - } - - case .failure(let error): - await send(.async(.logoutUseResponse(.failure(CustomError.map(error))))) - } - } - - - case .fetchQuestion: - return .run { send in - let questionResult = await Result { - try await questionUseCase.myQuestionList(page: 1, pageSize: 20) - } - - switch questionResult { - case .success(let questionResult): - if let questionData = questionResult { - await send(.async(.fetchQuestionResponse(.success(questionData)))) - } - case .failure(let error): - await send(.async(.logoutUseResponse(.failure( - CustomError.encodingError(error.localizedDescription))))) - } - } - - case .fetchQuestionResponse(let result): - switch result { - case .success(let questionData): - state.myQuestionListModel = questionData - - case .failure(let error): - Log.debug("피드 목록 에러", error.localizedDescription) - } - return .none - - case .deleteQuestion(let questionID): - return .run { send in - let deleteQuestionResult = await Result { - try await questionUseCase.deleteQuestion(questionID: questionID) - } - - switch deleteQuestionResult { - case .success(let deleteQuestionResult): - if let deleteQuestionResult = deleteQuestionResult { - await send(.async(.deleteQuestionResponse(.success(deleteQuestionResult)))) - - try await self.clock.sleep(for: .seconds(1)) - await send(.navigation(.presntDeleteQuestion)) - } - case .failure(let error): - await send(.async(.deleteQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) - } - } - - case .deleteQuestionResponse(let result): - switch result { - case .success(let deleteQuestionData): - state.deleteQuestionModel = deleteQuestionData - state.userInfoModel?.isDeleteQuestion = true - - case .failure(let error): - #logDebug("질문 삭제 에러", error.localizedDescription) - } - return .none - - case .statusQuestion(id: let id): - return .run { send in - let questionResult = await Result { - try await questionUseCase.statusQuestion(questionID: id) - } - - switch questionResult { - case .success(let questionStatusData): - if let questionStatusData = questionStatusData { - await send(.async(.statusQuestionResponse(.success(questionStatusData)))) - } - case .failure(let error): - await send(.async(.statusQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) - } - } - - case .statusQuestionResponse(let result): - switch result { - case .success(let statusQuestionData): - state.statusQuestionModel = statusQuestionData - case .failure(let error): - #logDebug("질문 에대한 결과 보여주기 실패", error.localizedDescription) - } - return .none - } + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) - case .inner(let InnerAction): - switch InnerAction { - - } + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) - case .navigation(let NavigationAction): - switch NavigationAction { - case .presntLogout: - return .none - - case .presntEditProfile: - return .none - - case .presntWithDraw: - return .none - - case .presnetCreateQuestionList: - return .none - - case .presntDeleteQuestion: - return .none - - case .presntUserBlock: - return .none - } - - default: - return .none + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) } - } .ifLet(\.$destination, action: \.destination) .onChange(of: \.myQuestionListModel) { oldValue, newValue in @@ -435,4 +169,294 @@ public struct Profile { } } } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .tapPresntSettingModal: + state.destination = .setting(.init()) + return .none + + case .closeModal: + state.destination = nil + return .none + + case .presntPopUp: + state.destination = .popup(.init()) + return .none + + case .closePopUp: + state.destination = nil + return .none + + case .updateGenerationInfo: + let (generation, color, textColor) = CheckRegister.getGeneration( + year: state.profileGenerationYear ?? .zero, + color: state.profileGenerationColor, + textColor: state.profileGenerationTextColor + ) + state.profileGenerationText = generation + state.profileGenerationColor = color + state.profileGenerationTextColor = textColor + + return .none + + case .switchModalAction(let settingprofile): + nonisolated(unsafe) var settingProfile = settingprofile + switch settingProfile { + case .editProfile: + Log.debug("프로필 수정") + case .blackManagement: + Log.debug("차단") + case .logout: + state.popUpText = "로그아웃 하시겠어요?" + state.isLogOutPopUp = true + case .withDraw: + state.popUpText = "정말 탈퇴하시겠어요?" + state.isDeleteUserPopUp = true + case .contactUs: + break + } + return .run { send in + switch settingProfile { + case .editProfile: + await send(.navigation(.presntEditProfile)) + case .blackManagement: + Log.debug("차단") + await send(.navigation(.presntUserBlock)) + case .logout: + await send(.view(.presntPopUp)) + case .withDraw: + await send(.view(.presntPopUp)) + case .contactUs: + await send(.view(.contartEmail)) + } + + } + + case .contartEmail: + if let subject = "문의/신고하기".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let body = """ + 아래 내용을 적어주세요. 빠르게 답변 드리겠습니다.\n + • 이용 중인 기기/OS 버전:\n + • 닉네임: \n + • 문의 내용: + """.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: "mailto:suhwj81@gmail.com?subject=\(subject)&body=\(body)") { + + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } + return .none + } + } + + + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .fetchUser: + return .run { send in + let fetchUserData = await Result { + try await authUseCase.fetchUserInfo() + } + + switch fetchUserData { + case .success(let fetchUserResult): + if let fetchUserResult = fetchUserResult { + await send(.async(.fetchUserProfileResponse(.success(fetchUserResult)))) + await send(.view(.updateGenerationInfo)) + } + case .failure(let error): + await send(.async(.fetchUserProfileResponse(.failure(CustomError.map(error))))) + + } + } + + case .fetchUserProfileResponse(let result): + switch result { + case .success(let resultData): + state.profileUserModel = resultData + state.profileGenerationYear = state.profileUserModel?.data.year + state.userInfoModel?.isChangeProfile = false + + case let .failure(error): + #logNetwork("프로필 오류", error.localizedDescription) + } + return .none + + case .socilalLogOutUser: + let socialType = UserDefaults.standard.string(forKey: "LoginSocialType") ?? "" + return .run { send in + switch socialType { + case "kakao": + await send(.async(.logoutUser)) + + case "apple": + await send(.async(.logoutUser)) + default: + break + } + } + + case .logoutUseResponse(let result): + switch result{ + case .success(let userData): + state.userLogoutModel = userData + #logDebug("유저 로그아웃 성공", userData) + state.userInfoModel?.isLogOut = true + state.destination = .home(.init(userInfoModel: state.userInfoModel)) + case .failure(let error): + #logDebug("유저 로그아웃 에러", error.localizedDescription) + } + return .none + + case .logoutUser: + nonisolated(unsafe) var userinfoModel = state.userInfoModel + return .run { send in + let userLogOutData = await Result { + try await authUseCase.logoutUser(refreshToken: "") + } + + switch userLogOutData { + case .success(let userLogOutData): + if let userLogOutData = userLogOutData { + await send(.async(.logoutUseResponse(.success(userLogOutData)))) + UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") + let loginSocialType = UserDefaults.standard.removeObject(forKey: "LoginSocialType") + await send(.view(.closePopUp)) + userinfoModel?.isLogOut = true + try await self.clock.sleep(for: .seconds(0.4)) + await send(.navigation(.presntLogout)) + } + + case .failure(let error): + await send(.async(.logoutUseResponse(.failure(CustomError.map(error))))) + } + } + + + case .fetchQuestion: + return .run { send in + let questionResult = await Result { + try await questionUseCase.myQuestionList(page: 1, pageSize: 20) + } + + switch questionResult { + case .success(let questionResult): + if let questionData = questionResult { + await send(.async(.fetchQuestionResponse(.success(questionData)))) + } + case .failure(let error): + await send(.async(.logoutUseResponse(.failure( + CustomError.encodingError(error.localizedDescription))))) + } + } + + case .fetchQuestionResponse(let result): + switch result { + case .success(let questionData): + state.myQuestionListModel = questionData + + case .failure(let error): + Log.debug("피드 목록 에러", error.localizedDescription) + } + return .none + + case .deleteQuestion(let questionID): + return .run { send in + let deleteQuestionResult = await Result { + try await questionUseCase.deleteQuestion(questionID: questionID) + } + + switch deleteQuestionResult { + case .success(let deleteQuestionResult): + if let deleteQuestionResult = deleteQuestionResult { + await send(.async(.deleteQuestionResponse(.success(deleteQuestionResult)))) + + try await self.clock.sleep(for: .seconds(1)) + await send(.navigation(.presntDeleteQuestion)) + } + case .failure(let error): + await send(.async(.deleteQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) + } + } + + case .deleteQuestionResponse(let result): + switch result { + case .success(let deleteQuestionData): + state.deleteQuestionModel = deleteQuestionData + state.userInfoModel?.isDeleteQuestion = true + + case .failure(let error): + #logDebug("질문 삭제 에러", error.localizedDescription) + } + return .none + + case .statusQuestion(id: let id): + return .run { send in + let questionResult = await Result { + try await questionUseCase.statusQuestion(questionID: id) + } + + switch questionResult { + case .success(let questionStatusData): + if let questionStatusData = questionStatusData { + await send(.async(.statusQuestionResponse(.success(questionStatusData)))) + } + case .failure(let error): + await send(.async(.statusQuestionResponse(.failure(CustomError.encodingError(error.localizedDescription))))) + } + } + + case .statusQuestionResponse(let result): + switch result { + case .success(let statusQuestionData): + state.statusQuestionModel = statusQuestionData + case .failure(let error): + #logDebug("질문 에대한 결과 보여주기 실패", error.localizedDescription) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntLogout: + return .none + + case .presntEditProfile: + return .none + + case .presntWithDraw: + return .none + + case .presnetCreateQuestionList: + return .none + + case .presntDeleteQuestion: + return .none + + case .presntUserBlock: + return .none + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/View/ProfileView.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/View/ProfileView.swift index 28e83cf..7f911b4 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/View/ProfileView.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/Main/View/ProfileView.swift @@ -15,303 +15,325 @@ import SwiftUIIntrospect import Utill public struct ProfileView: View { - @Bindable var store: StoreOf - @Shared(.appStorage("lastViewedPage")) var lastViewedPage: Int = .zero - - var backAction: () -> Void = {} - - - public init( - store: StoreOf, - backAction: @escaping () -> Void - ) { - self.store = store - self.backAction = backAction - } - - public var body: some View { - ZStack { - Color.gray600 - .edgesIgnoringSafeArea(.all) + @Bindable var store: StoreOf + @Shared(.appStorage("lastViewedPage")) var lastViewedPage: Int = .zero + + var backAction: () -> Void = {} + + + public init( + store: StoreOf, + backAction: @escaping () -> Void + ) { + self.store = store + self.backAction = backAction + } + + public var body: some View { + ZStack { + Color.gray600 + .edgesIgnoringSafeArea(.all) + + VStack { + Spacer() + .frame(height: 14) + + CustomTitleNaviagionBackButton(buttonAction: backAction, title: "마이페이지") + + loadingData() + + Spacer() + } + .onAppear { + store.send(.async(.fetchUser)) + store.send(.async(.fetchQuestion)) + } + .onDisappear { + lastViewedPage = .zero + } + .introspect(.navigationStack, on: .iOS(.v17, .v18)) { navigationController in + navigationController.interactivePopGestureRecognizer?.isEnabled = true + } + .sheet(item: $store.scope(state: \.destination?.setting, action: \.destination.setting)) { settingStore in + SettingView(store: settingStore) { + guard let settingtitem = settingStore.settingtitem else {return} + store.send(.view(.switchModalAction(settingtitem ))) + } closeModalAction: { + store.send(.view(.closeModal)) + } + .presentationDetents([.height(UIScreen.screenHeight * 0.4)]) + .presentationCornerRadius(20) + .presentationDragIndicator(.hidden) + } + + .popup(item: $store.scope(state: \.destination?.popup, action: \.destination.popup)) { customPopUp in + if store.isLogOutPopUp { + CustomBasicPopUpView(store: customPopUp, title: store.popUpText) { + store.send(.async(.logoutUser)) + } cancelAction: { + store.send(.view(.closeModal)) + } + } else if store.isDeleteUserPopUp { + CustomBasicPopUpView(store: customPopUp, title: store.popUpText) { + store.send(.view(.closeModal)) - VStack { - Spacer() - .frame(height: 14) - - CustomTitleNaviagionBackButton(buttonAction: backAction, title: "마이페이지") - - userInfoTitle( - nickName: store.profileUserModel?.data?.nickname ?? "", - job: store.profileUserModel?.data?.job ?? "", - generation: store.profileUserModel?.data?.generation ?? "") - - myPostWritingTitle() - - postingListView() - - Spacer() - } - .onAppear { - store.send(.async(.fetchUser)) - store.send(.async(.fetchQuestion)) - } - .onDisappear { - lastViewedPage = .zero - } - .introspect(.navigationStack, on: .iOS(.v17, .v18)) { navigationController in - navigationController.interactivePopGestureRecognizer?.isEnabled = true - } - .sheet(item: $store.scope(state: \.destination?.setting, action: \.destination.setting)) { settingStore in - SettingView(store: settingStore) { - guard let settingtitem = settingStore.settingtitem else {return} - store.send(.view(.switchModalAction(settingtitem ))) - } closeModalAction: { - store.send(.view(.closeModal)) - } - .presentationDetents([.height(UIScreen.screenHeight * 0.4)]) - .presentationCornerRadius(20) - .presentationDragIndicator(.hidden) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { + store.send(.navigation(.presntWithDraw)) } + } cancelAction: { + store.send(.view(.closeModal)) + } + } else if store.isDeleteQuestionPopUp { + CustomBasicPopUpView(store: customPopUp, title: store.popUpText) { + store.send(.view(.closeModal)) - .popup(item: $store.scope(state: \.destination?.popup, action: \.destination.popup)) { customPopUp in - if store.isLogOutPopUp { - CustomBasicPopUpView(store: customPopUp, title: store.popUpText) { - store.send(.async(.logoutUser)) - } cancelAction: { - store.send(.view(.closeModal)) - } - } else if store.isDeleteUserPopUp { - CustomBasicPopUpView(store: customPopUp, title: store.popUpText) { - store.send(.view(.closeModal)) - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { - store.send(.navigation(.presntWithDraw)) - } - } cancelAction: { - store.send(.view(.closeModal)) - } - } else if store.isDeleteQuestionPopUp { - CustomBasicPopUpView(store: customPopUp, title: store.popUpText) { - store.send(.view(.closeModal)) - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { - store.send(.async(.deleteQuestion(questionID: store.deleteQuestionId))) - } - } cancelAction: { - store.send(.view(.closeModal)) - } - } - - } customize: { popup in - popup - .type(.floater(verticalPadding: UIScreen.screenHeight * 0.35)) - .position(.bottom) - .animation(.spring) - .closeOnTap(true) - .closeOnTapOutside(true) - .backgroundColor(Color.basicBlack.opacity(0.8)) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { + store.send(.async(.deleteQuestion(questionID: store.deleteQuestionId))) } + } cancelAction: { + store.send(.view(.closeModal)) + } } + + } customize: { popup in + popup + .type(.floater(verticalPadding: UIScreen.screenHeight * 0.35)) + .position(.bottom) + .animation(.spring) + .closeOnTap(true) + .closeOnTapOutside(true) + .backgroundColor(Color.basicBlack.opacity(0.8)) + } } + } } extension ProfileView { - - @ViewBuilder - private func userInfoTitle( - nickName: String, - job: String, - generation: String - ) -> some View { - VStack { - Spacer() - .frame(height: 20) - + + @ViewBuilder + private func userInfoTitle( + nickName: String, + job: String, + generation: String + ) -> some View { + VStack { + Spacer() + .frame(height: 20) + + HStack { + Text(nickName) + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.basicWhite) + + Spacer() + + } + .padding(.horizontal, 20) + + Spacer() + .frame(height: 16) + + HStack { + RoundedRectangle(cornerRadius: 20) + .stroke(Color.gray400, style: .init(lineWidth: 1.13)) + .frame(width: job.calculateWidthProfile(for: job), height: 24) + .background(Color.gray600) + .overlay(alignment: .center) { HStack { - Text(nickName) - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.basicWhite) - - Spacer() - + Text(job) + .pretendardFont(family: .SemiBold, size: 12) + .foregroundStyle(Color.basicWhite) } - .padding(.horizontal, 20) - + .padding(.horizontal, 12) + } + + Spacer() + .frame(width: 4) + + RoundedRectangle(cornerRadius: 20) + .fill(store.profileGenerationColor) + .frame(width: generation.calculateWidthProfileGeneration(for: generation), height: 24) + .overlay(alignment: .center) { + Text(generation) + .pretendardFont(family: .SemiBold, size: 12) + .foregroundStyle(store.profileGenerationTextColor) + } + + + Spacer() + .frame(width: 4) + + Image(asset: .setting) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .onTapGesture { + store.send(.view(.tapPresntSettingModal)) + } + + Spacer() + } + .padding(.horizontal, 20) + + } + } + + @ViewBuilder + private func loadingData() -> some View { + if store.profileUserModel?.data == nil { + VStack { + Spacer() + + ProgressView() + .progressViewStyle(.circular) + .progressViewStyle(LinearProgressViewStyle(tint: .green)) + .frame(width: 100, height: 100) + + Spacer() + } + } + else { + VStack { + userInfoTitle( + nickName: store.profileUserModel?.data.nickname ?? "" , + job: store.profileUserModel?.data.job ?? "", + generation: store.profileUserModel?.data.generation ?? "" ) + + myPostWritingTitle() + + postingListView() + } + } + } + + @ViewBuilder + private func myPostWritingTitle() -> some View { + VStack { + Spacer() + .frame(height: 32) + + HStack { + Text("내가 올린 글") + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(Color.gray200) + + Spacer() + } + .padding(.horizontal, 20) + } + } + + @ViewBuilder + private func postingListView() -> some View { + if store.myQuestionListModel?.data?.content == [] { + noPostingListView() + } else { + myPostitngList() + } + } + + @ViewBuilder + private func myPostitngList() -> some View { + VStack { + Spacer() + .frame(height: 16) + + if let resultData = store.myQuestionListModel?.data?.content { + FlippableCardView( + data: resultData, + shouldSaveState: false, + onItemAppear: { item in + if let resultItem = item as? QuestionContentData { + store.questionId = resultItem.id + } + } , + content: { item in + CardItemView( + resultData: item, + statsData: store.statusQuestionModel?.data, + isProfile: true, + userLoginID: store.profileUserModel?.data.socialID ?? "", + generationColor: store.cardGenerationColor, + isTapAVote: .constant(false), + isTapBVote: .constant(false), + isLogOut: false, + isLookAround: false, + isDeleteUser: false, + answerRatio: ( + A: Int(item.answerRatio?.answerRatioA ?? .zero ), B: Int(item.answerRatio?.answerRatioB ?? .zero)), + editTapAction: { + store.send(.view(.presntPopUp)) + store.isDeleteQuestionPopUp = true + store.deleteQuestionId = item.id + store.popUpText = "고민을 삭제하시겠어요?" + }, + likeTapAction: {_ in}, + appearStatusAction: {}, + choiceTapAction: {} + ) + .onChange(of: store.questionId) { oldValue, newValue in + store.send(.async(.statusQuestion(id: newValue))) + } + }) + } + } + } + + @ViewBuilder + private func noPostingListView() -> some View { + VStack { + Spacer() + .frame(height: 16) + + RoundedRectangle(cornerRadius: 32) + .fill(Color.gray500) + .frame(height: UIScreen.screenHeight * 0.6) + .overlay(alignment: .center) { + VStack { Spacer() - .frame(height: 16) - HStack { - RoundedRectangle(cornerRadius: 20) - .stroke(Color.gray400, style: .init(lineWidth: 1.13)) - .frame(width: job.calculateWidthProfile(for: job), height: 24) - .background(Color.gray600) - .overlay(alignment: .center) { - HStack { - Text(job) - .pretendardFont(family: .SemiBold, size: 12) - .foregroundStyle(Color.basicWhite) - } - .padding(.horizontal, 12) - } - - Spacer() - .frame(width: 4) - - RoundedRectangle(cornerRadius: 20) - .fill(store.profileGenerationColor) - .frame(width: generation.calculateWidthProfileGeneration(for: generation), height: 24) - .overlay(alignment: .center) { - Text(generation) - .pretendardFont(family: .SemiBold, size: 12) - .foregroundStyle(store.profileGenerationTextColor) - } - - - Spacer() - .frame(width: 4) - - Image(asset: .setting) - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .onTapGesture { - store.send(.view(.tapPresntSettingModal)) - } - - Spacer() - } - .padding(.horizontal, 20) + Image(asset: .questonSmail) + .resizable() + .scaledToFit() + .frame(width: 62, height: 62) - } - } - - @ViewBuilder - private func myPostWritingTitle() -> some View { - VStack { Spacer() - .frame(height: 32) + .frame(height: 16) + + Text("아직 작성한 고민이 없어요!") + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.gray200) - HStack { - Text("내가 올린 글") - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(Color.gray200) - - Spacer() - } - .padding(.horizontal, 20) - } - } - - @ViewBuilder - private func postingListView() -> some View { - if store.myQuestionListModel?.data?.results == [] { - noPostingListView() - } else { - myPostitngList() - } - } - - @ViewBuilder - private func myPostitngList() -> some View { - VStack { Spacer() - .frame(height: 16) + .frame(height: 16) + + Text("지금 고민을 등록해 볼까요?") + .pretendardFont(family: .Regular, size: 16) + .foregroundStyle(Color.gray300) - if let resultData = store.myQuestionListModel?.data?.results { - FlippableCardView( - data: resultData, - shouldSaveState: false, - onItemAppear: { item in - if let resultItem = item as? ResultData { - store.questionId = resultItem.id ?? .zero - } - } , content: { item in - CardItemView( - resultData: item, - statsData: store.statusQuestionModel?.data, - isProfile: true, - userLoginID: store.profileUserModel?.data?.socialID ?? "", - generationColor: store.cardGenerationColor, - isTapAVote: .constant(false), - isTapBVote: .constant(false), - isLogOut: false, - isLookAround: false, - isDeleteUser: false, - answerRatio: (A: Int(item.answerRatio?.a ?? 0), B: Int(item.answerRatio?.b ?? 0)), - editTapAction: { - store.send(.view(.presntPopUp)) - store.isDeleteQuestionPopUp = true - store.deleteQuestionId = item.id ?? .zero - store.popUpText = "고민을 삭제하시겠어요?" - }, - likeTapAction: { _ in }, - appearStatusAction: { - - }, - choiceTapAction: {}) - .onChange(of: store.questionId) { oldValue, newValue in - store.send(.async(.statusQuestion(id: newValue))) - } - }) - } - } - } - - @ViewBuilder - private func noPostingListView() -> some View { - VStack { Spacer() - .frame(height: 16) + .frame(height: 32) - RoundedRectangle(cornerRadius: 32) - .fill(Color.gray500) - .frame(height: UIScreen.screenHeight * 0.6) - .overlay(alignment: .center) { - VStack { - Spacer() - - Image(asset: .questonSmail) - .resizable() - .scaledToFit() - .frame(width: 62, height: 62) - - Spacer() - .frame(height: 16) - - Text("아직 작성한 고민이 없어요!") - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.gray200) - - Spacer() - .frame(height: 16) - - Text("지금 고민을 등록해 볼까요?") - .pretendardFont(family: .Regular, size: 16) - .foregroundStyle(Color.gray300) - - Spacer() - .frame(height: 32) - - RoundedRectangle(cornerRadius: 20) - .fill(Color.basicWhite) - .frame(width: 120, height: 56) - .clipShape(Capsule()) - .overlay { - Text("글쓰기") - .pretendardFont(family: .Medium, size: 20) - .foregroundStyle(Color.textColor100) - } - .onTapGesture { - store.send(.navigation(.presnetCreateQuestionList)) - } - - Spacer() - } - - } + RoundedRectangle(cornerRadius: 20) + .fill(Color.basicWhite) + .frame(width: 120, height: 56) + .clipShape(Capsule()) + .overlay { + Text("글쓰기") + .pretendardFont(family: .Medium, size: 20) + .foregroundStyle(Color.textColor100) + } + .onTapGesture { + store.send(.navigation(.presnetCreateQuestionList)) + } + + Spacer() + } + } - .padding(.horizontal, 16) } + .padding(.horizontal, 16) + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/Setting/Reducer/Setting.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/Setting/Reducer/Setting.swift index 49c4f24..cba9a0e 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/Setting/Reducer/Setting.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/Setting/Reducer/Setting.swift @@ -9,9 +9,7 @@ import Foundation import ComposableArchitecture import Utill -import Model -import UseCase -import Utills +import Networkings @Reducer public struct Setting { @@ -29,7 +27,6 @@ public struct Setting { case async(AsyncAction) case inner(InnerAction) case navigation(NavigationAction) - case test } //MARK: - ViewAction @@ -60,37 +57,61 @@ public struct Setting { public var body: some ReducerOf { BindingReducer() - Reduce { state, action in switch action { case .binding(_): return .none - case .test: - return .none - case .view(let View): - switch View { - case .tapSettingitem(let item): - state.settingtitem = item - return .none - } + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) - case .async(let AsyncAction): - switch AsyncAction { - - - } + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) - case .inner(let InnerAction): - switch InnerAction { - - } + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) - case .navigation(let NavigationAction): - switch NavigationAction { - - } + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) } } } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .tapSettingitem(let item): + state.settingtitem = item + return .none + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + + } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Profile/WithDraw/Reducer/WithDraw.swift b/OPeace/Projects/Presentation/Presentation/Sources/Profile/WithDraw/Reducer/WithDraw.swift index c90e3eb..9befb81 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Profile/WithDraw/Reducer/WithDraw.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Profile/WithDraw/Reducer/WithDraw.swift @@ -17,173 +17,203 @@ import KakaoSDKUser @Reducer public struct WithDraw { - public init() {} + public init() {} + + @ObservableState + public struct State: Equatable { + var withDrawTitle: String = "" + var withDrawButtonComplete: String = "완료" - @ObservableState - public struct State: Equatable { - var withDrawTitle: String = "" - var withDrawButtonComplete: String = "완료" - - var userDeleteModel: DeleteUserModel? = nil - var revokeAppleResponseModel: AppleTokenResponse? = nil - - @Presents var destination: Destination.State? - - @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - - public init() {} - } + var userDeleteModel: DeletUserDTOModel? = nil + var revokeAppleResponseModel: OAuthDTOModel? = nil - public enum Action: ViewAction, BindableAction, FeatureAction { - case binding(BindingAction) - case destination(PresentationAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - } + @Presents var destination: Destination.State? - @Reducer(state: .equatable) - public enum Destination { - case home(Home) - } + @Shared(.inMemory("userInfoModel")) var userInfoModel: UserInfoModel? = .init() - //MARK: - ViewAction - @CasePathable - public enum View { - - } + public init() {} + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case binding(BindingAction) + case destination(PresentationAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + @Reducer(state: .equatable) + public enum Destination { + case home(Home) + } + + //MARK: - ViewAction + @CasePathable + public enum View { + } + + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case deleteUserResponse(Result) + case deleteUser(reason: String) + case deletUserSocialType(reason: String) + case appleRevoke + case appleRevokeResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case deleteUserResponse(Result) - case deleteUser(reason: String) - case deletUserSocialType(reason: String) - case appleRevoke - case appleRevokeResponse(Result) - } + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntDeleteUser - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { + } + + @Dependency(AuthUseCase.self) var authUseCase + @Dependency(\.continuousClock) var clock + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(_): + return .none - } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntDeleteUser + case .destination(_): + return .none + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } } + .ifLet(\.$destination, action: \.destination) - @Dependency(AuthUseCase.self) var authUseCase - @Dependency(\.continuousClock) var clock - - public var body: some ReducerOf { - BindingReducer() - Reduce { state, action in - switch action { - case .binding(_): - return .none - - case .destination(_): - return .none - - case .view(let View): - switch View { - - } - - case .async(let AsyncAction): - switch AsyncAction { - case .deleteUser(let reason): - return .run { @MainActor send in - let deleteUserData = await Result { - try await authUseCase.deleteUser(reason: reason) - } - - switch deleteUserData { - case .success(let deleteUserData): - if let deleteUserData = deleteUserData { - send(.async(.deleteUserResponse(.success(deleteUserData)))) - - try await self.clock.sleep(for: .seconds(1)) - if deleteUserData.data?.status == true { - send(.navigation(.presntDeleteUser)) - } - } - case .failure(let error): - send(.async(.deleteUserResponse(.failure(CustomError.map(error))))) - } - } - - case .deleteUserResponse(let result): - switch result { - case .success(let userDeleteData): - state.userDeleteModel = userDeleteData - UserDefaults.standard.removeObject(forKey: "REFRESH_TOKEN") - UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") - state.userInfoModel?.isDeleteUser = true - state.destination = .home(.init(userInfoModel: state.userInfoModel)) - case .failure(let error): - Log.debug("회원 탈퇴 에러", error.localizedDescription) - } - return .none - - case .deletUserSocialType(let reason): - nonisolated(unsafe) var socialType = UserDefaults.standard.string(forKey: "LoginSocialType") - return .run { send in - switch socialType { - case "kakao": - await send(.async(.deleteUser(reason: reason))) - case "apple": - await send(.async(.appleRevoke)) - try await clock.sleep(for: .seconds(0.3)) - await send(.async(.deleteUser(reason: reason))) - - default: - break - } - } - case .appleRevoke: - return .run { send in - let appleRevokeResult = await Result { - try await authUseCase.revokeAppleToken() - } - - switch appleRevokeResult { - case .success(let appleRevokeResultData): - if let appleRevokeResultData = appleRevokeResultData { - await send(.async(.appleRevokeResponse(.success(appleRevokeResultData)))) - } - case .failure(let error): - await send(.async(.appleRevokeResponse(.failure(CustomError.tokenError(error.localizedDescription))))) - } - } - - - case .appleRevokeResponse(let result): - switch result { - case .success(let appleTokenResponseData): - state.revokeAppleResponseModel = appleTokenResponseData - case .failure(let error): - Log.error("애플 탈퇴 실패", error.localizedDescription) - } - return .none - } - - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - case .presntDeleteUser: - return .none - } + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + + } + } + + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .deleteUser(let reason): + return .run { send in + let deleteUserData = await Result { + try await authUseCase.deleteUser(reason: reason) + } + + switch deleteUserData { + case .success(let deleteUserData): + if let deleteUserData = deleteUserData { + await send(.async(.deleteUserResponse(.success(deleteUserData)))) + + try await self.clock.sleep(for: .seconds(0.3)) + if deleteUserData.data.status == true { + await send(.navigation(.presntDeleteUser)) } + } + case .failure(let error): + await send(.async(.deleteUserResponse(.failure(CustomError.map(error))))) + } + } + + case .deleteUserResponse(let result): + switch result { + case .success(let userDeleteData): + state.userDeleteModel = userDeleteData + UserDefaults.standard.removeObject(forKey: "REFRESH_TOKEN") + UserDefaults.standard.removeObject(forKey: "ACCESS_TOKEN") + state.userInfoModel?.isDeleteUser = true + state.destination = .home(.init(userInfoModel: state.userInfoModel)) + case .failure(let error): + Log.debug("회원 탈퇴 에러", error.localizedDescription) + } + return .none + + case .deletUserSocialType(let reason): + nonisolated(unsafe) var socialType = UserDefaults.standard.string(forKey: "LoginSocialType") + return .run { send in + switch socialType { + case "kakao": + await send(.async(.deleteUser(reason: reason))) + case "apple": + await send(.async(.appleRevoke)) + try await clock.sleep(for: .seconds(0.3)) + await send(.async(.deleteUser(reason: reason))) + + default: + break } - .ifLet(\.$destination, action: \.destination) + } + case .appleRevoke: + return .run { send in + let appleRevokeResult = await Result { + try await authUseCase.revokeAppleToken() + } + + switch appleRevokeResult { + case .success(let appleRevokeResultData): + if let appleRevokeResultData = appleRevokeResultData { + await send(.async(.appleRevokeResponse(.success(appleRevokeResultData)))) + } + case .failure(let error): + await send(.async(.appleRevokeResponse(.failure(CustomError.tokenError(error.localizedDescription))))) + } + } + + + case .appleRevokeResponse(let result): + switch result { + case .success(let appleTokenResponseData): + state.revokeAppleResponseModel = appleTokenResponseData + case .failure(let error): + Log.error("애플 탈퇴 실패", error.localizedDescription) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntDeleteUser: + return .none } + } } diff --git a/OPeace/Projects/Presentation/Presentation/Sources/Splash/Reducer/Splash.swift b/OPeace/Projects/Presentation/Presentation/Sources/Splash/Reducer/Splash.swift index 143fceb..0c6be97 100644 --- a/OPeace/Projects/Presentation/Presentation/Sources/Splash/Reducer/Splash.swift +++ b/OPeace/Projects/Presentation/Presentation/Sources/Splash/Reducer/Splash.swift @@ -14,161 +14,187 @@ import Networkings @Reducer public struct Splash { + public init() {} + + @ObservableState + public struct State: Equatable { public init() {} + var applogoImage: ImageAsset = .spalshLogo + var backgroundmage: ImageAsset = .backGroud - @ObservableState - public struct State: Equatable { - public init() {} - var applogoImage: ImageAsset = .spalshLogo - var backgroundmage: ImageAsset = .backGroud - - var checkUserVerifyModel: CheckUserVerifyModel? = nil - var refreshTokenModel: RefreshModel? = nil - - } + var checkUserVerifyModel: CheckUserDTOModel? = nil + var refreshTokenModel: RefreshDTOModel? = nil - public enum Action: ViewAction, BindableAction, FeatureAction { - case binding(BindingAction) - case view(View) - case async(AsyncAction) - case inner(InnerAction) - case navigation(NavigationAction) - } - - //MARK: - ViewAction - @CasePathable - public enum View { - - } + } + + public enum Action: ViewAction, BindableAction, FeatureAction { + case binding(BindingAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + //MARK: - ViewAction + @CasePathable + public enum View { + } + + + + //MARK: - AsyncAction 비동기 처리 액션 + public enum AsyncAction: Equatable { + case refreshTokenResponse(Result) + case refreshTokenRequest(refreshToken: String) + case checkUserVerfiy + case checkUserVerfiyResponse(Result) + } + + //MARK: - 앱내에서 사용하는 액션 + public enum InnerAction: Equatable { - //MARK: - AsyncAction 비동기 처리 액션 - public enum AsyncAction: Equatable { - case refreshTokenResponse(Result) - case refreshTokenRequest(refreshToken: String) + } + + //MARK: - NavigationAction + public enum NavigationAction: Equatable { + case presntMain + case presntLogin + } + + @Dependency(AuthUseCase.self) var authUseCase + @Dependency(\.continuousClock) var clock + + public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding(_): + return .none - case checkUserVerfiy - case checkUserVerfiyResponse(Result) - } - - //MARK: - 앱내에서 사용하는 액션 - public enum InnerAction: Equatable { + case .view(let viewAction): + return handleViewAction(state: &state, action: viewAction) + case .async(let asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case .inner(let innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .navigation(let navigationAction): + return handleNavigationAction(state: &state, action: navigationAction) + } } - - //MARK: - NavigationAction - public enum NavigationAction: Equatable { - case presntMain - case presntLogin + } + + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + } - - @Dependency(AuthUseCase.self) var authUseCase - @Dependency(\.continuousClock) var clock - - public var body: some ReducerOf { - BindingReducer() - Reduce { - state, - action in - switch action { - case .binding(_): - return .none - - - case .view(let View): - switch View { - - } - - case .async(let AsyncAction): - switch AsyncAction { - case .refreshTokenRequest(let refreshToken): - return .run { send in - let refreshTokenResult = await Result { - try await authUseCase.requestRefreshToken(refreshToken: refreshToken) - } - - switch refreshTokenResult { - case .success(let refreshTokenData): - if let refreshTokenData = refreshTokenData { - await send(.async(.refreshTokenResponse(.success(refreshTokenData)))) - - if refreshTokenData.data?.refreshToken?.isEmpty != nil && refreshTokenData.data?.accessToken?.isEmpty != nil { - await send(.navigation(.presntMain)) - } - } - - case .failure(let error): - await send(.async(.refreshTokenResponse(.failure(CustomError.tokenError(error.localizedDescription))))) - } - } - - case .refreshTokenResponse(let result): - switch result { - case .success(let refreshTokenData): - state.refreshTokenModel = refreshTokenData - UserDefaults.standard.set(state.refreshTokenModel?.data?.refreshToken ?? "", forKey: "REFRESH_TOKEN") - UserDefaults.standard.set(state.refreshTokenModel?.data?.accessToken ?? "", forKey: "ACCESS_TOKEN") - case .failure(let error): - Log.error("토큰 재발급 실패", error.localizedDescription) - } - return .none - - - case .checkUserVerfiy: - return .run { send in - let checkUserVerifyResult = await Result { - try await authUseCase.checkUserVerify() - } - - switch checkUserVerifyResult { - case .success(let checkUserVerifyData): - if let checkUserVerifyData = checkUserVerifyData { - await send(.async(.checkUserVerfiyResponse(.success(checkUserVerifyData)))) - - if checkUserVerifyData.data?.status == true { - await send(.navigation(.presntMain)) - } else { - let refreshToken = UserDefaults.standard.string(forKey: "REFRESH_TOKEN") ?? "" - await send(.async(.refreshTokenRequest(refreshToken: refreshToken))) - - } - } - case .failure(let error): - await send(.async(.checkUserVerfiyResponse(.failure(CustomError.tokenError(error.localizedDescription))))) - - await send(.navigation(.presntLogin)) - - } - } - - case .checkUserVerfiyResponse(let result): - switch result { - case .success(let userVerifyData): - state.checkUserVerifyModel = userVerifyData - case .failure(let error): - Log.error("토큰 재발급 실패", error.localizedDescription) - } - return .none - } - - case .inner(let InnerAction): - switch InnerAction { - - } - - case .navigation(let NavigationAction): - switch NavigationAction { - case .presntMain: - return .none - - case .presntLogin: - return .none - } + } + + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case .refreshTokenRequest(let refreshToken): + return .run { send in + let refreshTokenResult = await Result { + try await authUseCase.requestRefreshToken(refreshToken: refreshToken) + } + + switch refreshTokenResult { + case .success(let refreshTokenData): + if let refreshTokenData = refreshTokenData { + await send(.async(.refreshTokenResponse(.success(refreshTokenData)))) + + if refreshTokenData.data.refreshToken.isEmpty != nil && refreshTokenData.data.accessToken.isEmpty != nil { + await send(.navigation(.presntMain)) } + } + + case .failure(let error): + await send(.async(.refreshTokenResponse(.failure(CustomError.tokenError(error.localizedDescription))))) + } + } + + case .refreshTokenResponse(let result): + switch result { + case .success(let refreshTokenData): + state.refreshTokenModel = refreshTokenData + UserDefaults.standard.set(state.refreshTokenModel?.data.refreshToken ?? "", forKey: "REFRESH_TOKEN") + UserDefaults.standard.set(state.refreshTokenModel?.data.accessToken ?? "", forKey: "ACCESS_TOKEN") + case .failure(let error): + Log.error("토큰 재발급 실패", error.localizedDescription) + } + return .none + + + case .checkUserVerfiy: + return .run { send in + let checkUserVerifyResult = await Result { + try await authUseCase.checkUserVerify() } + + switch checkUserVerifyResult { + case .success(let checkUserVerifyData): + if let checkUserVerifyData = checkUserVerifyData { + await send(.async(.checkUserVerfiyResponse(.success(checkUserVerifyData)))) + + if checkUserVerifyData.data.status == true { + await send(.navigation(.presntMain)) + } else { + let refreshToken = UserDefaults.standard.string(forKey: "REFRESH_TOKEN") ?? "" + await send(.async(.refreshTokenRequest(refreshToken: refreshToken))) + + } + } + case .failure(let error): + await send(.async(.checkUserVerfiyResponse(.failure(CustomError.tokenError(error.localizedDescription))))) + + await send(.navigation(.presntLogin)) + + } + } + + case .checkUserVerfiyResponse(let result): + switch result { + case .success(let userVerifyData): + state.checkUserVerifyModel = userVerifyData + case .failure(let error): + Log.error("토큰 재발급 실패", error.localizedDescription) + } + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + + } + } + + private func handleNavigationAction( + state: inout State, + action: NavigationAction + ) -> Effect { + switch action { + case .presntMain: + return .none + + case .presntLogin: + return .none } + } } diff --git a/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/CardItemView.swift b/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/CardItemView.swift index 2211a4e..e43a199 100644 --- a/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/CardItemView.swift +++ b/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/CardItemView.swift @@ -10,896 +10,895 @@ import Model import Collections public struct CardItemView: View { - @State private var isRotated: Bool = false - @State var isLikedTap: Bool = false - @Binding var isTapAVote: Bool - @Binding var isTapBVote: Bool - @State private var answerRatio: (A: Int, B: Int) - @State var currentOffset: CGFloat = 0 - @State private var statsUpdated: Bool = false - - private var statsData: QuestionStatusResponseModel? - private var resultData: ResultData - private var userLoginID: String - private var isProfile: Bool - private var isLogOut: Bool - private var isLookAround: Bool - private var isDeleteUser: Bool - private var generationColor: Color - - private var editTapAction: () -> Void = { } - private var appearStatusAction: () -> Void = { } - private var likeTapAction: (String) -> Void = { _ in } - private var choiceTapAction: () -> Void = { } - - public init( - resultData: ResultData, - statsData:QuestionStatusResponseModel?, - isProfile: Bool, - userLoginID: String, - generationColor: Color, - isTapAVote: Binding, - isTapBVote: Binding, - isLogOut: Bool, - isLookAround: Bool, - isDeleteUser: Bool, - answerRatio: (A: Int, B: Int), - editTapAction: @escaping () -> Void, - likeTapAction: @escaping (String) -> Void, - appearStatusAction: @escaping () -> Void, - choiceTapAction: @escaping () -> Void - ) { - self.resultData = resultData - self.statsData = statsData - self.isProfile = isProfile - self.userLoginID = userLoginID - self.generationColor = generationColor - self._isTapAVote = isTapAVote - self._isTapBVote = isTapBVote - self.isLogOut = isLogOut - self.isLookAround = isLookAround - self.isDeleteUser = isDeleteUser - self.editTapAction = editTapAction - self.likeTapAction = likeTapAction - self.choiceTapAction = choiceTapAction - self.appearStatusAction = appearStatusAction - _answerRatio = State(initialValue: (Int(resultData.answerRatio?.a ?? .zero), Int(resultData.answerRatio?.b ?? .zero))) - _isLikedTap = State(initialValue: isLikedTap) - } - - private var isUserInteractionDisabled: Bool { - return isLogOut || isLookAround || isDeleteUser - } - - public var body: some View { - if isProfile { - profileCardView() - } else { - votingCardView() - } + @State private var isRotated: Bool = false + @State var isLikedTap: Bool = false + @Binding var isTapAVote: Bool + @Binding var isTapBVote: Bool + @State private var answerRatio: (A: Int, B: Int) + @State var currentOffset: CGFloat = 0 + @State private var statsUpdated: Bool = false + + private var statsData: StatusQuestionResponseDTOModel? + private var resultData: QuestionContentData + private var userLoginID: String + private var isProfile: Bool + private var isLogOut: Bool + private var isLookAround: Bool + private var isDeleteUser: Bool + private var generationColor: Color + + private var editTapAction: () -> Void = { } + private var appearStatusAction: () -> Void = { } + private var likeTapAction: (String) -> Void = { _ in } + private var choiceTapAction: () -> Void = { } + + public init( + resultData: QuestionContentData, + statsData: StatusQuestionResponseDTOModel?, + isProfile: Bool, + userLoginID: String, + generationColor: Color, + isTapAVote: Binding, + isTapBVote: Binding, + isLogOut: Bool, + isLookAround: Bool, + isDeleteUser: Bool, + answerRatio: (A: Int, B: Int), + editTapAction: @escaping () -> Void, + likeTapAction: @escaping (String) -> Void, + appearStatusAction: @escaping () -> Void, + choiceTapAction: @escaping () -> Void + ) { + self.resultData = resultData + self.statsData = statsData + self.isProfile = isProfile + self.userLoginID = userLoginID + self.generationColor = generationColor + self._isTapAVote = isTapAVote + self._isTapBVote = isTapBVote + self.isLogOut = isLogOut + self.isLookAround = isLookAround + self.isDeleteUser = isDeleteUser + self.editTapAction = editTapAction + self.likeTapAction = likeTapAction + self.choiceTapAction = choiceTapAction + self.appearStatusAction = appearStatusAction + _answerRatio = State(initialValue: (Int(resultData.answerRatio?.answerRatioA ?? .zero), Int(resultData.answerRatio?.answerRatioB ?? .zero))) + _isLikedTap = State(initialValue: isLikedTap) + } + + private var isUserInteractionDisabled: Bool { + return isLogOut || isLookAround || isDeleteUser + } + + public var body: some View { + if isProfile { + profileCardView() + } else { + votingCardView() } + } } extension CardItemView { - - @ViewBuilder - private func profileCardView() -> some View { - VStack { - RoundedRectangle(cornerRadius: 30) - .fill(Color.gray500) - .frame(height: calculateVotingCardHeight()) - .padding(.horizontal, 20) - .overlay { - VStack { - cardHeaderView( - nickName: resultData.userInfo?.userNickname ?? "", - job: resultData.userInfo?.userJob ?? "", - generation: resultData.userInfo?.userGeneration ?? "" - ) - cardEmojiView(emoji: resultData.emoji ?? "u1FAE0") - - cardWriteAndAnswerView( - title: resultData.title ?? "", - choiceA: resultData.choiceA ?? "", - choiceB: resultData.choiceB ?? "", - isRoated: isRotated - ) - isVotedResultView( - responseCount: resultData.answerCount ?? 0, - likeCount: resultData.likeCount ?? 0 - ) - Spacer() - } - .padding(.horizontal, 24) - } + + @ViewBuilder + private func profileCardView() -> some View { + VStack { + RoundedRectangle(cornerRadius: 30) + .fill(Color.gray500) + .frame(height: calculateVotingCardHeight()) + .padding(.horizontal, 20) + .overlay { + VStack { + cardHeaderView( + nickName: resultData.userInfo?.userNickname ?? "", + job: resultData.userInfo?.userJob ?? "", + generation: resultData.userInfo?.userGeneration ?? "" + ) + cardEmojiView(emoji: resultData.emoji ?? "u1FAE0") + cardWriteAndAnswerView( + title: resultData.title ?? "", + choiceA: resultData.choiceA, + choiceB: resultData.choiceB, + isRoated: isRotated + ) + isVotedResultView( + responseCount: resultData.answerCount, + likeCount: resultData.likeCount + ) Spacer() - .frame(height: 16) + } + .padding(.horizontal, 24) } + + Spacer() + .frame(height: 16) } - - @ViewBuilder - private func votingCardView() -> some View { - VStack { - RoundedRectangle(cornerRadius: 30) - .fill(Color.gray500) - .frame(height: calculateVotingCardHeight()) - .padding(.horizontal, 20) - .overlay { - VStack { - cardHeaderView( - nickName: resultData.userInfo?.userNickname ?? "", - job: resultData.userInfo?.userJob ?? "", - generation: resultData.userInfo?.userGeneration ?? "" - ) - cardEmojiView(emoji: resultData.emoji ?? "") - - if isRotated { - cardWriteAndAnswerView( - title: resultData.title ?? "", - choiceA: resultData.choiceA ?? "", - choiceB: resultData.choiceB ?? "", - isRoated: isRotated - ) - isVotedResultView( - responseCount: resultData.answerCount ?? 0, - likeCount: resultData.likeCount ?? 0 - ) - } else { - cardWriteAndAnswerView( - title: resultData.title ?? "", - choiceA: resultData.choiceA ?? "", - choiceB: resultData.choiceB ?? "", - isRoated: isRotated - ) - questionChoiceVoteButton() - } - - Spacer() - } - .rotation3DEffect( - .degrees((isRotated ) ? 180 : 0), - axis: (x: 0, y: 1, z: 0), - perspective: 0.5 - ) - .padding(.horizontal, 24) - } - .onTapGesture { - updateStatusOnFlip() - } + } + + @ViewBuilder + private func votingCardView() -> some View { + VStack { + RoundedRectangle(cornerRadius: 30) + .fill(Color.gray500) + .frame(height: calculateVotingCardHeight()) + .padding(.horizontal, 20) + .overlay { + VStack { + cardHeaderView( + nickName: resultData.userInfo?.userNickname ?? "", + job: resultData.userInfo?.userJob ?? "", + generation: resultData.userInfo?.userGeneration ?? "" + ) + cardEmojiView(emoji: resultData.emoji ?? "") + + if isRotated { + cardWriteAndAnswerView( + title: resultData.title ?? "", + choiceA: resultData.choiceA , + choiceB: resultData.choiceB , + isRoated: isRotated + ) + isVotedResultView( + responseCount: resultData.answerCount, + likeCount: resultData.likeCount + ) + } else { + cardWriteAndAnswerView( + title: resultData.title ?? "", + choiceA: resultData.choiceA, + choiceB: resultData.choiceB, + isRoated: isRotated + ) + questionChoiceVoteButton() + } Spacer() - .frame(height: 16) - } - .rotation3DEffect( + } + .rotation3DEffect( .degrees((isRotated ) ? 180 : 0), axis: (x: 0, y: 1, z: 0), perspective: 0.5 - ) - .animation(.easeInOut(duration: 0.5), value: isRotated) + ) + .padding(.horizontal, 24) + } + .onTapGesture { + updateStatusOnFlip() + } + + Spacer() + .frame(height: 16) + } + .rotation3DEffect( + .degrees((isRotated ) ? 180 : 0), + axis: (x: 0, y: 1, z: 0), + perspective: 0.5 + ) + .animation(.easeInOut(duration: 0.5), value: isRotated) + } + + private func calculateVotingCardHeight() -> CGFloat { + let cleanedTitle = shouldCleanLineBreaks(resultData.title ?? "") ? cleanExcessiveLineBreaks(in: resultData.title ?? "") : resultData.title ?? "" + + + let titleHeight = calculateTextHeight(text: cleanedTitle, width: UIScreen.main.bounds.width - 88) + let titleHeightTextCount = calculateTextHeight(text: cleanedTitle, width: UIScreen.main.bounds.width - UIScreen.screenWidth * 0.2) + let smallSizeDevice = UIScreen.main.nativeBounds.width + if let title = resultData.title { + let titleCount = cleanedTitle.count + let lineBreakCount = title.components(separatedBy: "\n").count - 1 + if lineBreakCount >= 7 { + return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 + titleHeightTextCount : UIScreen.screenHeight * 0.5 + titleHeightTextCount + 10 + } else if titleCount < 12 { + return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.74 : UIScreen.screenHeight * 0.56 + } else if titleCount > 40 { + return smallSizeDevice == 750 ? UIScreen.screenHeight * 0.44 + titleHeightTextCount + 10 : UIScreen.screenHeight * 0.4 + titleHeightTextCount + 10 + } else if titleCount > 12 { + return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 + titleHeightTextCount + 15 : UIScreen.screenHeight * 0.5 + titleHeightTextCount + 15 + } else { + return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 : UIScreen.screenHeight * 0.56 + } + } else { + return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 : UIScreen.screenHeight * 0.56 } + } + + private func shouldCleanLineBreaks(_ text: String) -> Bool { + let lineBreakCount = text.components(separatedBy: "\n").count - 1 + return lineBreakCount >= 8 + } + + private func cleanExcessiveLineBreaks(in text: String) -> String { + let cleanedText = text.replacingOccurrences(of: "\\s*\\n+\\s*", with: " ", options: .regularExpression) + return cleanedText.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private func calculateTextHeight(text: String, width: CGFloat) -> CGFloat { + let font = UIFont.systemFont(ofSize: 28, weight: .bold) + let attributes: [NSAttributedString.Key: Any] = [.font: font] + let attributedText = NSAttributedString(string: text, attributes: attributes) + let constraintBox = CGSize(width: width, height: .greatestFiniteMagnitude) + let boundingBox = attributedText.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, context: nil) - private func calculateVotingCardHeight() -> CGFloat { - let cleanedTitle = shouldCleanLineBreaks(resultData.title ?? "") ? cleanExcessiveLineBreaks(in: resultData.title ?? "") : resultData.title ?? "" + return ceil(boundingBox.height) + 10 + } + + @ViewBuilder + private func cardHeaderView( + nickName: String, + job: String, + generation: String + ) -> some View { + VStack { + Spacer() + .frame(height: 32) + + HStack { + Text(nickName.isEmpty ? "탈퇴 한 유저" : nickName) + .pretendardFont(family: .Bold, size: 20) + .foregroundStyle(Color.basicWhite) + Spacer() - let titleHeight = calculateTextHeight(text: cleanedTitle, width: UIScreen.main.bounds.width - 88) - let titleHeightTextCount = calculateTextHeight(text: cleanedTitle, width: UIScreen.main.bounds.width - UIScreen.screenWidth * 0.2) - let smallSizeDevice = UIScreen.main.nativeBounds.width - if let title = resultData.title { - let titleCount = cleanedTitle.count - let lineBreakCount = title.components(separatedBy: "\n").count - 1 - if lineBreakCount >= 7 { - return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 + titleHeightTextCount : UIScreen.screenHeight * 0.5 + titleHeightTextCount + 10 - } else if titleCount < 12 { - return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.74 : UIScreen.screenHeight * 0.56 - } else if titleCount > 40 { - return smallSizeDevice == 750 ? UIScreen.screenHeight * 0.44 + titleHeightTextCount + 10 : UIScreen.screenHeight * 0.4 + titleHeightTextCount + 10 - } else if titleCount > 12 { - return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 + titleHeightTextCount + 15 : UIScreen.screenHeight * 0.5 + titleHeightTextCount + 15 - } else { - return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 : UIScreen.screenHeight * 0.56 + Image(asset: .questionEdit) + .resizable() + .scaledToFit() + .frame(width: 36, height: 36) + .onTapGesture { + if !isUserInteractionDisabled { + if isProfile { + editTapAction() + } else { + if userLoginID != resultData.userInfo?.userID { + editTapAction() + } + } + } else if isUserInteractionDisabled { + editTapAction() } - } else { - return smallSizeDevice <= 750 ? UIScreen.screenHeight * 0.66 : UIScreen.screenHeight * 0.56 + else if isProfile == true { + editTapAction() + } + } + } + + Spacer() + .frame(height: 3) + + HStack { + Text(job.isEmpty ? "기타" : job) + .pretendardFont(family: .Regular, size: 14) + .foregroundStyle(Color.gray200) + + Spacer() + .frame(width: 10) + + Rectangle() + .frame(width: 1, height: 10) + .foregroundStyle(Color.gray300) + + Spacer() + .frame(width: 10) + + let (generation, color) = CheckRegister.generatuionTextColor(generation: generation, color: generationColor) + + Text(generation) + .pretendardFont(family: .Bold, size: 14) + .foregroundStyle(color) + + Spacer() + } + } + .padding(.horizontal, 24) + } + + @ViewBuilder + private func cardEmojiView(emoji: String) -> some View { + VStack(alignment: .center) { + Spacer() + .frame(height: 26) + + if let emojiImage = Image.emojiToImage(emoji: emoji.convertUnicodeToEmoji(unicodeString: emoji) ?? "") { + emojiImage + .resizable() + .scaledToFit() + .frame(width: 80, height: 80) + } + } + } + + private func updateStatusOnFlip() { + if !isUserInteractionDisabled { + appearStatusAction() + statsUpdated = false + } + } + + @ViewBuilder + private func cardWriteAndAnswerView( + title: String, + choiceA: String, + choiceB: String, + isRoated: Bool + ) -> some View { + VStack(alignment: .center) { + Spacer() + .frame(height: 16) + + let processedTitle = handleExcessiveLineBreaks(in: title) + let smallSizeDevice = UIScreen.main.nativeBounds.width + Text(processedTitle) + .pretendardFont(family: .Bold, size: 28) + .foregroundStyle(Color.basicWhite) + .multilineTextAlignment(.center) + .padding(.horizontal, 24) + .minimumScaleFactor(0.5) + + Spacer() + .frame(height: 16) + + if isRoated { + if let stats = statsData?.status { + choiceAnswerRoundViewProfile( + choiceTitleA: choiceA, + choiceTitleB: choiceB, + percentageA: Int(statsData?.overallRatioA ?? .zero), + percentageB: Int(statsData?.overallRatioB ?? .zero), + stats: stats + ) + } + } else if isProfile { + if let stats = statsData?.status { + choiceAnswerRoundViewProfile( + choiceTitleA: choiceA, + choiceTitleB: choiceB, + percentageA: Int(statsData?.overallRatioA ?? .zero), + percentageB: Int(statsData?.overallRatioB ?? .zero), + stats: stats + ) + } else { + choiceAnswerRoundViewProfile( + choiceTitleA: choiceA, + choiceTitleB: choiceB, + percentageA: Int(statsData?.overallRatioA ?? .zero), + percentageB: Int(statsData?.overallRatioB ?? .zero), + stats: statsData?.status ?? .init(statusA: ["a" : 0], statusB: ["b" : 0])) } + } else { + choiceAnswerRoundView(choiceTitleA: choiceA, choiceTitleB: choiceB) + } } - - private func shouldCleanLineBreaks(_ text: String) -> Bool { - let lineBreakCount = text.components(separatedBy: "\n").count - 1 - return lineBreakCount >= 8 + .onAppear { + updateStatusOnFlip() } - - private func cleanExcessiveLineBreaks(in text: String) -> String { - let cleanedText = text.replacingOccurrences(of: "\\s*\\n+\\s*", with: " ", options: .regularExpression) - return cleanedText.trimmingCharacters(in: .whitespacesAndNewlines) + .onDisappear { + statsUpdated = false } - - private func calculateTextHeight(text: String, width: CGFloat) -> CGFloat { - let font = UIFont.systemFont(ofSize: 28, weight: .bold) - let attributes: [NSAttributedString.Key: Any] = [.font: font] - let attributedText = NSAttributedString(string: text, attributes: attributes) - let constraintBox = CGSize(width: width, height: .greatestFiniteMagnitude) - let boundingBox = attributedText.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, context: nil) - - return ceil(boundingBox.height) + 10 + } + + private func handleExcessiveLineBreaks(in text: String) -> String { + let lineBreaks = text.components(separatedBy: "\n").count - 1 + if lineBreaks > 10 { + return text.replacingOccurrences(of: "\\s*\\n+\\s*", with: " ", options: .regularExpression) } - - @ViewBuilder - private func cardHeaderView( - nickName: String, - job: String, - generation: String - ) -> some View { - VStack { - Spacer() - .frame(height: 32) - - HStack { - Text(nickName.isEmpty ? "탈퇴 한 유저" : nickName) - .pretendardFont(family: .Bold, size: 20) - .foregroundStyle(Color.basicWhite) - - Spacer() - - Image(asset: .questionEdit) - .resizable() - .scaledToFit() - .frame(width: 36, height: 36) - .onTapGesture { - if !isUserInteractionDisabled { - if isProfile { - editTapAction() - } else { - if userLoginID != resultData.userInfo?.userID { - editTapAction() - } - } - } else if isUserInteractionDisabled { - editTapAction() - } - else if isProfile == true { - editTapAction() - } - } - } - - Spacer() - .frame(height: 3) - - HStack { - Text(job.isEmpty ? "기타" : job) - .pretendardFont(family: .Regular, size: 14) - .foregroundStyle(Color.gray200) - - Spacer() - .frame(width: 10) - - Rectangle() - .frame(width: 1, height: 10) - .foregroundStyle(Color.gray300) - - Spacer() - .frame(width: 10) - - let (generation, color) = CheckRegister.generatuionTextColor(generation: generation, color: generationColor) - - Text(generation) - .pretendardFont(family: .Bold, size: 14) - .foregroundStyle(color) - - Spacer() - } + return text + } + + + + @ViewBuilder + private func choiceAnswerRoundViewProfile( + choiceTitleA: String, + choiceTitleB: String, + percentageA: Int, + percentageB: Int, + stats: StatusDTO + ) -> some View { + let colorMapping: [String: Color] = [ + "Z세대": .basicPrimary, + "M세대": .basicYellow, + "X세대": .basicLightBlue, + "베이비붐세대": .basicPurple, + "기타세대": .gray300 + ] + + VStack { + Spacer() + .frame(height: 16) + + if (stats.statusA?.isEmpty ?? true) && (stats.statusB?.isEmpty ?? true) { + makeDefaultRoundedView( + label: "A", + title: choiceTitleA, + percentageLabel: String(percentageA) + ) + Spacer() + .frame(height: 8) + makeDefaultRoundedView( + label: "B", + title: choiceTitleB, + percentageLabel: String(percentageB) + ) + } else { + if let percentagesA = stats.statusA { + renderSegmentedView( + choiceLabel: "A", + choiceTitle: choiceTitleA, + percentageLabel: String(percentageA), + percentages: percentagesA, + colorMapping: colorMapping + ) } - .padding(.horizontal, 24) + Spacer() + .frame(height: 8) + + if let percentagesB = stats.statusB { + renderSegmentedView( + choiceLabel: "B", + choiceTitle: choiceTitleB, + percentageLabel: String(percentageB), + percentages: percentagesB, + colorMapping: colorMapping + ) + } + } } + .padding(.horizontal, 24) - @ViewBuilder - private func cardEmojiView(emoji: String) -> some View { - VStack(alignment: .center) { - Spacer() - .frame(height: 26) - - if let emojiImage = Image.emojiToImage(emoji: emoji.convertUnicodeToEmoji(unicodeString: emoji) ?? "") { - emojiImage - .resizable() - .scaledToFit() - .frame(width: 80, height: 80) + } + + @ViewBuilder + private func makeDefaultRoundedView( + label: String, + title: String, + percentageLabel: String + ) -> some View { + RoundedRectangle(cornerRadius: 20) + .fill(Color.gray400) + .frame(height: 48) + .overlay { + HStack { + Spacer().frame(width: 16) + + Text(label) + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(label == "A" ? Color.gray600 : Color.gray200) + .frame(maxWidth: .infinity, alignment: .trailing) + + Spacer() + + Text(title) + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(label == "A" ? Color.gray600 : Color.gray200) + .frame(maxWidth: .infinity, alignment: .trailing) + + Spacer() + + Text("\(percentageLabel)%") + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(label == "A" ? Color.gray600 : Color.gray200) + .frame(maxWidth: .infinity, alignment: .trailing) + + Spacer() + .frame(width: 12) + } + } + .clipShape(Capsule()) + } + + @ViewBuilder + private func renderSegmentedView( + choiceLabel: String, + choiceTitle: String, + percentageLabel: String, + percentages: [String: Double], + colorMapping: [String: Color] + ) -> some View { + GeometryReader { geometry in + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 20) + .fill(Color.gray400) + .frame(height: 48) + + let totalWidth = geometry.size.width + let orderedDict = sortedOrderedDictionary(from: percentages) + let combinedSegments = combineSegments(orderedDict: orderedDict, totalWidth: totalWidth) + + ForEach(combinedSegments.indices, id: \.self) { index in + let segment = combinedSegments[index] + let key = segment.key + var width = segment.width + let offset = combinedSegments.prefix(index).reduce(0) { $0 + $1.width } + + + let corners: UIRectCorner = { + if index == 0 { + return [.topLeft, .bottomLeft] + } else if index == combinedSegments.count - 1 { + return [.topRight, .bottomRight] + } else { + return [] } + }() + + AnimatedRectangle( + key: key, + color: colorMapping[key] ?? .gray400, + targetWidth: width, + offset: offset, + corners: corners + ) } + + HStack { + Spacer() + .frame(width: 16) + + Text(choiceLabel) + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(choiceLabel == "A" ? Color.gray600 : Color.gray200) + .frame(alignment: .leading) + + Spacer() + + Text(choiceTitle) + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(choiceLabel == "A" ? Color.gray600 : Color.gray200) + .frame(maxWidth: .infinity, alignment: .center) + + Spacer() + + Text("\(percentageLabel)%") + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(choiceLabel == "A" ? Color.gray600 : Color.gray200) + .frame(alignment: .trailing) + + Spacer().frame(width: 12) + } + } } + .frame(height: 48) + .clipShape(Capsule()) + } + + private func sortedOrderedDictionary(from percentages: [String: Double]) -> OrderedDictionary { + let desiredOrder = ["Z세대", "M세대", "X세대", "베이비붐세대", "기타세대"] - private func updateStatusOnFlip() { - if !isUserInteractionDisabled { - appearStatusAction() - statsUpdated = false - } + var completeDict: [String: Double] = [:] + for key in desiredOrder { + completeDict[key] = percentages[key] ?? 0.0 } - @ViewBuilder - private func cardWriteAndAnswerView( - title: String, - choiceA: String, - choiceB: String, - isRoated: Bool - ) -> some View { - VStack(alignment: .center) { + let orderedDict = OrderedDictionary(uniqueKeysWithValues: completeDict) + + let sortedElements = orderedDict.elements.sorted { first, second in + let firstIndex = desiredOrder.firstIndex(of: first.key) ?? Int.max + let secondIndex = desiredOrder.firstIndex(of: second.key) ?? Int.max + return firstIndex < secondIndex + } + + return OrderedDictionary(uniqueKeysWithValues: sortedElements) + } + + private func combineSegments( + orderedDict: OrderedDictionary, + totalWidth: CGFloat) -> [(key: String, width: CGFloat)] { + var combinedSegments: [(key: String, width: CGFloat)] = [] + var currentSegment: (key: String, width: CGFloat)? = nil + + for element in orderedDict.elements { + let key = element.key + let value = element.value + let width = totalWidth * CGFloat(value / 100.0) + + if width == 0 { + continue + } + + if currentSegment == nil { + currentSegment = (key, width) + } else if currentSegment!.key == "기타세대" || key == "기타세대" { + currentSegment!.width += width + } else { + combinedSegments.append(currentSegment!) + currentSegment = (key, width) + } + } + + if let segment = currentSegment { + combinedSegments.append(segment) + } + + return combinedSegments + } + + + + + @ViewBuilder + private func choiceAnswerRoundView( + choiceTitleA: String, + choiceTitleB: String + ) -> some View { + let smallSizeDevice = UIScreen.main.nativeBounds.width + VStack { + Spacer() + .frame(height: 16) + + RoundedRectangle(cornerRadius: 20) + .fill(Color.gray400) + .frame(height: 48) + .overlay { + HStack { Spacer() - .frame(height: 16) + .frame(width: 16) + + Text("A") + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(Color.gray200) - let processedTitle = handleExcessiveLineBreaks(in: title) - let smallSizeDevice = UIScreen.main.nativeBounds.width - Text(processedTitle) - .pretendardFont(family: .Bold, size: 28) - .foregroundStyle(Color.basicWhite) - .multilineTextAlignment(.center) - .padding(.horizontal, 24) - .minimumScaleFactor(0.5) Spacer() - .frame(height: 16) - if isRoated { - if let stats = statsData?.stats { - choiceAnswerRoundViewProfile( - choiceTitleA: choiceA, - choiceTitleB: choiceB, - percentageA: Int(statsData?.overallRatio?.a ?? .zero), - percentageB: Int(statsData?.overallRatio?.b ?? .zero), - stats: stats - ) - } - } else if isProfile { - if let stats = statsData?.stats { - choiceAnswerRoundViewProfile( - choiceTitleA: choiceA, - choiceTitleB: choiceB, - percentageA: Int(statsData?.overallRatio?.a ?? .zero), - percentageB: Int(statsData?.overallRatio?.b ?? .zero), - stats: stats - ) - } else { - choiceAnswerRoundViewProfile( - choiceTitleA: choiceA, - choiceTitleB: choiceB, - percentageA: Int(statsData?.overallRatio?.a ?? .zero), - percentageB: Int(statsData?.overallRatio?.b ?? .zero), - stats: statsData?.stats ?? .init(a: ["a" : 0], b: ["b" : 0])) - } - } else { - choiceAnswerRoundView(choiceTitleA: choiceA, choiceTitleB: choiceB) - } - } - .onAppear { - updateStatusOnFlip() - } - .onDisappear { - statsUpdated = false - } - } - - private func handleExcessiveLineBreaks(in text: String) -> String { - let lineBreaks = text.components(separatedBy: "\n").count - 1 - if lineBreaks > 10 { - return text.replacingOccurrences(of: "\\s*\\n+\\s*", with: " ", options: .regularExpression) + Text(choiceTitleA) + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(Color.gray200) + + Spacer() + } } - return text - } - - - - @ViewBuilder - private func choiceAnswerRoundViewProfile( - choiceTitleA: String, - choiceTitleB: String, - percentageA: Int, - percentageB: Int, - stats: Stats - ) -> some View { - let colorMapping: [String: Color] = [ - "Z세대": .basicPrimary, - "M세대": .basicYellow, - "X세대": .basicLightBlue, - "베이비붐세대": .basicPurple, - "기타세대": .gray300 - ] - - VStack { + .clipShape(Capsule()) + + + Spacer() + .frame(height: 8) + + RoundedRectangle(cornerRadius: 20) + .fill(Color.gray400) + .frame(height: 48) + .overlay { + HStack { Spacer() - .frame(height: 16) + .frame(width: 16) - if (stats.a?.isEmpty ?? true) && (stats.b?.isEmpty ?? true) { - makeDefaultRoundedView( - label: "A", - title: choiceTitleA, - percentageLabel: String(percentageA) - ) - Spacer() - .frame(height: 8) - makeDefaultRoundedView( - label: "B", - title: choiceTitleB, - percentageLabel: String(percentageB) - ) - } else { - if let percentagesA = stats.a { - renderSegmentedView( - choiceLabel: "A", - choiceTitle: choiceTitleA, - percentageLabel: String(percentageA), - percentages: percentagesA, - colorMapping: colorMapping - ) - } - Spacer() - .frame(height: 8) - - if let percentagesB = stats.b { - renderSegmentedView( - choiceLabel: "B", - choiceTitle: choiceTitleB, - percentageLabel: String(percentageB), - percentages: percentagesB, - colorMapping: colorMapping - ) - } - } - } - .padding(.horizontal, 24) - - } - - @ViewBuilder - private func makeDefaultRoundedView( - label: String, - title: String, - percentageLabel: String - ) -> some View { - RoundedRectangle(cornerRadius: 20) - .fill(Color.gray400) - .frame(height: 48) - .overlay { - HStack { - Spacer().frame(width: 16) - - Text(label) - .pretendardFont(family: .SemiBold, size: 16) - .foregroundStyle(label == "A" ? Color.gray600 : Color.gray200) - .frame(maxWidth: .infinity, alignment: .trailing) - - Spacer() - - Text(title) - .pretendardFont(family: .SemiBold, size: 16) - .foregroundStyle(label == "A" ? Color.gray600 : Color.gray200) - .frame(maxWidth: .infinity, alignment: .trailing) - - Spacer() - - Text("\(percentageLabel)%") - .pretendardFont(family: .SemiBold, size: 16) - .foregroundStyle(label == "A" ? Color.gray600 : Color.gray200) - .frame(maxWidth: .infinity, alignment: .trailing) - - Spacer() - .frame(width: 12) - } - } - .clipShape(Capsule()) - } - - @ViewBuilder - private func renderSegmentedView( - choiceLabel: String, - choiceTitle: String, - percentageLabel: String, - percentages: [String: Double], - colorMapping: [String: Color] - ) -> some View { - GeometryReader { geometry in - ZStack(alignment: .leading) { - RoundedRectangle(cornerRadius: 20) - .fill(Color.gray400) - .frame(height: 48) - - let totalWidth = geometry.size.width - let orderedDict = sortedOrderedDictionary(from: percentages) - let combinedSegments = combineSegments(orderedDict: orderedDict, totalWidth: totalWidth) - - ForEach(combinedSegments.indices, id: \.self) { index in - let segment = combinedSegments[index] - let key = segment.key - var width = segment.width - let offset = combinedSegments.prefix(index).reduce(0) { $0 + $1.width } - - - let corners: UIRectCorner = { - if index == 0 { - return [.topLeft, .bottomLeft] - } else if index == combinedSegments.count - 1 { - return [.topRight, .bottomRight] - } else { - return [] - } - }() - - AnimatedRectangle( - key: key, - color: colorMapping[key] ?? .gray400, - targetWidth: width, - offset: offset, - corners: corners - ) - } - - HStack { - Spacer() - .frame(width: 16) - - Text(choiceLabel) - .pretendardFont(family: .SemiBold, size: 16) - .foregroundStyle(choiceLabel == "A" ? Color.gray600 : Color.gray200) - .frame(alignment: .leading) - - Spacer() - - Text(choiceTitle) - .pretendardFont(family: .SemiBold, size: 16) - .foregroundStyle(choiceLabel == "A" ? Color.gray600 : Color.gray200) - .frame(maxWidth: .infinity, alignment: .center) - - Spacer() - - Text("\(percentageLabel)%") - .pretendardFont(family: .SemiBold, size: 16) - .foregroundStyle(choiceLabel == "A" ? Color.gray600 : Color.gray200) - .frame(alignment: .trailing) - - Spacer().frame(width: 12) - } - } + Text("B") + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(Color.gray200) + + + Spacer() + + Text(choiceTitleB) + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(Color.gray200) + + Spacer() + } } - .frame(height: 48) .clipShape(Capsule()) } - - private func sortedOrderedDictionary(from percentages: [String: Double]) -> OrderedDictionary { - let desiredOrder = ["Z세대", "M세대", "X세대", "베이비붐세대", "기타세대"] + .padding(.horizontal, 24) + .offset(y: resultData.title?.count ?? .zero > 40 ? smallSizeDevice == 750 ? -10 : 0 : 0) + + } + + @ViewBuilder + private func isVotedResultView( + responseCount: Int, + likeCount: Int + ) -> some View { + VStack { + Spacer() + .frame(height: 36) + + HStack { + Spacer() - var completeDict: [String: Double] = [:] - for key in desiredOrder { - completeDict[key] = percentages[key] ?? 0.0 - } + Image(asset: .resultRespond) + .resizable() + .scaledToFit() + .frame(width: 18, height: 18) - let orderedDict = OrderedDictionary(uniqueKeysWithValues: completeDict) + Spacer() + .frame(width: 4) - let sortedElements = orderedDict.elements.sorted { first, second in - let firstIndex = desiredOrder.firstIndex(of: first.key) ?? Int.max - let secondIndex = desiredOrder.firstIndex(of: second.key) ?? Int.max - return firstIndex < secondIndex - } + Text("응답") + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(Color.gray200) - return OrderedDictionary(uniqueKeysWithValues: sortedElements) - } - - private func combineSegments( - orderedDict: OrderedDictionary, - totalWidth: CGFloat) -> [(key: String, width: CGFloat)] { - var combinedSegments: [(key: String, width: CGFloat)] = [] - var currentSegment: (key: String, width: CGFloat)? = nil + Spacer() + .frame(width: 4) - for element in orderedDict.elements { - let key = element.key - let value = element.value - let width = totalWidth * CGFloat(value / 100.0) - - if width == 0 { - continue - } - - if currentSegment == nil { - currentSegment = (key, width) - } else if currentSegment!.key == "기타세대" || key == "기타세대" { - currentSegment!.width += width - } else { - combinedSegments.append(currentSegment!) - currentSegment = (key, width) - } + if responseCount > 999 { + Text("\(responseCount)+") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(Color.gray200) + } else { + Text("\(responseCount)") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(Color.gray200) } - if let segment = currentSegment { - combinedSegments.append(segment) - } + Spacer() + .frame(width: 12) - return combinedSegments - } - - - - - @ViewBuilder - private func choiceAnswerRoundView( - choiceTitleA: String, - choiceTitleB: String - ) -> some View { - let smallSizeDevice = UIScreen.main.nativeBounds.width - VStack { - Spacer() - .frame(height: 16) + Rectangle() + .fill(Color.gray200) + .frame(width: 1, height: 15) + + Spacer() + .frame(width: 12) + + if resultData.metadata?.liked == true { + HStack { + Image(asset: isLikedTap ? .isTapResultLike : .resultLike) + .resizable() + .scaledToFit() + .frame(width: 18, height: 18) - RoundedRectangle(cornerRadius: 20) - .fill(Color.gray400) - .frame(height: 48) - .overlay { - HStack { - Spacer() - .frame(width: 16) - - Text("A") - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(Color.gray200) - - - Spacer() - - Text(choiceTitleA) - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(Color.gray200) - - Spacer() - } - } - .clipShape(Capsule()) + Spacer() + .frame(width: 4) + Text("공감") + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) Spacer() - .frame(height: 8) + .frame(width: 4) + + if likeCount > 999 { + Text("\(likeCount)+") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) + } else { + Text("\(likeCount)") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) + } + } + .onTapGesture { + if !isUserInteractionDisabled { + if userLoginID != resultData.userInfo?.userID { + isLikedTap.toggle() + likeTapAction(String(resultData.id)) + } + } else if userLoginID == resultData.userInfo?.userID { + isLikedTap = false + } else if isUserInteractionDisabled { + likeTapAction("") + } + } + + .onAppear { + isLikedTap = true + } + } else { + HStack { + Image(asset: isLikedTap ? .isTapResultLike : .resultLike) + .resizable() + .scaledToFit() + .frame(width: 18, height: 18) - RoundedRectangle(cornerRadius: 20) - .fill(Color.gray400) - .frame(height: 48) - .overlay { - HStack { - Spacer() - .frame(width: 16) - - Text("B") - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(Color.gray200) - - - Spacer() - - Text(choiceTitleB) - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(Color.gray200) - - Spacer() - } - } - .clipShape(Capsule()) - } - .padding(.horizontal, 24) - .offset(y: resultData.title?.count ?? .zero > 40 ? smallSizeDevice == 750 ? -10 : 0 : 0) - - } - - @ViewBuilder - private func isVotedResultView( - responseCount: Int, - likeCount: Int - ) -> some View { - VStack { Spacer() - .frame(height: 36) + .frame(width: 4) + + Text("공감") + .pretendardFont(family: .Bold, size: 16) + .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) - HStack { - Spacer() - - Image(asset: .resultRespond) - .resizable() - .scaledToFit() - .frame(width: 18, height: 18) - - Spacer() - .frame(width: 4) - - Text("응답") - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(Color.gray200) - - Spacer() - .frame(width: 4) - - if responseCount > 999 { - Text("\(responseCount)+") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(Color.gray200) - } else { - Text("\(responseCount)") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(Color.gray200) - } - - Spacer() - .frame(width: 12) - - Rectangle() - .fill(Color.gray200) - .frame(width: 1, height: 15) - - Spacer() - .frame(width: 12) - - if resultData.metadata?.liked == true { - HStack { - Image(asset: isLikedTap ? .isTapResultLike : .resultLike) - .resizable() - .scaledToFit() - .frame(width: 18, height: 18) - - Spacer() - .frame(width: 4) - - Text("공감") - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) - - Spacer() - .frame(width: 4) - - if likeCount > 999 { - Text("\(likeCount)+") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) - } else { - Text("\(likeCount)") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) - } - } - .onTapGesture { - if !isUserInteractionDisabled { - if userLoginID != resultData.userInfo?.userID { - isLikedTap.toggle() - likeTapAction(String(resultData.id ?? 0)) - } - } else if userLoginID == resultData.userInfo?.userID { - isLikedTap = false - } else if isUserInteractionDisabled { - likeTapAction("") - } - } - - .onAppear { - isLikedTap = true - } - } else { - HStack { - Image(asset: isLikedTap ? .isTapResultLike : .resultLike) - .resizable() - .scaledToFit() - .frame(width: 18, height: 18) - - Spacer() - .frame(width: 4) - - Text("공감") - .pretendardFont(family: .Bold, size: 16) - .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) - - Spacer() - .frame(width: 4) - - if likeCount > 999 { - Text("\(likeCount)+") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) - } else { - Text("\(likeCount)") - .pretendardFont(family: .Medium, size: 16) - .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) - } - } - .onTapGesture { - if !isUserInteractionDisabled { - if userLoginID != resultData.userInfo?.userID { - isLikedTap.toggle() - likeTapAction(String(resultData.id ?? 0)) - } - } else if userLoginID == resultData.userInfo?.userID { - isLikedTap = false - } else if isUserInteractionDisabled { - likeTapAction("") - } - } - - } - - Spacer() - } - } - } - - @ViewBuilder - private func questionChoiceVoteButton() -> some View { - let smallSizeDevice = UIScreen.main.nativeBounds.width - VStack { Spacer() - .frame(height: 24) + .frame(width: 4) - HStack { - RoundedRectangle(cornerRadius: 10) - .fill(Color.basicPrimary) - .frame(width: 144, height: 64) - .clipShape(Capsule()) - .overlay { - Text("A") - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.gray600) - } - .onTapGesture { - handleVote(for: "A") - } - - Spacer() - .frame(width: 8) - - RoundedRectangle(cornerRadius: 10) - .fill(Color.basicPrimary) - .frame(width: 144, height: 64) - .clipShape(Capsule()) - .overlay { - Text("B") - .pretendardFont(family: .SemiBold, size: 24) - .foregroundStyle(Color.gray600) - } - .onTapGesture { - handleVote(for: "B") - } + if likeCount > 999 { + Text("\(likeCount)+") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) + } else { + Text("\(likeCount)") + .pretendardFont(family: .Medium, size: 16) + .foregroundStyle(isLikedTap ? Color.basicPrimary : Color.gray200) } - } - .offset(y: resultData.title?.count ?? .zero > 40 ? smallSizeDevice == 750 ? -10 : 0 : 0) - } - - private func handleVote(for choice: String) { - if !isUserInteractionDisabled { - if choice == "A" { - if userLoginID != resultData.userInfo?.userID { - isTapAVote = true - if isTapAVote { - answerRatio.A += 1 - isRotated = true - } - choiceTapAction() - isTapBVote = false - } else if userLoginID == resultData.userInfo?.userID { -// isRotated.toggle() - } - } else if choice == "B" { - if userLoginID != resultData.userInfo?.userID { - isTapBVote = true - if isTapBVote { - answerRatio.B += 1 - isRotated = true - } - choiceTapAction() - isTapAVote = false - } else if userLoginID == resultData.userInfo?.userID { -// isRotated.toggle() - } + } + .onTapGesture { + if !isUserInteractionDisabled { + if userLoginID != resultData.userInfo?.userID { + isLikedTap.toggle() + likeTapAction(String(resultData.id)) + } } else if userLoginID == resultData.userInfo?.userID { - choiceTapAction() -// isRotated.toggle() - } else { - isRotated = false - choiceTapAction() + isLikedTap = false + } else if isUserInteractionDisabled { + likeTapAction("") } - } else if isUserInteractionDisabled { - choiceTapAction() + } + } + + Spacer() + } } - + } + + @ViewBuilder + private func questionChoiceVoteButton() -> some View { + let smallSizeDevice = UIScreen.main.nativeBounds.width + VStack { + Spacer() + .frame(height: 24) + + HStack { + RoundedRectangle(cornerRadius: 10) + .fill(Color.basicPrimary) + .frame(width: 144, height: 64) + .clipShape(Capsule()) + .overlay { + Text("A") + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.gray600) + } + .onTapGesture { + handleVote(for: "A") + } + + Spacer() + .frame(width: 8) + + RoundedRectangle(cornerRadius: 10) + .fill(Color.basicPrimary) + .frame(width: 144, height: 64) + .clipShape(Capsule()) + .overlay { + Text("B") + .pretendardFont(family: .SemiBold, size: 24) + .foregroundStyle(Color.gray600) + } + .onTapGesture { + handleVote(for: "B") + } + } + } + .offset(y: resultData.title?.count ?? .zero > 40 ? smallSizeDevice == 750 ? -10 : 0 : 0) + } + + private func handleVote(for choice: String) { + if !isUserInteractionDisabled { + if choice == "A" { + if userLoginID != resultData.userInfo?.userID { + isTapAVote = true + if isTapAVote { + answerRatio.A += 1 + isRotated = true + } + choiceTapAction() + isTapBVote = false + } else if userLoginID == resultData.userInfo?.userID { + // isRotated.toggle() + } + } else if choice == "B" { + if userLoginID != resultData.userInfo?.userID { + isTapBVote = true + if isTapBVote { + answerRatio.B += 1 + isRotated = true + } + choiceTapAction() + isTapAVote = false + } else if userLoginID == resultData.userInfo?.userID { + // isRotated.toggle() + } + } else if userLoginID == resultData.userInfo?.userID { + choiceTapAction() + // isRotated.toggle() + } else { + isRotated = false + choiceTapAction() + } + } else if isUserInteractionDisabled { + choiceTapAction() + } + } } diff --git a/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/FlippableCardView.swift b/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/FlippableCardView.swift index 7e619f4..8da11cb 100644 --- a/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/FlippableCardView.swift +++ b/OPeace/Projects/Shared/DesignSystem/Sources/UI/CardView/FlippableCardView.swift @@ -6,158 +6,156 @@ import SwiftUI import SwiftUIIntrospect +import ComposableArchitecture public struct FlippableCardView: View { - @Environment(\.scenePhase) var scenePhase - var data: [T] - let content: (T) -> Content - let onAppearLastItem: (() -> Void)? - let onItemAppear: ((T) -> Void)? - let shouldSaveState: Bool - - @AppStorage("lastViewedPage") private var lastViewedPage: Int = 0 - @GestureState private var dragOffset: CGFloat = 0 - @State private var currentPage: Int = 0 - @State private var isScrolling: Bool = false - @State private var dataCount: Int = 0 - - public init( - data: [T], - shouldSaveState: Bool = true, - onAppearLastItem: (() -> Void)? = nil, - onItemAppear: ((T) -> Void)? = nil, - content: @escaping (T) -> Content - ) { - self.data = data - self.shouldSaveState = shouldSaveState - self.content = content - self.onAppearLastItem = onAppearLastItem - self.onItemAppear = onItemAppear - self._dataCount = State(initialValue: data.count) - } - - public var body: some View { - GeometryReader { geometry in - ScrollViewReader { scrollViewProxy in - ScrollView(.vertical) { - LazyVStack(spacing: 0) { - ForEach(data.indices, id: \.self) { index in - content(data[index]) - .frame(width: geometry.size.width) - .background(Color.clear) - .scrollTargetLayout() - .id(index) - .onAppear { - if !isScrolling { - handleItemAppear(index: index, item: data[index]) - } - if index == data.indices.last { - onAppearLastItem?() - } - } - - Spacer() - .frame(height: index == data.indices.last ? UIScreen.main.bounds.height * 0.27 : 0) - } - } - .gesture(DragGesture() - .updating($dragOffset) { value, state, _ in - state = value.translation.height * 0.2 - } - .onChanged { _ in - isScrolling = true - } - .onEnded { value in - isScrolling = false - let velocity = value.predictedEndLocation.y - value.startLocation.y - let nearestIndex = calculateNearestIndex( - geometry: geometry, - currentPage: currentPage, - velocity: velocity - ) - updateCurrentPage(nearestIndex, with: scrollViewProxy) - }) - } - .scrollIndicators(.hidden) + @Environment(\.scenePhase) var scenePhase + var data: [T] + let content: (T) -> Content + let onAppearLastItem: (() -> Void)? + let onItemAppear: ((T) -> Void)? + let shouldSaveState: Bool + + @Shared(.appStorage("lastViewedPage")) private var lastViewedPage: Int = 0 + @GestureState private var dragOffset: CGFloat = 0 + @State private var currentPage: Int = 0 + @State private var isScrolling: Bool = false + @State private var dataCount: Int = 0 + + public init( + data: [T], + shouldSaveState: Bool = true, + onAppearLastItem: (() -> Void)? = nil, + onItemAppear: ((T) -> Void)? = nil, + content: @escaping (T) -> Content + ) { + self.data = data + self.shouldSaveState = shouldSaveState + self.content = content + self.onAppearLastItem = onAppearLastItem + self.onItemAppear = onItemAppear + self._dataCount = State(initialValue: data.count) + } + + public var body: some View { + GeometryReader { geometry in + ScrollViewReader { scrollViewProxy in + ScrollView(.vertical, showsIndicators: false) { + LazyVStack(spacing: 0) { + ForEach(data.indices, id: \.self) { index in + content(data[index]) + .frame(width: geometry.size.width) + .background(Color.clear) + .scrollTargetLayout() + .id(index) .onAppear { - if shouldSaveState { - currentPage = min(lastViewedPage, data.count - 1) - } else { - currentPage = 0 - } - updateCurrentPage(currentPage, with: scrollViewProxy) - } - .onDisappear { - if shouldSaveState { - lastViewedPage = currentPage - } - } - .onChange(of: scenePhase) { newValue in - switch newValue { - case .active: - if shouldSaveState { - currentPage = min(lastViewedPage, data.count - 1) - } else { - currentPage = 0 - } - updateCurrentPage(currentPage, with: scrollViewProxy) - case .background, .inactive: - if shouldSaveState { - lastViewedPage = currentPage - } - @unknown default: - lastViewedPage = 0 - } - } - .onChange(of: data.count) { newCount in - if newCount != dataCount { - currentPage = 0 - if shouldSaveState { - lastViewedPage = 0 - } - updateCurrentPage(currentPage, with: scrollViewProxy) - dataCount = newCount - } + if !isScrolling { + handleItemAppear(index: index, item: data[index]) + } + if index == data.indices.last { + onAppearLastItem?() + } } + + Spacer() + .frame(height: index == data.indices.last ? UIScreen.main.bounds.height * 0.27 : 0) + } + } + .gesture(DragGesture() + .updating($dragOffset) { value, state, _ in + state = value.translation.height + } + .onChanged { value in + isScrolling = true } + .onEnded { value in + isScrolling = false + let translation = value.translation.height + let predictedEndTranslation = value.predictedEndTranslation.height + let threshold: CGFloat = 50 + + var newIndex = currentPage + + if predictedEndTranslation < -threshold { + // 위로 스크롤 + newIndex = min(currentPage + 1, data.count - 1) + } else if predictedEndTranslation > threshold { + // 아래로 스크롤 + newIndex = max(currentPage - 1, 0) + } + + updateCurrentPage(newIndex, with: scrollViewProxy) + }) } - .edgesIgnoringSafeArea(.all) - } - - private func handleItemAppear(index: Int, item: T) { - guard index >= 0, index < data.count, index == currentPage else { return } - onItemAppear?(item) - } - - private func updateCurrentPage(_ index: Int, with scrollViewProxy: ScrollViewProxy) { - guard index >= 0, index < data.count else { return } - currentPage = index - if shouldSaveState { - lastViewedPage = index + .scrollIndicators(.hidden) + .onAppear { + if shouldSaveState { + currentPage = min(lastViewedPage, data.count - 1) + } else { + currentPage = 0 + } + updateCurrentPage(currentPage, with: scrollViewProxy) } - scrollToCenter(scrollViewProxy: scrollViewProxy, index: index) - handleItemAppear(index: index, item: data[index]) - } - - private func calculateNearestIndex( - geometry: GeometryProxy, - currentPage: Int, - velocity: CGFloat - ) -> Int { - let threshold: CGFloat = 30 - if velocity > threshold { - return max(currentPage - 1, 0) - } else if velocity < -threshold { - return min(currentPage + 1, data.count - 1) + .onDisappear { + if shouldSaveState { + lastViewedPage = currentPage + } } - return currentPage - } - - private func scrollToCenter(scrollViewProxy: ScrollViewProxy, index: Int) { - guard index >= 0, index < data.count else { return } - withAnimation(.spring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.3)) { - scrollViewProxy.scrollTo(index, anchor: .center) + .onChange(of: scenePhase) { oldValue, newValue in + switch newValue { + case .active: + if shouldSaveState { + currentPage = min(lastViewedPage, data.count - 1) + } else { + currentPage = 0 + } + updateCurrentPage(currentPage, with: scrollViewProxy) + case .background, .inactive: + if shouldSaveState { + lastViewedPage = 0 + } + @unknown default: + lastViewedPage = 0 + } } + .onChange(of: data.count) { oldValue, newValue in + if newValue != dataCount { + // 현재 페이지가 데이터 수보다 크면 마지막 페이지로 이동 + if currentPage >= newValue { + currentPage = max(0, newValue - 1) + } + if shouldSaveState { + lastViewedPage = currentPage + } + updateCurrentPage(currentPage, with: scrollViewProxy) + dataCount = newValue + } + } + } + } + .edgesIgnoringSafeArea(.all) + } + + private func handleItemAppear(index: Int, item: T) { + guard index >= 0, index < data.count, index == currentPage else { return } + onItemAppear?(item) + } + + private func updateCurrentPage(_ index: Int, with scrollViewProxy: ScrollViewProxy) { + guard index >= 0, index < data.count else { return } + currentPage = index + if shouldSaveState { + lastViewedPage = index + } + scrollToCenter(scrollViewProxy: scrollViewProxy, index: index) + handleItemAppear(index: index, item: data[index]) + } + + private func scrollToCenter(scrollViewProxy: ScrollViewProxy, index: Int) { + guard index >= 0, index < data.count else { return } + withAnimation(.easeInOut(duration: 0.3)) { + scrollViewProxy.scrollTo(index, anchor: .center) } + } } diff --git a/OPeace/Projects/Shared/ThirdParty/Project.swift b/OPeace/Projects/Shared/ThirdParty/Project.swift index 7c6c647..5f07b6f 100644 --- a/OPeace/Projects/Shared/ThirdParty/Project.swift +++ b/OPeace/Projects/Shared/ThirdParty/Project.swift @@ -19,6 +19,7 @@ let project = Project.makeModule( .SPM.popupView, .SPM.firebaseAnalytics, .SPM.firebaseCrashlytics, + .SPM.firebaseRemoteConfig, .SPM.swiftUIIntrospect, .SPM.tcaCoordinator diff --git a/OPeace/fastlane/metadata/ko/keywords.txt b/OPeace/fastlane/metadata/ko/keywords.txt index 0c4614a..550a430 100644 --- a/OPeace/fastlane/metadata/ko/keywords.txt +++ b/OPeace/fastlane/metadata/ko/keywords.txt @@ -1 +1 @@ -커뮤니티, 직장인 오피스, OPeace +커뮤니티, 직장인,오피스, OPeace, 세대, 블라인드, opeace diff --git a/OPeace/fastlane/metadata/ko/release_notes.txt b/OPeace/fastlane/metadata/ko/release_notes.txt index 8b13789..b21fd2e 100644 --- a/OPeace/fastlane/metadata/ko/release_notes.txt +++ b/OPeace/fastlane/metadata/ko/release_notes.txt @@ -1 +1,3 @@ - +[v 1.0.1] +- 버그 수정 +- 업데이트 팝업 추가 diff --git a/OPeace/fastlane/metadata/review_information/notes.txt b/OPeace/fastlane/metadata/review_information/notes.txt index bd8401d..1d374a0 100644 --- a/OPeace/fastlane/metadata/review_information/notes.txt +++ b/OPeace/fastlane/metadata/review_information/notes.txt @@ -1,2 +1,3 @@ 애플로그인 으로 가능 -신고 관련 영상 이고 너가 말한 Safety - User Generated Content 에 대한 규제에 대한 영상이야 +신고 관련 영상 이고 너가 말한 Safety - User Generated Content 에 대한 규제에 대한 영상이야 +말한겅 에대한 수정한 영상이야 diff --git a/OPeace/fastlane/report.xml b/OPeace/fastlane/report.xml index 84502e8..75c9315 100644 --- a/OPeace/fastlane/report.xml +++ b/OPeace/fastlane/report.xml @@ -5,62 +5,62 @@ - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/SignUpCheckInfoDTOModel.swift b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/SignUpCheckInfoDTOModel.swift new file mode 100644 index 0000000..de65d6a --- /dev/null +++ b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/SignUpCheckInfoDTOModel.swift @@ -0,0 +1,31 @@ +// +// SignUpCheckInfoDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public struct SignUpCheckInfoDTOModel: Codable , Equatable { + public let data: SignUpCheckInfoResponseDTOModel + + public init( + data: SignUpCheckInfoResponseDTOModel + ) { + self.data = data + } +} + +public struct SignUpCheckInfoResponseDTOModel: Codable, Equatable { + public let message: String + public let exists: Bool + + public init( + message: String, + exists: Bool + ) { + self.message = message + self.exists = exists + } +} diff --git a/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/SignUpListDTOModel.swift b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/SignUpListDTOModel.swift new file mode 100644 index 0000000..aa78ed4 --- /dev/null +++ b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/SignUpListDTOModel.swift @@ -0,0 +1,29 @@ +// +// SignUpListDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public struct SignUpListDTOModel: Codable, Equatable { + public let data: SignUpListResponseDTOModel + + public init( + data: SignUpListResponseDTOModel + ) { + self.data = data + } +} + + +public struct SignUpListResponseDTOModel: Codable, Equatable { + public let content: [String] + + public init( + content: [String] + ) { + self.content = content + } +} diff --git a/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/UpdateUserInfoDTOModel.swift b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/UpdateUserInfoDTOModel.swift new file mode 100644 index 0000000..6875f08 --- /dev/null +++ b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/DTO/UpdateUserInfoDTOModel.swift @@ -0,0 +1,48 @@ +// +// UpdateUserInfoDTOModel.swift +// Model +// +// Created by Wonji Suh on 11/11/24. +// + +import Foundation + +public struct UpdateUserInfoDTOModel: Codable, Equatable { + public let data: UpdateUserInfoResponseDTOModel + + public init( + data: UpdateUserInfoResponseDTOModel + ) { + self.data = data + } +} + +public struct UpdateUserInfoResponseDTOModel: Codable, Equatable { + public let socialID, socialType, email: String + public let createdAt, generation: String + public let year: Int? + public let job, nickname: String? + public let isFirstLogin: Bool + + public init( + socialID: String, + socialType: String, + email: String, + createdAt: String, + nickname: String?, + year: Int?, + job: String?, + generation: String, + isFirstLogin: Bool + ) { + self.socialID = socialID + self.socialType = socialType + self.email = email + self.createdAt = createdAt + self.nickname = nickname + self.year = year + self.job = job + self.generation = generation + self.isFirstLogin = isFirstLogin + } +} diff --git a/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/CheckGeneraion.swift b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/CheckGeneraion.swift new file mode 100644 index 0000000..f2e4de8 --- /dev/null +++ b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/CheckGeneraion.swift @@ -0,0 +1,19 @@ +// +// SignUpGeneraion.swift +// Model +// +// Created by 서원지 on 8/28/24. +// +import Foundation + +// MARK: - Welcome +public struct CheckGeneraionModel: Decodable { + let data: CheckGeneraionResponseModel? + +} + +// MARK: - DataClass +struct CheckGeneraionResponseModel: Decodable { + let data: String? + +} diff --git a/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/CheckNickNameResponse.swift b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/CheckNickNameResponse.swift new file mode 100644 index 0000000..f9c4377 --- /dev/null +++ b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/CheckNickNameResponse.swift @@ -0,0 +1,23 @@ +// +// CheckNickName.swift +// Model +// +// Created by 서원지 on 7/25/24. +// + +import Foundation + +public struct CheckNickNameModel: Decodable { + var data: CheckNickNameResponse? + +} + + +struct CheckNickNameResponse: Decodable { + var exists: Bool? + var message: String? + + enum CodingKeys: String, CodingKey { + case exists, message + } +} diff --git a/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/GenerationListResponse.swift b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/GenerationListResponse.swift new file mode 100644 index 0000000..160c3b5 --- /dev/null +++ b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/GenerationListResponse.swift @@ -0,0 +1,20 @@ +// +// GenerationListModel.swift +// Model +// +// Created by 염성훈 on 8/31/24. +// + +import Foundation + +public struct GenerationListResponse: Decodable { + let data: GenerationDataModel? + +} + + +struct GenerationDataModel: Decodable { + let data: [String]? + + +} diff --git a/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/UpdateUserInfoModel.swift b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/UpdateUserInfoModel.swift new file mode 100644 index 0000000..82c53d3 --- /dev/null +++ b/Opeace/Projects/Core/Networking/Model/Sources/SignUp/Model/UpdateUserInfoModel.swift @@ -0,0 +1,34 @@ +// +// UpdateUserInfoModel.swift +// Model +// +// Created by 서원지 on 7/30/24. +// + +import Foundation + +// MARK: - Welcome +public struct UpdateUserInfoModel: Decodable { + let data: UpdateUserInfoResponse? + +} + +// MARK: - DataClass +struct UpdateUserInfoResponse: Decodable { + let socialID, socialType, email: String? + let phone: String? + let createdAt, lastLogin, nickname: String? + let year: Int? + let job, generation: String? + let isFirstLogin: Bool? + + enum CodingKeys: String, CodingKey { + case socialID = "social_id" + case socialType = "social_type" + case email, phone + case createdAt = "created_at" + case lastLogin = "last_login" + case nickname, year, job, generation + case isFirstLogin = "is_first_login" + } +} diff --git a/Opeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/View/HomeCoordinatorView.swift b/Opeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/View/HomeCoordinatorView.swift index e25c331..432bd46 100644 --- a/Opeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/View/HomeCoordinatorView.swift +++ b/Opeace/Projects/Presentation/Presentation/Sources/Home/Coordinator/View/HomeCoordinatorView.swift @@ -13,60 +13,60 @@ import SwiftUIIntrospect import TCACoordinators public struct HomeCoordinatorView: View { - @Bindable var store: StoreOf - - public init( - store: StoreOf - ) { - self.store = store - } - - public var body: some View { - TCARouter(store.scope(state: \.routes, action: \.router)) { screen in - switch screen.case { - case .home(let homeStore): - HomeView(store: homeStore) - .navigationBarBackButtonHidden() - - case .profile(let profileStore): - ProfileView(store: profileStore) { - store.send(.inner(.removePath)) - } - .navigationBarBackButtonHidden() - - case .editProfile(let editProfileStore): - EditProfileView(store: editProfileStore) { - store.send(.inner(.removePath)) - } backToHomeAction: { - store.send(.inner(.removeToHome)) - } - .navigationBarBackButtonHidden() - - case .blockUser(let blockUserStore): - BlockUserView(store: blockUserStore) { - store.send(.inner(.removePath)) - } - .navigationBarBackButtonHidden() - - case .withDraw(let withDrawStore): - WithDrawView(store: withDrawStore) { - store.send(.inner(.removePath)) - } - .navigationBarBackButtonHidden() - - case .createQuestion(let createQuestionStore): - CreateQuestionCoordinatorView(store: createQuestionStore) - .navigationBarBackButtonHidden() - - case .report(let reportStore): - ReportView(store: reportStore) { - store.send(.inner(.removePath)) - } - .navigationBarBackButtonHidden() - } + @Bindable var store: StoreOf + + public init( + store: StoreOf + ) { + self.store = store + } + + public var body: some View { + TCARouter(store.scope(state: \.routes, action: \.router)) { screen in + switch screen.case { + case .home(let homeStore): + HomeView(store: homeStore) + .navigationBarBackButtonHidden() + + case .profile(let profileStore): + ProfileView(store: profileStore) { + store.send(.inner(.removePath)) + } + .navigationBarBackButtonHidden() + + case .editProfile(let editProfileStore): + EditProfileView(store: editProfileStore) { + store.send(.inner(.removePath)) + } backToHomeAction: { + store.send(.inner(.removeToHome)) } - .introspect(.navigationStack, on: .iOS(.v17, .v18)) { navigationController in - navigationController.interactivePopGestureRecognizer?.isEnabled = true + .navigationBarBackButtonHidden() + + case .blockUser(let blockUserStore): + BlockUserView(store: blockUserStore) { + store.send(.inner(.removePath)) } + .navigationBarBackButtonHidden() + + case .withDraw(let withDrawStore): + WithDrawView(store: withDrawStore) { + store.send(.inner(.removePath)) + } + .navigationBarBackButtonHidden() + + case .createQuestion(let createQuestionStore): + CreateQuestionCoordinatorView(store: createQuestionStore) + .navigationBarBackButtonHidden() + + case .report(let reportStore): + ReportView(store: reportStore) { + store.send(.inner(.removePath)) + } + .navigationBarBackButtonHidden() + } + } + .introspect(.navigationStack, on: .iOS(.v17, .v18)) { navigationController in + navigationController.interactivePopGestureRecognizer?.isEnabled = true } + } }