Warning, /network/kdeconnect-ios/KDE Connect/KDE Connect/Views/Devices/DevicesDetailView.swift is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2021 Lucas Wang <lucas.wang@tuta.io> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 // Original header below: 0008 // 0009 // DevicesDetailView.swift 0010 // KDE Connect Test 0011 // 0012 // Created by Lucas Wang on 2021-06-17. 0013 // 0014 0015 import SwiftUI 0016 import UniformTypeIdentifiers 0017 import MediaPicker 0018 0019 struct DevicesDetailView: View { 0020 let detailsDeviceId: String 0021 @EnvironmentObject var alertManager: AlertManager 0022 0023 @State private var showingPhotosPicker: Bool = false 0024 @State private var showingFilePicker: Bool = false 0025 @State private var showingPluginSettingsView: Bool = false 0026 0027 @State var chosenFileURLs: [URL] = [] 0028 @ObservedObject var viewModel = connectedDevicesViewModel 0029 private let logger = Logger(category: "DevicesDetailView") 0030 0031 var isStillConnected: Bool { 0032 viewModel.connectedDevices.keys.contains(detailsDeviceId) 0033 } 0034 0035 var body: some View { 0036 if isStillConnected { 0037 VStack { 0038 deviceActionsList 0039 0040 NavigationLink(destination: DeviceDetailPluginSettingsView(detailsDeviceId: self.detailsDeviceId), isActive: $showingPluginSettingsView) { 0041 EmptyView() 0042 } 0043 } 0044 .navigationTitle(backgroundService._devices[detailsDeviceId]!._name) 0045 .navigationBarItems(trailing: 0046 Menu { 0047 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.ping] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.ping] as! Bool) { 0048 Button { 0049 (backgroundService._devices[detailsDeviceId]!._plugins[.ping] as! Ping).sendPing() 0050 } label: { 0051 Label("Send Ping", systemImage: "megaphone") 0052 } 0053 } 0054 0055 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.findMyPhoneRequest] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.findMyPhoneRequest] as! Bool) { 0056 Button { 0057 (backgroundService._devices[detailsDeviceId]!._plugins[.findMyPhoneRequest] as! FindMyPhone).sendFindMyPhoneRequest() 0058 } label: { 0059 Label("Ring Device", systemImage: "bell") 0060 } 0061 } 0062 0063 Button { 0064 showingPluginSettingsView = true 0065 } label: { 0066 Label("Plugin Settings", systemImage: "dot.arrowtriangles.up.right.down.left.circle") 0067 } 0068 0069 Button { 0070 alertManager.queueAlert(prioritize: true, title: "Encryption Info") { 0071 Text("SHA256 fingerprint of your device certificate is:\n\((certificateService.hostCertificateSHA256HashFormattedString == nil) ? "ERROR" : certificateService.hostCertificateSHA256HashFormattedString!)\n\nSHA256 fingerprint of remote device certificate is: \n\((backgroundService._devices[detailsDeviceId]!._SHA256HashFormatted == nil || backgroundService._devices[detailsDeviceId]!._SHA256HashFormatted.isEmpty) ? "Unable to retrieve fingerprint of remote device. Add the remote device's IP address directly using Configure Devices By IP and Refresh Discovery" : backgroundService._devices[detailsDeviceId]!._SHA256HashFormatted)") 0072 } 0073 } label: { 0074 Label("Encryption Info", systemImage: "lock.doc") 0075 } 0076 0077 Button { 0078 alertManager.queueAlert(prioritize: true, title: "Unpair With Device?") { 0079 Text("Unpair with \(backgroundService._devices[detailsDeviceId]!._name)?") 0080 } buttons: { 0081 Button("No, Stay Paired", role: .cancel) {} 0082 Button("Yes, Unpair", role: .destructive) { 0083 backgroundService.unpairDevice(detailsDeviceId) 0084 } 0085 } 0086 } label: { 0087 Label("Unpair", systemImage: "wifi.slash") 0088 } 0089 } label: { 0090 Image(systemName: "ellipsis.circle") 0091 } 0092 ) 0093 .mediaImporter(isPresented: $showingPhotosPicker, allowedMediaTypes: .all, allowsMultipleSelection: true) { result in 0094 switch result { 0095 case .success(let chosenMediaURLs): 0096 if chosenMediaURLs.isEmpty { 0097 logger.info("Media Picker picked nothing") 0098 } else { 0099 DispatchQueue.main.async { 0100 (backgroundService._devices[detailsDeviceId]!._plugins[.share] as! Share) 0101 .prepAndInitFileSend(fileURLs: chosenMediaURLs) 0102 } 0103 } 0104 case .failure(let error): 0105 logger.error("Media Picker Error: \(error.localizedDescription, privacy: .public)") 0106 } 0107 } loadingOverlay: { progress in 0108 NavigationView { 0109 ProgressView(progress) 0110 .padding() 0111 .navigationTitle(Text("Preparing Media…")) 0112 } 0113 .transition(.move(edge: .bottom).combined(with: .opacity)) 0114 } 0115 .fileImporter(isPresented: $showingFilePicker, allowedContentTypes: allUTTypes, allowsMultipleSelection: true) { result in 0116 switch result { 0117 case .success(let chosenFileURLs): 0118 if chosenFileURLs.isEmpty { 0119 logger.info("Document Picker picked nothing") 0120 } else { 0121 (backgroundService._devices[detailsDeviceId]!._plugins[.share] as! Share).prepAndInitFileSend(fileURLs: chosenFileURLs) 0122 } 0123 case .failure(let error): 0124 logger.error("Document Picker Error: \(error.localizedDescription, privacy: .public)") 0125 } 0126 } 0127 .onAppear { 0128 // TODO: use if let as 0129 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.runCommand] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.runCommand] as! Bool) { 0130 (backgroundService._devices[detailsDeviceId]!._plugins[.runCommand] as! RunCommand).requestCommandList() 0131 } 0132 } 0133 } else { 0134 VStack { 0135 Spacer() 0136 Image(systemName: "wifi.slash") 0137 .foregroundColor(.red) 0138 .font(.system(size: 40)) 0139 Text("Device Offline") 0140 Spacer() 0141 } 0142 } 0143 } 0144 0145 var deviceActionsList: some View { 0146 List { 0147 Section(header: Text("Actions")) { 0148 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.clipboard] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.clipboard] as! Bool) { 0149 Button { 0150 (backgroundService._devices[detailsDeviceId]!._plugins[.clipboard] as! Clipboard).sendClipboardContentOut() 0151 } label: { 0152 Label("Push Local Clipboard", systemImage: "square.and.arrow.up.on.square.fill") 0153 } 0154 .accentColor(.primary) 0155 } 0156 0157 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.share] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.share] as! Bool) { 0158 Button { 0159 showingPhotosPicker = true 0160 } label: { 0161 Label("Send Photos and Videos", systemImage: "photo.on.rectangle") 0162 } 0163 .accentColor(.primary) 0164 0165 Button { 0166 showingFilePicker = true 0167 } label: { 0168 Label("Send Files", systemImage: "folder") 0169 } 0170 .accentColor(.primary) 0171 } 0172 0173 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.presenter] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.presenter] as! Bool) { 0174 NavigationLink(destination: PresenterView(detailsDeviceId: detailsDeviceId)) { 0175 Label("Slideshow Remote", systemImage: "slider.horizontal.below.rectangle") 0176 } 0177 .accentColor(.primary) 0178 } 0179 0180 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.runCommand] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.runCommand] as! Bool) { 0181 NavigationLink(destination: RunCommandView(detailsDeviceId: self.detailsDeviceId)) { 0182 Label("Run Command", systemImage: "terminal") 0183 } 0184 .accentColor(.primary) 0185 } 0186 0187 if ((backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.mousePadRequest] != nil) && backgroundService._devices[detailsDeviceId]!._pluginsEnableStatus[.mousePadRequest] as! Bool) { 0188 NavigationLink(destination: RemoteInputView(detailsDeviceId: self.detailsDeviceId)) { 0189 Label("Remote Input", systemImage: "hand.tap") 0190 } 0191 .accentColor(.primary) 0192 } 0193 } 0194 0195 Section(header: Text("Device Status")) { 0196 BatteryStatus(device: backgroundService._devices[detailsDeviceId]!) { battery in 0197 HStack { 0198 Label { 0199 Text("Battery Level") 0200 } icon: { 0201 Image(systemName: battery.statusSFSymbolName) 0202 .foregroundColor(battery.statusColor ?? .primary) 0203 } 0204 Spacer() 0205 Text("\(percent: battery.remoteChargeLevel)") 0206 } 0207 } 0208 } 0209 0210 if let device = backgroundService._devices[detailsDeviceId], 0211 device._pluginsEnableStatus[.share] as? Bool == true { 0212 FileTransferStatusSection(share: device._plugins[.share] as! Share) 0213 } 0214 } 0215 .environment(\.defaultMinListRowHeight, 50) // TODO: make this dynamic with GeometryReader??? 0216 } 0217 } 0218 0219 #if DEBUG 0220 struct DevicesDetailView_Previews: PreviewProvider { 0221 static var previews: some View { 0222 let detailsDeviceId = "MacBook" 0223 UIPreview.setupFakeDevices() 0224 0225 return NavigationView { 0226 DevicesDetailView(detailsDeviceId: detailsDeviceId) 0227 } 0228 .environmentObject(AlertManager()) 0229 } 0230 } 0231 #endif