diff --git a/igoMoney/igoMoney.xcodeproj/project.pbxproj b/igoMoney/igoMoney.xcodeproj/project.pbxproj index 99dc4cf..4ca352b 100644 --- a/igoMoney/igoMoney.xcodeproj/project.pbxproj +++ b/igoMoney/igoMoney.xcodeproj/project.pbxproj @@ -27,6 +27,15 @@ 180FC6A42ABB4BC500DD8477 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180FC6A32ABB4BC500DD8477 /* APIClient.swift */; }; 184BBE2D2AB9F61D00A810DB /* SignInWithAppleDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184BBE2C2AB9F61D00A810DB /* SignInWithAppleDelegate.swift */; }; 184BBE322AB9FAF800A810DB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 184BBE312AB9FAF800A810DB /* LaunchScreen.storyboard */; }; + 18599A432ABC12FF0075DE76 /* RoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A422ABC12FF0075DE76 /* RoundedCorner.swift */; }; + 18599A452ABC2A950075DE76 /* ChallengeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A442ABC2A950075DE76 /* ChallengeCore.swift */; }; + 18599A492ABC32160075DE76 /* MyChallengeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A482ABC32160075DE76 /* MyChallengeSection.swift */; }; + 18599A4B2ABC321E0075DE76 /* MyChallengeSectionCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A4A2ABC321E0075DE76 /* MyChallengeSectionCore.swift */; }; + 18599A4E2ABC32790075DE76 /* EmptyChallengeListSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A4D2ABC32790075DE76 /* EmptyChallengeListSection.swift */; }; + 18599A502ABC329C0075DE76 /* EmptyChallengeListSectionCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A4F2ABC329C0075DE76 /* EmptyChallengeListSectionCore.swift */; }; + 18599A532ABC34640075DE76 /* ChallengeInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A522ABC34640075DE76 /* ChallengeInformation.swift */; }; + 18599A572ABC35C70075DE76 /* EmptyChallengeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A562ABC35C70075DE76 /* EmptyChallengeDetail.swift */; }; + 18599A592ABC43D80075DE76 /* EmptyChallengeDetailCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18599A582ABC43D80075DE76 /* EmptyChallengeDetailCore.swift */; }; 18606F492AB9D20A00025C37 /* ProfileSettingCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18606F482AB9D20A00025C37 /* ProfileSettingCore.swift */; }; 187252C72ABC07AE00DCF08C /* MainTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187252C62ABC07AE00DCF08C /* MainTab.swift */; }; 187252C92ABC07C700DCF08C /* MainCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187252C82ABC07C700DCF08C /* MainCore.swift */; }; @@ -64,6 +73,15 @@ 184BBE2C2AB9F61D00A810DB /* SignInWithAppleDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInWithAppleDelegate.swift; sourceTree = ""; }; 184BBE302AB9FA7B00A810DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 184BBE312AB9FAF800A810DB /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 18599A422ABC12FF0075DE76 /* RoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorner.swift; sourceTree = ""; }; + 18599A442ABC2A950075DE76 /* ChallengeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeCore.swift; sourceTree = ""; }; + 18599A482ABC32160075DE76 /* MyChallengeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyChallengeSection.swift; sourceTree = ""; }; + 18599A4A2ABC321E0075DE76 /* MyChallengeSectionCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyChallengeSectionCore.swift; sourceTree = ""; }; + 18599A4D2ABC32790075DE76 /* EmptyChallengeListSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyChallengeListSection.swift; sourceTree = ""; }; + 18599A4F2ABC329C0075DE76 /* EmptyChallengeListSectionCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyChallengeListSectionCore.swift; sourceTree = ""; }; + 18599A522ABC34640075DE76 /* ChallengeInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeInformation.swift; sourceTree = ""; }; + 18599A562ABC35C70075DE76 /* EmptyChallengeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyChallengeDetail.swift; sourceTree = ""; }; + 18599A582ABC43D80075DE76 /* EmptyChallengeDetailCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyChallengeDetailCore.swift; sourceTree = ""; }; 18606F482AB9D20A00025C37 /* ProfileSettingCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingCore.swift; sourceTree = ""; }; 187252C62ABC07AE00DCF08C /* MainTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTab.swift; sourceTree = ""; }; 187252C82ABC07C700DCF08C /* MainCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCore.swift; sourceTree = ""; }; @@ -204,10 +222,41 @@ path = UserClient; sourceTree = ""; }; + 18599A472ABC32060075DE76 /* MyChallengeSection */ = { + isa = PBXGroup; + children = ( + 18599A482ABC32160075DE76 /* MyChallengeSection.swift */, + 18599A4A2ABC321E0075DE76 /* MyChallengeSectionCore.swift */, + ); + path = MyChallengeSection; + sourceTree = ""; + }; + 18599A4C2ABC325E0075DE76 /* EmptyChallengeListSection */ = { + isa = PBXGroup; + children = ( + 18599A552ABC35B40075DE76 /* EmptyChallengeDetail */, + 18599A4D2ABC32790075DE76 /* EmptyChallengeListSection.swift */, + 18599A4F2ABC329C0075DE76 /* EmptyChallengeListSectionCore.swift */, + ); + path = EmptyChallengeListSection; + sourceTree = ""; + }; + 18599A552ABC35B40075DE76 /* EmptyChallengeDetail */ = { + isa = PBXGroup; + children = ( + 18599A562ABC35C70075DE76 /* EmptyChallengeDetail.swift */, + 18599A582ABC43D80075DE76 /* EmptyChallengeDetailCore.swift */, + ); + path = EmptyChallengeDetail; + sourceTree = ""; + }; 187252CC2ABC0B6200DCF08C /* ChallengeScene */ = { isa = PBXGroup; children = ( + 18599A4C2ABC325E0075DE76 /* EmptyChallengeListSection */, + 18599A472ABC32060075DE76 /* MyChallengeSection */, 187252CD2ABC0B7B00DCF08C /* ChallengeScene.swift */, + 18599A442ABC2A950075DE76 /* ChallengeCore.swift */, ); path = ChallengeScene; sourceTree = ""; @@ -233,6 +282,7 @@ isa = PBXGroup; children = ( 18CD8F2F2AB9A1EF006FEA6F /* Provier.swift */, + 18599A522ABC34640075DE76 /* ChallengeInformation.swift */, ); path = Models; sourceTree = ""; @@ -266,6 +316,7 @@ 18CD8F312AB9A57D006FEA6F /* Constants.swift */, 180FC68C2ABB12FC00DD8477 /* UIApplication + Extensions.swift */, 180FC6902ABB157200DD8477 /* Bundle+Extensions.swift */, + 18599A422ABC12FF0075DE76 /* RoundedCorner.swift */, ); path = Utility; sourceTree = ""; @@ -357,13 +408,18 @@ 184BBE2D2AB9F61D00A810DB /* SignInWithAppleDelegate.swift in Sources */, 18D6236C2AB8BACD006EAC39 /* AuthButton.swift in Sources */, 180FC68D2ABB12FC00DD8477 /* UIApplication + Extensions.swift in Sources */, + 18599A4B2ABC321E0075DE76 /* MyChallengeSectionCore.swift in Sources */, 180FC6A42ABB4BC500DD8477 /* APIClient.swift in Sources */, 18606F492AB9D20A00025C37 /* ProfileSettingCore.swift in Sources */, 180FC6A22ABB48DC00DD8477 /* Networking.swift in Sources */, + 18599A572ABC35C70075DE76 /* EmptyChallengeDetail.swift in Sources */, 18CD8F2D2AB995F5006FEA6F /* SignUpCore.swift in Sources */, + 18599A532ABC34640075DE76 /* ChallengeInformation.swift in Sources */, + 18599A452ABC2A950075DE76 /* ChallengeCore.swift in Sources */, 187252CB2ABC081F00DCF08C /* MainRoundTabBar.swift in Sources */, 180FC6912ABB157200DD8477 /* Bundle+Extensions.swift in Sources */, 180FC69B2ABB35D200DD8477 /* APIError.swift in Sources */, + 18599A492ABC32160075DE76 /* MyChallengeSection.swift in Sources */, 187252C72ABC07AE00DCF08C /* MainTab.swift in Sources */, 180FC67D2ABAF62500DD8477 /* AuthController.swift in Sources */, 18CD8F302AB9A1EF006FEA6F /* Provier.swift in Sources */, @@ -372,13 +428,17 @@ 180FC69E2ABB488F00DD8477 /* UserClient.swift in Sources */, 187252CE2ABC0B7B00DCF08C /* ChallengeScene.swift in Sources */, 180FC6972ABB35A800DD8477 /* HTTPMethod.swift in Sources */, + 18599A502ABC329C0075DE76 /* EmptyChallengeListSectionCore.swift in Sources */, + 18599A432ABC12FF0075DE76 /* RoundedCorner.swift in Sources */, 180363AA2AB82DCD00082F8A /* igoMoneyApp.swift in Sources */, 18CD8F272AB970C3006FEA6F /* AuthCore.swift in Sources */, 180FC6992ABB35C000DD8477 /* HTTPBody.swift in Sources */, 18CD8F322AB9A57D006FEA6F /* Constants.swift in Sources */, 18CD8F352AB9BC58006FEA6F /* ProfileSettingView.swift in Sources */, + 18599A4E2ABC32790075DE76 /* EmptyChallengeListSection.swift in Sources */, 187252D12ABC0BA800DCF08C /* MyPageScene.swift in Sources */, 187252C92ABC07C700DCF08C /* MainCore.swift in Sources */, + 18599A592ABC43D80075DE76 /* EmptyChallengeDetailCore.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/igoMoney/igoMoney/Resource/Assets.xcassets/default_profile.imageset/Contents.json b/igoMoney/igoMoney/Resource/Assets.xcassets/default_profile.imageset/Contents.json new file mode 100644 index 0000000..12cc894 --- /dev/null +++ b/igoMoney/igoMoney/Resource/Assets.xcassets/default_profile.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "default_profile.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/igoMoney/igoMoney/Resource/Assets.xcassets/default_profile.imageset/default_profile.svg b/igoMoney/igoMoney/Resource/Assets.xcassets/default_profile.imageset/default_profile.svg new file mode 100644 index 0000000..47a8384 --- /dev/null +++ b/igoMoney/igoMoney/Resource/Assets.xcassets/default_profile.imageset/default_profile.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/igoMoney/igoMoney/Resource/Assets.xcassets/gray.colorset/Contents.json b/igoMoney/igoMoney/Resource/Assets.xcassets/gray.colorset/Contents.json new file mode 100644 index 0000000..bc6fcf7 --- /dev/null +++ b/igoMoney/igoMoney/Resource/Assets.xcassets/gray.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x45", + "green" : "0x45", + "red" : "0x45" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/igoMoney/igoMoney/Resource/Assets.xcassets/gray2.colorset/Contents.json b/igoMoney/igoMoney/Resource/Assets.xcassets/gray2.colorset/Contents.json new file mode 100644 index 0000000..5320a82 --- /dev/null +++ b/igoMoney/igoMoney/Resource/Assets.xcassets/gray2.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x64", + "green" : "0x64", + "red" : "0x64" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/igoMoney/igoMoney/Resource/Assets.xcassets/gray6.colorset/Contents.json b/igoMoney/igoMoney/Resource/Assets.xcassets/gray3.colorset/Contents.json similarity index 100% rename from igoMoney/igoMoney/Resource/Assets.xcassets/gray6.colorset/Contents.json rename to igoMoney/igoMoney/Resource/Assets.xcassets/gray3.colorset/Contents.json diff --git a/igoMoney/igoMoney/Resource/Assets.xcassets/gray7.colorset/Contents.json b/igoMoney/igoMoney/Resource/Assets.xcassets/gray4.colorset/Contents.json similarity index 100% rename from igoMoney/igoMoney/Resource/Assets.xcassets/gray7.colorset/Contents.json rename to igoMoney/igoMoney/Resource/Assets.xcassets/gray4.colorset/Contents.json diff --git a/igoMoney/igoMoney/Resource/Assets.xcassets/gray8.colorset/Contents.json b/igoMoney/igoMoney/Resource/Assets.xcassets/gray5.colorset/Contents.json similarity index 100% rename from igoMoney/igoMoney/Resource/Assets.xcassets/gray8.colorset/Contents.json rename to igoMoney/igoMoney/Resource/Assets.xcassets/gray5.colorset/Contents.json diff --git a/igoMoney/igoMoney/Source/AuthScene/ProfileSettingView/ProfileSettingView.swift b/igoMoney/igoMoney/Source/AuthScene/ProfileSettingView/ProfileSettingView.swift index 81a957c..e02dea9 100644 --- a/igoMoney/igoMoney/Source/AuthScene/ProfileSettingView/ProfileSettingView.swift +++ b/igoMoney/igoMoney/Source/AuthScene/ProfileSettingView/ProfileSettingView.swift @@ -62,12 +62,12 @@ struct ProfileSettingView: View { .font(.system(size: 18, weight: .bold)) .foregroundColor( viewStore.nickNameState == .completeConfirm ? - Color.black : ColorConstants.gray7 + Color.black : ColorConstants.gray4 ) .padding(.vertical) .background( viewStore.nickNameState == .completeConfirm ? - ColorConstants.primary3 : ColorConstants.gray8 + ColorConstants.primary3 : ColorConstants.gray5 ) .cornerRadius(8) .padding([.horizontal, .bottom], 24) @@ -98,7 +98,7 @@ struct InputHeaderView: View { if let detail = detail { Text(detail) .font(.system(size: 12, weight: .medium)) - .foregroundColor(ColorConstants.gray6) + .foregroundColor(ColorConstants.gray3) } } } @@ -127,13 +127,13 @@ struct InputFormView: View { } .font(.system(size: 12, weight: .medium)) .foregroundColor( - viewStore.nickNameState == .disableConfirm ? ColorConstants.gray7 : .white + viewStore.nickNameState == .disableConfirm ? ColorConstants.gray4 : .white ) .padding(.horizontal, 8) .padding(.vertical, 5) .background( viewStore.nickNameState == .disableConfirm ? - ColorConstants.gray7 : viewStore.nickNameState == .completeConfirm ? + ColorConstants.gray4 : viewStore.nickNameState == .completeConfirm ? ColorConstants.primary3 : ColorConstants.primary ) .cornerRadius(.infinity) diff --git a/igoMoney/igoMoney/Source/ChallengeScene/ChallengeCore.swift b/igoMoney/igoMoney/Source/ChallengeScene/ChallengeCore.swift new file mode 100644 index 0000000..735a936 --- /dev/null +++ b/igoMoney/igoMoney/Source/ChallengeScene/ChallengeCore.swift @@ -0,0 +1,39 @@ +// +// ChallengeCore.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import ComposableArchitecture +import SwiftUI + +struct ChallengeCore: Reducer { + struct State: Equatable { + var myChallengeState = MyChallengeSectionCore.State(color: .red) + var emptyChallengeListState = EmptyChallengeListSectionCore.State() + } + + enum Action { + case myChallengeAction(MyChallengeSectionCore.Action) + case emptyChallengeAction(EmptyChallengeListSectionCore.Action) + } + + var body: some Reducer { + Reduce { state, action in + switch action { + case .myChallengeAction: + return .none + case .emptyChallengeAction: + return .none + } + } + + Scope(state: \.myChallengeState, action: /Action.myChallengeAction) { + MyChallengeSectionCore() + } + + Scope(state: \.emptyChallengeListState, action: /Action.emptyChallengeAction) { + EmptyChallengeListSectionCore() + } + } +} diff --git a/igoMoney/igoMoney/Source/ChallengeScene/ChallengeScene.swift b/igoMoney/igoMoney/Source/ChallengeScene/ChallengeScene.swift index 79283fd..939bfbd 100644 --- a/igoMoney/igoMoney/Source/ChallengeScene/ChallengeScene.swift +++ b/igoMoney/igoMoney/Source/ChallengeScene/ChallengeScene.swift @@ -6,10 +6,130 @@ import SwiftUI +import ComposableArchitecture + struct ChallengeScene: View { + let store: StoreOf + + @ViewBuilder + var titleHeader: some View { + HStack { + Image("icon_text_main") + .resizable() + .scaledToFit() + .frame(height: 20) + + Spacer() + } + .padding(24) + } + var body: some View { VStack { - Text("Challenge Scene") + titleHeader + + VStack { + ScrollView(showsIndicators: false) { + VStack(spacing: 16) { + MyChallengeSection( + store: store.scope( + state: \.myChallengeState, + action: ChallengeCore.Action.myChallengeAction + ) + ) + + EmptyChallengeListSection( + store: store.scope( + state: \.emptyChallengeListState, + action: ChallengeCore.Action.emptyChallengeAction + ) + ) + } + .padding([.horizontal, .top], 24) + .padding(.bottom, 28) + } + } + .background(Color.white) + .cornerRadius(20, corner: .topLeft) + .cornerRadius(20, corner: .topRight) + } + .background( + Color("background_color") + .edgesIgnoringSafeArea(.top) + ) + } +} + +extension ChallengeScene { + enum Section { + case myChallenge + case emptyChallenge + + var title: String { + switch self { + case .myChallenge: + return "🔥 참여중인 챌린지" + case .emptyChallenge: + return "📣 대기중인 챌린지" + } + } + + var detail: String? { + switch self { + case .myChallenge: + return nil + case .emptyChallenge: + return "내가 도전하고 싶은 챌린지를 선택하세요." + } + } + + var hasButton: Bool { + return self == .emptyChallenge } } } + +struct ChallengeSectionTitleView: View { + let sectionType: ChallengeScene.Section + let buttonAction: (() -> Void)? + + var body: some View { + VStack(alignment: .leading, spacing: 2) { + HStack { + Text(sectionType.title) + .font(.system(size: 30, weight: .bold)) + + Spacer() + + if sectionType.hasButton { + // TODO: - Button 생성 + Button { + buttonAction?() + } label: { + Image(systemName: "chevron.right") + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + } + + } + } + + if let detail = sectionType.detail { + Text(detail) + .font(.system(size: 14, weight: .medium)) + } + } + } +} + +struct ChallengeScene_Previews: PreviewProvider { + static var previews: some View { + ChallengeScene( + store: Store( + initialState: ChallengeCore.State(), + reducer: { ChallengeCore() } + ) + ) + } +} diff --git a/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeDetail/EmptyChallengeDetail.swift b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeDetail/EmptyChallengeDetail.swift new file mode 100644 index 0000000..cafc203 --- /dev/null +++ b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeDetail/EmptyChallengeDetail.swift @@ -0,0 +1,78 @@ +// +// EmptyChallengeDetail.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import SwiftUI + +import ComposableArchitecture + +struct EmptyChallengeDetail: View { + let store: StoreOf + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + // 챌린지 타이틀 + WithViewStore(store, observe: { $0.title }) { viewStore in + Text("같이 절약 챌린지 성공해봐요!") + .multilineTextAlignment(.leading) + .minimumScaleFactor(0.5) + .lineLimit(2) + .font(.system(size: 16, weight: .bold)) + } + + // 챌린지 생성자 닉네임 + WithViewStore(store, observe: { $0.user }) { viewStore in + Text(viewStore.nickName) + .font(.system(size: 12, weight: .medium)) + } + + VStack(alignment: .leading, spacing: 2) { + // 챌린지 머니 + WithViewStore(store, observe: { $0.targetMoneyDescription }) { viewStore in + Text(viewStore.state) + .padding(.horizontal, 2) + .background(ColorConstants.primary7) + .cornerRadius(4) + } + + Text("내일부터 시작") + .padding(.horizontal, 2) + .background(ColorConstants.primary7) + .cornerRadius(4) + } + .font(.system(size: 12, weight: .medium)) + + HStack { + Spacer() + + // 사용자 이미지 + WithViewStore(store, observe: { $0.user.profileImagePath }) { viewStore in + if let path = viewStore.state { + // 사용자 프로필 이미지로 변경하기 + Image("default_profile") + .resizable() + .scaledToFit() + .frame(width: 60) + } else { + Image("default_profile") + .resizable() + .scaledToFit() + .frame(width: 60) + } + } + } + } + .padding(16) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(.white) + .shadow( + color: ColorConstants.gray2.opacity(0.3), + radius: 8, + y: 2 + ) + ) + } +} diff --git a/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeDetail/EmptyChallengeDetailCore.swift b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeDetail/EmptyChallengeDetailCore.swift new file mode 100644 index 0000000..75d443c --- /dev/null +++ b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeDetail/EmptyChallengeDetailCore.swift @@ -0,0 +1,33 @@ +// +// EmptyChallengeDetailCore.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import Foundation + +import ComposableArchitecture + +struct ChallengeDetailCore: Reducer { + struct State: Equatable, Identifiable { + let id: UUID + + var title: String + var content: String + var targetAmount: Int + var user: User + + var targetMoneyDescription: String { + return "💸 \(targetAmount)원" + } + } + + enum Action: Equatable, Sendable { + + } + + func reduce(into state: inout State, action: Action) -> Effect { + return .none + } +} + diff --git a/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeListSection.swift b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeListSection.swift new file mode 100644 index 0000000..85aed98 --- /dev/null +++ b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeListSection.swift @@ -0,0 +1,38 @@ +// +// EmptyChallengeListSection.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import SwiftUI + +import ComposableArchitecture + +struct EmptyChallengeListSection: View { + let store: StoreOf + + private func generateGridItem(count: Int, spacing: CGFloat) -> [GridItem] { + let gridItem = GridItem(.flexible(), spacing: spacing) + return Array(repeating: gridItem, count: count) + } + + var body: some View { + VStack { + ChallengeSectionTitleView(sectionType: .emptyChallenge) { + print("Move Empty List") + } + + LazyVGrid(columns: generateGridItem(count: 2, spacing: 16), spacing: 12) { + ForEachStore(store.scope( + state: \.challenges, + action: EmptyChallengeListSectionCore.Action.challengeDetail) + ) { store in + EmptyChallengeDetail(store: store) + } + } + } + .onAppear { + store.send(._onAppear) + } + } +} diff --git a/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeListSectionCore.swift b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeListSectionCore.swift new file mode 100644 index 0000000..1eafe02 --- /dev/null +++ b/igoMoney/igoMoney/Source/ChallengeScene/EmptyChallengeListSection/EmptyChallengeListSectionCore.swift @@ -0,0 +1,53 @@ +// +// EmptyChallengeListSectionCore.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import Foundation + +import ComposableArchitecture + +struct EmptyChallengeListSectionCore: Reducer { + struct State: Equatable { + var challenges: IdentifiedArrayOf = [] + } + + enum Action: Equatable, Sendable { + // User Action + // Inner Action + case _onAppear + + // Child Action + case challengeDetail(id: ChallengeDetailCore.State.ID, action: ChallengeDetailCore.Action) + } + + var body: some Reducer { + Reduce { state, action in + switch action { + // Inner Action + case ._onAppear: + let informations = ChallengeInformation.default + informations.forEach { + let detailState = ChallengeDetailCore.State( + id: UUID(), + title: $0.title, + content: $0.content, + targetAmount: $0.targetAmount, + user: $0.user + ) + state.challenges.append(detailState) + } + + return .none + + // Child Action + case .challengeDetail: + return .none + } + } + .forEach(\.challenges, action: /Action.challengeDetail) { + ChallengeDetailCore() + } + } +} diff --git a/igoMoney/igoMoney/Source/ChallengeScene/MyChallengeSection/MyChallengeSection.swift b/igoMoney/igoMoney/Source/ChallengeScene/MyChallengeSection/MyChallengeSection.swift new file mode 100644 index 0000000..b747012 --- /dev/null +++ b/igoMoney/igoMoney/Source/ChallengeScene/MyChallengeSection/MyChallengeSection.swift @@ -0,0 +1,35 @@ +// +// MyChallengeSection.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import SwiftUI + +import ComposableArchitecture + +struct MyChallengeSection: View { + let store: StoreOf + // TODO: - 섹션 reducer 연결하기 + var body: some View { + ChallengeSectionTitleView( + sectionType: .myChallenge, + buttonAction: nil + ) + + WithViewStore(store, observe: { $0 }) { viewStore in + // TODO: - 상태에 따른 화면 구현 + RoundedRectangle(cornerRadius: 8) + .fill(viewStore.color) + .frame(height: 100) + .onTapGesture { + let randomColor = Color( + red: .random(in: 0...1), + green: .random(in: 0...1), + blue: .random(in: 0...1) + ) + viewStore.send(.changeColor(randomColor)) + } + } + } +} diff --git a/igoMoney/igoMoney/Source/ChallengeScene/MyChallengeSection/MyChallengeSectionCore.swift b/igoMoney/igoMoney/Source/ChallengeScene/MyChallengeSection/MyChallengeSectionCore.swift new file mode 100644 index 0000000..335800a --- /dev/null +++ b/igoMoney/igoMoney/Source/ChallengeScene/MyChallengeSection/MyChallengeSectionCore.swift @@ -0,0 +1,28 @@ +// +// MyChallengeSectionCore.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import SwiftUI + +import ComposableArchitecture + +struct MyChallengeSectionCore: Reducer { + struct State: Equatable { + var color: Color // Action Test 용 + } + + enum Action: Equatable { + case changeColor(Color) + } + + func reduce(into state: inout State, action: Action) -> Effect { + switch action { + case .changeColor(let color): + state.color = color + return .none + } + } +} + diff --git a/igoMoney/igoMoney/Source/MainScene/MainCore.swift b/igoMoney/igoMoney/Source/MainScene/MainCore.swift index 7565465..6f097b7 100644 --- a/igoMoney/igoMoney/Source/MainScene/MainCore.swift +++ b/igoMoney/igoMoney/Source/MainScene/MainCore.swift @@ -9,17 +9,31 @@ import ComposableArchitecture struct MainCore: Reducer { struct State: Equatable { var selectedTab: MainTab = .challenge + + var challengeState = ChallengeCore.State() } enum Action { case selectedTabChange(MainTab) + + // Child Action + case challengeAction(ChallengeCore.Action) } - func reduce(into state: inout State, action: Action) -> Effect { - switch action { - case .selectedTabChange(let tab): - state.selectedTab = tab - return .none + var body: some Reducer { + Reduce { state, action in + switch action { + case .selectedTabChange(let tab): + state.selectedTab = tab + return .none + // Child Action + case .challengeAction: + return .none + } + } + + Scope(state: \.challengeState, action: /Action.challengeAction) { + ChallengeCore() } } } diff --git a/igoMoney/igoMoney/Source/MainScene/MainRoundTabBar.swift b/igoMoney/igoMoney/Source/MainScene/MainRoundTabBar.swift index 3863f9b..b74945b 100644 --- a/igoMoney/igoMoney/Source/MainScene/MainRoundTabBar.swift +++ b/igoMoney/igoMoney/Source/MainScene/MainRoundTabBar.swift @@ -27,7 +27,7 @@ public struct TabConfiguration { static let `default` = TabConfiguration( accentColor: ColorConstants.primary, - defaultColor: ColorConstants.gray6, + defaultColor: ColorConstants.gray3, cornerRadius: 10 ) } @@ -81,6 +81,7 @@ struct RoundTabBar: View { ) } .edgesIgnoringSafeArea(.bottom) + .background(selectedTab == .challenge ? Color("background_color") : .white) } } diff --git a/igoMoney/igoMoney/Source/MainScene/MainScene.swift b/igoMoney/igoMoney/Source/MainScene/MainScene.swift index 87127a0..c259112 100644 --- a/igoMoney/igoMoney/Source/MainScene/MainScene.swift +++ b/igoMoney/igoMoney/Source/MainScene/MainScene.swift @@ -11,29 +11,63 @@ import ComposableArchitecture struct MainScene: View { let store: StoreOf var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in - RoundTabBar( - selectedTab: viewStore.binding( - get: \.selectedTab, - send: MainCore.Action.selectedTabChange - ), - tabSetting: .default, - shadowSetting: ShadowConfiguration( - color: ColorConstants.gray6, - radius: 10, - x: .zero, - y: 5 - ) - ) { - ZStack { - if viewStore.selectedTab == .challenge { - ChallengeScene() - } + ZStack { + Color.white + .edgesIgnoringSafeArea(.all) + + WithViewStore(store, observe: { $0 }) { viewStore in + if viewStore.selectedTab == .challenge { + ChallengeScene( + store: store.scope( + state: \.challengeState, + action: MainCore.Action.challengeAction + ) + ) + .padding(.bottom, 60) + } + + VStack { + Spacer() - if viewStore.selectedTab == .myPage { - MyPageScene() + HStack { + Spacer() + + ForEach(MainTab.allCases, id: \.title) { tab in + VStack { + Image( + viewStore.selectedTab == tab ? + tab.selectedIconName : tab.unSelectedIconName + ) + .resizable() + .scaledToFit() + .frame(width: 20) + .foregroundColor( + viewStore.selectedTab == tab ? + ColorConstants.primary : ColorConstants.gray3 + ) + + Text(tab.title) + .font(.system(size: 12, weight: .medium)) + .foregroundColor( + viewStore.selectedTab == tab ? + ColorConstants.primary : ColorConstants.gray3 + ) + } + + Spacer() + } } + .padding(.top, 8) + .padding(.bottom, 32) + .background(Color.white) + .cornerRadius(20) + .shadow( + color: ColorConstants.gray3, + radius: 10, + y: 5 + ) } + .edgesIgnoringSafeArea(.bottom) } } } diff --git a/igoMoney/igoMoney/Source/Models/ChallengeInformation.swift b/igoMoney/igoMoney/Source/Models/ChallengeInformation.swift new file mode 100644 index 0000000..e18dfd1 --- /dev/null +++ b/igoMoney/igoMoney/Source/Models/ChallengeInformation.swift @@ -0,0 +1,43 @@ +// +// ChallengeInformation.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import Foundation + +struct ChallengeInformation: Decodable, Equatable { + let title: String + let content: String + let targetAmount: Int + let user: User + + static let `default`: [Self] = Array( + repeating: ChallengeInformation( + title: "일주일에 3만원으로 살아남기 👊🏻", + content: "", + targetAmount: 30000, + user: .default + ), + count: 16 + ) + + + static func == (lhs: ChallengeInformation, rhs: ChallengeInformation) -> Bool { + return lhs.title == rhs.title && lhs.user.id == rhs.user.id + } +} + +struct User: Decodable, Equatable { + let id: String + let nickName: String + let profileImagePath: String? + let email: String + + static let `default` = User( + id: UUID().uuidString, + nickName: "아이고머니", + profileImagePath: nil, + email: "cow970814@naver.com" + ) +} diff --git a/igoMoney/igoMoney/Source/Utility/Constants.swift b/igoMoney/igoMoney/Source/Utility/Constants.swift index 8d34375..97e81d2 100644 --- a/igoMoney/igoMoney/Source/Utility/Constants.swift +++ b/igoMoney/igoMoney/Source/Utility/Constants.swift @@ -16,7 +16,9 @@ enum ColorConstants { static let primary7 = Color("AccentColor7") static let primary8 = Color("AccentColor8") - static let gray6 = Color("gray6") - static let gray7 = Color("gray7") - static let gray8 = Color("gray8") + static let gray = Color("gray") + static let gray2 = Color("gray2") + static let gray3 = Color("gray3") + static let gray4 = Color("gray4") + static let gray5 = Color("gray8") } diff --git a/igoMoney/igoMoney/Source/Utility/RoundedCorner.swift b/igoMoney/igoMoney/Source/Utility/RoundedCorner.swift new file mode 100644 index 0000000..75e17a0 --- /dev/null +++ b/igoMoney/igoMoney/Source/Utility/RoundedCorner.swift @@ -0,0 +1,29 @@ +// +// RoundedCorner.swift +// igoMoney +// +// Copyright (c) 2023 Minii All rights reserved. + +import SwiftUI + +struct RoundedCorner: Shape { + var radius: CGFloat = .infinity + var corner: UIRectCorner = .allCorners + + func path(in rect: CGRect) -> Path { + let path = UIBezierPath( + roundedRect: rect, + byRoundingCorners: corner, + cornerRadii: CGSize(width: radius, height: radius) + ) + return Path(path.cgPath) + } +} + +extension View { + func cornerRadius(_ radius: CGFloat, corner: UIRectCorner) -> some View { + clipShape( + RoundedCorner(radius: radius, corner: corner) + ) + } +}