// // ContactResources.swift // Monal // // Created by Friedrich Altheide on 24.12.21. // Copyright © 2021 Monal.im. All rights reserved. // import OrderedCollections @ViewBuilder func resourceRowElement(_ k: String, _ v: some View, space: CGFloat = 5) -> some View { HStack { Text(k).font(.headline) Spacer() v.foregroundColor(.secondary) } } struct ContactResources: View { @StateObject var contact: ObservableKVOWrapper @State var contactVersionInfos: [String:ObservableKVOWrapper] @State private var showCaps: String? init(contact: ObservableKVOWrapper, previewMock: [String:ObservableKVOWrapper]? = nil) { _contact = StateObject(wrappedValue: contact) if previewMock != nil { self.contactVersionInfos = previewMock! } else { var tmpInfos:[String:ObservableKVOWrapper] = [:] for ressourceName in DataLayer.sharedInstance().resources(for: contact.obj) { // load already known software version info from database if let softwareInfo = DataLayer.sharedInstance().getSoftwareVersionInfo(forContact:contact.obj.contactJid, resource:ressourceName, andAccount:contact.obj.accountID) { tmpInfos[ressourceName] = ObservableKVOWrapper(softwareInfo) } } self.contactVersionInfos = tmpInfos } } var body: some View { List { ForEach(self.contactVersionInfos.sorted(by:{ $0.0 < $1.0 }), id: \.key) { key, versionInfo in Section { VStack { resourceRowElement("Resource:", Text(versionInfo.resource as String)) resourceRowElement("Client Name:", Text(versionInfo.appName as String? ?? "")) resourceRowElement("Client Version:", Text(versionInfo.appVersion as String? ?? "")) resourceRowElement("OS:", Text(versionInfo.platformOs as String? ?? "")) if let lastInteraction = versionInfo.lastInteraction as Date? { if lastInteraction.timeIntervalSince1970 == 0 { resourceRowElement("Last Interaction:", Text("Currently Online")) } else { resourceRowElement("Last Interaction:", Text(lastInteraction.formatted(date:.numeric, time:.standard))) } } else { resourceRowElement("Last Interaction:", Text("unsupported")) } } .onTapGesture(count: 2, perform: { showCaps = versionInfo.resource }) } } } .richAlert(isPresented:$showCaps, title:Text("XMPP Capabilities")) { resource in VStack(alignment: .leading) { Text("The resource '\(resource)' has the following capabilities:") .font(Font.body.weight(.semibold)) Spacer() .frame(height: 20) Section { let capsVer = DataLayer.sharedInstance().getVerForUser(self.contact.contactJid, andResource:resource, onAccountID:self.contact.accountID) Text("Caps hash: \(String(describing:capsVer))") Divider() if let capsSet = DataLayer.sharedInstance().getCapsforVer(capsVer, onAccountID:contact.obj.accountID) as? Set { let caps = Array(capsSet) VStack(alignment: .leading) { ForEach(caps, id: \.self) { cap in Text(cap) .font(.system(.footnote, design:.monospaced)) if cap != caps.last { Divider() } } } } } } } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalXmppUserSoftWareVersionRefresh")).receive(on: RunLoop.main)) { notification in if let xmppAccount = notification.object as? xmpp, let softwareInfo = notification.userInfo?["versionInfo"] as? MLContactSoftwareVersionInfo { DDLogVerbose("Got software version info from account \(xmppAccount)...") if softwareInfo.fromJid == contact.obj.contactJid && xmppAccount.accountID == contact.obj.accountID { DispatchQueue.main.async { DDLogVerbose("Successfully matched software version info update to current contact: \(contact.obj)") self.contactVersionInfos[softwareInfo.resource ?? ""] = ObservableKVOWrapper(softwareInfo) } } } } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalNewPresenceNotice")).receive(on: RunLoop.main)) { notification in if let xmppAccount = notification.object as? xmpp, let jid = notification.userInfo?["jid"] as? String, let resource = notification.userInfo?["resource"] as? String, let available = notification.userInfo?["available"] as? NSNumber { DDLogVerbose("Got presence update from account \(xmppAccount)...") if jid == contact.obj.contactJid && xmppAccount.accountID == contact.obj.accountID { DispatchQueue.main.async { DDLogVerbose("Successfully matched presence update to current contact: \(contact.obj)") if available.boolValue { if let softwareInfo = DataLayer.sharedInstance().getSoftwareVersionInfo(forContact:contact.obj.contactJid, resource:resource, andAccount:contact.obj.accountID) { self.contactVersionInfos[resource] = ObservableKVOWrapper(softwareInfo) } // query software version from contact MLXMPPManager.sharedInstance().getEntitySoftWareVersion(for:contact.obj, andResource:resource) } else { self.contactVersionInfos[resource] = nil } } } } } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalLastInteractionUpdatedNotice")).receive(on: RunLoop.main)) { notification in if let xmppAccount = notification.object as? xmpp, let jid = notification.userInfo?["jid"] as? String, let resource = notification.userInfo?["resource"] as? String, notification.userInfo?["lastInteraction"] as? NSDate != nil { DDLogVerbose("Got lastInteraction update from account \(xmppAccount)...") if jid == contact.obj.contactJid && xmppAccount.accountID == contact.obj.accountID { DispatchQueue.main.async { DDLogVerbose("Successfully matched lastInteraction update to current contact: \(contact.obj)") self.contactVersionInfos[resource]?.obj.lastInteraction = DataLayer.sharedInstance().lastInteraction(ofJid:self.contact.obj.contactJid, andResource:resource, forAccountID:contact.obj.accountID) } } } } .onAppear { DDLogVerbose("View will appear...") let newTimeout = DispatchTime.now() + 1.0; DispatchQueue.main.asyncAfter(deadline: newTimeout) { DDLogVerbose("Refreshing software version info...") for ressourceName in DataLayer.sharedInstance().resources(for: contact.obj) { // query software version from contact MLXMPPManager.sharedInstance().getEntitySoftWareVersion(for:contact.obj, andResource:ressourceName) } } } .navigationBarTitle(Text("Devices of \(contact.contactDisplayName as String)"), displayMode: .inline) } } func previewMock() -> [String:ObservableKVOWrapper] { var previewMock:[String:ObservableKVOWrapper] = [:] previewMock["m1"] = ObservableKVOWrapper(MLContactSoftwareVersionInfo.init(jid: "test1@monal.im", andRessource: "m1", andAppName: "Monal", andAppVersion: "1.1.1", andPlatformOS: "ios", andLastInteraction: Date())) previewMock["m2"] = ObservableKVOWrapper(MLContactSoftwareVersionInfo.init(jid: "test1@monal.im", andRessource: "m2", andAppName: "Monal", andAppVersion: "1.1.2", andPlatformOS: "macOS", andLastInteraction: Date())) previewMock["m3"] = ObservableKVOWrapper(MLContactSoftwareVersionInfo.init(jid: "test1@monal.im", andRessource: "m3", andAppName: "Monal", andAppVersion: "1.1.2", andPlatformOS: "macOS", andLastInteraction: Date())) return previewMock } struct ContactResources_Previews: PreviewProvider { static var previews: some View { ContactResources(contact:ObservableKVOWrapper(MLContact.makeDummyContact(0)), previewMock:previewMock()) } }