From df5d5ab71258f697839f36084c15e5072532222a Mon Sep 17 00:00:00 2001 From: dlrjswns Date: Thu, 17 Sep 2026 17:11:28 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EB=8B=AC=20=ED=91=9C?= =?UTF-8?q?=EC=B6=9C=EA=B4=80=EB=A0=A8=20=EC=A1=B0=EA=B1=B4=EB=B6=80=20?= =?UTF-8?q?=EC=83=9D=EB=9E=B5=20=EB=B0=8F=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Modal/ModalContainerView.swift | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) 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 } } } From 1768464a66b9c8286754ff8215fc6e881a96629f Mon Sep 17 00:00:00 2001 From: dlrjswns Date: Thu, 17 Sep 2026 17:13:35 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[CHORE]=20ModalContainerView=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Presentation/Chat/ChatView.swift | 48 +++++++-------- .../ConversationListView.swift | 20 ++++++- .../Setting/Account/AccountView.swift | 58 +++++++++---------- 3 files changed, 68 insertions(+), 58 deletions(-) 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()