diff --git a/GAMSS/Sources/Core/Components/Modal/ModalContainerView.swift b/GAMSS/Sources/Core/Components/Modal/ModalContainerView.swift index 369abb7..69f52f7 100644 --- a/GAMSS/Sources/Core/Components/Modal/ModalContainerView.swift +++ b/GAMSS/Sources/Core/Components/Modal/ModalContainerView.swift @@ -9,6 +9,7 @@ import SwiftUI struct ModalContainerView: View { @Binding private var isPresented: Bool + @State private var isAppeared = false private let dismissOnBackgroundTap: Bool private let content: () -> Content @@ -24,22 +25,33 @@ struct ModalContainerView: View { } var body: some View { - ZStack { - Color.colorBlack - .opacity(0.7) - .ignoresSafeArea() - .onTapGesture { - guard dismissOnBackgroundTap else { return } + Color.clear + .frame(width: 0, height: 0) + .fullScreenCover(isPresented: $isPresented) { + ZStack { + Color.colorBlack + .opacity(isAppeared ? 0.7 : 0) + .ignoresSafeArea() + .onTapGesture { + guard dismissOnBackgroundTap else { return } + isPresented = false + } - withAnimation(.easeInOut(duration: 0.25)) { - isPresented = false + content() + .padding(.horizontal, 28) + .scaleEffect(isAppeared ? 1 : 0.9) + .opacity(isAppeared ? 1 : 0) + } + .presentationBackground(.clear) + .onAppear { + withAnimation(.easeOut(duration: 0.25)) { + isAppeared = true } } - - content() - .transition(.scale.combined(with: .opacity)) - .padding(.horizontal, 28) - } - .animation(.easeInOut(duration: 0.25), value: isPresented) + .onDisappear { + isAppeared = false + } + } + .transaction { $0.disablesAnimations = true } } } diff --git a/GAMSS/Sources/Presentation/Chat/ChatView.swift b/GAMSS/Sources/Presentation/Chat/ChatView.swift index 8e06446..5dc454f 100644 --- a/GAMSS/Sources/Presentation/Chat/ChatView.swift +++ b/GAMSS/Sources/Presentation/Chat/ChatView.swift @@ -32,22 +32,20 @@ struct ChatView: View { ZStack(alignment: .topTrailing) { chatContent - if viewModel.isEndConfirmationPresented { - ModalContainerView(isPresented: $viewModel.isEndConfirmationPresented) { - ModalContentView( - title: "대화를 종료하고 감정 기록을 생성할게요", - subtitle: "감정 기록 생성 시 대화는 종료되며,\n더 이상 대화를 이어갈 수 없어요.", - actions: [ - .init(title: "뒤로가기", style: .secondary, action: { - viewModel.isEndConfirmationPresented = false - }), - .init(title: "기록 생성하기", style: .primary, action: { - viewModel.isEndConfirmationPresented = false - Task { await viewModel.confirmEndConversation() } - }) - ] - ) - } + ModalContainerView(isPresented: $viewModel.isEndConfirmationPresented) { + ModalContentView( + title: "대화를 종료하고 감정 기록을 생성할게요", + subtitle: "감정 기록 생성 시 대화는 종료되며,\n더 이상 대화를 이어갈 수 없어요.", + actions: [ + .init(title: "뒤로가기", style: .secondary, action: { + viewModel.isEndConfirmationPresented = false + }), + .init(title: "기록 생성하기", style: .primary, action: { + viewModel.isEndConfirmationPresented = false + Task { await viewModel.confirmEndConversation() } + }) + ] + ) } if viewModel.isTokenUsagePopoverPresented { @@ -71,16 +69,14 @@ struct ChatView: View { .padding(.trailing, Spacing.spacing400) } - if viewModel.riskDetection != nil { - ModalContainerView(isPresented: Binding( - get: { viewModel.riskDetection != nil }, - set: { if !$0 { viewModel.riskDetection = nil } } - )) { - SupportAgencyDialogContentView( - detection: viewModel.riskDetection ?? .none, - onDismiss: { viewModel.riskDetection = nil } - ) - } + ModalContainerView(isPresented: Binding( + get: { viewModel.riskDetection != nil }, + set: { if !$0 { viewModel.riskDetection = nil } } + )) { + SupportAgencyDialogContentView( + detection: viewModel.riskDetection ?? .none, + onDismiss: { viewModel.riskDetection = nil } + ) } if viewModel.isEnding { diff --git a/GAMSS/Sources/Presentation/ConversationList/ConversationListView.swift b/GAMSS/Sources/Presentation/ConversationList/ConversationListView.swift index 681a454..b373805 100644 --- a/GAMSS/Sources/Presentation/ConversationList/ConversationListView.swift +++ b/GAMSS/Sources/Presentation/ConversationList/ConversationListView.swift @@ -16,6 +16,7 @@ struct ConversationListView: View { @StateObject private var viewModel: ConversationListViewModel @State private var selectedConversation: ConversationSummary? @State private var isSettingPresented = false + @State private var isDeleteConversationPresented = false init(viewModel: ConversationListViewModel) { _viewModel = StateObject(wrappedValue: viewModel) @@ -83,7 +84,7 @@ struct ConversationListView: View { if viewModel.currentMode == .delete { Button { - Task { await viewModel.deleteConversations() } + isDeleteConversationPresented = true } label: { Text("삭제하기") .typography(.body3Medium) @@ -140,6 +141,23 @@ struct ConversationListView: View { )) { Button("확인", role: .cancel) {} } + .background { + ModalContainerView(isPresented: $isDeleteConversationPresented) { + ModalContentView( + title: "대화를 정말 삭제할까요?", + subtitle: "대화를 이어갈 수 없으며, 카드를 생성할 수 없습니다.\n채팅은 영구적으로 삭제됩니다.", + actions: [ + .init(title: "뒤로가기", style: .secondary, action: { + isDeleteConversationPresented = false + }), + .init(title: "삭제하기", style: .destructive, action: { + isDeleteConversationPresented = false + Task { await viewModel.deleteConversations() } + }) + ] + ) + } + } } private var dateHeader: some View { diff --git a/GAMSS/Sources/Presentation/Setting/Account/AccountView.swift b/GAMSS/Sources/Presentation/Setting/Account/AccountView.swift index 1b1ee86..bdda216 100644 --- a/GAMSS/Sources/Presentation/Setting/Account/AccountView.swift +++ b/GAMSS/Sources/Presentation/Setting/Account/AccountView.swift @@ -46,43 +46,39 @@ struct AccountView: View { Button("확인", role: .cancel) {} } - if isLogoutModalPresented { - ModalContainerView(isPresented: $isLogoutModalPresented) { - ModalContentView(title: "로그아웃 하시겠어요?", actions: [ - .init(title: "로그아웃", style: .secondary, action: { - isLogoutModalPresented = false + 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 + }) + ]) + } + + 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.logout() + await viewModel.deleteMember() 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 - } - }) - ]) - } } } .hidesTabBar()