// // ContentView.swift // QuickDropiOS // // Created by Leon Böttger on 28.07.25. // import SwiftUI import LUI struct ContentView: View { @Environment(\.scenePhase) var scenePhase var body: some View { AppRootView(isPlus: .constant(true), phoneView: { DeviceListView() .environment(\.sheetActive, isShareExtension()) }, settingsView: { CustomSection { LUIButton { SaveFilesManager.shared.openDownloadedFilesFolder() } label: { NavigationLinkLabel(imageName: "folder.fill", text: "BrowseDownloadedFiles") } } }) .onChange(of: scenePhase) { newValue in if newValue == .active { #if !EXTENSION NearbyConnectionManager.shared.becomeVisible() #endif NearbyConnectionManager.shared.startDeviceDiscovery() } if newValue == .background { #if !EXTENSION NearbyConnectionManager.shared.becomeInvisible() #endif NearbyConnectionManager.shared.stopDeviceDiscovery() } } } } struct DeviceListView: View { @StateObject var sendModel = SendModel() #if !EXTENSION @StateObject var receiveModel = ReceiveModel() #endif @ObservedObject var nearbyConnectionManager = NearbyConnectionManager.shared var body: some View { NavigationSubView(header: "QuickDrop ", navigationBarLayout: isShareExtension() ? .SmallOnlyAlways : .Default) { VStack(alignment: .leading, spacing: 8) { let attachments = nearbyConnectionManager.attachments FooterView(text: attachments == nil ? "YouAreVisibleAs".localized() : "YouAreSending".localized()) HStack { if let image = attachments?.previewImage { image .resizable() .scaledToFill() .frame(width: 10, height: 10) } LUIText(attachments?.shortDescription ?? NearbyConnectionManager.shared.deviceInfo.name ?? "Unknown".localized(), isBold: true) .lineLimit(1) } .padding(.horizontal, 16) .padding(.vertical, 6) .background(Color.defaultForegroundColor) .cornerRadius(24) .padding(.horizontal) } .padding(.top, 8) .frame(maxWidth: .infinity, alignment: .leading) if !nearbyConnectionManager.hasLocalNetworkAccess { CardView(backgroundColor: .red, title: "NoNetworkAccess", titleSymbol: "network.slash") { HStack { CardSubView(symbol: "exclamationmark.triangle.fill", text: "NoLocalNetworkAccessDescription") } } .padding(.bottom, 10) .onTapGesture { openAppSettings() } } if sendModel.foundDevices.isEmpty { CustomSection(header: "NoDevicesFound") { HStack(spacing: 12) { Image(systemName: "square.and.arrow.down") .font(.system(size: 30)) .foregroundColor(.gray) .padding(.leading, -10) Text("DownloadQuickDropOnPlayStore") } .padding(.vertical) } } else { CustomSection(header: "AvailableDevices") { ForEach(sendModel.foundDevices) { device in let isSelected = sendModel.selectedDevice == device ZStack { if let attachments = NearbyConnectionManager.shared.attachments { LUIButton { lightVibration() sendModel.urls = attachments.urls sendModel.textToSend = attachments.textToSend sendModel.selectDevice(device: device) } label: { DeviceButtonLabel(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue) } } else { DeviceFilePickerButton(device: device, isSelected: isSelected, progressState: sendModel.progressState, progressValue: sendModel.progressValue, sendModel: sendModel) } } .animation(.easeInOut, value: sendModel.progressValue) .animation(.easeInOut, value: isSelected) } } } if nearbyConnectionManager.hasLocalNetworkAccess { HStack(spacing: 8) { Text("SearchingForDevices") .font(.system(size: 14)) .foregroundColor(Color.primary.opacity(0.6)) ProgressView() .frame(width: 12, height: 12) } .padding(.vertical, 16) } } .animation(.smooth, value: nearbyConnectionManager.hasLocalNetworkAccess) .navigationBarItems(trailing: ZStack { if isShareExtension() { XButton(action: { NearbyConnectionManager.shared.attachments?.closeView?() }) } else { LUISettingsButton() } }) } } struct DeviceFilePickerButton: View { let device: RemoteDeviceInfo let isSelected: Bool let progressState: String? let progressValue: Double? let sendModel: SendModel @State private var isPreparing = false var body: some View { SendPickerButton { DeviceButtonLabel(device: device, isSelected: isPreparing || isSelected, progress: isPreparing ? "Preparing".localized() : progressState, progressValue: progressValue) } onResult: { urls, text in isPreparing = false sendModel.urls = urls ?? [] sendModel.textToSend = text sendModel.selectDevice(device: device) } onPrepare: { isPreparing = true } } } struct DeviceButtonLabel: View { let device: RemoteDeviceInfo let isSelected: Bool let progress: String? let progressValue: Double? var body: some View { HStack(spacing: 12) { let name = device.name ?? "Android" ZStack { Circle() .fill( LinearGradient( colors: [Color(red: 0.623, green: 0.659, blue: 0.855), Color(red: 0.474, green: 0.525, blue: 0.796)], startPoint: .top, endPoint: .bottom ) ) .frame(width: 40, height: 40) Image(systemName: device.icon) .foregroundColor(.white) .fontWeight(.bold) } VStack(alignment: .leading, spacing: 5) { Text(name) .fontWeight(.medium) .foregroundColor(Color.primary) Text(isSelected ? progress ?? "..." : "Available".localized()) .font(.system(size: 12)) .foregroundColor(Color.primary.opacity(0.6)) } Spacer() if isSelected, let progress = progressValue { PieProgressView(progress: progress, size: 20) } else { Image(systemName: "chevron.right") .foregroundColor(Color.primary.opacity(0.6)) } } .padding(.vertical, 13) } } func isShareExtension() -> Bool { #if EXTENSION return true #else return false #endif } //extension DeviceListView { // static var preview: DeviceListView { // let model = SendModel() // model.foundDevices = [ // RemoteDeviceInfo(name: "MacBook Pro", type: .computer, id: "macbook-pro-id"), // RemoteDeviceInfo(name: "iPhone 14", type: .phone, id: "iphone-14-id"), // RemoteDeviceInfo(name: "Samsung Galaxy S21", type: .phone, id: "samsung-galaxy-s21-id") // ] // // return DeviceListView(sendModel: model, receiveModel: ReceiveModel()) // } //} // #Preview { NavigationView { DeviceListView() } }