Skip to content
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
24 changes: 24 additions & 0 deletions GAMSS/Sources/Core/Components/Modal/ModalAction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// ModalAction.swift
// GAMSS
//
// Created by 이건준 on 8/14/26.
//

import Foundation

struct ModalAction {
let title: String
let style: ModalButtonStyle
let action: () -> Void

init(
title: String,
style: ModalButtonStyle,
action: @escaping () -> Void
) {
self.title = title
self.style = style
self.action = action
}
}
36 changes: 36 additions & 0 deletions GAMSS/Sources/Core/Components/Modal/ModalButtonStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// ModalButtonStyle.swift
// GAMSS
//
// Created by 이건준 on 8/14/26.
//

import SwiftUI

enum ModalButtonStyle {
case secondary
case primary
case destructive

var foregroundColor: Color {
switch self {
case .secondary:
return .colorGray600
case .primary:
return .colorWhite
case .destructive:
return .colorWhite
}
}

var backgroundColor: Color {
switch self {
case .secondary:
return .colorGray100
case .primary:
return .colorGray950
case .destructive:
return .colorRed
}
}
}
3 changes: 2 additions & 1 deletion GAMSS/Sources/Core/Components/Modal/ModalContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct ModalContainerView<Content: View>: View {
var body: some View {
ZStack {
Color.colorBlack
.opacity(0.3)
.opacity(0.7)
.ignoresSafeArea()
.onTapGesture {
guard dismissOnBackgroundTap else { return }
Expand All @@ -38,6 +38,7 @@ struct ModalContainerView<Content: View>: View {

content()
.transition(.scale.combined(with: .opacity))
.padding(.horizontal, 28)
}
.animation(.easeInOut(duration: 0.25), value: isPresented)
}
Expand Down
87 changes: 87 additions & 0 deletions GAMSS/Sources/Core/Components/Modal/ModalContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// ModalContentView.swift
// GAMSS
//
// Created by 이건준 on 8/14/26.
//

import SwiftUI

struct ModalContentView: View {
let title: String
let subtitle: String?
let actions: [ModalAction]

init(
title: String,
subtitle: String? = nil,
actions: [ModalAction]
) {
self.title = title
self.subtitle = subtitle
self.actions = actions
}

var body: some View {
VStack(alignment: .leading, spacing: 0) {
titleSection

Spacer()
.frame(height: 20)

actionSection
}
.padding(.all, 20)
.background(Color.colorWhite)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}

private extension ModalContentView {
@ViewBuilder
var titleSection: some View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.typography(.subtitle2)
.foregroundStyle(Color.colorGray950)

if let subtitle {
Text(subtitle)
.typography(.body4Medium)
.foregroundStyle(Color.colorGray500)
}
}
}
}


private extension ModalContentView {
@ViewBuilder
var actionSection: some View {
switch actions.count {
case 1:
actionButton(actions[0])

case 2:
HStack(spacing: 8) {
actionButton(actions[0])
actionButton(actions[1])
}

default:
EmptyView()
}
}

func actionButton(_ action: ModalAction) -> some View {
Button(action: action.action) {
Text(action.title)
.typography(.title5)
.frame(maxWidth: .infinity)
.frame(height: 52)
}
.foregroundStyle(action.style.foregroundColor)
.background(action.style.backgroundColor)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
93 changes: 65 additions & 28 deletions GAMSS/Sources/Presentation/Setting/Account/AccountView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,79 @@ struct AccountView: View {
@SwiftUI.Environment(LoginSession.self) private var loginSession
@SwiftUI.Environment(UserManager.self) private var userManager
@SwiftUI.Environment(\.dismiss) private var dismiss
@State private var isLogoutModalPresented = false
@State private var isWithdrawModalPresented = false

init(title: String, viewModel: AccountViewModel) {
self.title = title
_viewModel = StateObject(wrappedValue: viewModel)
}

var body: some View {
VStack(alignment: .leading, spacing: 0) {
header
.padding(.horizontal, 18)
.padding(.vertical, Spacing.spacing400)

ForEach(AccountItem.allCases) { item in
accountItem(item)
ZStack {
VStack(alignment: .leading, spacing: 0) {
header
.padding(.horizontal, 18)
.padding(.vertical, Spacing.spacing400)

ForEach(AccountItem.allCases) { item in
accountItem(item)
.padding(.horizontal, 18)
}

Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(Color.colorWhite)
.toolbar(.hidden, for: .navigationBar)
.alert(
viewModel.errorMessage ?? "",
isPresented: Binding(
get: { viewModel.errorMessage != nil },
set: { if !$0 { viewModel.errorMessage = nil } }
)
) {
Button("확인", role: .cancel) {}
}

Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(Color.colorWhite)
.toolbar(.hidden, for: .navigationBar)
.alert(
viewModel.errorMessage ?? "",
isPresented: Binding(
get: { viewModel.errorMessage != nil },
set: { if !$0 { viewModel.errorMessage = nil } }
)
) {
Button("확인", role: .cancel) {}
if isLogoutModalPresented {
ModalContainerView(isPresented: $isLogoutModalPresented) {
ModalContentView(title: "로그아웃 하시겠어요?", actions: [
.init(title: "로그아웃", style: .secondary, action: {
isLogoutModalPresented = false

Task {
await viewModel.logout()
loginSession.value = .current
}
}),
.init(title: "마저 사용하기", style: .primary, action: {
isLogoutModalPresented = false
})
])
}
}

if isWithdrawModalPresented {
ModalContainerView(isPresented: $isWithdrawModalPresented) {
ModalContentView(
title: "정말 탈퇴하시겠어요?",
subtitle: "회원 탈퇴 시 지금까지 기록된 카드와 대화 내용은\n 영원히 삭제되며 복구되지 않아요.",
actions: [
.init(title: "뒤로가기", style: .secondary, action: {
isWithdrawModalPresented = false
}),
.init(title: "탈퇴하기", style: .destructive, action: {
isWithdrawModalPresented = false

Task {
await viewModel.deleteMember()
loginSession.value = .current
}
})
])
}
}
}
}

Expand Down Expand Up @@ -81,10 +124,7 @@ struct AccountView: View {

case .logout:
Button {
Task {
await viewModel.logout()
loginSession.value = .current
}
isLogoutModalPresented = true
} label: {
row
}
Expand All @@ -94,10 +134,7 @@ struct AccountView: View {
row
case .withdraw:
Button {
Task {
await viewModel.deleteMember()
loginSession.value = .current
}
isWithdrawModalPresented = true
} label: {
row
}
Expand Down
Loading