- 应用图标改用与 Web favicon 同源的扁平标记,金色 SIM 卡体加三格递增信号,29pt 下仍可读 - 标签统一为胶囊形状,提高深色主题下的描边权重,避免标签在暗色下消失 - 消息卡片与详情分区卡片收紧行距,单屏信息密度明显提升 - 详情头部指标精简为 4 行,数据模块改为单条读数 - 批量选择勾选框移出名称区域,工具栏控件统一为等高 34pt - 分组编辑器改为点选即生效,移除说明文字与常驻禁用按钮
89 lines
3.2 KiB
Swift
89 lines
3.2 KiB
Swift
import Foundation
|
|
|
|
@MainActor
|
|
final class MessagesViewModel: ObservableObject {
|
|
enum DirectionFilter: String, CaseIterable, Identifiable {
|
|
case all = "全部"
|
|
case incoming = "收件"
|
|
case outgoing = "发件"
|
|
|
|
var id: String { rawValue }
|
|
}
|
|
|
|
@Published var rows: [SMSMessage] = []
|
|
@Published var instances: [Instance] = []
|
|
@Published var selectedInstanceID: UUID?
|
|
@Published var searchText = ""
|
|
@Published var direction: DirectionFilter = .all
|
|
@Published var isLoading = false
|
|
@Published var errorMessage: String?
|
|
@Published var showingSendForm = false
|
|
|
|
private let repository: any InstanceRepository
|
|
private let service: MessageService
|
|
|
|
init(repository: any InstanceRepository, service: MessageService) {
|
|
self.repository = repository
|
|
self.service = service
|
|
}
|
|
|
|
var filteredRows: [SMSMessage] {
|
|
rows.filter { message in
|
|
selectedInstanceID == nil || message.instanceID == selectedInstanceID
|
|
}.filter { message in
|
|
switch direction {
|
|
case .all: return true
|
|
case .incoming: return message.direction == .incoming
|
|
case .outgoing: return message.direction == .outgoing
|
|
}
|
|
}.filter { message in
|
|
let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !query.isEmpty else { return true }
|
|
return message.phoneNumber.localizedCaseInsensitiveContains(query)
|
|
|| message.content.localizedCaseInsensitiveContains(query)
|
|
|| message.instanceName.localizedCaseInsensitiveContains(query)
|
|
}
|
|
}
|
|
|
|
var selectedInstance: Instance? {
|
|
instances.first { $0.id == selectedInstanceID }
|
|
}
|
|
|
|
func refresh() async {
|
|
isLoading = true
|
|
errorMessage = nil
|
|
do {
|
|
let loaded = try await repository.list(InstanceQuery())
|
|
instances = loaded
|
|
if selectedInstanceID == nil || !loaded.contains(where: { $0.id == selectedInstanceID }) {
|
|
selectedInstanceID = nil
|
|
}
|
|
let page = try await service.messages(loaded, limit: 100)
|
|
rows = page.messages
|
|
} catch MessageServiceError.unauthorized {
|
|
errorMessage = "节点需要访问密码,请在编辑节点中填写后重试。"
|
|
} catch {
|
|
errorMessage = "所有节点都没有返回消息,可能节点离线或未开启短信接口。"
|
|
}
|
|
isLoading = false
|
|
}
|
|
|
|
func send(phoneNumber: String, content: String) async throws {
|
|
guard let instance = selectedInstance ?? instances.first else {
|
|
throw MessageServiceError.invalidInput
|
|
}
|
|
try await service.send(instance, phoneNumber: phoneNumber, content: content)
|
|
await refresh()
|
|
}
|
|
|
|
func delete(_ message: SMSMessage) async {
|
|
guard let instance = instances.first(where: { $0.id == message.instanceID }) else { return }
|
|
do {
|
|
try await service.delete([message.id], from: instance)
|
|
rows.removeAll { $0.id == message.id }
|
|
} catch {
|
|
errorMessage = "删除消息失败。"
|
|
}
|
|
}
|
|
}
|