Skip to content

Commit

Permalink
Merge pull request #47 from YAPP-Github/TNT-205-alarmPage
Browse files Browse the repository at this point in the history
[TNT-205] AlarmCheck ํ™”๋ฉด ์ž‘์„ฑ
  • Loading branch information
FpRaArNkK authored Feb 3, 2025
2 parents 9dcef4f + 8a41354 commit c69d970
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 0 deletions.
33 changes: 33 additions & 0 deletions TnT/Projects/Domain/Sources/Entity/AlarmItemEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// AlarmItemEntity.swift
// Domain
//
// Created by ๋ฐ•๋ฏผ์„œ on 2/2/25.
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

/// ์•Œ๋žŒ ํ™•์ธ์‹œ ์‚ฌ์šฉ๋˜๋Š” ์•Œ๋žŒ ์ •๋ณด ๊ตฌ์กฐ์ฒด
public struct AlarmItemEntity: Equatable {
/// ์•Œ๋žŒ id
public let alarmId: Int
/// ์•Œ๋žŒ ํƒ€์ž…
public let alarmType: AlarmType
/// ์•Œ๋žŒ ๋„์ฐฉ ์‹œ๊ฐ
public let alarmDate: Date
/// ์•Œ๋žŒ ํ™•์ธ ์—ฌ๋ถ€
public let alarmSeenBefore: Bool

public init(
alarmId: Int,
alarmType: AlarmType,
alarmDate: Date,
alarmSeenBefore: Bool
) {
self.alarmId = alarmId
self.alarmType = alarmType
self.alarmDate = alarmDate
self.alarmSeenBefore = alarmSeenBefore
}
}
19 changes: 19 additions & 0 deletions TnT/Projects/Domain/Sources/Entity/AlarmType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// AlarmType.swift
// Domain
//
// Created by ๋ฐ•๋ฏผ์„œ on 2/2/25.
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

