ios: show notification token status in UI (#552)

* ios: show notification token status in UI

* show notification token status
This commit is contained in:
Evgeny Poberezkin
2022-04-23 06:32:16 +01:00
committed by GitHub
parent 8257842914
commit 0091e9f162
4 changed files with 68 additions and 17 deletions

View File

@@ -19,12 +19,13 @@ class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)")
ChatModel.shared.deviceToken = token
let m = ChatModel.shared
m.deviceToken = token
let useNotifications = UserDefaults.standard.bool(forKey: "useNotifications")
if useNotifications {
Task {
do {
try await apiRegisterToken(token: token)
m.tokenStatus = try await apiRegisterToken(token: token)
} catch {
logger.error("apiRegisterToken error: \(responseError(error))")
}
@@ -47,10 +48,16 @@ class AppDelegate: NSObject, UIApplicationDelegate {
if let token = ChatModel.shared.deviceToken {
logger.debug("AppDelegate: didReceiveRemoteNotification: verification, confirming \(verification)")
Task {
let m = ChatModel.shared
do {
if case .active = m.tokenStatus {} else { m.tokenStatus = .confirmed }
try await apiVerifyToken(token: token, code: verification, nonce: nonce)
m.tokenStatus = .active
try await apiIntervalNofication(token: token, interval: 20)
} catch {
if let cr = error as? ChatResponse, case .chatCmdError(.errorAgent(.NTF(.AUTH))) = cr {
m.tokenStatus = .expired
}
logger.error("AppDelegate: didReceiveRemoteNotification: apiVerifyToken or apiIntervalNofication error: \(responseError(error))")
}
completionHandler(.newData)

View File

@@ -24,6 +24,7 @@ final class ChatModel: ObservableObject {
@Published var userSMPServers: [String]?
@Published var appOpenUrl: URL?
@Published var deviceToken: String?
@Published var tokenStatus = NtfTknStatus.new
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
@@ -930,3 +931,12 @@ struct LinkPreview: Codable {
var description: String = ""
var image: String
}
enum NtfTknStatus: String, Decodable {
case new = "NEW"
case registered = "REGISTERED"
case invalid = "INVALID"
case confirmed = "CONFIRMED"
case active = "ACTIVE"
case expired = "EXPIRED"
}

View File

@@ -173,6 +173,7 @@ enum ChatResponse: Decodable, Error {
case chatItemDeleted(deletedChatItem: AChatItem, toChatItem: AChatItem)
case rcvFileAccepted
case rcvFileComplete(chatItem: AChatItem)
case ntfTokenStatus(status: NtfTknStatus)
case cmdOk
case chatCmdError(chatError: ChatError)
case chatError(chatError: ChatError)
@@ -218,6 +219,7 @@ enum ChatResponse: Decodable, Error {
case .chatItemDeleted: return "chatItemDeleted"
case .rcvFileAccepted: return "rcvFileAccepted"
case .rcvFileComplete: return "rcvFileComplete"
case .ntfTokenStatus: return "ntfTokenStatus"
case .cmdOk: return "cmdOk"
case .chatCmdError: return "chatCmdError"
case .chatError: return "chatError"
@@ -266,6 +268,7 @@ enum ChatResponse: Decodable, Error {
case let .chatItemDeleted(deletedChatItem, toChatItem): return "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))"
case .rcvFileAccepted: return noDetails
case let .rcvFileComplete(chatItem): return String(describing: chatItem)
case let .ntfTokenStatus(status): return String(describing: status)
case .cmdOk: return noDetails
case let .chatCmdError(chatError): return String(describing: chatError)
case let .chatError(chatError): return String(describing: chatError)
@@ -459,8 +462,10 @@ func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteM
throw r
}
func apiRegisterToken(token: String) async throws {
try await sendCommandOkResp(.apiRegisterToken(token: token))
func apiRegisterToken(token: String) async throws -> NtfTknStatus {
let r = await chatSendCmd(.apiRegisterToken(token: token))
if case let .ntfTokenStatus(status) = r { return status }
throw r
}
func apiVerifyToken(token: String, code: String, nonce: String) async throws {
@@ -959,7 +964,8 @@ enum StoreError: Decodable {
enum AgentErrorType: Decodable {
case CMD(cmdErr: CommandErrorType)
case CONN(connErr: ConnectionErrorType)
case SMP(smpErr: SMPErrorType)
case SMP(smpErr: ProtocolErrorType)
case NTF(ntfErr: ProtocolErrorType)
case BROKER(brokerErr: BrokerErrorType)
case AGENT(agentErr: SMPAgentError)
case INTERNAL(internalErr: String)
@@ -982,17 +988,17 @@ enum ConnectionErrorType: Decodable {
}
enum BrokerErrorType: Decodable {
case RESPONSE(smpErr: SMPErrorType)
case RESPONSE(smpErr: ProtocolErrorType)
case UNEXPECTED
case NETWORK
case TRANSPORT(transportErr: SMPTransportError)
case TRANSPORT(transportErr: ProtocolTransportError)
case TIMEOUT
}
enum SMPErrorType: Decodable {
enum ProtocolErrorType: Decodable {
case BLOCK
case SESSION
case CMD(cmdErr: SMPCommandError)
case CMD(cmdErr: ProtocolCommandError)
case AUTH
case QUOTA
case NO_MSG
@@ -1000,15 +1006,15 @@ enum SMPErrorType: Decodable {
case INTERNAL
}
enum SMPCommandError: Decodable {
enum ProtocolCommandError: Decodable {
case UNKNOWN
case SYNTAX
case NO_AUTH
case HAS_AUTH
case NO_QUEUE
case NO_ENTITY
}
enum SMPTransportError: Decodable {
enum ProtocolTransportError: Decodable {
case badBlock
case largeMsg
case badSession

View File

@@ -133,9 +133,8 @@ struct SettingsView: View {
}
if let token = chatModel.deviceToken {
HStack {
Image(systemName: "bolt.fill")
.padding(.trailing, 4)
NotificationsToggle(token)
notificationsIcon()
notificationsToggle(token)
}
}
Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))")
@@ -150,7 +149,35 @@ struct SettingsView: View {
case error(LocalizedStringKey, String)
}
private func NotificationsToggle(_ token: String) -> some View {
private func notificationsIcon() -> some View {
let icon: String
let color: Color
switch (chatModel.tokenStatus) {
case .new:
icon = "bolt"
color = .primary
case .registered:
icon = "bolt.fill"
color = .primary
case .invalid:
icon = "bolt.slash"
color = .primary
case .confirmed:
icon = "bolt.fill"
color = .yellow
case .active:
icon = "bolt.fill"
color = .green
case .expired:
icon = "bolt.slash.fill"
color = .primary
}
return Image(systemName: icon)
.padding(.trailing, 9)
.foregroundColor(color)
}
private func notificationsToggle(_ token: String) -> some View {
Toggle("Check messages", isOn: $useNotifications)
.onChange(of: useNotifications) { enable in
if enable {
@@ -160,6 +187,7 @@ struct SettingsView: View {
Task {
do {
try await apiDeleteToken(token: token)
chatModel.tokenStatus = .new
}
catch {
DispatchQueue.main.async {
@@ -191,7 +219,7 @@ struct SettingsView: View {
primaryButton: .destructive(Text("Confirm")) {
Task {
do {
try await apiRegisterToken(token: token)
chatModel.tokenStatus = try await apiRegisterToken(token: token)
} catch {
DispatchQueue.main.async {
useNotifications = false