Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CAT-176] Onboarding paging뷰 ui 및 기능구현 #25

Merged
merged 11 commits into from
Aug 15, 2024
5 changes: 2 additions & 3 deletions Projects/Feature/Feature/Sources/AppCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import SplashFeature
import HomeFeature
import HomeFeatureInterface
import OnboardingFeature
import OnboardingFeatureInterface
import PushService

import ComposableArchitecture
Expand Down Expand Up @@ -95,8 +94,8 @@ public struct AppCore {
case .home:
return .none

case .onboarding:
return .none
// case .onboarding:
// return .none
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved

default:
return .none
Expand Down
1 change: 0 additions & 1 deletion Projects/Feature/Feature/Sources/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import SplashFeature
import HomeFeature
import HomeFeatureInterface
import OnboardingFeature
import OnboardingFeatureInterface

import ComposableArchitecture

Expand Down

This file was deleted.

28 changes: 0 additions & 28 deletions Projects/Feature/OnboardingFeature/Interface/OnboardingView.swift

This file was deleted.

3 changes: 1 addition & 2 deletions Projects/Feature/OnboardingFeature/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ let project: Project = .makeTMABasedProject(
scripts: [],
targets: [
.sources,
.interface,
.tests,
.testing,
.example
],
dependencies: [
.interface: [
.sources: [
.dependency(rootModule: Domain.self)
]
]
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// OnboardingCore.swift
// OnboardingFeature
//
// Created by devMinseok on 7/22/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import Foundation
import OnboardingFeatureInterface
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved
import ComposableArchitecture

@Reducer
public struct OnboardingCore {
@ObservableState
public struct State: Equatable {
public init() { }
var data = RandomAccessEquatableItems<OnboardingItem>(elements: OnboardingItemsData)
var fakedData: RandomAccessEquatableItems<OnboardingItem> = .init(elements: [])
devMinseok marked this conversation as resolved.
Show resolved Hide resolved
var width: CGFloat = .zero
var currentIdx: Int = 0
var currentItemID: String = ""
}

public enum Action: BindableAction {
case onApear
case setIndex(Int)
case calculateOffset(CGFloat, OnboardingItem)
case dragStart
case _timerStart
case _timerEnd
case _timerTicked
case _nextPage(Int)
case _resetTofront
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved
case binding(BindingAction<State>)
}

public init() { }

private enum CancelID { case timer }
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved
@Dependency(\.continuousClock) var clock

public var body: some ReducerOf<Self> {
BindingReducer()
Reduce(self.core)
}

private func core(_ state: inout State, _ action: Action) -> EffectOf<Self> {
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 .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
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

case .dragStart:
let timerEndAction: Effect<Action> = .send(._timerEnd)
let timerStartAction: Effect<Action> = .send(._timerStart)
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved
.debounce(id: "timerStart", for: .seconds(2), scheduler: DispatchQueue.main)
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved
return .merge(
timerEndAction,
timerStartAction
)

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
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// OnboardingItem.swift
// OnboardingFeature
//
// Created by 김지현 on 8/9/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import Foundation
import SwiftUI

import DesignSystem

public struct OnboardingItem: Hashable, Identifiable {
public var id: UUID = .init()
let image: UIImage
var title: String
let subTitle: String
}

let OnboardingItemsData: [OnboardingItem] = [
OnboardingItem(
image: UIImage(systemName: "star")!,
title: "모하냥과 함께 집중시간을 늘려보세요",
subTitle: "고양이 종에 따라 성격이 달라요.\n취향에 맞는 고양이를 선택해 몰입해 보세요."),
OnboardingItem(
image: UIImage(systemName: "star.fill")!,
title: "다른 앱을 실행하면 방해 알림을 보내요",
subTitle: "뽀모도로를 실행한 후, 다른 앱을 사용하면\n설정한 주기로 방해 알림을 보내드려요."),
OnboardingItem(
image: UIImage(systemName: "house")!,
title: "집중과 휴식 반복을 통해 몰입을 관리해요",
subTitle: "일정 시간 집중과 휴식을 반복해 번아웃을 방지하고\n짧은 시간의 몰입을 경험해보세요.")
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved
]

public struct RandomAccessEquatableItems<Element>: 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
}
}
Jihyun247 marked this conversation as resolved.
Show resolved Hide resolved

Loading
Loading