diff --git a/DependencyGraph/mohanyang_dev_graph.png b/DependencyGraph/mohanyang_dev_graph.png index 970d4af..23c2ed3 100644 Binary files a/DependencyGraph/mohanyang_dev_graph.png and b/DependencyGraph/mohanyang_dev_graph.png differ diff --git a/DependencyGraph/mohanyang_prod_graph.png b/DependencyGraph/mohanyang_prod_graph.png index c4dd20b..16d3cd4 100644 Binary files a/DependencyGraph/mohanyang_prod_graph.png and b/DependencyGraph/mohanyang_prod_graph.png differ diff --git a/Projects/Feature/Feature/Sources/AppCore.swift b/Projects/Feature/Feature/Sources/AppCore.swift index 60816c4..3cdee87 100644 --- a/Projects/Feature/Feature/Sources/AppCore.swift +++ b/Projects/Feature/Feature/Sources/AppCore.swift @@ -10,7 +10,6 @@ import SwiftUI import SplashFeature import HomeFeature -import HomeFeatureInterface import OnboardingFeature import PushService diff --git a/Projects/Feature/Feature/Sources/AppView.swift b/Projects/Feature/Feature/Sources/AppView.swift index 9b6a249..a899c4f 100644 --- a/Projects/Feature/Feature/Sources/AppView.swift +++ b/Projects/Feature/Feature/Sources/AppView.swift @@ -10,7 +10,6 @@ import SwiftUI import SplashFeature import HomeFeature -import HomeFeatureInterface import OnboardingFeature import ComposableArchitecture diff --git a/Projects/Feature/HomeFeature/Example/Resources/LaunchScreen.storyboard b/Projects/Feature/HomeFeature/Example/Resources/LaunchScreen.storyboard index be85df4..76f11c4 100644 --- a/Projects/Feature/HomeFeature/Example/Resources/LaunchScreen.storyboard +++ b/Projects/Feature/HomeFeature/Example/Resources/LaunchScreen.storyboard @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Projects/Feature/HomeFeature/Example/Sources/AppDelegate.swift b/Projects/Feature/HomeFeature/Example/Sources/AppDelegate.swift deleted file mode 100644 index 0d538be..0000000 --- a/Projects/Feature/HomeFeature/Example/Sources/AppDelegate.swift +++ /dev/null @@ -1,11 +0,0 @@ -import UIKit - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) -> Bool { - return true - } -} diff --git a/Projects/Feature/HomeFeature/Example/Sources/ContentView.swift b/Projects/Feature/HomeFeature/Example/Sources/ContentView.swift new file mode 100644 index 0000000..7ef2fc6 --- /dev/null +++ b/Projects/Feature/HomeFeature/Example/Sources/ContentView.swift @@ -0,0 +1,33 @@ +// +// ContentView.swift +// HomeFeature +// +// Created by devMinseok on 8/10/24. +// + +import SwiftUI + +import HomeFeature + +struct ContentView: View { + var body: some View { + NavigationStack { + List { + NavigationLink { + HomeView( + store: .init( + initialState: .init(), + reducer: { HomeCore() } + ) + ) + } label: { + Text("Home") + } + } + } + } +} + +#Preview { + ContentView() +} diff --git a/Projects/Feature/HomeFeature/Example/Sources/HomeFeatureApp.swift b/Projects/Feature/HomeFeature/Example/Sources/HomeFeatureApp.swift new file mode 100644 index 0000000..4167e7d --- /dev/null +++ b/Projects/Feature/HomeFeature/Example/Sources/HomeFeatureApp.swift @@ -0,0 +1,17 @@ +// +// HomeFeatureApp.swift +// HomeFeature +// +// Created by <#T##Author name#> on 8/10/24. +// + +import SwiftUI + +@main +struct HomeFeatureApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Projects/Feature/HomeFeature/Example/Sources/TimeWheelPickerDetailView.swift b/Projects/Feature/HomeFeature/Example/Sources/TimeWheelPickerDetailView.swift new file mode 100644 index 0000000..a799663 --- /dev/null +++ b/Projects/Feature/HomeFeature/Example/Sources/TimeWheelPickerDetailView.swift @@ -0,0 +1,20 @@ +// +// TimeWheelPickerDetailView.swift +// DesignSystemExample +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import HomeFeature + +#Preview { + TimeSelectView( + store: .init( + initialState: .init(), + reducer: { TimeSelectCore() } + ) + ) +} diff --git a/Projects/Feature/HomeFeature/Interface/HomeCoreInterface.swift b/Projects/Feature/HomeFeature/Interface/HomeCoreInterface.swift deleted file mode 100644 index d601cd1..0000000 --- a/Projects/Feature/HomeFeature/Interface/HomeCoreInterface.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// HomeCoreInterface.swift -// HomeFeature -// -// Created by devMinseok on 7/22/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import Foundation - -import ComposableArchitecture - -@Reducer -public struct HomeCore { - private let reducer: Reduce - - public init(reducer: Reduce) { - self.reducer = reducer - } - - @ObservableState - public struct State: Equatable { - public init() {} - } - - public enum Action { - case onAppear - case localPushButtonTapped - } - - public var body: some ReducerOf { - reducer - } -} diff --git a/Projects/Feature/HomeFeature/Interface/HomeView.swift b/Projects/Feature/HomeFeature/Interface/HomeView.swift deleted file mode 100644 index 51efe7a..0000000 --- a/Projects/Feature/HomeFeature/Interface/HomeView.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// HomeView.swift -// HomeFeatureInterface -// -// Created by devMinseok on 7/22/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import SwiftUI - -import ComposableArchitecture - -public struct HomeView: View { - let store: StoreOf - - public init(store: StoreOf) { - self.store = store - } - - public var body: some View { - VStack { - Text("Home") - .foregroundStyle(Color.black) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.white) - .onAppear { - store.send(.onAppear) - } - } -} diff --git a/Projects/Feature/HomeFeature/Project.swift b/Projects/Feature/HomeFeature/Project.swift index 6f25a9e..a08dfe8 100644 --- a/Projects/Feature/HomeFeature/Project.swift +++ b/Projects/Feature/HomeFeature/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/HomeFeature/Resources/dummy.txt b/Projects/Feature/HomeFeature/Resources/dummy.txt deleted file mode 100644 index 3c546eb..0000000 --- a/Projects/Feature/HomeFeature/Resources/dummy.txt +++ /dev/null @@ -1 +0,0 @@ -dummy file \ No newline at end of file diff --git a/Projects/Feature/HomeFeature/Sources/CategorySelect/CategorySelectCore.swift b/Projects/Feature/HomeFeature/Sources/CategorySelect/CategorySelectCore.swift new file mode 100644 index 0000000..0dfdccf --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/CategorySelect/CategorySelectCore.swift @@ -0,0 +1,44 @@ +// +// CategorySelectCore.swift +// HomeFeature +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import ComposableArchitecture + +@Reducer +public struct CategorySelectCore { + @ObservableState + public struct State: Equatable { + + } + + public enum Action { + case onAppear + case dismissButtonTapped + case bottomCheckButtonTapped + } + +// <#@Dependency() var#> + + public init() {} + + public var body: some ReducerOf { + Reduce(self.core) + } + + private func core(state: inout State, action: Action) -> EffectOf { + switch action { + case .onAppear: + return .none + + case .dismissButtonTapped: + return .none + + case .bottomCheckButtonTapped: + return .none + } + } +} diff --git a/Projects/Feature/HomeFeature/Sources/CategorySelect/CategorySelectView.swift b/Projects/Feature/HomeFeature/Sources/CategorySelect/CategorySelectView.swift new file mode 100644 index 0000000..56cea4e --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/CategorySelect/CategorySelectView.swift @@ -0,0 +1,92 @@ +// +// CategorySelectView.swift +// HomeFeature +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem + +import ComposableArchitecture + +public struct CategorySelectView: View { + @Bindable var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: Alias.Spacing.large) { + HStack(spacing: .zero) { + Text("카테고리 변경") + .font(Typography.header3) + .foregroundStyle(Alias.Color.Text.primary) + Spacer() + Button( + icon: DesignSystemAsset.Image._24CancelPrimary.swiftUIImage, + action: { + store.send(.dismissButtonTapped) + } + ) + .buttonStyle(.icon(isFilled: false, level: .primary)) + } + .padding(.leading, Alias.Spacing.xLarge) + .padding(.trailing, Alias.Spacing.small) + .frame(height: 40) + + VStack(spacing: Alias.Spacing.small) { + // TODO: - list select button 배치 + + Button( + title: "기본", + subtitle: "집중 25분 | 휴식 10분", + leftIcon: DesignSystemAsset.Image._24DefaultCategoryPrimary.swiftUIImage, + action: {} + ) + .buttonStyle(.selectList(isSelected: true)) + + Button( + title: "독서", + subtitle: "집중 25분 | 휴식 10분", + leftIcon: DesignSystemAsset.Image._24DefaultCategoryPrimary.swiftUIImage, + action: {} + ) + .buttonStyle(.selectList(isSelected: false)) + + Button( + title: "공부", + subtitle: "집중 25분 | 휴식 10분", + leftIcon: DesignSystemAsset.Image._24DefaultCategoryPrimary.swiftUIImage, + action: {} + ) + .buttonStyle(.selectList(isSelected: false)) + + Button( + title: "작업", + subtitle: "집중 25분 | 휴식 10분", + leftIcon: DesignSystemAsset.Image._24DefaultCategoryPrimary.swiftUIImage, + action: {} + ) + .buttonStyle(.selectList(isSelected: false)) + } + .padding(.horizontal, Alias.Spacing.large) + + Button( + title: "확인", + action: { + store.send(.bottomCheckButtonTapped) + } + ) + .buttonStyle(.box(level: .secondary, size: .large, width: .low)) + .padding(.horizontal, Alias.Spacing.large) + .padding(.bottom, Alias.Spacing.medium) + } + .onAppear { + store.send(.onAppear) + } + } +} diff --git a/Projects/Feature/HomeFeature/Sources/Home/HomeCore.swift b/Projects/Feature/HomeFeature/Sources/Home/HomeCore.swift new file mode 100644 index 0000000..af0b66c --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/Home/HomeCore.swift @@ -0,0 +1,115 @@ +// +// HomeCore.swift +// HomeFeature +// +// Created by devMinseok on 7/22/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import UserNotifications + +import PushService +import UserDefaultsClientInterface + +import ComposableArchitecture + +@Reducer +public struct HomeCore { + @ObservableState + public struct State: Equatable { + + var homeCatTooltip: HomeCatDialogueTooltip? + var homeCategoryGuideTooltip: HomeCategoryGuideTooltip? + var homeTimeGuideTooltip: HomeTimeGuideTooltip? + @Presents var categorySelect: CategorySelectCore.State? + @Presents var timeSelect: TimeSelectCore.State? + + public init() {} + } + + public enum Action { + case onLoad + case setHomeCatTooltip(HomeCatDialogueTooltip?) + case setHomeCategoryGuideTooltip(HomeCategoryGuideTooltip?) + case setHomeTimeGuideTooltip(HomeTimeGuideTooltip?) + case categoryButtonTapped + case focusTimeButtonTapped + case relaxTimeButtonTapped + case mypageButtonTappd + case playButtonTapped + + case categorySelect(PresentationAction) + case timeSelect(PresentationAction) + } + + @Dependency(UserDefaultsClient.self) var userDefaultsClient + let isHomeGuideCompletedKey = "mohanyang_userdefaults_isHomeGuideCompleted" + + public init() {} + + public var body: some ReducerOf { + Reduce(self.core) + .ifLet(\.$categorySelect, action: \.categorySelect) { + CategorySelectCore() + } + .ifLet(\.$timeSelect, action: \.timeSelect) { + TimeSelectCore() + } + } + + private func core(_ state: inout State, _ action: Action) -> EffectOf { + switch action { + case .onLoad: + return .run { send in + await send(.setHomeCatTooltip(nil)) + if self.userDefaultsClient.boolForKey(isHomeGuideCompletedKey) == false { + await self.userDefaultsClient.setBool(true, key: isHomeGuideCompletedKey) + await send(.setHomeCategoryGuideTooltip(HomeCategoryGuideTooltip())) + } + } + + case .setHomeCatTooltip: + state.homeCatTooltip = .init(title: "오랜만이다냥") // TODO: - 문구 랜덤변경하기 + return .none + + case let .setHomeCategoryGuideTooltip(tooltip): + state.homeCategoryGuideTooltip = tooltip + if tooltip == nil { + state.homeTimeGuideTooltip = .init() + } + return .none + + case let .setHomeTimeGuideTooltip(tooltip): + state.homeTimeGuideTooltip = tooltip + return .none + + case .categoryButtonTapped: + state.categorySelect = CategorySelectCore.State() + return .none + + case .focusTimeButtonTapped: + state.timeSelect = TimeSelectCore.State() + return .none + + case .relaxTimeButtonTapped: + state.timeSelect = TimeSelectCore.State() + return .none + + case .mypageButtonTappd: + return .none + + case .playButtonTapped: + return .none + + case .categorySelect(.presented(.dismissButtonTapped)): + state.categorySelect = nil + return .none + + case .categorySelect: + return .none + + case .timeSelect: + return .none + } + } +} diff --git a/Projects/Feature/HomeFeature/Sources/Home/HomeView.swift b/Projects/Feature/HomeFeature/Sources/Home/HomeView.swift new file mode 100644 index 0000000..e9cbf1f --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/Home/HomeView.swift @@ -0,0 +1,130 @@ +// +// HomeView.swift +// HomeFeatureInterface +// +// Created by devMinseok on 7/22/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem +import Utils + +import ComposableArchitecture + +public struct HomeView: View { + @Bindable var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + NavigationStack { + NavigationContainer( + leading: { + Spacer() + }, + trailing: { + Button( + icon: DesignSystemAsset.Image._24MenuPrimary.swiftUIImage, + action: { + store.send(.mypageButtonTappd) + } + ) + .buttonStyle(.icon(isFilled: false, level: .primary)) + }, + style: .navigation + ) { + VStack(spacing: 40) { + VStack(spacing: Alias.Spacing.xLarge) { + Rectangle() + .fill(Alias.Color.Background.secondary) + .frame(width: 240, height: 240) + .setTooltipTarget(tooltip: HomeCatDialogueTooltip.self) + Text("치즈냥") + .font(Typography.header4) + .foregroundStyle(Alias.Color.Text.tertiary) + } + VStack(spacing: Alias.Spacing.medium) { + Button( + title: "\("기본")", + leftIcon: Image(systemName: "left"), + action: { + store.send(.categoryButtonTapped) + } + ) + .buttonStyle(.box(level: .tertiary, size: .small)) + .setTooltipTarget(tooltip: HomeCategoryGuideTooltip.self) + + HStack(spacing: Alias.Spacing.medium) { + HStack(spacing: Alias.Spacing.small) { + Text("집중") + .font(Typography.bodySB) + .foregroundStyle(Global.Color.gray500) + Text("\(0)분") + .font(Typography.header3) + .foregroundStyle(Alias.Color.Text.secondary) + } + .padding(Alias.Spacing.small) + .onTapGesture { + store.send(.focusTimeButtonTapped) + } + + Rectangle() + .fill(Global.Color.gray200) + .frame(width: 2, height: Global.Dimension._20f) + + HStack(spacing: Alias.Spacing.small) { + Text("휴식") + .font(Typography.bodySB) + .foregroundStyle(Global.Color.gray500) + Text("\(0)분") + .font(Typography.header3) + .foregroundStyle(Alias.Color.Text.secondary) + } + .padding(Alias.Spacing.small) + .onTapGesture { + store.send(.relaxTimeButtonTapped) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + .setTooltipTarget(tooltip: HomeTimeGuideTooltip.self) + } + Button( + icon: DesignSystemAsset.Image._32PlayPrimary.swiftUIImage, + action: { + store.send(.playButtonTapped) + } + ) + .buttonStyle(.round(level: .primary)) + } + } + .background(Global.Color.gray50) + } + .tooltipDestination(tooltip: $store.homeCatTooltip.sending(\.setHomeCatTooltip)) + .tooltipDestination(tooltip: $store.homeCategoryGuideTooltip.sending(\.setHomeCategoryGuideTooltip)) + .tooltipDestination(tooltip: $store.homeTimeGuideTooltip.sending(\.setHomeTimeGuideTooltip)) + .bottomSheet( + item: $store.scope( + state: \.categorySelect, + action: \.categorySelect + ) + ) { store in + CategorySelectView(store: store) + } + .fullScreenCover( + item: $store.scope( + state: \.timeSelect, + action: \.timeSelect + ) + ) { store in + TimeSelectView(store: store) + } + .onLoad { + store.send(.onLoad) + } + } +} diff --git a/Projects/Feature/HomeFeature/Sources/HomeCore.swift b/Projects/Feature/HomeFeature/Sources/HomeCore.swift deleted file mode 100644 index 1b105d7..0000000 --- a/Projects/Feature/HomeFeature/Sources/HomeCore.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// HomeCore.swift -// HomeFeature -// -// Created by devMinseok on 7/22/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import UserNotifications - -import HomeFeatureInterface -import PushService -import UserNotificationClientInterface - -import ComposableArchitecture - -extension HomeCore { - public init() { - @Dependency(UserNotificationClient.self) var userNotificationClient - - let reducer = Reduce { _, action in - switch action { - case .onAppear: - return .run { send in - _ = try await userNotificationClient.requestAuthorization([.alert, .badge, .sound]) - } - - case .localPushButtonTapped: - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false) - return .run { send in - do { - try await scheduleNotification( - userNotificationClient: userNotificationClient, - contentType: .test, - trigger: trigger - ) - } catch { - print(error) - } - } - } - } - self.init(reducer: reducer) - } -} diff --git a/Projects/Feature/HomeFeature/Sources/TimeSelect/TimeSelectCore.swift b/Projects/Feature/HomeFeature/Sources/TimeSelect/TimeSelectCore.swift new file mode 100644 index 0000000..86c91e1 --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/TimeSelect/TimeSelectCore.swift @@ -0,0 +1,70 @@ +// +// TimeSelectCore.swift +// HomeFeature +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import Foundation + +import FeedbackGeneratorClientInterface + +import ComposableArchitecture + +@Reducer +public struct TimeSelectCore { + @ObservableState + public struct State: Equatable { + var timeList: [TimeItem] = [] + var selectedTime: TimeItem? + + public init() {} + } + + public enum Action { + case onAppear + case pickerSelection(TimeItem?) + } + + @Dependency(FeedbackGeneratorClient.self) var feedbackGeneratorClient + + public init() {} + + public var body: some ReducerOf { + Reduce(self.core) + } + + private func core(state: inout State, action: Action) -> EffectOf { + switch action { + case .onAppear: + state.timeList = generateFocusTimeByMinute().map { .init(title: "\($0):00", data: $0) } + state.selectedTime = state.timeList.last + return .none + + case let .pickerSelection(selection): + state.selectedTime = selection + return .run { _ in + await self.feedbackGeneratorClient.impactOccurred(.soft) + } + } + } + + /// 집중시간 리스트 생성 (분) + private func generateFocusTimeByMinute() -> [Int] { + var result: [Int] = [] + for i in stride(from: 10, through: 60, by: 5) { + result.append(i) + } + return result.reversed() + } + + /// 휴식시간 리스트 생성 (분) + private func generateRelaxTimeByMinute() -> [Int] { + var result: [Int] = [] + for i in stride(from: 5, through: 30, by: 5) { + result.append(i) + } + return result.reversed() + } +} diff --git a/Projects/Feature/HomeFeature/Sources/TimeSelect/TimeSelectView.swift b/Projects/Feature/HomeFeature/Sources/TimeSelect/TimeSelectView.swift new file mode 100644 index 0000000..37d97fb --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/TimeSelect/TimeSelectView.swift @@ -0,0 +1,70 @@ +// +// TimeSelectView.swift +// HomeFeature +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem + +import ComposableArchitecture + +public struct TimeSelectView: View { + @Bindable var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + NavigationContainer( + style: .modal + ) { + VStack(spacing: .zero) { + Button( + title: "작업", + leftIcon: DesignSystemAsset.Image._24WorkPrimary.swiftUIImage, + action: {} + ) + .buttonStyle(.text(level: .primary, size: .medium)) + + WheelPicker( + image: DesignSystemAsset.Image._24Focus.swiftUIImage, + sources: store.timeList, + selection: $store.selectedTime.sending(\.pickerSelection) + ) + + Button( + icon: DesignSystemAsset.Image._32PlayPrimary.swiftUIImage, + action: { + + } + ) + .buttonStyle(.round(level: .secondary)) + .padding(.bottom, 40) + } + } + .background(Global.Color.gray50) + .onAppear { + store.send(.onAppear) + } + } +} + + +public struct TimeItem: WheelPickerData { + public let id: UUID = .init() + let title: String + let data: Int + + init( + title: String, + data: Int + ) { + self.title = title + self.data = data + } +} diff --git a/Projects/Feature/HomeFeature/Sources/Tooltip/HomeCatDialogueTooltip.swift b/Projects/Feature/HomeFeature/Sources/Tooltip/HomeCatDialogueTooltip.swift new file mode 100644 index 0000000..9eba9b3 --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/Tooltip/HomeCatDialogueTooltip.swift @@ -0,0 +1,31 @@ +// +// HomeCatDialogueTooltip.swift +// HomeFeature +// +// Created by devMinseok on 8/12/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem + +public struct HomeCatDialogueTooltip: Tooltip { + let _title: String + + public init(title: String) { + _title = title + } + + public var title: Text { Text(_title) } + + public var color: TooltipColor { .white } + + public var direction: TooltipDirection { .down } + + public var dimEnabled: Bool { false } + + public var targetCornerRadius: CGFloat? { nil } + + public var padding: CGFloat { -12 } +} diff --git a/Projects/Feature/HomeFeature/Sources/Tooltip/HomeCategoryGuideTooltip.swift b/Projects/Feature/HomeFeature/Sources/Tooltip/HomeCategoryGuideTooltip.swift new file mode 100644 index 0000000..10cf6b0 --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/Tooltip/HomeCategoryGuideTooltip.swift @@ -0,0 +1,26 @@ +// +// HomeCategoryGuideTooltip.swift +// HomeFeature +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem + +public struct HomeCategoryGuideTooltip: Tooltip { + public var title: Text { Text("눌러서 카테고리를 변경할 수 있어요") } + + public var color: TooltipColor { .white } + + public var direction: TooltipDirection { .down } + + public var dimEnabled: Bool { true } + + public var targetCornerRadius: CGFloat? { Alias.BorderRadius.xSmall } + + public var padding: CGFloat { 12 } +} + diff --git a/Projects/Feature/HomeFeature/Sources/Tooltip/HomeTimeGuideTooltip.swift b/Projects/Feature/HomeFeature/Sources/Tooltip/HomeTimeGuideTooltip.swift new file mode 100644 index 0000000..e681c69 --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/Tooltip/HomeTimeGuideTooltip.swift @@ -0,0 +1,25 @@ +// +// HomeTimeGuideTooltip.swift +// HomeFeature +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem + +public struct HomeTimeGuideTooltip: Tooltip { + public var title: Text { Text("눌러서 시간을 조정할 수 있어요") } + + public var color: TooltipColor { .white } + + public var direction: TooltipDirection { .down } + + public var dimEnabled: Bool { true } + + public var targetCornerRadius: CGFloat? { Alias.BorderRadius.xSmall } + + public var padding: CGFloat { 12 } +} diff --git a/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPicker.swift b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPicker.swift new file mode 100644 index 0000000..c9cf5ad --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPicker.swift @@ -0,0 +1,112 @@ +// +// WheelPicker.swift +// DesignSystem +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem +import Utils + +struct WheelPicker: View { + @Namespace var backgroundFrameID + @State var selectionID: D.ID? + @Binding var selection: D? + @State var backgroundRect: CGRect = .zero + let sources: [D] + let image: Image + + var itemHeight: CGFloat = 98 + var menuHeightMultiplier: CGFloat { + return floor(backgroundRect.height / itemHeight) + } + + init( + image: Image, + sources: [D], + selection: Binding + ) { + self.image = image + self.sources = sources + _selection = selection + } + + var body: some View { + let itemsCountAbove = Double(Int((menuHeightMultiplier - 1) / 2)) + let pickerHeight = itemHeight * (itemsCountAbove * 2 + 1) + let paddingAdjustment = (Int(menuHeightMultiplier) % 2 == 0) ? itemHeight * 0.5 : 0 + + ZStack(alignment: .center) { + RoundedRectangle(cornerRadius: 16) + .fill(Alias.Color.Background.secondary) + .frame(width: 209, height: itemHeight) + .overlay(alignment: .leading) { + self.image + .padding(.leading, 20) + } + ScrollViewReader { scrollViewProxy in + ScrollView(.vertical) { + LazyVStack(spacing: .zero) { + ForEach(sources) { item in + GeometryReader { geometryProxy in + let itemCenterY = geometryProxy.frame(in: .global).midY + let viewCenterY = backgroundRect.midY + let distance = abs(viewCenterY - itemCenterY) + let scale = max(1, 1.4 - (distance / (itemHeight * 3))) + + Text(item.title) + .foregroundStyle( + item == selection ? Alias.Color.Text.primary : Alias.Color.Text.disabled + ) + .font(Typography.header2) + .lineLimit(1) + .frame(height: itemHeight) + .scaleEffect(x: scale, y: scale, anchor: .leading) + .frame(maxWidth: .infinity) + .id(item.id) + } + .frame(height: itemHeight) + } + } + .scrollTargetLayout() + .padding(.vertical, itemHeight * itemsCountAbove) + } + .scrollPosition(id: $selectionID, anchor: .center) + .frame(height: pickerHeight) + .scrollClipDisabled() + .padding(.vertical, paddingAdjustment) + .clipShape(Rectangle()) + .scrollTargetBehavior(.viewAligned(limitBehavior: .always)) + .scrollIndicators(.hidden) + .onChange(of: selection) { _, value in + guard let id = value?.id, + selectionID != id + else { return } + scrollViewProxy.scrollTo(id, anchor: .center) + } + } + + LinearGradient( + colors: [ + Alias.Color.Background.primary, + .clear, + Alias.Color.Background.primary + ], + startPoint: .top, + endPoint: .bottom + ) + .allowsHitTesting(false) + } + .setFrameMeasure(space: .global, identifier: backgroundFrameID) + .getFrameMeasure { value in + guard let frame = value[backgroundFrameID] else { return } + backgroundRect = frame + } + .onChange(of: selectionID) { _, value in + selection = sources.first { $0.id == value } + } + } +} diff --git a/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPickerData.swift b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPickerData.swift new file mode 100644 index 0000000..89a147a --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPickerData.swift @@ -0,0 +1,16 @@ +// +// WheelPickerData.swift +// HomeFeature +// +// Created by devMinseok on 8/15/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import Foundation + +protocol WheelPickerData: Equatable, Identifiable { + associatedtype Data: Equatable + + var title: String { get } + var data: Data { get } +} diff --git a/Projects/Feature/HomeFeature/Testing/HomeFeatureTesting.swift b/Projects/Feature/HomeFeature/Testing/HomeFeatureTesting.swift index ca81c97..f4aa7d1 100644 --- a/Projects/Feature/HomeFeature/Testing/HomeFeatureTesting.swift +++ b/Projects/Feature/HomeFeature/Testing/HomeFeatureTesting.swift @@ -1,3 +1,10 @@ +// +// HomeFeatureTesting.swift +// HomeFeature +// +// Created by <#T##Author name#> on 8/10/24. +// + import Foundation public struct HomeFeatureTesting { diff --git a/Projects/Feature/HomeFeature/Tests/HomeFeatureTests.swift b/Projects/Feature/HomeFeature/Tests/HomeFeatureTests.swift index c050739..21fab14 100644 --- a/Projects/Feature/HomeFeature/Tests/HomeFeatureTests.swift +++ b/Projects/Feature/HomeFeature/Tests/HomeFeatureTests.swift @@ -1,5 +1,33 @@ -import Foundation +// +// HomeFeatureTests.swift +// HomeFeature +// +// Created by <#T##Author name#> on 8/10/24. +// -public struct HomeFeatureTests { - public init() {} +import XCTest + +final class HomeFeatureTests: XCTestCase { + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } } diff --git a/Projects/Shared/DesignSystem/Project.swift b/Projects/Shared/DesignSystem/Project.swift index e2ef347..6397bd2 100644 --- a/Projects/Shared/DesignSystem/Project.swift +++ b/Projects/Shared/DesignSystem/Project.swift @@ -22,7 +22,11 @@ let project: Project = .makeTMABasedProject( .sources, .example ], - dependencies: [:], + dependencies: [ + .sources: [ + .dependency(module: Shared.Utils) + ] + ], resourceSynthesizers: [ .fonts(), // for font .assets(), // for .xcassets, diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24_arrow_left_primary.imageset/24_arrow_left_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_arrow_left_primary.imageset/24_arrow_left_primary.svg similarity index 100% rename from Projects/Shared/DesignSystem/Resources/Image.xcassets/24_arrow_left_primary.imageset/24_arrow_left_primary.svg rename to Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_arrow_left_primary.imageset/24_arrow_left_primary.svg diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24_arrow_left_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_arrow_left_primary.imageset/Contents.json similarity index 100% rename from Projects/Shared/DesignSystem/Resources/Image.xcassets/24_arrow_left_primary.imageset/Contents.json rename to Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_arrow_left_primary.imageset/Contents.json diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24_cancel_primary.imageset/24_cancel_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_cancel_primary.imageset/24_cancel_primary.svg similarity index 100% rename from Projects/Shared/DesignSystem/Resources/Image.xcassets/24_cancel_primary.imageset/24_cancel_primary.svg rename to Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_cancel_primary.imageset/24_cancel_primary.svg diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24_cancel_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_cancel_primary.imageset/Contents.json similarity index 100% rename from Projects/Shared/DesignSystem/Resources/Image.xcassets/24_cancel_primary.imageset/Contents.json rename to Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_cancel_primary.imageset/Contents.json diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_defaultCategory_primary.imageset/24_defaultCategory_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_defaultCategory_primary.imageset/24_defaultCategory_primary.svg new file mode 100644 index 0000000..36b3815 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_defaultCategory_primary.imageset/24_defaultCategory_primary.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_defaultCategory_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_defaultCategory_primary.imageset/Contents.json new file mode 100644 index 0000000..6114028 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_defaultCategory_primary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "24_defaultCategory_primary.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_focus.imageset/24_focus.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_focus.imageset/24_focus.svg new file mode 100644 index 0000000..c73356f --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_focus.imageset/24_focus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_focus.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_focus.imageset/Contents.json new file mode 100644 index 0000000..56f0a60 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_focus.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "24_focus.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_menu_primary.imageset/24_menu_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_menu_primary.imageset/24_menu_primary.svg new file mode 100644 index 0000000..7d8a86b --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_menu_primary.imageset/24_menu_primary.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_menu_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_menu_primary.imageset/Contents.json new file mode 100644 index 0000000..1a4b5d1 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_menu_primary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "24_menu_primary.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_rest.imageset/24_rest.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_rest.imageset/24_rest.svg new file mode 100644 index 0000000..b759725 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_rest.imageset/24_rest.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_rest.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_rest.imageset/Contents.json new file mode 100644 index 0000000..87d9f8f --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_rest.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "24_rest.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_study_primary.imageset/24_study_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_study_primary.imageset/24_study_primary.svg new file mode 100644 index 0000000..55d7592 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_study_primary.imageset/24_study_primary.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_study_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_study_primary.imageset/Contents.json new file mode 100644 index 0000000..21b8966 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_study_primary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "24_study_primary.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_work_primary.imageset/24_work_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_work_primary.imageset/24_work_primary.svg new file mode 100644 index 0000000..c987167 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_work_primary.imageset/24_work_primary.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_work_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_work_primary.imageset/Contents.json new file mode 100644 index 0000000..3d60a8d --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_work_primary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "24_work_primary.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_workout_primary.imageset/24_workout_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_workout_primary.imageset/24_workout_primary.svg new file mode 100644 index 0000000..4de760b --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_workout_primary.imageset/24_workout_primary.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_workout_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_workout_primary.imageset/Contents.json new file mode 100644 index 0000000..a23e46b --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/24_workout_primary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "24_workout_primary.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/24/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/32_play_primary.imageset/32_play_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/32_play_primary.imageset/32_play_primary.svg new file mode 100644 index 0000000..c41a295 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/32_play_primary.imageset/32_play_primary.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/32_play_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/32_play_primary.imageset/Contents.json new file mode 100644 index 0000000..21afad3 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/32_play_primary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "32_play_primary.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/32/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/FrameMeasurePreferenceKey.swift b/Projects/Shared/DesignSystem/Sources/Component/Tooltip/TooltipFramePreferenceKey.swift similarity index 88% rename from Projects/Shared/DesignSystem/Sources/Component/Tooltip/FrameMeasurePreferenceKey.swift rename to Projects/Shared/DesignSystem/Sources/Component/Tooltip/TooltipFramePreferenceKey.swift index 17056e4..8183dee 100644 --- a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/FrameMeasurePreferenceKey.swift +++ b/Projects/Shared/DesignSystem/Sources/Component/Tooltip/TooltipFramePreferenceKey.swift @@ -8,7 +8,7 @@ import SwiftUI -struct FrameMeasurePreferenceKey: PreferenceKey { +struct TooltipFramePreferenceKey: PreferenceKey { typealias Value = [AnyHashable: CGRect] static var defaultValue: Value = Value() diff --git a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/TooltipTarget.swift b/Projects/Shared/DesignSystem/Sources/Component/Tooltip/TooltipTarget.swift index cc3676f..ac3f484 100644 --- a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/TooltipTarget.swift +++ b/Projects/Shared/DesignSystem/Sources/Component/Tooltip/TooltipTarget.swift @@ -8,6 +8,8 @@ import SwiftUI +import Utils + struct TooltipTarget: View { let identifier: AnyHashable @@ -15,7 +17,7 @@ struct TooltipTarget: View { GeometryReader { geometry in Color.clear .preference( - key: FrameMeasurePreferenceKey.self, + key: TooltipFramePreferenceKey.self, value: [identifier: geometry.frame(in: .global)] ) } @@ -32,7 +34,7 @@ extension View { public func tooltipDestination(tooltip: Binding) -> some View { return self - .overlayWithOnPreferenceChange(FrameMeasurePreferenceKey.self) { value in + .overlayWithOnPreferenceChange(TooltipFramePreferenceKey.self) { value in if let content = tooltip.wrappedValue, let position = value[ObjectIdentifier(type(of: content))] { TooltipView(content: content, position: position) .transition(.opacity.animation(.easeInOut)) diff --git a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/Triangle.swift b/Projects/Shared/DesignSystem/Sources/Component/Tooltip/Triangle.swift deleted file mode 100644 index c8eeaed..0000000 --- a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/Triangle.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Triangle.swift -// DesignSystem -// -// Created by devMinseok on 8/9/24. -// Copyright © 2024 PomoNyang. All rights reserved. -// - -import SwiftUI - -public struct Triangle: View { - public enum Direction: Double { - case down = 180 - case left = -90 - case up = 0 - case right = 90 - } - - private let direction: Direction - private let color: Color - - public init(direction: Direction, color: Color) { - self.direction = direction - self.color = color - } - - public var body: some View { - TriangleShape() - .fill(color) - .rotationEffect(Angle.degrees(direction.rawValue)) - } - - private struct TriangleShape: Shape { - func path(in rect: CGRect) -> Path { - var path = Path() - path.move(to: CGPoint(x: rect.midX, y: rect.minY)) - path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) - path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) - path.addLine(to: CGPoint(x: rect.midX, y: rect.minY)) - return path - } - } -} diff --git a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/ExcludeRoundedRectMask.swift b/Projects/Shared/DesignSystem/Sources/Helper/ExcludeRoundedRectMask.swift similarity index 81% rename from Projects/Shared/DesignSystem/Sources/Component/Tooltip/ExcludeRoundedRectMask.swift rename to Projects/Shared/DesignSystem/Sources/Helper/ExcludeRoundedRectMask.swift index a23999f..ad7e0e1 100644 --- a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/ExcludeRoundedRectMask.swift +++ b/Projects/Shared/DesignSystem/Sources/Helper/ExcludeRoundedRectMask.swift @@ -12,6 +12,14 @@ struct ExcludeRoundedRectMask: View { let excludedRect: CGRect let cornerRadius: CGFloat + init( + excludedRect: CGRect, + cornerRadius: CGFloat + ) { + self.excludedRect = excludedRect + self.cornerRadius = cornerRadius + } + var body: some View { GeometryReader { geometry in let fullRect = geometry.frame(in: .local) diff --git a/Projects/Shared/DesignSystem/Sources/Helper/Triangle.swift b/Projects/Shared/DesignSystem/Sources/Helper/Triangle.swift new file mode 100644 index 0000000..da26141 --- /dev/null +++ b/Projects/Shared/DesignSystem/Sources/Helper/Triangle.swift @@ -0,0 +1,43 @@ +// +// Triangle.swift +// DesignSystem +// +// Created by devMinseok on 8/9/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +struct Triangle: View { + enum Direction: Double { + case down = 180 + case left = -90 + case up = 0 + case right = 90 + } + + private let direction: Direction + private let color: Color + + init(direction: Direction, color: Color) { + self.direction = direction + self.color = color + } + + var body: some View { + TriangleShape() + .fill(color) + .rotationEffect(Angle.degrees(direction.rawValue)) + } +} + +private struct TriangleShape: Shape { + func path(in rect: CGRect) -> Path { + var path = Path() + path.move(to: CGPoint(x: rect.midX, y: rect.minY)) + path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.midX, y: rect.minY)) + return path + } +} diff --git a/Projects/Shared/Shared/Sources/Exports.swift b/Projects/Shared/Shared/Sources/Exports.swift index 0531316..2553052 100644 --- a/Projects/Shared/Shared/Sources/Exports.swift +++ b/Projects/Shared/Shared/Sources/Exports.swift @@ -9,7 +9,6 @@ @_exported import Logger @_exported import DesignSystem @_exported import Utils -@_exported import UtilsInterface @_exported import ThirdParty_SPM @_exported import ThirdParty_Firebase diff --git a/Projects/Shared/Utils/Interface/UtilsInterface.swift b/Projects/Shared/Utils/Interface/UtilsInterface.swift deleted file mode 100644 index 1465e61..0000000 --- a/Projects/Shared/Utils/Interface/UtilsInterface.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public struct UtilsInterface { - public init() {} -} diff --git a/Projects/Shared/Utils/Preview/Resources/dummy.txt b/Projects/Shared/Utils/Preview/Resources/dummy.txt deleted file mode 100644 index 3c546eb..0000000 --- a/Projects/Shared/Utils/Preview/Resources/dummy.txt +++ /dev/null @@ -1 +0,0 @@ -dummy file \ No newline at end of file diff --git a/Projects/Shared/Utils/Preview/Sources/Utils.swift b/Projects/Shared/Utils/Preview/Sources/Utils.swift deleted file mode 100644 index 55c51f0..0000000 --- a/Projects/Shared/Utils/Preview/Sources/Utils.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public struct Utils { - public init() {} -} diff --git a/Projects/Shared/Utils/Project.swift b/Projects/Shared/Utils/Project.swift index 95b9007..befea77 100644 --- a/Projects/Shared/Utils/Project.swift +++ b/Projects/Shared/Utils/Project.swift @@ -8,10 +8,7 @@ let project: Project = .makeTMABasedProject( module: Shared.Utils, scripts: [], targets: [ - .sources, - .interface, - .tests, - .testing + .sources ], dependencies: [:] ) diff --git a/Projects/Shared/Utils/Resources/dummy.txt b/Projects/Shared/Utils/Resources/dummy.txt deleted file mode 100644 index 3c546eb..0000000 --- a/Projects/Shared/Utils/Resources/dummy.txt +++ /dev/null @@ -1 +0,0 @@ -dummy file \ No newline at end of file diff --git a/Projects/Shared/Utils/Sources/Extensions/Data+.swift b/Projects/Shared/Utils/Sources/Extension/Foundation/Data+Extension.swift similarity index 100% rename from Projects/Shared/Utils/Sources/Extensions/Data+.swift rename to Projects/Shared/Utils/Sources/Extension/Foundation/Data+Extension.swift diff --git a/Projects/Shared/Utils/Sources/Extensions/Encodable+.swift b/Projects/Shared/Utils/Sources/Extension/Swift/Encodable+Extension.swift similarity index 100% rename from Projects/Shared/Utils/Sources/Extensions/Encodable+.swift rename to Projects/Shared/Utils/Sources/Extension/Swift/Encodable+Extension.swift diff --git a/Projects/Shared/DesignSystem/Sources/Helper/View+Extension.swift b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+CornerRadius.swift similarity index 78% rename from Projects/Shared/DesignSystem/Sources/Helper/View+Extension.swift rename to Projects/Shared/Utils/Sources/Extension/SwiftUI/View+CornerRadius.swift index 61aa651..cebe4a7 100644 --- a/Projects/Shared/DesignSystem/Sources/Helper/View+Extension.swift +++ b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+CornerRadius.swift @@ -1,19 +1,13 @@ // -// View+Extension.swift +// RoundedCorner.swift // DesignSystem // -// Created by devMinseok on 8/12/24. +// Created by devMinseok on 8/15/24. // Copyright © 2024 PomoNyang. All rights reserved. // import SwiftUI -extension View { - func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { - clipShape(RoundedCorner(radius: radius, corners: corners)) - } -} - private struct RoundedCorner: Shape { var radius: CGFloat = .infinity var corners: UIRectCorner = .allCorners @@ -27,3 +21,9 @@ private struct RoundedCorner: Shape { return Path(path.cgPath) } } + +extension View { + public func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape(RoundedCorner(radius: radius, corners: corners)) + } +} diff --git a/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+FrameMeasure.swift b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+FrameMeasure.swift new file mode 100644 index 0000000..60b0075 --- /dev/null +++ b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+FrameMeasure.swift @@ -0,0 +1,52 @@ +// +// FrameMeasure.swift +// DesignSystem +// +// Created by devMinseok on 8/15/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +public struct FrameMeasurePreferenceKey: PreferenceKey { + public typealias Value = [AnyHashable: CGRect] + + public static var defaultValue: Value = Value() + + public static func reduce(value: inout Value, nextValue: () -> Value) { + value.merge(nextValue()) { _, new in + new + } + } +} + +private struct FrameMeasureGeometry: View { + let space: CoordinateSpace + let identifier: AnyHashable + + var body: some View { + GeometryReader { geometry in + Color.clear + .preference( + key: FrameMeasurePreferenceKey.self, + value: [identifier: geometry.frame(in: space)] + ) + } + } +} + +extension View { + /// Frame 측정을 위한 메서드 + public func setFrameMeasure(space: CoordinateSpace, identifier: AnyHashable) -> some View { + return self.background( + FrameMeasureGeometry(space: space, identifier: identifier) + ) + } + + /// 측정된 Frame을 가져오기 위한 메서드 + public func getFrameMeasure(perform action: @escaping (FrameMeasurePreferenceKey.Value) -> Void) -> some View { + return self.onPreferenceChange(FrameMeasurePreferenceKey.self) { value in + action(value) + } + } +} diff --git a/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+Onload.swift b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+Onload.swift new file mode 100644 index 0000000..3d60c10 --- /dev/null +++ b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+Onload.swift @@ -0,0 +1,33 @@ +// +// View+Extension.swift +// Utils +// +// Created by devMinseok on 8/16/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +extension View { + public func onLoad(perform action: (() -> Void)? = nil) -> some View { + modifier(ViewDidLoadModifier(perform: action)) + } +} + +private struct ViewDidLoadModifier: ViewModifier { + @State private var didLoad = false + private let action: (() -> Void)? + + init(perform action: (() -> Void)? = nil) { + self.action = action + } + + func body(content: Content) -> some View { + content.onAppear { + if didLoad == false { + didLoad = true + action?() + } + } + } +} diff --git a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/OverlayWithOnPreferenceChange.swift b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+OverlayWithOnPreferenceChange.swift similarity index 87% rename from Projects/Shared/DesignSystem/Sources/Component/Tooltip/OverlayWithOnPreferenceChange.swift rename to Projects/Shared/Utils/Sources/Extension/SwiftUI/View+OverlayWithOnPreferenceChange.swift index 7f8c7dd..9ec03ed 100644 --- a/Projects/Shared/DesignSystem/Sources/Component/Tooltip/OverlayWithOnPreferenceChange.swift +++ b/Projects/Shared/Utils/Sources/Extension/SwiftUI/View+OverlayWithOnPreferenceChange.swift @@ -8,7 +8,7 @@ import SwiftUI -struct OverlayWithOnPreferenceChange< +public struct OverlayWithOnPreferenceChange< K, OverlayContent >: ViewModifier where K: PreferenceKey, K.Value: Equatable, OverlayContent: View { let preferenceKey: K.Type @@ -23,7 +23,7 @@ struct OverlayWithOnPreferenceChange< self.contentForOverlay = contentForOverlay } - func body(content: Content) -> some View { + public func body(content: Content) -> some View { content .overlay { if let value { @@ -37,7 +37,7 @@ struct OverlayWithOnPreferenceChange< } extension View { - func overlayWithOnPreferenceChange( + public func overlayWithOnPreferenceChange( _ key: K.Type, @ViewBuilder content: @escaping (K.Value) -> Content ) -> some View where K: PreferenceKey, K.Value: Equatable, Content: View { diff --git a/Projects/Shared/Utils/Testing/UtilsTesting.swift b/Projects/Shared/Utils/Testing/UtilsTesting.swift deleted file mode 100644 index 0ebcae6..0000000 --- a/Projects/Shared/Utils/Testing/UtilsTesting.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public struct UtilsTesting { - public init() {} -} diff --git a/Projects/Shared/Utils/Tests/UtilsTests.swift b/Projects/Shared/Utils/Tests/UtilsTests.swift deleted file mode 100644 index d27665a..0000000 --- a/Projects/Shared/Utils/Tests/UtilsTests.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public struct UtilsTests { - public init() {} -}