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

[#84] SettingView 내 ListView 분리 및 추가 리팩터링 #89

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion Soomsil-USaint/Application/AppReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ struct AppReducer {
case .login(.loginResponse(.success(let (info, report)))):
state = .loggedIn(HomeReducer.State(studentInfo: info, totalReportCard: report))
return .none
case .home(.path(.element(id: _, action: .setting(.alert(.presented(.logout)))))):
case .home(.path(.element(id: _, action: .setting(.alert(.presented(.confirmLogoutTapped)))))):
state = .loggedOut(LoginReducer.State())
return .none
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ struct SettingReducer {
struct State {
@Shared(.appStorage("permission")) var permission = false
@Presents var alert: AlertState<Action.Alert>?
// var path = StackState<Path.State>()
// var appState: AppReducer.State?
}

enum Action: BindableAction {
Expand All @@ -28,13 +26,11 @@ struct SettingReducer {
case requestPushAuthorizationResponse(Result<Bool, Error>)
case termsOfServiceButtonTapped
case privacyPolicyButtonTapped
// case path(StackActionOf<Path>)
case alert(PresentationAction<Alert>)
// case appState(AppReducer.Action)

enum Alert: Equatable {
case logout
case configurePushAuthorization
case confirmLogoutTapped
case configurePushAuthorizationTapped
}
}

Expand All @@ -50,7 +46,7 @@ struct SettingReducer {
} actions: {
ButtonState(
role: .destructive,
action: .logout) {
action: .confirmLogoutTapped) {
TextState("로그아웃")
}
ButtonState(
Expand All @@ -59,8 +55,8 @@ struct SettingReducer {
}
}
return .none
case .alert(.presented(.logout)):
// YDSToast("로그아웃", haptic: .success)
case .alert(.presented(.confirmLogoutTapped)):
YDSToast("로그아웃 완료", haptic: .success)
return .none
case .togglePushAuthorization(true):
return .run { send in
Expand All @@ -70,6 +66,7 @@ struct SettingReducer {
}
case .togglePushAuthorization(false):
state.$permission.withLock { $0 = false }
YDSToast("알림권한 거부", haptic: .success)
return .none
case .pushAuthorizationResponse(.success(let granted)):
state.$permission.withLock { $0 = granted }
Expand All @@ -79,7 +76,7 @@ struct SettingReducer {
} actions: {
ButtonState(
role: .destructive,
action: .configurePushAuthorization
action: .configurePushAuthorizationTapped
) {
TextState("설정")
}
Expand All @@ -90,6 +87,8 @@ struct SettingReducer {
} message: {
TextState("알림에 대한 권한 사용을 거부하였습니다. 기능 사용을 원하실 경우 설정 > 앱 > 숨쉴때 유세인트 > 알림 권한 허용을 해주세요.")
}
} else {
YDSToast("알림권한 허용", haptic: .success)
}
return .none
case .requestPushAuthorizationResponse(.success(let granted)):
Expand All @@ -103,37 +102,17 @@ struct SettingReducer {
}
}
return .none
case .alert(.presented(.configurePushAuthorization)):
case .alert(.presented(.configurePushAuthorizationTapped)):
debugPrint("alert permission")
return .run { send in
await send(.requestPushAuthorizationResponse(Result {
try await localNotificationClient.requestPushAuthorization()
}))
}
// case .termsOfServiceButtonTapped:
// state.path.append(
// .navigateToTermsWebView(WebReducer.State(
// url: URL(string: "https://auth.yourssu.com/terms/service.html")!)))
// return .none
// case .privacyPolicyButtonTapped:
// state.path.append(.navigateToTermsWebView(WebReducer.State(
// url: URL(string: "https://auth.yourssu.com/terms/information.html")!)))
// return .none
default:
return .none
}
}
// .ifLet(\.appState, action: \.appState) {
// AppReducer()
// }
.ifLet(\.$alert, action: \.alert)
// .forEach(\.path, action: \.path)
}
}

//extension SettingReducer {
// @Reducer
// enum Path {
// case navigateToTermsWebView(WebReducer)
// }
//}
92 changes: 92 additions & 0 deletions Soomsil-USaint/Application/Feature/Setting/View/ListRowView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// ListRowView.swift
// Soomsil-USaint
//
// Created by 최지우 on 2/5/25.
//

import SwiftUI

import YDS_SwiftUI

enum RightItem {
case none
case toggle(isPushAuthorizationEnabled: Binding<Bool>)
}

struct ListRowView: View {
let title: String
let items: [ItemModel]

init(title: String, items: [ItemModel]) {
self.title = title
self.items = items
}

var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(title)
.font(YDSFont.subtitle3)
.foregroundColor(YDSColor.textSecondary)
.padding(20)
.frame(height: 48)

ForEach(items.indices, id: \.self) { index in
HStack {
items[index]
}
}
}
}
}

struct ItemModel: View {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한 가지 걸리는 건 ItemModel이란 네이밍이 적절할까 고민이긴 한데,, ItemModel하면 뭔가 View가 아닌 느낌이 더 들어서, Row나 RowView와 같은 이름이 더 적절할 것 같아요!

let text: String
let rightItem: RightItem
let action: () -> Void
@State private var isPressed: Bool = false

var body: some View {
HStack {
Text(text)
.font(YDSFont.button3)
.foregroundColor(YDSColor.textSecondary)
.padding(20)
.frame(height: 48)
.frame(maxWidth: .infinity, alignment: .leading)
.background(isPressed ? Color(red: 0.95, green: 0.96, blue: 0.97) : Color.white)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in isPressed = true }
.onEnded { _ in
isPressed = false
switch rightItem {
case .none:
action()
case .toggle(let isPushAuthorizationEnabled):
return
}
}
)

switch rightItem {
case .none:
EmptyView()
case .toggle(let isPushAuthorizationEnabled):
Toggle("", isOn: isPushAuthorizationEnabled)
.labelsHidden()
.padding(.horizontal, 20)
.padding(.vertical, 20)
.tint(YDSColor.buttonPoint)
.frame(height: 48)
// TODO: onChange 액션 추가
.onChange(of: isPushAuthorizationEnabled) {
action()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.onChange(of: isPushAuthorizationEnabled) {
action()
}
.onChange(of: isPushAuthorizationEnabled.wrappedValue) { newValue in
action()
}

이렇게 사용하면 에러가 안나요!

제가 추측하기로는 onChange(of:perform:) 메서드를 사용할 때, of: 매개변수로 전달하는 값이 Equatable 프로토콜을 준수해야 해서 값을 전달할 때 .wrappedValue를 사용하고, 클로저의 매개변수를 newValue로 선언함으로써 Bool 타입임을 명확히 알 수 있어서 에러가 안나는거 같아요...!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.wrappedValue 를 통해 Bool 타입을 명시적으로 받아왔다고 생각했는데 아닌가보군요..!
감사합니다! 저도 조금 더 알아볼께요.

// .onReceive(isPushAuthorizationEnabled.projectedValue) { newValue in
// action()
// }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석이 필요한 부분이 아니면 삭제하는게 좋을 것 같아요! 위에 TODO도 마찬가지로요!

}
}
}
}
Loading