From 6d780d7e9ca5f88d516dd22d275d7bae62d544c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Sat, 16 Aug 2025 21:16:08 +0200 Subject: [PATCH] Add progress for file transfer --- QuickDrop.xcodeproj/project.pbxproj | 44 ++++++++++++++---- QuickDropiOS/ContentView.swift | 15 +++++-- QuickDropiOS/PieProgressView.swift | 69 +++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 QuickDropiOS/PieProgressView.swift diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 8afda4f..2c165cc 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 70; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -103,6 +103,12 @@ EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; + EB979F942E50CA23008B8C5C /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8D2E50CA23008B8C5C /* Configuration.swift */; }; + EB979F952E50CA23008B8C5C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8E2E50CA23008B8C5C /* ContentView.swift */; }; + EB979F962E50CA23008B8C5C /* FilePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8F2E50CA23008B8C5C /* FilePicker.swift */; }; + EB979F972E50CA23008B8C5C /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F902E50CA23008B8C5C /* Model.swift */; }; + EB979F982E50CA23008B8C5C /* QuickDropiOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F922E50CA23008B8C5C /* QuickDropiOSApp.swift */; }; + EB979F9A2E50CAA6008B8C5C /* PieProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F992E50CAA6008B8C5C /* PieProgressView.swift */; }; EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; }; EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; }; EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; }; @@ -231,6 +237,13 @@ EB886E662E4F2F7C0052B7D9 /* License.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = License.swift; sourceTree = ""; }; EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallSheetView.swift; sourceTree = ""; }; EB96ED8E2D7F8103004B3FF1 /* Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = ""; }; + EB979F8D2E50CA23008B8C5C /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; + EB979F8E2E50CA23008B8C5C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + EB979F8F2E50CA23008B8C5C /* FilePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePicker.swift; sourceTree = ""; }; + EB979F902E50CA23008B8C5C /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; + EB979F912E50CA23008B8C5C /* QuickDropiOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickDropiOS.entitlements; sourceTree = ""; }; + EB979F922E50CA23008B8C5C /* QuickDropiOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDropiOSApp.swift; sourceTree = ""; }; + EB979F992E50CAA6008B8C5C /* PieProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PieProgressView.swift; sourceTree = ""; }; EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = sharing_enums.pb.swift; sourceTree = ""; }; EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlertHandler.swift; sourceTree = ""; }; EBC05C6D2E33B57D00EECC18 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = ""; }; @@ -248,10 +261,6 @@ EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = ""; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedRootGroup section */ - EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = QuickDropiOS; sourceTree = ""; }; -/* End PBXFileSystemSynchronizedRootGroup section */ - /* Begin PBXFrameworksBuildPhase section */ 699DEB9E2AB0573200115D22 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -315,7 +324,7 @@ 69DA9A1029E0BF5100A442DA /* QuickDrop */, 699DEBA22AB0573200115D22 /* ShareExtension */, 69DCF4822AB70D0600CBE2CC /* NearbyShare */, - EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */, + EB979F932E50CA23008B8C5C /* QuickDropiOS */, 698DFAEC29E353220064F247 /* Frameworks */, 69DA9A0F29E0BF5100A442DA /* Products */, EBC05C8B2E33B59E00EECC18 /* Resources */, @@ -423,6 +432,20 @@ path = Model; sourceTree = ""; }; + EB979F932E50CA23008B8C5C /* QuickDropiOS */ = { + isa = PBXGroup; + children = ( + EB979F8D2E50CA23008B8C5C /* Configuration.swift */, + EB979F8E2E50CA23008B8C5C /* ContentView.swift */, + EB979F8F2E50CA23008B8C5C /* FilePicker.swift */, + EB979F902E50CA23008B8C5C /* Model.swift */, + EB979F992E50CAA6008B8C5C /* PieProgressView.swift */, + EB979F912E50CA23008B8C5C /* QuickDropiOS.entitlements */, + EB979F922E50CA23008B8C5C /* QuickDropiOSApp.swift */, + ); + path = QuickDropiOS; + sourceTree = ""; + }; EBC05C6A2E33B55100EECC18 /* View */ = { isa = PBXGroup; children = ( @@ -549,9 +572,6 @@ ); dependencies = ( ); - fileSystemSynchronizedGroups = ( - EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */, - ); name = QuickDropiOS; packageProductDependencies = ( EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */, @@ -779,6 +799,12 @@ EB886E6A2E4F37B90052B7D9 /* ErrorAlertHandler.swift in Sources */, EB886E5E2E4F13EB0052B7D9 /* Zip.swift in Sources */, EB5C383D2E38C2D90052EFD7 /* AudioManager.swift in Sources */, + EB979F942E50CA23008B8C5C /* Configuration.swift in Sources */, + EB979F952E50CA23008B8C5C /* ContentView.swift in Sources */, + EB979F962E50CA23008B8C5C /* FilePicker.swift in Sources */, + EB979F9A2E50CAA6008B8C5C /* PieProgressView.swift in Sources */, + EB979F972E50CA23008B8C5C /* Model.swift in Sources */, + EB979F982E50CA23008B8C5C /* QuickDropiOSApp.swift in Sources */, EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */, EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */, diff --git a/QuickDropiOS/ContentView.swift b/QuickDropiOS/ContentView.swift index bfadf81..8ba2cc1 100644 --- a/QuickDropiOS/ContentView.swift +++ b/QuickDropiOS/ContentView.swift @@ -63,6 +63,8 @@ struct DeviceListView: View { CustomSection(header: "AvailableDevices") { ForEach(model.foundDevices) { device in + let isSelected = model.selectedDevice == device + SendPickerButton { HStack(spacing: 12) { @@ -90,15 +92,20 @@ struct DeviceListView: View { .fontWeight(.medium) .foregroundColor(Color.primary) - Text(model.selectedDevice == device ? model.progressState ?? "..." : "Available".localized()) + Text(isSelected ? model.progressState ?? "..." : "Available".localized()) .font(.system(size: 12)) .foregroundColor(Color.primary.opacity(0.6)) } Spacer() - Image(systemName: "chevron.right") - .foregroundColor(Color.primary.opacity(0.6)) + if isSelected, let progress = model.progressValue { + PieProgressView(progress: progress, size: 20) + } + else { + Image(systemName: "chevron.right") + .foregroundColor(Color.primary.opacity(0.6)) + } } .padding(.vertical, 13) } onResult: { urls, text in @@ -106,6 +113,8 @@ struct DeviceListView: View { model.textToSend = text model.selectDevice(device: device) } + .animation(.easeInOut, value: model.progressValue) + .animation(.easeInOut, value: isSelected) } } } diff --git a/QuickDropiOS/PieProgressView.swift b/QuickDropiOS/PieProgressView.swift new file mode 100644 index 0000000..73ad746 --- /dev/null +++ b/QuickDropiOS/PieProgressView.swift @@ -0,0 +1,69 @@ +// +// PieProgressView.swift +// QuickDrop +// +// Created by Leon Böttger on 16.08.25. +// + +import SwiftUI + +struct PieProgressView: View { + let progress: CGFloat // value between 0.0 and 1.0 + var size: CGFloat = 25.0 + + var color: Color? = nil + + var body: some View { + + let color = color ?? .gray + + ZStack { + + Circle() + .stroke(lineWidth: size * 0.08) + .foregroundColor(color) + + ZStack { + PieSlice(progress: progress) + .fill(color) + .rotationEffect(.degrees(-90)) // Start from the top + } + .overlay ( + Image(systemName: "checkmark") + .font(.system(size: 12, weight: .bold)) + .foregroundColor(color == .white ? .blue : .white) + .opacity(progress >= 1.0 ? 1.0 : 0.0) // Show checkmark only when complete + ) + } + .frame(width: size, height: size) + } +} + + +struct PieSlice: Shape { + var progress: CGFloat // from 0.0 to 1.0 + + func path(in rect: CGRect) -> Path { + var path = Path() + + let center = CGPoint(x: rect.midX, y: rect.midY) + let radius = rect.width / 2 + let endAngle = Angle(degrees: 360 * Double(progress)) + + path.move(to: center) + path.addArc(center: center, + radius: radius, + startAngle: .degrees(0), + endAngle: endAngle, + clockwise: false) + path.closeSubpath() + + return path + } +} + + +#Preview { + PieProgressView(progress: 0.76) + .padding() +}