150 lines
5.4 KiB
Swift
150 lines
5.4 KiB
Swift
import SwiftUI
|
|
|
|
struct EncryptionDevicesView: View {
|
|
@ObservedObject var model: AppModel
|
|
let conversation: Conversation
|
|
@Environment(\.dismiss) private var dismiss
|
|
@State private var devices: [OMEMODevice] = []
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
List {
|
|
Section {
|
|
Label(
|
|
statusTitle,
|
|
systemImage: statusIcon
|
|
)
|
|
.foregroundStyle(statusColor)
|
|
} footer: {
|
|
Text("Luma использует trust-on-first-use. Сверьте отпечаток по другому каналу и отметьте устройство как проверенное.")
|
|
}
|
|
|
|
if devices.isEmpty {
|
|
Section("Устройства контакта") {
|
|
ContentUnavailableView(
|
|
"Ключи ещё не получены",
|
|
systemImage: "key.horizontal",
|
|
description: Text("Они появятся после первой попытки отправки или получения OMEMO-сообщения.")
|
|
)
|
|
}
|
|
} else {
|
|
Section("Устройства контакта") {
|
|
ForEach(devices) { device in
|
|
DeviceRow(device: device) {
|
|
model.setDeviceVerified(
|
|
device.trust != .verified,
|
|
jid: device.jid,
|
|
deviceID: device.deviceID
|
|
)
|
|
reload()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let fingerprint = model.ownFingerprint {
|
|
Section("Это устройство") {
|
|
Text(fingerprint.chunked(every: 8).joined(separator: " "))
|
|
.font(.system(.caption, design: .monospaced))
|
|
.textSelection(.enabled)
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Шифрование")
|
|
.toolbar {
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("Готово") { dismiss() }
|
|
}
|
|
}
|
|
.onAppear(perform: reload)
|
|
}
|
|
#if os(macOS)
|
|
.frame(minWidth: 480, minHeight: 500)
|
|
#endif
|
|
}
|
|
|
|
private func reload() {
|
|
devices = model.devices(for: conversation.jid).filter { !$0.isOwn }
|
|
}
|
|
|
|
private var statusTitle: String {
|
|
guard model.encryptionEnabled(for: conversation.jid) else { return "OMEMO выключено для этого чата" }
|
|
return model.isOMEMOReady ? "OMEMO активно" : "OMEMO подготавливается"
|
|
}
|
|
|
|
private var statusIcon: String {
|
|
guard model.encryptionEnabled(for: conversation.jid) else { return "lock.open" }
|
|
return model.isOMEMOReady ? "lock.shield.fill" : "lock.rotation"
|
|
}
|
|
|
|
private var statusColor: Color {
|
|
model.encryptionEnabled(for: conversation.jid) && model.isOMEMOReady ? .green : .orange
|
|
}
|
|
}
|
|
|
|
private struct DeviceRow: View {
|
|
let device: OMEMODevice
|
|
let toggleVerification: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HStack {
|
|
Label("Устройство \(device.deviceID)", systemImage: device.isActive ? "iphone" : "iphone.slash")
|
|
.font(.headline)
|
|
Spacer()
|
|
Button(action: toggleVerification) {
|
|
Label(trustTitle, systemImage: trustIcon)
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(trustColor)
|
|
}
|
|
Text(device.formattedFingerprint)
|
|
.font(.system(.caption, design: .monospaced))
|
|
.foregroundStyle(.secondary)
|
|
.textSelection(.enabled)
|
|
}
|
|
.padding(.vertical, 5)
|
|
}
|
|
|
|
private var trustTitle: String {
|
|
switch device.trust {
|
|
case .verified: return "Проверено"
|
|
case .trusted: return "Доверено"
|
|
case .undecided: return "Проверить"
|
|
case .compromised: return "Скомпрометировано"
|
|
}
|
|
}
|
|
|
|
private var trustIcon: String {
|
|
switch device.trust {
|
|
case .verified: return "checkmark.seal.fill"
|
|
case .trusted: return "checkmark.shield"
|
|
case .undecided: return "questionmark.diamond"
|
|
case .compromised: return "exclamationmark.triangle.fill"
|
|
}
|
|
}
|
|
|
|
private var trustColor: Color {
|
|
switch device.trust {
|
|
case .verified: return .green
|
|
case .trusted: return .blue
|
|
case .undecided: return .orange
|
|
case .compromised: return .red
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension String {
|
|
func chunked(every size: Int) -> [String] {
|
|
guard size > 0 else { return [self] }
|
|
var result: [String] = []
|
|
var cursor = startIndex
|
|
while cursor < endIndex {
|
|
let end = index(cursor, offsetBy: size, limitedBy: endIndex) ?? endIndex
|
|
result.append(String(self[cursor..<end]))
|
|
cursor = end
|
|
}
|
|
return result
|
|
}
|
|
}
|