mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
The API wrappers existed but iOS had no UI for these actions, while web and Android did. The session row's context menu and the chat's … menu now offer Rename for every session. Active sessions keep Archive. Inactive sessions get Reopen and a confirmed Delete instead of Archive, which the hub rejects with 409 for them. The store applies renames and deletes optimistically. A failed rename refetches server state; a failed delete restores the row and the cached detail. Reopen marks the returned session active right away. When reopen returns a superseding id, the chat seeds the new window, moves the draft and navigates to it, as send-resume already does. A reopen from the list also moves the draft and opens the session. A 422 shows the hub's message with the missing fields. On iPhone, an authoritative removal (delete, or archive elsewhere) now pops that chat, matching the iPad split, which already clears the selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
32 lines
1.2 KiB
Swift
32 lines
1.2 KiB
Swift
import Foundation
|
|
import HapiProtocol
|
|
|
|
/// Hub-provided reopen failure text (the Android `formatReopenError` port):
|
|
/// a `{error, missing?}` body becomes `"<error> (missing: a, b)"`. Nil when
|
|
/// the failure carries no hub message, so the caller shows its own
|
|
/// localized fallback.
|
|
public func formatReopenError(_ error: any Error) -> String? {
|
|
guard let apiError = error as? APIError,
|
|
let body = apiError.body,
|
|
let value = try? HapiJSON.decoder.decode(JSONValue.self, from: Data(body.utf8)),
|
|
case .object(let object) = value,
|
|
case .string(let message)? = object["error"],
|
|
!message.isEmpty else {
|
|
return nil
|
|
}
|
|
var missing: [String] = []
|
|
if case .array(let items)? = object["missing"] {
|
|
missing = items.compactMap { item in
|
|
if case .string(let field) = item { return field }
|
|
return nil
|
|
}
|
|
}
|
|
return missing.isEmpty ? message : "\(message) (missing: \(missing.joined(separator: ", ")))"
|
|
}
|
|
|
|
/// `DELETE /sessions/:id` answers 409 while the session is active (or
|
|
/// became active again) — the caller shows "archive first" wording.
|
|
public func isActiveSessionConflict(_ error: any Error) -> Bool {
|
|
(error as? APIError)?.status == 409
|
|
}
|