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

[TNT-205] AlarmCheck 화면 작성 #47

Merged
merged 5 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) 트레이너가 연결을 끊었어요"
}
}
}