Skip to content
Merged
3 changes: 2 additions & 1 deletion airsync-mac/Components/Buttons/SaveAndRestartButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ struct SaveAndRestartButton: View {
ipAddress: ipAddress,
port: Int(portNumber),
version: version,
adbPorts: []
adbPorts: [],
deviceId: UserDefaults.standard.string(forKey: "trialDeviceIdentifier") ?? "mac_device"
)

// Save
Expand Down
40 changes: 40 additions & 0 deletions airsync-mac/Components/Custom/WhatsNewModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// WhatsNewModifier.swift
// AirSync
//
// Created by Sameera Sandakelum on 2026-05-29.
//

import SwiftUI

struct WhatsNewModifier: ViewModifier {
let item: WhatsNewTourItem
let arrowEdge: Edge

@ObservedObject var tourManager = WhatsNewTourManager.shared

func body(content: Content) -> some View {
content
.popover(
isPresented: Binding(
get: { tourManager.activeItem == item },
set: { isPresented in
if !isPresented {
tourManager.dismiss(item)
}
}
),
arrowEdge: arrowEdge
) {
WhatsNewTourPopover(item: item) {
tourManager.dismiss(item)
}
}
}
}

extension View {
func whatsNewPopover(item: WhatsNewTourItem, arrowEdge: Edge = .top) -> some View {
self.modifier(WhatsNewModifier(item: item, arrowEdge: arrowEdge))
}
}
55 changes: 55 additions & 0 deletions airsync-mac/Components/Custom/WhatsNewTourPopover.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// WhatsNewTourPopover.swift
// AirSync
//
// Created by Sameera Sandakelum on 2026-05-29.
//

import SwiftUI

struct WhatsNewTourPopover: View {
let item: WhatsNewTourItem
let onDismiss: () -> Void

@State private var animateSparkles = false

var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 8) {
Image(systemName: "sparkles")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.accentColor)
.symbolEffect(.bounce, value: animateSparkles)

Text(L(item.titleKey))
.font(.headline)
.fontWeight(.bold)
}
.onAppear {
animateSparkles = true
}

Text(L(item.messageKey))
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)

HStack {
Spacer()

GlassButtonView(
label: "",
systemImage: "checkmark.circle",
iconOnly: true,
size: .large,
primary: false,
action: onDismiss
)
}
}
.padding(16)
.frame(width: 280)
}
}
21 changes: 19 additions & 2 deletions airsync-mac/Core/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class AppState: ObservableObject {
@Published var isOS26: Bool = true

init() {
self.deviceAdbSerials = UserDefaults.standard.dictionary(forKey: "deviceAdbSerials") as? [String: String] ?? [:]
self.selectedWiredSerial = UserDefaults.standard.string(forKey: "selectedWiredSerial")

let isPlusLoaded = UserDefaults.standard.bool(forKey: "isPlus")
self.isPlus = isPlusLoaded

Expand Down Expand Up @@ -119,7 +122,8 @@ class AppState: ObservableObject {
ipAddress: adapterIP,
port: portNum,
version: appVersion,
adbPorts: []
adbPorts: [],
deviceId: UserDefaults.standard.string(forKey: "trialDeviceIdentifier") ?? "mac_device"
)

self.licenseDetails = AppState.loadLicenseDetailsFromUserDefaults()
Expand Down Expand Up @@ -327,6 +331,18 @@ class AppState: ObservableObject {
UserDefaults.standard.set(selectedNetworkAdapterName, forKey: "selectedNetworkAdapterName")
}
}
@Published var deviceAdbSerials: [String: String] {
didSet {
UserDefaults.standard.set(deviceAdbSerials, forKey: "deviceAdbSerials")
}
}

@Published var selectedWiredSerial: String? {
didSet {
UserDefaults.standard.set(selectedWiredSerial, forKey: "selectedWiredSerial")
}
}

@Published var showMenubarText: Bool {
didSet {
UserDefaults.standard.set(showMenubarText, forKey: "showMenubarText")
Expand Down Expand Up @@ -1546,7 +1562,8 @@ class AppState: ObservableObject {
ipAddress: "BLE",
port: 0,
version: "2.0.0",
adbPorts: []
adbPorts: [],
deviceId: BLECentralManager.shared.connectingDeviceUUID ?? "ble_device"
)
print("[state] (BLE) Created virtual device: \(name)")
}
Expand Down
8 changes: 3 additions & 5 deletions airsync-mac/Core/AppleScriptSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class AirSyncGetNotificationsCommand: NSScriptCommand {
"title": notif.title,
"body": notif.body,
"app": notif.app,
"id": notif.id.uuidString,
"id": notif.id,
"package": notif.package,
"nid": notif.nid
]
Expand Down Expand Up @@ -773,8 +773,7 @@ class AirSyncNotificationActionCommand: NSScriptCommand {
return "No device connected"
}

// Find the notification by ID
guard let notification = appState.notifications.first(where: { $0.id.uuidString == notificationId }) else {
guard let notification = appState.notifications.first(where: { $0.id == notificationId }) else {
let errorInfo: [String: Any] = [
"status": "error",
"message": "Notification not found with ID: \(notificationId)"
Expand Down Expand Up @@ -854,8 +853,7 @@ class AirSyncDismissNotificationCommand: NSScriptCommand {
return "No device connected"
}

// Find the notification by ID
guard let notification = appState.notifications.first(where: { $0.id.uuidString == notificationId }) else {
guard let notification = appState.notifications.first(where: { $0.id == notificationId }) else {
let errorInfo: [String: Any] = [
"status": "error",
"message": "Notification not found with ID: \(notificationId)"
Expand Down
3 changes: 2 additions & 1 deletion airsync-mac/Core/QuickConnect/QuickConnectManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ class QuickConnectManager: ObservableObject {
ipAddress: bestIP,
port: discoveredDevice.port,
version: "Unknown",
adbPorts: []
adbPorts: [],
deviceId: discoveredDevice.deviceId
)

saveLastConnectedDevice(device)
Expand Down
9 changes: 8 additions & 1 deletion airsync-mac/Core/Storage/WebDAVManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@

import Foundation
import Cocoa
import Combine

class WebDAVManager {
static let shared = WebDAVManager()

private let mountPoint = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library/Caches/com.airsync.mac/AndroidVolume")

private var isMounted = false
private(set) var isMounted = false

private init() {}

Expand Down Expand Up @@ -51,6 +52,9 @@ class WebDAVManager {
if process.terminationStatus == 0 {
self.isMounted = true
print("[webdav] Successfully mounted Android volume")
DispatchQueue.main.async {
AppState.shared.objectWillChange.send()
}
} else {
print("[webdav] Failed to mount WebDAV volume. Status: \(process.terminationStatus)")
}
Expand All @@ -64,6 +68,9 @@ class WebDAVManager {
DispatchQueue.global(qos: .userInitiated).async {
self.unmountSilently()
self.isMounted = false
DispatchQueue.main.async {
AppState.shared.objectWillChange.send()
}
}
}

Expand Down
Loading