Add preparing label when sending large files from within app

This commit is contained in:
Leon Böttger committed 2025-08-18 11:06:25 +02:00
1 parent 7d6d0f3482
commit c7a3635689
6 files changed
+347 -40

No files matched your search

@@ -43,7 +43,7 @@
"location" : "https://github.com/leonboe1/LUI.git",
"state" : {
"branch" : "main",
"revision" : "0fa79d937a5948c59f44c64b12af21c7d3b408e5"
"revision" : "dc041b1fee0be5a8546044b219ac03d7580da6ad"
}
},
{
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EB5C37E12E377C2E0052EFD7"
BuildableName = "QuickDropiOS.app"
BlueprintName = "QuickDropiOS"
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EB5C37E12E377C2E0052EFD7"
BuildableName = "QuickDropiOS.app"
BlueprintName = "QuickDropiOS"
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EB5C37E12E377C2E0052EFD7"
BuildableName = "QuickDropiOS.app"
BlueprintName = "QuickDropiOS"
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EB950B2C2E52027F002D68FA"
BuildableName = "iOSShareExtension.appex"
BlueprintName = "iOSShareExtension"
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EB5C37E12E377C2E0052EFD7"
BuildableName = "QuickDropiOS.app"
BlueprintName = "QuickDropiOS"
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<RemoteRunnable
runnableDebuggingMode = "0"
BundleIdentifier = "com.apple.mobileslideshow"
RemotePath = "/var/containers/Bundle/Application/3D45D27D-5ADC-417F-B4DE-19000341E9B7/Photos.app">
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EB5C37E12E377C2E0052EFD7"
BuildableName = "QuickDropiOS.app"
BlueprintName = "QuickDropiOS"
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EB5C37E12E377C2E0052EFD7"
BuildableName = "QuickDropiOS.app"
BlueprintName = "QuickDropiOS"
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+107
View File
@@ -6094,6 +6094,113 @@
}
}
},
"Preparing" : {
"extractionState" : "manual",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "جارٍ التحضير..."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bereite vor..."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Preparing..."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Preparando..."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Préparation..."
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Előkészítés folyamatban..."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Preparazione in corso..."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "準備中..."
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "준비 중..."
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Voorbereiden..."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Preparando..."
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "A preparar..."
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se pregătește..."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Подготовка..."
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "กำลังเตรียม..."
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Підготовка..."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "准备中…"
}
}
}
},
"PrivacyPolicy" : {
"extractionState" : "manual",
"localizations" : {
+28 -9
View File
@@ -112,17 +112,11 @@ struct DeviceListView: View {
sendModel.textToSend = attachments.textToSend
sendModel.selectDevice(device: device)
} label: {
DeviceButton(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue)
DeviceButtonLabel(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue)
}
}
else {
SendPickerButton {
DeviceButton(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue)
} onResult: { urls, text in
sendModel.urls = urls ?? []
sendModel.textToSend = text
sendModel.selectDevice(device: device)
}
DeviceFilePickerButton(device: device, isSelected: isSelected, progressState: sendModel.progressState, progressValue: sendModel.progressValue, sendModel: sendModel)
}
}
.animation(.easeInOut, value: sendModel.progressValue)
@@ -153,7 +147,32 @@ struct DeviceListView: View {
}
struct DeviceButton: View {
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
+32 -30
View File
@@ -13,6 +13,7 @@ import UniformTypeIdentifiers
struct SendPickerButton<Label: View>: View {
var label: () -> Label
var onResult: (_ urls: [URL]?, _ text: String?) -> Void
var onPrepare: (() -> Void)? = nil
@State private var showPhotoPicker = false
@State private var selectedPhotoItems: [PhotosPickerItem] = []
@@ -45,6 +46,7 @@ struct SendPickerButton<Label: View>: View {
)
.onChange(of: selectedPhotoItems) { newItems in
guard !newItems.isEmpty else { return }
onPrepare?()
updatedPhotoSelection(content: newItems)
}
// File picker
@@ -83,37 +85,37 @@ struct SendPickerButton<Label: View>: View {
if let dataUrl = try await item.loadTransferable(type: Data.self) {
if let contentType = item.supportedContentTypes.first {
// Step 2: make the URL file name and a get a file extention.
// Step 2: make the URL file name and a get a file extention.
let url = fileManager.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).\(contentType.preferredFilenameExtension ?? "")")
do {
// Step 3: write to temp App file directory and return in completionHandler
try dataUrl.write(to: url)
return url
} catch {
log("Faied to write data to file: \(error)")
throw error
}
}
do {
// Step 3: write to temp App file directory and return in completionHandler
try dataUrl.write(to: url)
return url
} catch {
log("Failed to write data to file: \(error)")
throw error
}
}
// let destination = fileManager.temporaryDirectory.appendingPathComponent(dataUrl.url.lastPathComponent)
//
// // overwrite if file exists
// if fileManager.fileExists(atPath: destination.path) {
// try fileManager.removeItem(at: destination)
// }
//
// log("Copying photo at \(dataUrl.url) to \(destination)")
//
// do {
// try fileManager.copyItem(at: dataUrl.url, to: destination)
// }
// catch {
// log("Failed to copy photo: \(error)")
// throw error
// }
// return destination
// let destination = fileManager.temporaryDirectory.appendingPathComponent(dataUrl.url.lastPathComponent)
//
// // overwrite if file exists
// if fileManager.fileExists(atPath: destination.path) {
// try fileManager.removeItem(at: destination)
// }
//
// log("Copying photo at \(dataUrl.url) to \(destination)")
//
// do {
// try fileManager.copyItem(at: dataUrl.url, to: destination)
// }
// catch {
// log("Failed to copy photo: \(error)")
// throw error
// }
// return destination
}
return nil
}
@@ -135,7 +137,7 @@ struct SendPickerButton<Label: View>: View {
/// File representation
fileprivate struct DataUrl: Transferable {
let url: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .data) { data in