/// ์•ฑ์—์„œ ์กด์žฌํ•˜๋Š” ์•Œ๋žŒ ์œ ํ˜•์„ ์ •์˜ํ•œ ์—ด๊ฑฐํ˜•
public enum AlarmType: Equatable {
/// ํŠธ๋ ˆ์ด๋‹ˆ ์—ฐ๊ฒฐ ์™„๋ฃŒ
case traineeConnected(name: String)
/// ํŠธ๋ ˆ์ด๋‹ˆ ์—ฐ๊ฒฐ ํ•ด์ œ
case traineeDisconnected(name: String)
/// ํŠธ๋ ˆ์ด๋„ˆ ์—ฐ๊ฒฐ ํ•ด์ œ
case trainerDisconnected(name: String)
}
2 changes: 2 additions & 0 deletions TnT/Projects/Domain/Sources/Policy/TDateFormat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public enum TDateFormat: String {
case yyyyMMddSlash = "yyyy/MM/dd"
/// "yyyy.MM.dd"
case yyyyMMddDot = "yyyy.MM.dd"
/// ""M์›” d์ผ"
case M์›”_d์ผ = "M์›” d์ผ"
/// "01์›” 10์ผ ํ™”์š”์ผ"
case MM์›”_dd์ผ_EEEE = "MM์›” dd์ผ EEEE"
/// "EE"
Expand Down
86 changes: 86 additions & 0 deletions TnT/Projects/Presentation/Sources/Alarm/AlarmCheckFeature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// AlarmCheckFeature.swift
// Presentation
//
// Created by ๋ฐ•๋ฏผ์„œ on 2/2/25.
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation
import ComposableArchitecture

import Domain
import DesignSystem

@Reducer
public struct AlarmCheckFeature {

@ObservableState
public struct State: Equatable {
// MARK: Data related state
/// ์œ ์ € ํƒ€์ž…
var userType: UserType
/// ์•Œ๋žŒ ์ •๋ณด ๋ชฉ๋ก
var alarmList: [AlarmItemEntity]

/// `AlarmCheckFeature.State`์˜ ์ƒ์„ฑ์ž
/// - Parameters:
/// - userType: ์œ ์ € ํƒ€์ž…
/// - alarmList: ์œ ์ €์—๊ฒŒ ๋„์ฐฉํ•œ ์•Œ๋žŒ ๋ชฉ๋ก (๊ธฐ๋ณธ๊ฐ’: `[]`)
public init(
userType: UserType,
alarmList: [AlarmItemEntity] = []
) {
self.userType = userType
self.alarmList = alarmList
}
}

@Dependency(\.dismiss) private var dismiss

public enum Action: Sendable, ViewAction {
/// ๋ทฐ์—์„œ ๋ฐœ์ƒํ•œ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
case view(View)
/// ๋„ค๋น„๊ฒŒ์ด์…˜ ์—ฌ๋ถ€ ์„ค์ •
case setNavigating

@CasePathable
public enum View: Sendable, BindableAction {
/// ๋ฐ”์ธ๋”ฉ ์•ก์…˜ ์ฒ˜๋ฆฌ
case binding(BindingAction<State>)
/// ์•Œ๋žŒ ์•„์ดํ…œ ํƒญ ๋˜์—ˆ์„ ๋•Œ
case tapAlarmItem(Int)
/// ๋„ค๋น„๊ฒŒ์ด์…˜ back ๋ฒ„ํŠผ ํƒญ ๋˜์—ˆ์„ ๋•Œ
case tapNavBackButton
}
}

public init() {}

public var body: some ReducerOf<Self> {
BindingReducer(action: \.view)

Reduce { state, action in
switch action {
case .view(let action):
switch action {
case .binding:
return .none

case .tapAlarmItem(let id):
print("alarmId: \(id)")
return .none

case .tapNavBackButton:
return .run { _ in
// TODO: ์„œ๋ฒ„ API ๋ช…์„ธ ๋‚˜์˜ค๋ฉด ์—ฐ๊ฒฐ
// ํ˜„์žฌ ๋ชจ๋“  ์•Œ๋žŒ ํ™•์ธ ํ‘œ์‹œ
await self.dismiss()
}
}
case .setNavigating:
return .none
}
}
}
}
94 changes: 94 additions & 0 deletions TnT/Projects/Presentation/Sources/Alarm/AlarmCheckView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// AlarmCheckView.swift
// Presentation
//
// Created by ๋ฐ•๋ฏผ์„œ on 2/2/25.
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
//

import SwiftUI
import ComposableArchitecture

import Domain
import DesignSystem

/// ์•Œ๋žŒ ๋ชฉ๋ก์„ ์ž…๋ ฅํ•˜๋Š” ํ™”๋ฉด
/// ์œ ์ €์—๊ฒŒ ๋„์ฐฉํ•œ ์•Œ๋žŒ์„ ํ‘œ์‹œ - ์œ ์ € ํƒ€์ž…์— ๋”ฐ๋ผ ๋ถ„๋ฅ˜
@ViewAction(for: AlarmCheckFeature.self)
public struct AlarmCheckView: View {

@Bindable public var store: StoreOf<AlarmCheckFeature>
@Environment(\.dismiss) var dismiss: DismissAction

public init(store: StoreOf<AlarmCheckFeature>) {
self.store = store
}

public var body: some View {
VStack(spacing: 0) {
TNavigation(
type: .LButtonWithTitle(leftImage: .icnArrowLeft, centerTitle: "์•Œ๋ฆผ"),
leftAction: { send(.tapNavBackButton) }
)

ScrollView {
AlarmList()
Spacer()
}
}
.navigationBarBackButtonHidden(true)
}

// MARK: - Sections
@ViewBuilder
private func AlarmList() -> some View {
VStack(spacing: 0) {
ForEach(store.alarmList, id: \.alarmId) { item in
AlarmListItem(
alarmTypeText: item.alarmTypeText,
alarmMainText: item.alarmMainText,
alarmTimeText: item.alarmDate.timeAgoDisplay(),
alarmSeenBefore: item.alarmSeenBefore
)
}
}
}
}

private extension AlarmCheckView {
struct AlarmListItem: View {
/// ์—ฐ๊ฒฐ ์™„๋ฃŒ, ์—ฐ๊ฒฐ ํ•ด์ œ ๋“ฑ
let alarmTypeText: String
/// ์•Œ๋žŒ ๋ณธ๋ฌธ
let alarmMainText: String
/// ์•Œ๋žŒ ์‹œ๊ฐ
let alarmTimeText: String
/// ์•Œ๋žŒ ํ™•์ธ ์—ฌ๋ถ€
let alarmSeenBefore: Bool

var body: some View {
HStack(spacing: 16) {
Image(.icnBombEmpty)
.resizable()
.scaledToFit()
.frame(width: 32, height: 32)

VStack(alignment: .leading, spacing: 4) {
HStack {
Text(alarmTypeText)
.typographyStyle(.label1Bold, with: .neutral400)
.padding(.bottom, 3)
Spacer()
Text(alarmTimeText)
.typographyStyle(.label1Medium, with: .neutral400)
}

Text(alarmMainText)
.typographyStyle(.body2Medium, with: .neutral800)
}
}
.padding(20)
.background(alarmSeenBefore ? Color.common0 : Color.neutral100)
}
}
}
29 changes: 29 additions & 0 deletions TnT/Projects/Presentation/Sources/Extension/Date+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Date+.swift
// Presentation
//
// Created by ๋ฐ•๋ฏผ์„œ on 2/2/25.
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

import Domain

extension Date {
func timeAgoDisplay() -> String {
let now = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.second, .minute, .hour, .day], from: self, to: now)

if let day = components.day, day >= 1 {
return TDateFormatUtility.formatter(for: .M์›”_d์ผ).string(from: self)
} else if let hour = components.hour, hour >= 1 {
return "\(hour)์‹œ๊ฐ„ ์ „"
} else if let minute = components.minute, minute >= 1 {
return "\(minute)๋ถ„ ์ „"
} else {
return "๋ฐฉ๊ธˆ"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// AlarmItemEntity.swift
// Presentation
//
// Created by ๋ฐ•๋ฏผ์„œ on 2/2/25.
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
//

import Domain

extension AlarmItemEntity {
/// ์—ฐ๊ฒฐ ์™„๋ฃŒ, ์—ฐ๊ฒฐ ํ•ด์ œ ๋“ฑ
var alarmTypeText: String {
switch self.alarmType {
case .traineeConnected:
return "ํŠธ๋ ˆ์ด๋‹ˆ ์—ฐ๊ฒฐ ์™„๋ฃŒ"
case .traineeDisconnected:
return "ํŠธ๋ ˆ์ด๋‹ˆ ์—ฐ๊ฒฐ ํ•ด์ œ"
case .trainerDisconnected:
return "ํŠธ๋ ˆ์ด๋„ˆ ์—ฐ๊ฒฐ ํ•ด์ œ"
}
}

/// ์•Œ๋žŒ ๋ณธ๋ฌธ
var alarmMainText: String {
switch self.alarmType {
case .traineeConnected(let name):
return "\(name) ํšŒ์›๊ณผ ์—ฐ๊ฒฐ๋˜์—ˆ์–ด์š”"
case .traineeDisconnected(let name):
return "\(name) ํšŒ์›์ด ์—ฐ๊ฒฐ์„ ๋Š์—ˆ์–ด์š”"
case .trainerDisconnected(let name):
return "\(name) ํŠธ๋ ˆ์ด๋„ˆ๊ฐ€ ์—ฐ๊ฒฐ์„ ๋Š์—ˆ์–ด์š”"
}
}
}

0 comments on commit c69d970

Please sign in to comment.