From f18649b95957b68159af101f02030d93dc504d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 12:29:22 +0200 Subject: [PATCH] WIP: Add SwiftUI browse view --- NearbyShare/Model/DataModel.swift | 2 +- NearbyShare/Util/SleepManager.swift | 15 +++--- QuickDropiOS/ContentView.swift | 50 +++++++++++++++++-- QuickDropiOS/Model.swift | 75 +++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 QuickDropiOS/Model.swift diff --git a/NearbyShare/Model/DataModel.swift b/NearbyShare/Model/DataModel.swift index 1d0927f..4586d21 100644 --- a/NearbyShare/Model/DataModel.swift +++ b/NearbyShare/Model/DataModel.swift @@ -44,7 +44,7 @@ public protocol ShareExtensionDelegate: AnyObject { } -public struct RemoteDeviceInfo { +public struct RemoteDeviceInfo: Identifiable, Equatable { public let name: String public let type: DeviceType public var id: String? diff --git a/NearbyShare/Util/SleepManager.swift b/NearbyShare/Util/SleepManager.swift index b56a537..ac026a2 100644 --- a/NearbyShare/Util/SleepManager.swift +++ b/NearbyShare/Util/SleepManager.swift @@ -19,6 +19,8 @@ class SleepManager { #if os(macOS) private var assertionID: IOPMAssertionID = 0 + #else + private var assertionID: Int = 0 #endif private var timer: Timer? @@ -33,11 +35,12 @@ class SleepManager { } private func disableSleep() { - #if os(macOS) + if assertionID != 0 { return } log("[SleepManager] Enabling Wakelock") - + + #if os(macOS) IOPMAssertionCreateWithName( kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString, IOPMAssertionLevel(kIOPMAssertionLevelOn), @@ -45,23 +48,23 @@ class SleepManager { &assertionID ) #elseif os(iOS) - log("[SleepManager] Keeping screen awake (iOS)") UIApplication.shared.isIdleTimerDisabled = true + assertionID = 1 #endif } private func enableSleep() { - #if os(macOS) if assertionID == 0 { return } log("[SleepManager] Disabling Wakelock") + #if os(macOS) IOPMAssertionRelease(assertionID) - assertionID = 0 #elseif os(iOS) - log("[SleepManager] Allowing screen to sleep (iOS)") UIApplication.shared.isIdleTimerDisabled = false #endif + + assertionID = 0 } private func updateSleepState() { diff --git a/QuickDropiOS/ContentView.swift b/QuickDropiOS/ContentView.swift index b60dec9..387dbce 100644 --- a/QuickDropiOS/ContentView.swift +++ b/QuickDropiOS/ContentView.swift @@ -8,14 +8,54 @@ import SwiftUI struct ContentView: View { + var body: some View { + DeviceListView() + } +} + +struct DeviceListView: View { + @StateObject var model = ShareViewModel() + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") + Text("Available Devices") + .font(.title) + .padding() + + if model.foundDevices.isEmpty { + Text("Searching for devices...") + .italic() + .padding() + } else { + List(model.foundDevices) { device in + HStack { + Image(systemName: "smartphone") + .resizable() + .frame(width: 30, height: 30) + Text(device.name.isEmpty ? "Android" : device.name) + Spacer() + + if model.selectedDevice == device { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } + } + .contentShape(Rectangle()) // Make entire row tappable + .onTapGesture { + model.selectDevice(device: device) + } + .background(model.selectedDevice == device ? Color.blue.opacity(0.2) : Color.clear) + } + .listStyle(PlainListStyle()) + } } - .padding() + .frame(minWidth: 300, minHeight: 400) + } +} + +struct DeviceListView_Previews: PreviewProvider { + static var previews: some View { + DeviceListView() } } diff --git a/QuickDropiOS/Model.swift b/QuickDropiOS/Model.swift new file mode 100644 index 0000000..7081d46 --- /dev/null +++ b/QuickDropiOS/Model.swift @@ -0,0 +1,75 @@ +// +// Model.swift +// QuickDrop +// +// Created by Leon Böttger on 28.07.25. +// + +import SwiftUI + +class ShareViewModel: ObservableObject, ShareExtensionDelegate { + + @Published var foundDevices: [RemoteDeviceInfo] = [] + @Published var selectedDevice: RemoteDeviceInfo? + @Published var lastError: Error? + + @Published var progressState: String? = nil + @Published var progressValue: Double? = nil + + private var urls: [URL] = [] + private var textToSend: String? = nil + //private var errorAlertHandler = ErrorAlertHandler.shared + + private var connectionEstablished = false + private var timeoutDispatchWorkItem: DispatchWorkItem? = nil + + init() { + NearbyConnectionManager.shared.startDeviceDiscovery() + } + + func addDevice(device: RemoteDeviceInfo) { + withAnimation { + foundDevices.append(device) + } + } + + func removeDevice(id: String) { + withAnimation { + foundDevices.removeAll { $0.id == id } + } + } + + func connectionWasEstablished(pinCode: String) { + connectionEstablished = true + progressState = String(format: "PinCode".localized(), arguments: [pinCode]) + progressValue = 0 + } + + func connectionFailed(with error: any Error) { + progressValue = nil + lastError = error + + //ErrorAlertHandler.shared.showErrorAlert(for: chosenDevice?.name ?? "", error: error) + } + + func transferAccepted() { + progressState = "Sending".localized() + } + + func transferProgress(progress: Double) { + progressValue = progress + } + + func transferFinished() { + progressState = "TransferFinished".localized() + } + + func selectDevice(device: RemoteDeviceInfo) { + NearbyConnectionManager.shared.stopDeviceDiscovery() + + progressValue = 0 + progressState = "Connecting".localized() + selectedDevice = device + NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls, textToSend: textToSend) + } +}