add delete solo session

This commit is contained in:
cheykrym 2025-10-24 10:35:44 +03:00
parent 020aa8de5d
commit 854561b5f7
2 changed files with 71 additions and 3 deletions

View File

@ -345,6 +345,9 @@
} }
} }
}, },
"Вы выйдете из выбранной сессии." : {
"comment" : "Описание подтверждения завершения конкретной сессии"
},
"Вы выйдете со всех устройств, кроме текущего." : { "Вы выйдете со всех устройств, кроме текущего." : {
"comment" : "Описание подтверждения завершения сессий" "comment" : "Описание подтверждения завершения сессий"
}, },
@ -458,7 +461,7 @@
}, },
"Завершить" : { "Завершить" : {
"comment" : "Подтверждение завершения других сессий" "comment" : "Кнопка завершения конкретной сессии\nПодтверждение завершения других сессий\nПодтверждение завершения конкретной сессии"
}, },
"Завершить другие сессии" : { "Завершить другие сессии" : {
"comment" : "Кнопка завершения других сессий" "comment" : "Кнопка завершения других сессий"
@ -466,6 +469,9 @@
"Завершить сессии на других устройствах?" : { "Завершить сессии на других устройствах?" : {
"comment" : "Заголовок подтверждения завершения сессий" "comment" : "Заголовок подтверждения завершения сессий"
}, },
"Завершить эту сессию?" : {
"comment" : "Заголовок подтверждения завершения отдельной сессии"
},
"Заглушка: Push-уведомления" : { "Заглушка: Push-уведомления" : {
}, },

View File

@ -7,6 +7,8 @@ struct ActiveSessionsView: View {
@State private var revokeInProgress = false @State private var revokeInProgress = false
@State private var activeAlert: SessionsAlert? @State private var activeAlert: SessionsAlert?
@State private var showRevokeConfirmation = false @State private var showRevokeConfirmation = false
@State private var sessionPendingRevoke: SessionViewData?
@State private var revokingSessionIds: Set<UUID> = []
private let sessionsService = SessionsService() private let sessionsService = SessionsService()
private var currentSession: SessionViewData? { private var currentSession: SessionViewData? {
@ -60,7 +62,18 @@ struct ActiveSessionsView: View {
if !otherSessions.isEmpty { if !otherSessions.isEmpty {
Section(header: Text(String(format: NSLocalizedString("Другие устройства (%d)", comment: "Заголовок секции других устройств с количеством"), otherSessions.count))) { Section(header: Text(String(format: NSLocalizedString("Другие устройства (%d)", comment: "Заголовок секции других устройств с количеством"), otherSessions.count))) {
ForEach(otherSessions) { session in ForEach(otherSessions) { session in
sessionRow(for: session) let isRevoking = isRevoking(session: session)
sessionRow(for: session, isRevoking: isRevoking)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(role: .destructive) {
sessionPendingRevoke = session
} label: {
Label(NSLocalizedString("Завершить", comment: "Кнопка завершения конкретной сессии"), systemImage: "trash")
}
.disabled(isRevoking)
}
.disabled(isRevoking)
} }
} }
} }
@ -74,6 +87,24 @@ struct ActiveSessionsView: View {
.refreshable { .refreshable {
await loadSessions(force: true) await loadSessions(force: true)
} }
.confirmationDialog(
NSLocalizedString("Завершить эту сессию?", comment: "Заголовок подтверждения завершения отдельной сессии"),
isPresented: Binding(
get: { sessionPendingRevoke != nil },
set: { if !$0 { sessionPendingRevoke = nil } }
),
presenting: sessionPendingRevoke
) { session in
Button(NSLocalizedString("Завершить", comment: "Подтверждение завершения конкретной сессии"), role: .destructive) {
sessionPendingRevoke = nil
Task { await revoke(session: session) }
}
Button(NSLocalizedString("Отмена", comment: "Общий текст кнопки отмены"), role: .cancel) {
sessionPendingRevoke = nil
}
} message: { _ in
Text(NSLocalizedString("Вы выйдете из выбранной сессии.", comment: "Описание подтверждения завершения конкретной сессии"))
}
.alert(item: $activeAlert) { alert in .alert(item: $activeAlert) { alert in
Alert( Alert(
title: Text(alert.title), title: Text(alert.title),
@ -136,7 +167,7 @@ struct ActiveSessionsView: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} }
private func sessionRow(for session: SessionViewData) -> some View { private func sessionRow(for session: SessionViewData, isRevoking: Bool = false) -> some View {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .top) { HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
@ -157,6 +188,9 @@ struct ActiveSessionsView: View {
.background(Color.accentColor.opacity(0.15)) .background(Color.accentColor.opacity(0.15))
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.clipShape(Capsule()) .clipShape(Capsule())
} else if isRevoking {
ProgressView()
.progressViewStyle(.circular)
} }
} }
@ -225,6 +259,34 @@ struct ActiveSessionsView: View {
} }
} }
@MainActor
private func revoke(session: SessionViewData) async {
guard !session.isCurrent, !isRevoking(session: session) else {
return
}
revokingSessionIds.insert(session.id)
defer { revokingSessionIds.remove(session.id) }
do {
let message = try await sessionsService.revoke(sessionId: session.id)
sessions.removeAll { $0.id == session.id }
activeAlert = SessionsAlert(
title: NSLocalizedString("Готово", comment: "Заголовок успешного уведомления"),
message: message
)
} catch {
activeAlert = SessionsAlert(
title: NSLocalizedString("Ошибка", comment: "Заголовок сообщения об ошибке"),
message: error.localizedDescription
)
}
}
private func isRevoking(session: SessionViewData) -> Bool {
revokingSessionIds.contains(session.id)
}
private var revokeOtherSessionsButton: some View { private var revokeOtherSessionsButton: some View {
let primaryColor: Color = revokeInProgress ? .secondary : .red let primaryColor: Color = revokeInProgress ? .secondary : .red