From 2e40aa5683f2e282ee47edcf5cb1a79a4aa7706c Mon Sep 17 00:00:00 2001 From: Robin Kutesa Date: Sat, 16 May 2026 18:39:35 +0300 Subject: [PATCH] fix(signing): remove Access Wi-Fi entitlement (unsupported on free teams) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop com.apple.developer.networking.wifi-info from project.yml entitlements and NSLocationWhenInUseUsageDescription from Info.plist - SSID detection gracefully returns nil without the capability; all UI handles nil SSID already via optional chaining feat: branding, ring polish, sign-out UX, version/support - LoginView: replace title with "kisani." monospaced wordmark + UGREEN logo, show username then NAS display name after login - BackupView: "kisani." + UGREEN logo chip in toolbar; ring color is orange (in-progress), green (completed), destructive (failed), dim (idle) - BackupJob: cap progress at min(1.0, ...) to prevent >100% display - SettingsView sign-out dialog: two actions — "Sign Out" (lock only, keeps NAS) and "Sign Out & Forget NAS" (full clear); clearer message - SettingsView ACCOUNT section: App Version v1.0 + Support mailto rows - AppMenuView: About section with kisani. wordmark, version, support link Co-Authored-By: Claude Sonnet 4.6 --- App/Info.plist | 2 - Core/Models/BackupJob.swift | 2 +- Features/Backup/AppMenuView.swift | 55 +++++++++++++++++++++++ Features/Backup/BackupView.swift | 17 ++++--- Features/Login/LoginView.swift | 47 ++++++++++--------- Features/Settings/SettingsView.swift | 67 +++++++++++++++++++++++----- NASBackup.entitlements | 5 +-- project.yml | 4 +- 8 files changed, 150 insertions(+), 49 deletions(-) diff --git a/App/Info.plist b/App/Info.plist index b13f579..d397705 100644 --- a/App/Info.plist +++ b/App/Info.plist @@ -27,8 +27,6 @@ 1 NSFaceIDUsageDescription Sign in quickly with Face ID. - NSLocationWhenInUseUsageDescription - Used to detect your home Wi-Fi network name for automatic backup. NSPhotoLibraryUsageDescription NASBackup needs access to your photos to back them up to your NAS. UIApplicationSceneManifest diff --git a/Core/Models/BackupJob.swift b/Core/Models/BackupJob.swift index 340638d..bb9314b 100644 --- a/Core/Models/BackupJob.swift +++ b/Core/Models/BackupJob.swift @@ -35,7 +35,7 @@ final class BackupJob: ObservableObject { var progress: Double { guard totalFiles > 0 else { return 0 } - return Double(uploadedFiles + skippedFiles + failedFiles) / Double(totalFiles) + return min(1.0, Double(uploadedFiles + skippedFiles + failedFiles) / Double(totalFiles)) } var eta: TimeInterval? { diff --git a/Features/Backup/AppMenuView.swift b/Features/Backup/AppMenuView.swift index 086b0a4..e8f1548 100644 --- a/Features/Backup/AppMenuView.swift +++ b/Features/Backup/AppMenuView.swift @@ -22,6 +22,7 @@ struct AppMenuView: View { if !errorEntries.isEmpty { errorsSection } + aboutSection } .padding(.horizontal, AppTheme.hPad) .padding(.top, 8) @@ -248,6 +249,60 @@ struct AppMenuView: View { } } + // MARK: — About + + private var aboutSection: some View { + VStack(alignment: .leading, spacing: 8) { + menuSectionLabel("ABOUT") + + VStack(spacing: 0) { + HStack(spacing: 12) { + Image("ugreen_logo") + .resizable() + .scaledToFit() + .frame(width: 28, height: 28) + .clipShape(RoundedRectangle(cornerRadius: 7, style: .continuous)) + Text("kisani.") + .font(.system(size: 13, weight: .medium, design: .monospaced)) + .foregroundStyle(AppTheme.ink) + .kerning(0.5) + Spacer() + Text("v1.0") + .font(AppTheme.micro()) + .foregroundStyle(AppTheme.inkTertiary) + } + .padding(.horizontal, AppTheme.cardPad) + .padding(.vertical, 14) + + menuHairline + + Link(destination: URL(string: "mailto:apps@provoc.ug")!) { + HStack(spacing: 12) { + Image(systemName: "envelope") + .font(.system(size: 13, weight: .regular)) + .foregroundStyle(AppTheme.inkSecondary) + .frame(width: 20) + Text("Support") + .font(AppTheme.body()) + .foregroundStyle(AppTheme.ink) + Spacer() + Text("apps@provoc.ug") + .font(AppTheme.micro()) + .foregroundStyle(AppTheme.inkTertiary) + Image(systemName: "arrow.up.right") + .font(.system(size: 10, weight: .medium)) + .foregroundStyle(AppTheme.inkQuaternary) + } + .padding(.horizontal, AppTheme.cardPad) + .padding(.vertical, 14) + } + } + .background(AppTheme.surfaceRaised) + .clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous)) + .shadow(color: AppTheme.cardShadow, radius: 12, x: 0, y: 2) + } + } + // MARK: — Helpers private func menuSectionLabel(_ text: String) -> some View { diff --git a/Features/Backup/BackupView.swift b/Features/Backup/BackupView.swift index ceddbd7..699cc6e 100644 --- a/Features/Backup/BackupView.swift +++ b/Features/Backup/BackupView.swift @@ -81,14 +81,15 @@ struct BackupView: View { private var logoChip: some View { HStack(spacing: 6) { - Image("nas_logo_clean") + Image("ugreen_logo") .resizable() .scaledToFit() .frame(width: 22, height: 22) .clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous)) - Text("Kisani") - .font(.system(size: 15, weight: .semibold)) - .foregroundStyle(AppTheme.ink) + Text("kisani.") + .font(.system(size: 13, weight: .medium, design: .monospaced)) + .foregroundStyle(AppTheme.inkSecondary) + .kerning(0.5) } } @@ -235,9 +236,11 @@ struct BackupView: View { private var ringColor: Color { switch engine.job.status { - case .completed: return AppTheme.positive - case .failed: return AppTheme.destructive - default: return AppTheme.ink + case .completed: return AppTheme.positive + case .failed: return AppTheme.destructive + case .running, .paused, + .preparing: return Color(red: 1.0, green: 0.58, blue: 0.0) + default: return AppTheme.inkQuaternary.opacity(0.6) } } diff --git a/Features/Login/LoginView.swift b/Features/Login/LoginView.swift index 6773037..07cd1b3 100644 --- a/Features/Login/LoginView.swift +++ b/Features/Login/LoginView.swift @@ -14,33 +14,28 @@ struct LoginView: View { VStack(spacing: 0) { Spacer() - // ── App identity ────────────────────────── - VStack(spacing: 6) { - Text("Kisani") - .font(.system(size: 34, weight: .bold, design: .default)) - .foregroundStyle(AppTheme.ink) + // ── Brand + logo ────────────────────────── + VStack(spacing: 20) { + brandMark .opacity(appeared ? 1 : 0) - .offset(y: appeared ? 0 : 8) + .offset(y: appeared ? 0 : 10) .animation(.spring(response: 0.44, dampingFraction: 0.82).delay(0.06), value: appeared) + + logoView + .scaleEffect(appeared ? 1 : 0.88) + .opacity(appeared ? 1 : 0) + .animation(.spring(response: 0.48, dampingFraction: 0.74).delay(0.12), value: appeared) } - Spacer().frame(height: 28) - - // ── Logo ────────────────────────────────── - logoView - .scaleEffect(appeared ? 1 : 0.88) - .opacity(appeared ? 1 : 0) - .animation(.spring(response: 0.48, dampingFraction: 0.74).delay(0.12), value: appeared) - - Spacer().frame(height: 24) + Spacer().frame(height: 32) // ── Session context ─────────────────────── if let conn = connection { - VStack(spacing: 5) { - Text(conn.displayName) + VStack(spacing: 4) { + Text(conn.username) .font(.system(size: 17, weight: .semibold)) .foregroundStyle(AppTheme.ink) - Text(conn.username) + Text(conn.displayName) .font(.system(size: 14, weight: .regular)) .foregroundStyle(AppTheme.inkTertiary) } @@ -49,7 +44,7 @@ struct LoginView: View { .animation(.spring(response: 0.4, dampingFraction: 0.8).delay(0.18), value: appeared) } - Spacer().frame(height: 44) + Spacer().frame(height: 40) // ── Auth ────────────────────────────────── VStack(spacing: 14) { @@ -109,21 +104,29 @@ struct LoginView: View { } } + // MARK: — Brand mark + + private var brandMark: some View { + Text("kisani.") + .font(.system(size: 13, weight: .medium, design: .monospaced)) + .foregroundStyle(AppTheme.inkTertiary) + .kerning(1.2) + } + // MARK: — Logo private var logoView: some View { ZStack { - // Subtle ring so logo edge is always legible against any background RoundedRectangle(cornerRadius: 24, style: .continuous) .stroke(AppTheme.inkQuaternary, lineWidth: 0.5) .frame(width: 92, height: 92) - Image("nas_logo_clean") + Image("ugreen_logo") .resizable() .scaledToFit() .frame(width: 90, height: 90) .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous)) } - .shadow(color: .black.opacity(0.14), radius: 20, x: 0, y: 6) + .shadow(color: .black.opacity(0.12), radius: 20, x: 0, y: 6) } } diff --git a/Features/Settings/SettingsView.swift b/Features/Settings/SettingsView.swift index f6e1ff6..54f1c33 100644 --- a/Features/Settings/SettingsView.swift +++ b/Features/Settings/SettingsView.swift @@ -33,10 +33,11 @@ struct SettingsView: View { isPresented: $showSignOutConfirm, titleVisibility: .visible ) { + Button("Sign Out", role: .destructive) { store.lock() } Button("Sign Out & Forget NAS", role: .destructive) { store.signOut() } Button("Cancel", role: .cancel) {} } message: { - Text("Your NAS connection and backup settings will be removed from this device.") + Text("\"Sign Out\" keeps your NAS saved and requires Face ID next time. \"Sign Out & Forget NAS\" removes everything from this device.") } } @@ -463,24 +464,70 @@ struct SettingsView: View { VStack(alignment: .leading, spacing: 8) { sectionLabel("ACCOUNT") - Button(action: { showSignOutConfirm = true }) { + VStack(spacing: 0) { + // App version row HStack(spacing: 12) { - Image(systemName: "rectangle.portrait.and.arrow.right") + Image(systemName: "info.circle") .font(.system(size: 14, weight: .regular)) - .foregroundStyle(AppTheme.destructive) + .foregroundStyle(AppTheme.inkSecondary) .frame(width: 20) - Text("Sign Out") + Text("App version") .font(AppTheme.body()) - .foregroundStyle(AppTheme.destructive) + .foregroundStyle(AppTheme.ink) Spacer() + Text("v1.0") + .font(AppTheme.caption()) + .foregroundStyle(AppTheme.inkTertiary) } .padding(.horizontal, AppTheme.cardPad) .padding(.vertical, 14) - .background(AppTheme.surfaceRaised) - .clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous)) - .shadow(color: AppTheme.cardShadow, radius: 12, x: 0, y: 2) + + hairline + + // Support row + Link(destination: URL(string: "mailto:apps@provoc.ug")!) { + HStack(spacing: 12) { + Image(systemName: "envelope") + .font(.system(size: 14, weight: .regular)) + .foregroundStyle(AppTheme.inkSecondary) + .frame(width: 20) + Text("Support") + .font(AppTheme.body()) + .foregroundStyle(AppTheme.ink) + Spacer() + Text("apps@provoc.ug") + .font(AppTheme.caption()) + .foregroundStyle(AppTheme.inkTertiary) + Image(systemName: "arrow.up.right") + .font(.system(size: 10, weight: .medium)) + .foregroundStyle(AppTheme.inkQuaternary) + } + .padding(.horizontal, AppTheme.cardPad) + .padding(.vertical, 14) + } + + hairline + + // Sign Out button + Button(action: { showSignOutConfirm = true }) { + HStack(spacing: 12) { + Image(systemName: "rectangle.portrait.and.arrow.right") + .font(.system(size: 14, weight: .regular)) + .foregroundStyle(AppTheme.destructive) + .frame(width: 20) + Text("Sign Out") + .font(AppTheme.body()) + .foregroundStyle(AppTheme.destructive) + Spacer() + } + .padding(.horizontal, AppTheme.cardPad) + .padding(.vertical, 14) + } + .buttonStyle(ScaleButtonStyle()) } - .buttonStyle(ScaleButtonStyle()) + .background(AppTheme.surfaceRaised) + .clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous)) + .shadow(color: AppTheme.cardShadow, radius: 12, x: 0, y: 2) } } diff --git a/NASBackup.entitlements b/NASBackup.entitlements index ba21fbd..0c67376 100644 --- a/NASBackup.entitlements +++ b/NASBackup.entitlements @@ -1,8 +1,5 @@ - - com.apple.developer.networking.wifi-info - - + diff --git a/project.yml b/project.yml index 6972971..4097b30 100644 --- a/project.yml +++ b/project.yml @@ -65,12 +65,10 @@ targets: - fetch - processing NSPhotoLibraryUsageDescription: NASBackup needs access to your photos to back them up to your NAS. - NSLocationWhenInUseUsageDescription: Used to detect your home Wi-Fi network name for automatic backup. NSFaceIDUsageDescription: Sign in quickly with Face ID. entitlements: path: NASBackup.entitlements - properties: - com.apple.developer.networking.wifi-info: true + properties: {} dependencies: - package: SMBClient product: SMBClient