Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
import Foundation
|
2026-05-17 15:03:12 +03:00
|
|
|
import UIKit
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
|
2026-05-17 13:58:03 +03:00
|
|
|
enum ConnectField { case host, username, password }
|
|
|
|
|
|
2026-05-17 15:03:12 +03:00
|
|
|
enum ConnectionState: Equatable {
|
|
|
|
|
case idle
|
|
|
|
|
case authenticating
|
|
|
|
|
case authenticated
|
|
|
|
|
case destinationSelected
|
|
|
|
|
case failed(String)
|
|
|
|
|
|
|
|
|
|
var isAuthenticated: Bool {
|
|
|
|
|
switch self { case .authenticated, .destinationSelected: return true; default: return false }
|
|
|
|
|
}
|
|
|
|
|
var errorMessage: String? {
|
|
|
|
|
if case .failed(let msg) = self { return msg }
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-05-17 13:58:03 +03:00
|
|
|
}
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
|
|
|
|
|
@MainActor
|
|
|
|
|
final class ConnectViewModel: ObservableObject {
|
2026-05-17 15:03:12 +03:00
|
|
|
@Published var host: String = ""
|
|
|
|
|
@Published var username: String = ""
|
|
|
|
|
@Published var password: String = ""
|
|
|
|
|
@Published var selectedProtocol: NASProtocol = .smb
|
|
|
|
|
@Published var selectedDestination: String = ""
|
|
|
|
|
@Published var connectionState: ConnectionState = .idle
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
@Published var showFolderBrowser = false
|
|
|
|
|
|
feat: Kisani UI overhaul — brand identity, gallery, visual polish
- Rename app to Kisani (CFBundleDisplayName, xcodeproj → Kisani.xcodeproj)
- BackupView: kisani. wordmark + SwiftUI-drawn server logo mark (no asset,
adaptive ink/bg), 206pt progress ring with active-state glow, 4-page
swipeable ring (progress/pending/failed/uploaded), 4-metric stats strip,
friendly NAS card (Home Server label), hamburger → AppMenuView sheet
- GalleryView: new NAS + local photo grid with source picker chip, 3-col
lazy grid, ThumbnailCache, sync-status dots (green = backed up)
- MainTabView: Browse tab → Gallery tab; tab bar uses ultraThinMaterial
blur background (systemGray3 icons, 9.5pt labels)
- All app icon sizes regenerated from 1024pt source via sips
- LANMonitor: nasReachable Bool?, checkNASReachability(host:port:) TCP ping
- project.yml: photo library usage description updated to Kisani branding
Co-Authored-By: Kutesir <kutesir@provoc.ug>
Co-Authored-By: Sentry <sentry@provoc.ug>
2026-05-17 11:17:13 +03:00
|
|
|
init() {
|
|
|
|
|
if let conn = ConnectionStore.shared.savedConnection {
|
2026-05-17 15:03:12 +03:00
|
|
|
host = conn.host
|
|
|
|
|
username = conn.username
|
|
|
|
|
password = conn.password
|
|
|
|
|
selectedProtocol = conn.nasProtocol
|
|
|
|
|
selectedDestination = conn.remotePath == "/" ? "" : conn.remotePath
|
|
|
|
|
connectionState = conn.remotePath == "/" ? .authenticated : .destinationSelected
|
feat: Kisani UI overhaul — brand identity, gallery, visual polish
- Rename app to Kisani (CFBundleDisplayName, xcodeproj → Kisani.xcodeproj)
- BackupView: kisani. wordmark + SwiftUI-drawn server logo mark (no asset,
adaptive ink/bg), 206pt progress ring with active-state glow, 4-page
swipeable ring (progress/pending/failed/uploaded), 4-metric stats strip,
friendly NAS card (Home Server label), hamburger → AppMenuView sheet
- GalleryView: new NAS + local photo grid with source picker chip, 3-col
lazy grid, ThumbnailCache, sync-status dots (green = backed up)
- MainTabView: Browse tab → Gallery tab; tab bar uses ultraThinMaterial
blur background (systemGray3 icons, 9.5pt labels)
- All app icon sizes regenerated from 1024pt source via sips
- LANMonitor: nasReachable Bool?, checkNASReachability(host:port:) TCP ping
- project.yml: photo library usage description updated to Kisani branding
Co-Authored-By: Kutesir <kutesir@provoc.ug>
Co-Authored-By: Sentry <sentry@provoc.ug>
2026-05-17 11:17:13 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 15:03:12 +03:00
|
|
|
var canConnect: Bool {
|
|
|
|
|
!host.trimmingCharacters(in: .whitespaces).isEmpty
|
|
|
|
|
&& !username.isEmpty
|
|
|
|
|
&& !password.isEmpty
|
|
|
|
|
&& connectionState != .authenticating
|
2026-05-17 13:58:03 +03:00
|
|
|
}
|
|
|
|
|
|
2026-05-17 15:03:12 +03:00
|
|
|
var canContinue: Bool {
|
|
|
|
|
connectionState == .destinationSelected && !selectedDestination.isEmpty
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
}
|
|
|
|
|
|
2026-05-17 13:58:03 +03:00
|
|
|
var statusMessage: String {
|
2026-05-17 15:03:12 +03:00
|
|
|
switch connectionState {
|
|
|
|
|
case .idle: return "Connect to your NAS to choose a destination"
|
|
|
|
|
case .authenticating: return "Connecting to NAS…"
|
|
|
|
|
case .authenticated: return "Authenticated — select a destination folder"
|
|
|
|
|
case .destinationSelected: return "Backup destination ready"
|
|
|
|
|
case .failed(let msg): return msg
|
2026-05-17 13:58:03 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 15:03:12 +03:00
|
|
|
func authenticate() async {
|
|
|
|
|
let trimmedHost = host.trimmingCharacters(in: .whitespaces)
|
|
|
|
|
connectionState = .authenticating
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
|
feat: Kisani rebrand, step-based onboarding flow, dark mode fixes
- Rename app to Kisani in all UI text and Face ID prompt
- Replace flat 2-state RootView with 5-step flow: ConnectView →
LoginView → FolderSetupView → PhotoPermissionView → MainTabView
- Remove NavigationStack from LoginView (was causing nested-stack crash);
drive navigation via ConnectionStore.isSessionActive
- Add FolderSetupView and PhotoPermissionView onboarding screens with
step indicator (2/3, 3/3), spring entrance animations, and UGreen
connection chip
- Fix dark mode button invisibility: PrimaryButtonStyle and PillButtonStyle
now use AppTheme.inkInverse; GhostButtonStyle border uses ink.opacity(0.25)
- Add AppTheme.inkInverse adaptive token (dark bg ink on dark, white on light)
- Add ConnectionStore.isSessionActive (non-persisted) and
onboardingPhotoShown (UserDefaults-persisted)
- Add os_log logging in LoginViewModel at auth start/success/failure
- Remove dead Help button from ConnectView
- UGreenCompatibilityBadge, KeychainStore, SavedConnections, extensions,
PrivacyInfo.xcprivacy, unit test target and test stubs
- AppTheme: full UIColor dynamic provider tokens for dark/light adaptive color
- AppearanceMode enum + segmented picker in SettingsView
- BackupView: enlarged ring, removed options card and linear progress bar
- HistoryView: duration and bytes formatting, improved typography hierarchy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:20:53 +03:00
|
|
|
let service: any NASTransferProtocol = selectedProtocol == .smb ? SMBService() : SFTPService()
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
let connection = NASConnection(
|
2026-05-17 15:03:12 +03:00
|
|
|
host: trimmedHost,
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
nasProtocol: selectedProtocol,
|
|
|
|
|
username: username,
|
|
|
|
|
password: password,
|
2026-05-17 15:03:12 +03:00
|
|
|
remotePath: selectedDestination.isEmpty ? "/" : selectedDestination
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
do {
|
2026-05-17 15:03:12 +03:00
|
|
|
try await service.connect(
|
2026-05-17 13:58:03 +03:00
|
|
|
to: connection.host, port: connection.port,
|
|
|
|
|
username: connection.username, password: connection.password
|
|
|
|
|
)
|
2026-05-17 15:03:12 +03:00
|
|
|
service.disconnect()
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
ConnectionStore.shared.savedConnection = connection
|
2026-05-17 15:03:12 +03:00
|
|
|
connectionState = .authenticated
|
|
|
|
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
} catch let e as BackupError {
|
2026-05-17 15:03:12 +03:00
|
|
|
connectionState = .failed(e.errorDescription ?? e.localizedDescription)
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
} catch {
|
2026-05-17 15:03:12 +03:00
|
|
|
connectionState = .failed(error.localizedDescription)
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
}
|
2026-05-17 15:03:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func confirmDestination(_ path: String) {
|
|
|
|
|
guard !path.isEmpty, path != "/" else { return }
|
|
|
|
|
selectedDestination = path
|
|
|
|
|
connectionState = .destinationSelected
|
|
|
|
|
guard var conn = ConnectionStore.shared.savedConnection else { return }
|
|
|
|
|
conn.remotePath = path
|
|
|
|
|
ConnectionStore.shared.savedConnection = conn
|
|
|
|
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resetAuth() {
|
|
|
|
|
connectionState = .idle
|
|
|
|
|
selectedDestination = ""
|
Initial scaffold — NASBackup iOS app
Full project scaffold: XcodeGen project.yml, Core models/protocols/errors,
SMBService (kishikawakatsumi/SMBClient), SFTPService (Citadel 0.9.2),
PhotoLibraryService, LANMonitor, BackupEngine, BackgroundTaskManager,
all feature UIs (Login, Connect, Browse, Backup, History, Settings),
Shared components and theme. Builds clean with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:04 +03:00
|
|
|
}
|
|
|
|
|
}
|