From 1ca1cf106851f2ae404f1633ce4781e9aa379d57 Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Fri, 9 Aug 2024 23:26:16 +0900 Subject: [PATCH 01/11] =?UTF-8?q?fix:=20OnboardingFeature=20interface=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/OnboardingCoreInterface.swift | 33 ------------------- .../Interface/OnboardingView.swift | 28 ---------------- .../Feature/OnboardingFeature/Project.swift | 3 +- .../Sources/OnboardingView.swift | 9 +++++ 4 files changed, 10 insertions(+), 63 deletions(-) delete mode 100644 Projects/Feature/OnboardingFeature/Interface/OnboardingCoreInterface.swift delete mode 100644 Projects/Feature/OnboardingFeature/Interface/OnboardingView.swift create mode 100644 Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift diff --git a/Projects/Feature/OnboardingFeature/Interface/OnboardingCoreInterface.swift b/Projects/Feature/OnboardingFeature/Interface/OnboardingCoreInterface.swift deleted file mode 100644 index 72fd03e..0000000 --- a/Projects/Feature/OnboardingFeature/Interface/OnboardingCoreInterface.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// OnboardingCoreInterface.swift -// OnboardingFeatureInterface -// -// Created by devMinseok on 7/22/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import Foundation - -import ComposableArchitecture - -@Reducer -public struct OnboardingCore { - private let reducer: Reduce - - public init(reducer: Reduce) { - self.reducer = reducer - } - - @ObservableState - public struct State: Equatable { - public init() {} - } - - public enum Action { - case onAppear - } - - public var body: some ReducerOf { - reducer - } -} diff --git a/Projects/Feature/OnboardingFeature/Interface/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Interface/OnboardingView.swift deleted file mode 100644 index 6a32071..0000000 --- a/Projects/Feature/OnboardingFeature/Interface/OnboardingView.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// OnboardingView.swift -// OnboardingFeatureInterface -// -// Created by devMinseok on 7/22/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import SwiftUI - -import ComposableArchitecture - -public struct OnboardingView: View { - let store: StoreOf - - public init(store: StoreOf) { - self.store = store - } - - public var body: some View { - VStack { - Text("Onboarding") - .foregroundStyle(Color.black) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.white) - } -} diff --git a/Projects/Feature/OnboardingFeature/Project.swift b/Projects/Feature/OnboardingFeature/Project.swift index 8ab4340..57628ca 100644 --- a/Projects/Feature/OnboardingFeature/Project.swift +++ b/Projects/Feature/OnboardingFeature/Project.swift @@ -10,13 +10,12 @@ let project: Project = .makeTMABasedProject( scripts: [], targets: [ .sources, - .interface, .tests, .testing, .example ], dependencies: [ - .interface: [ + .sources: [ .dependency(rootModule: Domain.self) ] ] diff --git a/Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift new file mode 100644 index 0000000..ce74d66 --- /dev/null +++ b/Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift @@ -0,0 +1,9 @@ +// +// OnboardingView.swift +// OnboardingFeature +// +// Created by 김지현 on 8/9/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import Foundation From ae1974af8151f71a10f4f6292c26f41fff768526 Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Sat, 10 Aug 2024 01:06:42 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feauture:=20onboarding=20Ui=20=ED=8B=80?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Feature/Sources/AppCore.swift | 1 - .../Feature/Feature/Sources/AppView.swift | 1 - .../Sources/Onboarding/OnboardingCore.swift | 37 ++++++++++++ .../Sources/Onboarding/OnboardingItem.swift | 34 +++++++++++ .../Sources/Onboarding/OnboardingView.swift | 60 +++++++++++++++++++ .../Sources/OnboardingCore.swift | 23 ------- .../Sources/OnboardingView.swift | 9 --- 7 files changed, 131 insertions(+), 34 deletions(-) create mode 100644 Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift create mode 100644 Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift create mode 100644 Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift delete mode 100644 Projects/Feature/OnboardingFeature/Sources/OnboardingCore.swift delete mode 100644 Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift diff --git a/Projects/Feature/Feature/Sources/AppCore.swift b/Projects/Feature/Feature/Sources/AppCore.swift index 5c46a93..05330b9 100644 --- a/Projects/Feature/Feature/Sources/AppCore.swift +++ b/Projects/Feature/Feature/Sources/AppCore.swift @@ -12,7 +12,6 @@ import SplashFeature import HomeFeature import HomeFeatureInterface import OnboardingFeature -import OnboardingFeatureInterface import PushService import ComposableArchitecture diff --git a/Projects/Feature/Feature/Sources/AppView.swift b/Projects/Feature/Feature/Sources/AppView.swift index b9abd97..9b6a249 100644 --- a/Projects/Feature/Feature/Sources/AppView.swift +++ b/Projects/Feature/Feature/Sources/AppView.swift @@ -12,7 +12,6 @@ import SplashFeature import HomeFeature import HomeFeatureInterface import OnboardingFeature -import OnboardingFeatureInterface import ComposableArchitecture diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift new file mode 100644 index 0000000..3cd7116 --- /dev/null +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift @@ -0,0 +1,37 @@ +// +// OnboardingCore.swift +// OnboardingFeature +// +// Created by devMinseok on 7/22/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import OnboardingFeatureInterface + +import ComposableArchitecture + +@Reducer +public struct OnboardingCore { + @ObservableState + public struct State: Equatable { + public init() { } + var data: [OnboardingItem] = OnboardingItemsData + } + + public enum Action { + case onApear + } + + public init() { } + + public var body: some ReducerOf { + Reduce(self.core) + } + + private func core(_ state: inout State, _ action: Action) -> EffectOf { + switch action { + case .onApear: + return .none + } + } +} diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift new file mode 100644 index 0000000..38a1754 --- /dev/null +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift @@ -0,0 +1,34 @@ +// +// OnboardingItem.swift +// OnboardingFeature +// +// Created by 김지현 on 8/9/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import Foundation +import SwiftUI + +import DesignSystem + +struct OnboardingItem: Identifiable, Equatable { + let id = UUID().uuidString + let image: Image + let title: String + let subTitle: String +} + +let OnboardingItemsData: [OnboardingItem] = [ + OnboardingItem( + image: Image(systemName: "star"), + title: "모하냥과 함께 집중시간을 늘려보세요", + subTitle: "고양이 종에 따라 성격이 달라요.\n취향에 맞는 고양이를 선택해 몰입해 보세요."), + OnboardingItem( + image: Image(systemName: "star.fill"), + title: "다른 앱을 실행하면 방해 알림을 보내요", + subTitle: "뽀모도로를 실행한 후, 다른 앱을 사용하면\n설정한 주기로 방해 알림을 보내드려요."), + OnboardingItem( + image: Image(systemName: "home"), + title: "다른 앱을 실행하면 방해 알림을 보내요", + subTitle: "뽀모도로를 실행한 후, 다른 앱을 사용하면\n설정한 주기로 방해 알림을 보내드려요.") +] diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift new file mode 100644 index 0000000..42e21ac --- /dev/null +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -0,0 +1,60 @@ +// +// OnboardingView.swift +// OnboardingFeature +// +// Created by 김지현 on 8/9/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI +import DesignSystem + +import ComposableArchitecture + +public struct OnboardingView: View { + let store: StoreOf + @State var height: CGFloat = .zero + @State var currentData: OnboardingItem = OnboardingItemsData[0] + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack { + Spacer() + VStack { + OnboardingCarouselView(data: $currentData) + Button(title: "시작하기") { + // go to selectCatView + } + .buttonStyle(.box(size: .large, color: .primary)) + } + Spacer() + } + } +} + +struct OnboardingCarouselView: View { + @Binding var data: OnboardingItem + + var body: some View { + VStack { + ZStack { + Rectangle() + .foregroundStyle(Alias.Color.Background.secondary) + data.image + } + Text(data.title) + Text(data.subTitle) + } + + + } +} + +struct OnboardingView_Previews: PreviewProvider { + static var previews: some View { + OnboardingView(store: Store(initialState: OnboardingCore.State()) { OnboardingCore() }) + } +} diff --git a/Projects/Feature/OnboardingFeature/Sources/OnboardingCore.swift b/Projects/Feature/OnboardingFeature/Sources/OnboardingCore.swift deleted file mode 100644 index 9328ed0..0000000 --- a/Projects/Feature/OnboardingFeature/Sources/OnboardingCore.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// OnboardingCore.swift -// OnboardingFeature -// -// Created by devMinseok on 7/22/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import OnboardingFeatureInterface - -import ComposableArchitecture - -extension OnboardingCore { - public init() { - let reducer = Reduce { _, action in - switch action { - case .onAppear: - return .none - } - } - self.init(reducer: reducer) - } -} diff --git a/Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift deleted file mode 100644 index ce74d66..0000000 --- a/Projects/Feature/OnboardingFeature/Sources/OnboardingView.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// OnboardingView.swift -// OnboardingFeature -// -// Created by 김지현 on 8/9/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import Foundation From 180a96d74d20ae9f0972e253ef4f431a76ffdc04 Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Sat, 10 Aug 2024 22:53:59 +0900 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20onboarding=20ui=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Onboarding/OnboardingView.swift | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index 42e21ac..49494b7 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -21,16 +21,21 @@ public struct OnboardingView: View { } public var body: some View { - VStack { - Spacer() + ZStack { + Alias.Color.Background.primary + .ignoresSafeArea() VStack { - OnboardingCarouselView(data: $currentData) - Button(title: "시작하기") { - // go to selectCatView + Spacer() + VStack { + OnboardingCarouselView(data: $currentData) + Button(title: "시작하기") { + // go to selectCatView + } + .buttonStyle(.box(size: .large, color: .primary)) } - .buttonStyle(.box(size: .large, color: .primary)) + .padding(.horizontal, 20) + Spacer() } - Spacer() } } } @@ -39,17 +44,24 @@ struct OnboardingCarouselView: View { @Binding var data: OnboardingItem var body: some View { - VStack { + VStack(spacing: Alias.Spacing.xxxLarge) { ZStack { Rectangle() .foregroundStyle(Alias.Color.Background.secondary) data.image } - Text(data.title) - Text(data.subTitle) - } - + .frame(width: 240, height: 240) + VStack(spacing: Alias.Spacing.small) { + Text(data.title) + .font(Typography.header4) + .foregroundStyle(Alias.Color.Text.primary) + Text(data.subTitle) + .font(Typography.bodyR) + .foregroundStyle(Alias.Color.Text.secondary) + } + .multilineTextAlignment(.center) + } } } From 128841c2d7aa9c21c9038faf2f2702060302b389 Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Wed, 14 Aug 2024 03:21:02 +0900 Subject: [PATCH 04/11] feature: inifinite carousel --- .../Feature/Feature/Sources/AppCore.swift | 4 +- .../Onboarding/Helpers/OffsetReader.swift | 34 ++++++++++++ .../Sources/Onboarding/OnboardingCore.swift | 51 ++++++++++++++++-- .../Sources/Onboarding/OnboardingItem.swift | 50 +++++++++++++++--- .../Sources/Onboarding/OnboardingView.swift | 52 ++++++++++++++----- 5 files changed, 166 insertions(+), 25 deletions(-) create mode 100644 Projects/Feature/OnboardingFeature/Sources/Onboarding/Helpers/OffsetReader.swift diff --git a/Projects/Feature/Feature/Sources/AppCore.swift b/Projects/Feature/Feature/Sources/AppCore.swift index 05330b9..898c6d1 100644 --- a/Projects/Feature/Feature/Sources/AppCore.swift +++ b/Projects/Feature/Feature/Sources/AppCore.swift @@ -94,8 +94,8 @@ public struct AppCore { case .home: return .none - case .onboarding: - return .none +// case .onboarding: +// return .none default: return .none diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/Helpers/OffsetReader.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/Helpers/OffsetReader.swift new file mode 100644 index 0000000..cf029d8 --- /dev/null +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/Helpers/OffsetReader.swift @@ -0,0 +1,34 @@ +// +// OffsetReader.swift +// OnboardingFeature +// +// Created by 김지현 on 8/14/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +struct OffsetKey: PreferenceKey { + static var defaultValue: CGFloat = .zero + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} + +extension View { + @ViewBuilder + func offsetX(_ addObserver: Bool, completion: @escaping (CGFloat) -> Void) -> some View { + self + .frame(maxWidth: .infinity) + .overlay { + if addObserver { + GeometryReader { geometry in + let minX = geometry.frame(in: .global).minX + Color.clear + .preference(key: OffsetKey.self, value: minX) + .onPreferenceChange(OffsetKey.self, perform: completion) + } + } + } + } +} diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift index 3cd7116..102b156 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift @@ -6,8 +6,8 @@ // Copyright © 2024 PomoNyang. All rights reserved. // +import Foundation import OnboardingFeatureInterface - import ComposableArchitecture @Reducer @@ -15,22 +15,67 @@ public struct OnboardingCore { @ObservableState public struct State: Equatable { public init() { } - var data: [OnboardingItem] = OnboardingItemsData + var data = RandomAccessEquatableItems(elements: OnboardingItemsData) + var fakedData: RandomAccessEquatableItems = .init(elements: []) + var width: CGFloat = .zero + var currentIdx: Int = 0 + var currentItemID: String = "" } - public enum Action { + public enum Action: BindableAction { case onApear + case setIndex(Int) + case calculateOffset(CGFloat, OnboardingItem) + case binding(BindingAction) } public init() { } + private enum CancelID { case timer } + @Dependency(\.continuousClock) var clock + public var body: some ReducerOf { + BindingReducer() Reduce(self.core) } private func core(_ state: inout State, _ action: Action) -> EffectOf { switch action { case .onApear: + state.currentItemID = state.data.first!.id.uuidString + state.fakedData.elements = state.data.elements + guard var first = state.data.first, + var last = state.data.last else { return .none } + first.id = .init() + last.id = .init() + state.fakedData.elements.append(first) + state.fakedData.elements.insert(last, at: 0) + return .none + + case .setIndex(let idx): + state.currentIdx = idx + return .none + + case .calculateOffset(let minX, let item): + let fakeIndex = state.fakedData.firstIndex(of: item) ?? 0 + state.currentIdx = state.data.firstIndex { item in + item.id.uuidString == state.currentItemID + } ?? 0 + let pageOffset = minX - state.width * CGFloat(fakeIndex) + let pageProgress: CGFloat = pageOffset / state.width + if -pageProgress < 1.0 { + if state.fakedData.elements.indices.contains(state.fakedData.count - 1) { + state.currentItemID = state.fakedData[state.fakedData.count - 1].id.uuidString + } + } + if -pageProgress > CGFloat(state.fakedData.count - 1) { + if state.fakedData.elements.indices.contains(1) { + state.currentItemID = state.fakedData[1].id.uuidString + } + } + return .none + + default: return .none } } diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift index 38a1754..c80160f 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift @@ -11,24 +11,58 @@ import SwiftUI import DesignSystem -struct OnboardingItem: Identifiable, Equatable { - let id = UUID().uuidString - let image: Image +public struct OnboardingItem: Hashable, Identifiable { + public var id: UUID = .init() + let image: UIImage let title: String let subTitle: String } +func getIndex( + item: OnboardingItem +) -> Int { + let index = OnboardingItemsData.firstIndex { currentItem in + return currentItem.title == item.title + } ?? 0 + return index +} + let OnboardingItemsData: [OnboardingItem] = [ OnboardingItem( - image: Image(systemName: "star"), + image: UIImage(systemName: "star")!, title: "모하냥과 함께 집중시간을 늘려보세요", subTitle: "고양이 종에 따라 성격이 달라요.\n취향에 맞는 고양이를 선택해 몰입해 보세요."), OnboardingItem( - image: Image(systemName: "star.fill"), + image: UIImage(systemName: "star.fill")!, title: "다른 앱을 실행하면 방해 알림을 보내요", subTitle: "뽀모도로를 실행한 후, 다른 앱을 사용하면\n설정한 주기로 방해 알림을 보내드려요."), OnboardingItem( - image: Image(systemName: "home"), - title: "다른 앱을 실행하면 방해 알림을 보내요", - subTitle: "뽀모도로를 실행한 후, 다른 앱을 사용하면\n설정한 주기로 방해 알림을 보내드려요.") + image: UIImage(systemName: "house")!, + title: "집중과 휴식 반복을 통해 몰입을 관리해요", + subTitle: "일정 시간 집중과 휴식을 반복해 번아웃을 방지하고\n짧은 시간의 몰입을 경험해보세요.") ] + +public struct RandomAccessEquatableItems: RandomAccessCollection, Equatable where Element: Equatable { + var elements: [Element] + public init(elements: [Element]) { + self.elements = elements + } + + public var startIndex: Int { elements.startIndex } + public var endIndex: Int { elements.endIndex } + + public subscript(position: Int) -> Element { + return elements[position] + } + + public static func == (lhs: RandomAccessEquatableItems, rhs: RandomAccessEquatableItems) -> Bool { + guard lhs.count == rhs.count else { return false } + for (index, element) in lhs.enumerated() { + if element != rhs[index] { + return false + } + } + return true + } +} + diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index 49494b7..77ee4f2 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -12,9 +12,7 @@ import DesignSystem import ComposableArchitecture public struct OnboardingView: View { - let store: StoreOf - @State var height: CGFloat = .zero - @State var currentData: OnboardingItem = OnboardingItemsData[0] + @Bindable var store: StoreOf public init(store: StoreOf) { self.store = store @@ -26,42 +24,72 @@ public struct OnboardingView: View { .ignoresSafeArea() VStack { Spacer() - VStack { - OnboardingCarouselView(data: $currentData) + VStack(spacing: 0) { + TabView(selection: $store.currentItemID) { + ForEach(store.fakedData, id: \.hashValue) { item in + OnboardingCarouselContentView(width: $store.width, item: item) + .tag(item.id.uuidString) + .offsetX(store.currentItemID == item.id.uuidString) { minX in + store.send(.calculateOffset(minX, item)) + } + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) + + HStack { + ForEach(0..<3, id: \.self) { idx in + Circle() + .frame(width: 8, height: 8) + .foregroundStyle( + idx == store.currentIdx ? + Alias.Color.Background.tertiary : Alias.Color.Background.secondary + ) + } + } + .padding(.vertical, 32) + Button(title: "시작하기") { // go to selectCatView } .buttonStyle(.box(size: .large, color: .primary)) + .padding(.top, 16) } - .padding(.horizontal, 20) Spacer() } } + .onAppear { store.send(.onApear) } + .background { + GeometryReader { geometry in + Color.clear + .onAppear { store.width = geometry.size.width } + } + } } } -struct OnboardingCarouselView: View { - @Binding var data: OnboardingItem - +struct OnboardingCarouselContentView: View { + @Binding var width: CGFloat + var item: OnboardingItem var body: some View { VStack(spacing: Alias.Spacing.xxxLarge) { ZStack { Rectangle() .foregroundStyle(Alias.Color.Background.secondary) - data.image + Image(uiImage: item.image) } .frame(width: 240, height: 240) VStack(spacing: Alias.Spacing.small) { - Text(data.title) + Text(item.title) .font(Typography.header4) .foregroundStyle(Alias.Color.Text.primary) - Text(data.subTitle) + Text(item.subTitle) .font(Typography.bodyR) .foregroundStyle(Alias.Color.Text.secondary) } .multilineTextAlignment(.center) } + .frame(width: width, height: 390) } } From e1b32498c9dc449aa292fc9c35f8346d533a094f Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Wed, 14 Aug 2024 05:34:22 +0900 Subject: [PATCH 05/11] feature: timer infinite carousel --- .../Sources/Onboarding/OnboardingCore.swift | 55 ++++++++++++++++++- .../Sources/Onboarding/OnboardingItem.swift | 11 +--- .../Sources/Onboarding/OnboardingView.swift | 10 +++- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift index 102b156..37842d1 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift @@ -26,6 +26,13 @@ public struct OnboardingCore { case onApear case setIndex(Int) case calculateOffset(CGFloat, OnboardingItem) + case dragStart + case dragEnd + case _timerStart + case _timerEnd + case _timerTicked + case _nextPage(Int) + case _resetTofront case binding(BindingAction) } @@ -50,7 +57,10 @@ public struct OnboardingCore { last.id = .init() state.fakedData.elements.append(first) state.fakedData.elements.insert(last, at: 0) - return .none + print(state.fakedData.map { $0.id.uuidString }) + return .run { send in + await send(._timerStart) + } case .setIndex(let idx): state.currentIdx = idx @@ -62,6 +72,7 @@ public struct OnboardingCore { item.id.uuidString == state.currentItemID } ?? 0 let pageOffset = minX - state.width * CGFloat(fakeIndex) + let pageProgress: CGFloat = pageOffset / state.width if -pageProgress < 1.0 { if state.fakedData.elements.indices.contains(state.fakedData.count - 1) { @@ -75,6 +86,48 @@ public struct OnboardingCore { } return .none + case .dragStart: + return .run { send in + await send(._timerEnd) + } + + case .dragEnd: + return .run { send in + await send(._timerStart) + } + + case ._timerStart: + return .run { send in + for await _ in self.clock.timer(interval: .seconds(3)) { + await send(._timerTicked) + } + } + .cancellable(id: CancelID.timer) + + case ._timerEnd: + return .cancel(id: CancelID.timer) + + case ._timerTicked: + let index = state.fakedData.firstIndex { item in + item.id.uuidString == state.currentItemID + } ?? 0 + return .run { [index = index] send in + if index == 4 { + await send(._resetTofront) + await send(._nextPage(2), animation: .easeInOut(duration: 0.3)) + } else { + await send(._nextPage(index + 1), animation: .easeInOut(duration: 0.3)) + } + } + + case ._nextPage(let index): + state.currentItemID = state.fakedData[index].id.uuidString + return .none + + case ._resetTofront: + state.currentItemID = state.fakedData[1].id.uuidString + return .none + default: return .none } diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift index c80160f..0183c37 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift @@ -14,19 +14,10 @@ import DesignSystem public struct OnboardingItem: Hashable, Identifiable { public var id: UUID = .init() let image: UIImage - let title: String + var title: String let subTitle: String } -func getIndex( - item: OnboardingItem -) -> Int { - let index = OnboardingItemsData.firstIndex { currentItem in - return currentItem.title == item.title - } ?? 0 - return index -} - let OnboardingItemsData: [OnboardingItem] = [ OnboardingItem( image: UIImage(systemName: "star")!, diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index 77ee4f2..ace5b34 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -35,6 +35,12 @@ public struct OnboardingView: View { } } .tabViewStyle(.page(indexDisplayMode: .never)) + .gesture( + DragGesture() + .onChanged { _ in store.send(.dragStart) } + .onEnded { _ in store.send(.dragEnd) } + ) + .frame(width: store.width, height: 350) HStack { ForEach(0..<3, id: \.self) { idx in @@ -49,7 +55,7 @@ public struct OnboardingView: View { .padding(.vertical, 32) Button(title: "시작하기") { - // go to selectCatView + // 다음 뷰 이동 } .buttonStyle(.box(size: .large, color: .primary)) .padding(.top, 16) @@ -89,7 +95,7 @@ struct OnboardingCarouselContentView: View { } .multilineTextAlignment(.center) } - .frame(width: width, height: 390) + .frame(width: width, height: 350) } } From c31bff679dab5e396192e579ae7c48f94e0f73d6 Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Wed, 14 Aug 2024 05:45:14 +0900 Subject: [PATCH 06/11] =?UTF-8?q?fix:=20rebase=EB=B0=8F=20boxbutton=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=ED=99=95=EC=9D=B8=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OnboardingFeature/Sources/Onboarding/OnboardingView.swift | 2 +- .../Button/Detail/ButtonHuggingPriorityHorizontal.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index ace5b34..b7a339a 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -57,7 +57,7 @@ public struct OnboardingView: View { Button(title: "시작하기") { // 다음 뷰 이동 } - .buttonStyle(.box(size: .large, color: .primary)) + .buttonStyle(.box(level: .primary, size: .large, width: .high)) .padding(.top, 16) } Spacer() diff --git a/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift b/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift index 5673d14..1c3678e 100644 --- a/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift +++ b/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift @@ -17,7 +17,7 @@ extension ButtonHuggingPriorityHorizontal { var width: CGFloat? { switch self { case .high: - return nil + return Alias.Size.ButtonWidth.fixed case .low: return .infinity } From a27d210e0058d9adb0d327d33946a78bc43934fe Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Wed, 14 Aug 2024 06:06:15 +0900 Subject: [PATCH 07/11] fix: drag ended -> drag onchange debounce --- .../Sources/Onboarding/OnboardingCore.swift | 17 +++++++---------- .../Sources/Onboarding/OnboardingView.swift | 1 - 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift index 37842d1..b704b5b 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift @@ -27,7 +27,6 @@ public struct OnboardingCore { case setIndex(Int) case calculateOffset(CGFloat, OnboardingItem) case dragStart - case dragEnd case _timerStart case _timerEnd case _timerTicked @@ -57,7 +56,6 @@ public struct OnboardingCore { last.id = .init() state.fakedData.elements.append(first) state.fakedData.elements.insert(last, at: 0) - print(state.fakedData.map { $0.id.uuidString }) return .run { send in await send(._timerStart) } @@ -87,14 +85,13 @@ public struct OnboardingCore { return .none case .dragStart: - return .run { send in - await send(._timerEnd) - } - - case .dragEnd: - return .run { send in - await send(._timerStart) - } + let timerEndAction: Effect = .send(._timerEnd) + let timerStartAction: Effect = .send(._timerStart) + .debounce(id: "timerStart", for: .seconds(2), scheduler: DispatchQueue.main) + return .merge( + timerEndAction, + timerStartAction + ) case ._timerStart: return .run { send in diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index b7a339a..0bb96f0 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -38,7 +38,6 @@ public struct OnboardingView: View { .gesture( DragGesture() .onChanged { _ in store.send(.dragStart) } - .onEnded { _ in store.send(.dragEnd) } ) .frame(width: store.width, height: 350) From 7050abb9599b6ca05abe8d5a6d01ee55cef0c6a3 Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Wed, 14 Aug 2024 18:22:28 +0900 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20box=20button=20width=20case=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OnboardingFeature/Sources/Onboarding/OnboardingView.swift | 2 +- .../Button/Detail/ButtonHuggingPriorityHorizontal.swift | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index 0bb96f0..ded4f00 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -56,7 +56,7 @@ public struct OnboardingView: View { Button(title: "시작하기") { // 다음 뷰 이동 } - .buttonStyle(.box(level: .primary, size: .large, width: .high)) + .buttonStyle(.box(level: .primary, size: .large, width: .medium)) .padding(.top, 16) } Spacer() diff --git a/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift b/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift index 1c3678e..ce86b5b 100644 --- a/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift +++ b/Projects/Shared/DesignSystem/Sources/Component/Button/Detail/ButtonHuggingPriorityHorizontal.swift @@ -10,6 +10,7 @@ import Foundation public enum ButtonHuggingPriorityHorizontal { case high + case medium case low } @@ -17,6 +18,8 @@ extension ButtonHuggingPriorityHorizontal { var width: CGFloat? { switch self { case .high: + return nil + case .medium: return Alias.Size.ButtonWidth.fixed case .low: return .infinity From a3a3fdc15053c3b29e8f0cdb38bcd1601c8ecf94 Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Wed, 14 Aug 2024 20:20:16 +0900 Subject: [PATCH 09/11] fix: remove import onboardingFeature on onBoardingCore --- .../OnboardingFeature/Sources/Onboarding/OnboardingCore.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift index b704b5b..5e10aa4 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift @@ -7,7 +7,7 @@ // import Foundation -import OnboardingFeatureInterface + import ComposableArchitecture @Reducer From 76daeed5fc7f31238d0a6a04c640771fe1c198fc Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Thu, 15 Aug 2024 16:30:49 +0900 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Feature/Sources/AppCore.swift | 8 ++--- .../Sources/Onboarding/OnboardingCore.swift | 29 +++++++++---------- .../Sources/Onboarding/OnboardingItem.swift | 26 +---------------- .../Sources/Onboarding/OnboardingView.swift | 2 +- 4 files changed, 19 insertions(+), 46 deletions(-) diff --git a/Projects/Feature/Feature/Sources/AppCore.swift b/Projects/Feature/Feature/Sources/AppCore.swift index 898c6d1..60816c4 100644 --- a/Projects/Feature/Feature/Sources/AppCore.swift +++ b/Projects/Feature/Feature/Sources/AppCore.swift @@ -91,13 +91,13 @@ public struct AppCore { state.onboarding = OnboardingCore.State() return .none + case .splash: + return .none + case .home: return .none -// case .onboarding: -// return .none - - default: + case .onboarding: return .none } } diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift index 5e10aa4..d71336e 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingCore.swift @@ -15,8 +15,8 @@ public struct OnboardingCore { @ObservableState public struct State: Equatable { public init() { } - var data = RandomAccessEquatableItems(elements: OnboardingItemsData) - var fakedData: RandomAccessEquatableItems = .init(elements: []) + var data: [OnboardingItem] = OnboardingItemsData + var fakedData: [OnboardingItem] = [] var width: CGFloat = .zero var currentIdx: Int = 0 var currentItemID: String = "" @@ -24,7 +24,6 @@ public struct OnboardingCore { public enum Action: BindableAction { case onApear - case setIndex(Int) case calculateOffset(CGFloat, OnboardingItem) case dragStart case _timerStart @@ -37,8 +36,8 @@ public struct OnboardingCore { public init() { } - private enum CancelID { case timer } @Dependency(\.continuousClock) var clock + @Dependency(\.mainQueue) var mainQueue public var body: some ReducerOf { BindingReducer() @@ -46,24 +45,22 @@ public struct OnboardingCore { } private func core(_ state: inout State, _ action: Action) -> EffectOf { + enum CancelID { case timer, timerDebounce } + switch action { case .onApear: state.currentItemID = state.data.first!.id.uuidString - state.fakedData.elements = state.data.elements + state.fakedData = state.data guard var first = state.data.first, var last = state.data.last else { return .none } first.id = .init() last.id = .init() - state.fakedData.elements.append(first) - state.fakedData.elements.insert(last, at: 0) + state.fakedData.append(first) + state.fakedData.insert(last, at: 0) return .run { send in await send(._timerStart) } - case .setIndex(let idx): - state.currentIdx = idx - return .none - case .calculateOffset(let minX, let item): let fakeIndex = state.fakedData.firstIndex(of: item) ?? 0 state.currentIdx = state.data.firstIndex { item in @@ -73,21 +70,21 @@ public struct OnboardingCore { let pageProgress: CGFloat = pageOffset / state.width if -pageProgress < 1.0 { - if state.fakedData.elements.indices.contains(state.fakedData.count - 1) { + if state.fakedData.indices.contains(state.fakedData.count - 1) { state.currentItemID = state.fakedData[state.fakedData.count - 1].id.uuidString } } if -pageProgress > CGFloat(state.fakedData.count - 1) { - if state.fakedData.elements.indices.contains(1) { + if state.fakedData.indices.contains(1) { state.currentItemID = state.fakedData[1].id.uuidString } } return .none case .dragStart: - let timerEndAction: Effect = .send(._timerEnd) - let timerStartAction: Effect = .send(._timerStart) - .debounce(id: "timerStart", for: .seconds(2), scheduler: DispatchQueue.main) + let timerEndAction: Effect = .run { send in await send(._timerEnd) } + let timerStartAction: Effect = .run { send in await send(._timerStart) } + .debounce(id: CancelID.timerDebounce, for: .seconds(2), scheduler: self.mainQueue) return .merge( timerEndAction, timerStartAction diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift index 0183c37..68b0d63 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift @@ -11,7 +11,7 @@ import SwiftUI import DesignSystem -public struct OnboardingItem: Hashable, Identifiable { +public struct OnboardingItem: Equatable, Identifiable { public var id: UUID = .init() let image: UIImage var title: String @@ -33,27 +33,3 @@ let OnboardingItemsData: [OnboardingItem] = [ subTitle: "일정 시간 집중과 휴식을 반복해 번아웃을 방지하고\n짧은 시간의 몰입을 경험해보세요.") ] -public struct RandomAccessEquatableItems: RandomAccessCollection, Equatable where Element: Equatable { - var elements: [Element] - public init(elements: [Element]) { - self.elements = elements - } - - public var startIndex: Int { elements.startIndex } - public var endIndex: Int { elements.endIndex } - - public subscript(position: Int) -> Element { - return elements[position] - } - - public static func == (lhs: RandomAccessEquatableItems, rhs: RandomAccessEquatableItems) -> Bool { - guard lhs.count == rhs.count else { return false } - for (index, element) in lhs.enumerated() { - if element != rhs[index] { - return false - } - } - return true - } -} - diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index ded4f00..a26c924 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -26,7 +26,7 @@ public struct OnboardingView: View { Spacer() VStack(spacing: 0) { TabView(selection: $store.currentItemID) { - ForEach(store.fakedData, id: \.hashValue) { item in + ForEach(store.fakedData) { item in OnboardingCarouselContentView(width: $store.width, item: item) .tag(item.id.uuidString) .offsetX(store.currentItemID == item.id.uuidString) { minX in From e1282d04a176ad9f72f4d4e1c1d750bd3259b3cc Mon Sep 17 00:00:00 2001 From: Jihyun247 Date: Thu, 15 Aug 2024 16:37:50 +0900 Subject: [PATCH 11/11] fix: onboardingItem uiimage -> image --- .../Sources/Onboarding/OnboardingItem.swift | 9 ++++----- .../Sources/Onboarding/OnboardingView.swift | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift index 68b0d63..e38a710 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingItem.swift @@ -6,29 +6,28 @@ // Copyright © 2024 PomoNyang. All rights reserved. // -import Foundation import SwiftUI import DesignSystem public struct OnboardingItem: Equatable, Identifiable { public var id: UUID = .init() - let image: UIImage + let image: Image var title: String let subTitle: String } let OnboardingItemsData: [OnboardingItem] = [ OnboardingItem( - image: UIImage(systemName: "star")!, + image: Image(systemName: "star"), title: "모하냥과 함께 집중시간을 늘려보세요", subTitle: "고양이 종에 따라 성격이 달라요.\n취향에 맞는 고양이를 선택해 몰입해 보세요."), OnboardingItem( - image: UIImage(systemName: "star.fill")!, + image: Image(systemName: "star.fill"), title: "다른 앱을 실행하면 방해 알림을 보내요", subTitle: "뽀모도로를 실행한 후, 다른 앱을 사용하면\n설정한 주기로 방해 알림을 보내드려요."), OnboardingItem( - image: UIImage(systemName: "house")!, + image: Image(systemName: "house"), title: "집중과 휴식 반복을 통해 몰입을 관리해요", subTitle: "일정 시간 집중과 휴식을 반복해 번아웃을 방지하고\n짧은 시간의 몰입을 경험해보세요.") ] diff --git a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift index a26c924..65e52b6 100644 --- a/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Feature/OnboardingFeature/Sources/Onboarding/OnboardingView.swift @@ -80,7 +80,7 @@ struct OnboardingCarouselContentView: View { ZStack { Rectangle() .foregroundStyle(Alias.Color.Background.secondary) - Image(uiImage: item.image) + item.image } .frame(width: 240, height: 240)