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