Fix CLI installation animation issues in welcome dialog

- Added withAnimation wrapper to all state changes in CLIInstaller
- Added small delay before checking installation status to allow view to settle
- Updated permission text to say "AppleScript automation"
- All state transitions now animate smoothly with 0.3s easeInOut
- Fixed the "CLI tool is installed" text popping in without animation
This commit is contained in:
Peter Steinberger 2025-06-17 22:44:03 +02:00
parent 042163d3ab
commit 90849a11e2
3 changed files with 41 additions and 14 deletions

View file

@ -229,9 +229,15 @@ private struct VTCommandPageView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.padding() .padding()
.onAppear { .onAppear {
// Delay slightly to allow view to settle before animating
Task {
try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds
await MainActor.run {
cliInstaller.checkInstallationStatus() cliInstaller.checkInstallationStatus()
} }
} }
}
}
} }
// MARK: - Request Permissions Page // MARK: - Request Permissions Page
@ -255,7 +261,7 @@ private struct RequestPermissionsPageView: View {
.fontWeight(.semibold) .fontWeight(.semibold)
Text( Text(
"VibeTunnel needs AppleScript to launch and manage terminal sessions\nand accessibility to send commands to certain terminals." "VibeTunnel needs AppleScript automation to launch and manage terminal sessions\nand accessibility to send commands to certain terminals."
) )
.font(.body) .font(.body)
.foregroundColor(.secondary) .foregroundColor(.secondary)

View file

@ -1,6 +1,7 @@
import AppKit import AppKit
import Foundation import Foundation
import Observation import Observation
import SwiftUI
import os.log import os.log
/// Service responsible for creating symlinks to command line tools with sudo authentication. /// Service responsible for creating symlinks to command line tools with sudo authentication.
@ -36,7 +37,13 @@ final class CLIInstaller {
/// Checks if the CLI tool is installed /// Checks if the CLI tool is installed
func checkInstallationStatus() { func checkInstallationStatus() {
let targetPath = "/usr/local/bin/vt" let targetPath = "/usr/local/bin/vt"
isInstalled = FileManager.default.fileExists(atPath: targetPath) let installed = FileManager.default.fileExists(atPath: targetPath)
// Animate the state change for smooth UI transitions
withAnimation(.easeInOut(duration: 0.3)) {
isInstalled = installed
}
logger.info("CLIInstaller: CLI tool installed: \(self.isInstalled)") logger.info("CLIInstaller: CLI tool installed: \(self.isInstalled)")
} }
@ -50,14 +57,18 @@ final class CLIInstaller {
/// Installs the vt CLI tool to /usr/local/bin with proper symlink /// Installs the vt CLI tool to /usr/local/bin with proper symlink
func installCLITool() { func installCLITool() {
logger.info("CLIInstaller: Starting CLI tool installation...") logger.info("CLIInstaller: Starting CLI tool installation...")
withAnimation(.easeInOut(duration: 0.3)) {
isInstalling = true isInstalling = true
lastError = nil lastError = nil
}
guard let resourcePath = Bundle.main.path(forResource: "vt", ofType: nil) else { guard let resourcePath = Bundle.main.path(forResource: "vt", ofType: nil) else {
logger.error("CLIInstaller: Could not find vt binary in app bundle") logger.error("CLIInstaller: Could not find vt binary in app bundle")
lastError = "The vt command line tool could not be found in the application bundle." lastError = "The vt command line tool could not be found in the application bundle."
showError("The vt command line tool could not be found in the application bundle.") showError("The vt command line tool could not be found in the application bundle.")
withAnimation(.easeInOut(duration: 0.3)) {
isInstalling = false isInstalling = false
}
return return
} }
@ -79,7 +90,9 @@ final class CLIInstaller {
let response = alert.runModal() let response = alert.runModal()
if response != .alertFirstButtonReturn { if response != .alertFirstButtonReturn {
logger.info("CLIInstaller: User cancelled replacement") logger.info("CLIInstaller: User cancelled replacement")
withAnimation(.easeInOut(duration: 0.3)) {
isInstalling = false isInstalling = false
}
return return
} }
} }
@ -98,7 +111,9 @@ final class CLIInstaller {
let response = confirmAlert.runModal() let response = confirmAlert.runModal()
if response != .alertFirstButtonReturn { if response != .alertFirstButtonReturn {
logger.info("CLIInstaller: User cancelled installation") logger.info("CLIInstaller: User cancelled installation")
withAnimation(.easeInOut(duration: 0.3)) {
isInstalling = false isInstalling = false
}
return return
} }
@ -174,21 +189,27 @@ final class CLIInstaller {
if task.terminationStatus == 0 { if task.terminationStatus == 0 {
logger.info("CLIInstaller: Installation completed successfully") logger.info("CLIInstaller: Installation completed successfully")
withAnimation(.easeInOut(duration: 0.3)) {
isInstalled = true isInstalled = true
isInstalling = false isInstalling = false
}
showSuccess() showSuccess()
} else { } else {
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile() let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
let errorString = String(data: errorData, encoding: .utf8) ?? "Unknown error" let errorString = String(data: errorData, encoding: .utf8) ?? "Unknown error"
logger.error("CLIInstaller: Installation failed with status \(task.terminationStatus): \(errorString)") logger.error("CLIInstaller: Installation failed with status \(task.terminationStatus): \(errorString)")
withAnimation(.easeInOut(duration: 0.3)) {
lastError = "Installation failed: \(errorString)" lastError = "Installation failed: \(errorString)"
isInstalling = false isInstalling = false
}
showError("Installation failed: \(errorString)") showError("Installation failed: \(errorString)")
} }
} catch { } catch {
logger.error("CLIInstaller: Installation failed with error: \(error)") logger.error("CLIInstaller: Installation failed with error: \(error)")
withAnimation(.easeInOut(duration: 0.3)) {
lastError = "Installation failed: \(error.localizedDescription)" lastError = "Installation failed: \(error.localizedDescription)"
isInstalling = false isInstalling = false
}
showError("Installation failed: \(error.localizedDescription)") showError("Installation failed: \(error.localizedDescription)")
} }
} }