Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,4 @@ AGENTS.md
.rambles
.tend-stack
docs/plans/
build.log
12 changes: 6 additions & 6 deletions AirSync.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = WCB4HTANA6;
ENABLE_APP_SANDBOX = NO;
Expand All @@ -274,7 +274,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.5;
MARKETING_VERSION = 2.6.0;
MARKETING_VERSION = 2.6.1;
PRODUCT_BUNDLE_IDENTIFIER = "sameerasw.airsync-mac";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down Expand Up @@ -432,7 +432,7 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = WCB4HTANA6;
ENABLE_APP_SANDBOX = NO;
Expand All @@ -450,7 +450,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.5;
MARKETING_VERSION = 2.6.0;
MARKETING_VERSION = 2.6.1;
PRODUCT_BUNDLE_IDENTIFIER = "sameerasw.airsync-mac";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down Expand Up @@ -480,7 +480,7 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = WCB4HTANA6;
ENABLE_APP_SANDBOX = NO;
Expand All @@ -498,7 +498,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.5;
MARKETING_VERSION = 2.6.0;
MARKETING_VERSION = 2.6.1;
PRODUCT_BUNDLE_IDENTIFIER = "sameerasw.airsync-mac";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down
80 changes: 79 additions & 1 deletion airsync-mac/Core/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import AVFoundation

class AppState: ObservableObject {
static let shared = AppState()

enum ADBConnectionMode: String, Codable {
case wireless
case wired
}

private var clipboardCancellable: AnyCancellable?
private var lastClipboardValue: String? = nil
Expand All @@ -29,6 +34,7 @@ class AppState: ObservableObject {
self.adbConnectedIP = UserDefaults.standard.string(forKey: "adbConnectedIP") ?? ""
self.mirroringPlus = UserDefaults.standard.bool(forKey: "mirroringPlus")
self.adbEnabled = UserDefaults.standard.bool(forKey: "adbEnabled")
self.wiredAdbEnabled = UserDefaults.standard.bool(forKey: "wiredAdbEnabled")
self.suppressAdbFailureAlerts = UserDefaults.standard.bool(forKey: "suppressAdbFailureAlerts")

let savedFallbackToMdns = UserDefaults.standard.object(forKey: "fallbackToMdns")
Expand Down Expand Up @@ -119,6 +125,9 @@ class AppState: ObservableObject {
QuickConnectManager.shared.saveLastConnectedDevice(newDevice)
// Validate pinned apps when connecting to a device
validatePinnedApps()
loadRecentApps()
} else {
recentApps = []
}

// Automatically switch to the appropriate tab when device connection state changes
Expand Down Expand Up @@ -159,11 +168,26 @@ class AppState: ObservableObject {
@Published var webSocketStatus: WebSocketStatus = .stopped
@Published var selectedTab: TabIdentifier = .qr

@Published var adbConnected: Bool = false
@Published var adbConnected: Bool = false {
didSet {
if !adbConnected {
adbConnectionMode = nil
}
}
}
@Published var adbConnecting: Bool = false
@Published var manualAdbConnectionPending: Bool = false
@Published var currentDeviceWallpaperBase64: String? = nil
@Published var isMenubarWindowOpen: Bool = false
@Published var adbConnectionMode: ADBConnectionMode? = nil

@Published var recentApps: [AndroidApp] = []

var isConnectedOverLocalNetwork: Bool {
guard let ip = device?.ipAddress else { return true }
// Tailscale IPs usually start with 100.
return !ip.hasPrefix("100.")
}

// Audio player for ringtone
private var ringtonePlayer: AVAudioPlayer?
Expand Down Expand Up @@ -234,6 +258,11 @@ class AppState: ObservableObject {
UserDefaults.standard.set(adbEnabled, forKey: "adbEnabled")
}
}
@Published var wiredAdbEnabled: Bool {
didSet {
UserDefaults.standard.set(wiredAdbEnabled, forKey: "wiredAdbEnabled")
}
}

@Published var suppressAdbFailureAlerts: Bool {
didSet {
Expand Down Expand Up @@ -1060,6 +1089,55 @@ class AppState: ObservableObject {
}
}

// MARK: - Recent Apps Tracking

func trackAppUse(_ app: AndroidApp) {
DispatchQueue.main.async {
self.recentApps.removeAll { $0.packageName == app.packageName }

self.recentApps.insert(app, at: 0)

if self.recentApps.count > 9 {
self.recentApps = Array(self.recentApps.prefix(9))
}

self.saveRecentApps()
}
}

private func saveRecentApps() {
guard let deviceName = device?.name else { return }
do {
let data = try JSONEncoder().encode(recentApps)
UserDefaults.standard.set(data, forKey: "recentApps_\(deviceName)")
} catch {
print("[state] (recent) Error saving recent apps: \(error)")
}
}

private func loadRecentApps() {
guard let deviceName = device?.name else {
recentApps = []
return
}

guard let data = UserDefaults.standard.data(forKey: "recentApps_\(deviceName)") else {
recentApps = []
return
}

do {
recentApps = try JSONDecoder().decode([AndroidApp].self, from: data)
// Filter out apps that are no longer in the androidApps list (in case they were uninstalled)
recentApps.removeAll { app in
androidApps[app.packageName] == nil
}
} catch {
print("[state] (recent) Error loading recent apps: \(error)")
recentApps = []
}
}

func updateDockIconVisibility() {
DispatchQueue.main.async {
if self.hideDockIcon {
Expand Down
Loading