Skip to content

Commit f052262

Browse files
authored
Merge pull request #118 from sameerasw/main
Upstream
2 parents 1931e30 + dc9bf51 commit f052262

10 files changed

Lines changed: 151 additions & 40 deletions

File tree

‎airsync-mac/Components/Buttons/SaveAndRestartButton.swift‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ struct SaveAndRestartButton: View {
4646
WebSocketServer.shared.start(port: portNumber)
4747
onRestart?(portNumber)
4848

49-
appState.shouldRefreshQR = true
49+
// Delay QR refresh to ensure server has restarted
50+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
51+
appState.shouldRefreshQR = true
52+
}
5053
}
5154
.controlSize(.large)
5255
.applyGlassIfAvailable()

‎airsync-mac/Core/AppState.swift‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ class AppState: ObservableObject {
7575
// Initialize persisted UI toggles
7676
self.isMusicCardHidden = UserDefaults.standard.bool(forKey: "isMusicCardHidden")
7777

78+
// Load and validate saved network adapter
79+
let savedAdapterName = UserDefaults.standard.string(forKey: "selectedNetworkAdapterName")
80+
self.selectedNetworkAdapterName = validateAndGetNetworkAdapter(savedName: savedAdapterName)
81+
7882
self.myDevice = Device(
7983
name: name,
8084
ipAddress: WebSocketServer.shared
@@ -119,7 +123,11 @@ class AppState: ObservableObject {
119123
@Published var adbConnected: Bool = false
120124
@Published var adbConnecting: Bool = false
121125
@Published var currentDeviceWallpaperBase64: String? = nil
122-
@Published var selectedNetworkAdapterName: String? // e.g., "en0"
126+
@Published var selectedNetworkAdapterName: String? { // e.g., "en0"
127+
didSet {
128+
UserDefaults.standard.set(selectedNetworkAdapterName, forKey: "selectedNetworkAdapterName")
129+
}
130+
}
123131
@Published var showMenubarText: Bool {
124132
didSet {
125133
UserDefaults.standard.set(showMenubarText, forKey: "showMenubarText")
@@ -598,4 +606,43 @@ class AppState: ObservableObject {
598606
}
599607
}
600608
}
609+
610+
/// Revalidates the current network adapter selection and falls back to auto if no longer valid
611+
func revalidateNetworkAdapter() {
612+
let currentSelection = selectedNetworkAdapterName
613+
let validated = validateAndGetNetworkAdapter(savedName: currentSelection)
614+
615+
if currentSelection != validated {
616+
print("[state] Network adapter changed from '\(currentSelection ?? "auto")' to '\(validated ?? "auto")'")
617+
selectedNetworkAdapterName = validated
618+
shouldRefreshQR = true
619+
}
620+
}
621+
622+
/// Validates a saved network adapter name and returns it if available with valid IP, otherwise returns nil (auto)
623+
private func validateAndGetNetworkAdapter(savedName: String?) -> String? {
624+
guard let savedName = savedName else {
625+
print("[state] No saved network adapter, using auto selection")
626+
return nil // Auto mode
627+
}
628+
629+
// Get available adapters from WebSocketServer
630+
let availableAdapters = WebSocketServer.shared.getAvailableNetworkAdapters()
631+
632+
// Check if the saved adapter is still available
633+
guard let matchingAdapter = availableAdapters.first(where: { $0.name == savedName }) else {
634+
print("[state] Saved network adapter '\(savedName)' not found, falling back to auto")
635+
return nil // Fall back to auto
636+
}
637+
638+
// Verify the adapter has a valid IP address
639+
let ipAddress = WebSocketServer.shared.getLocalIPAddress(adapterName: savedName)
640+
guard let validIP = ipAddress, !validIP.isEmpty, validIP != "127.0.0.1" else {
641+
print("[state] Saved network adapter '\(savedName)' has no valid IP (\(ipAddress ?? "nil")), falling back to auto")
642+
return nil // Fall back to auto
643+
}
644+
645+
print("[state] Using saved network adapter: \(savedName) -> \(validIP)")
646+
return savedName
647+
}
601648
}

‎airsync-mac/Core/WebSocket/WebSocketServer.swift‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,9 @@ class WebSocketServer: ObservableObject {
10831083
if adapterAddresses != lastAddresses {
10841084
lastKnownAdapters = adapters
10851085

1086+
// Revalidate the current network adapter selection
1087+
AppState.shared.revalidateNetworkAdapter()
1088+
10861089
for adapter in adapters {
10871090
let activeMark = (adapter.address == chosenIP) ? " [ACTIVE]" : ""
10881091
print("[websocket] (network) \(adapter.name) -> \(adapter.address)\(activeMark)")

‎airsync-mac/Localization/it.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"apps.tab": "Applicazioni",
1616
"transfers.tab": "Trasferimenti",
1717
"settings.tab": "Impostazioni",
18-
"qr.tab": "Scan QR",
18+
"qr.tab": "Scansiona QR",
1919
"transfers.empty": "Non è ancora stato trasferito nulla",
2020
"plus.title": "AirSync+",
2121
"plus.why": "Perché Plus?",
Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
{
22
"app.name": "AirSync",
3-
"menu.mirror": "Mirror",
4-
"menu.about": "About AirSync",
5-
"menu.checkUpdates": "Check for Updates…",
6-
"menu.quit": "Quit",
3+
"menu.mirror": "鏡像",
4+
"menu.about": "關於 AirSync",
5+
"menu.checkUpdates": "檢查更新…",
6+
"menu.quit": "結束",
77
"home.title": "AirSync",
8-
"home.sidebar.ready": "Ready",
9-
"onboarding.title": "Welcome to AirSync",
10-
"notifications.empty.title": "You're all caught up!",
8+
"home.sidebar.ready": "準備就緒",
9+
"onboarding.title": "歡迎使用 AirSync",
10+
"notifications.empty.title": "沒有未讀通知!",
1111
"notifications.empty.emoji": "└(=^‥^=)┐",
12-
"notifications.actions.hide": "Hide",
13-
"notifications.actions.dismiss": "Dismiss",
14-
"notifications.tab": "Notifications",
15-
"apps.tab": "Apps",
16-
"transfers.tab": "Transfers",
17-
"settings.tab": "Settings",
18-
"qr.tab": "Scan QR",
19-
"transfers.empty": "Nothing transferred yet",
12+
"notifications.actions.hide": "隱藏",
13+
"notifications.actions.dismiss": "關閉",
14+
"notifications.tab": "通知",
15+
"apps.tab": "應用程式",
16+
"transfers.tab": "傳輸",
17+
"settings.tab": "設定",
18+
"qr.tab": "掃描二維碼",
19+
"transfers.empty": "未有傳輸記錄",
2020
"plus.title": "AirSync+",
21-
"plus.why": "Why Plus?",
22-
"plus.why.body": "Keeps me inspired to continue and maybe even to publish to the Apple app store and Google Play Store. Think of it as a little donation to keep this project alive and evolving.\nThat said, I know not everyone who wants the full experience can afford it. If that’s you, please don’t hesitate to reach out. 😊\n\nThe source code is available on GitHub, and you're more than welcome to build with all Plus features free — for personal use which also opens for contributions which is a win-win!.\nAs a thank-you for supporting the app, AirSync+ unlocks some nice extras: media controls, synced widgets, low battery alerts, wireless ADB, and more to come as I keep adding new features.\n\nEnjoy the app!\n(っ◕‿◕)っ",
23-
"license.enterKey": "Enter license key",
24-
"status.battery": "Battery",
25-
"status.volume": "Volume",
26-
"media.play": "Play",
27-
"media.pause": "Pause"
21+
"plus.why": "為什麼 Plus?",
22+
"plus.why.body": "這份支持讓我保持創作動力,甚至考慮將應用程式上架至 Apple App Store 與 Google Play 商店。不妨將此視為一筆小額捐款,讓這個專案得以延續並不斷進化。\n當然,我明白並非所有渴望完整體驗的使用者都能負擔這筆費用。若您正處於這種情況,歡迎隨時與我聯繫。😊\n\n原始碼已開放於 GitHub,歡迎免費自行建構並使用所有 Plus 功能作個人用途,更歡迎您的貢獻,實現雙贏局面!\n\n為感謝您對本應用程式的支持,AirSync+ 特別解鎖多項實用功能:媒體控制、同步小工具、低電量提醒、無線 ADB 連線,未來我將陸續開發新功能。\n\n祝您使用愉快!\n(っ◕‿◕)っ",
23+
"license.enterKey": "輸入授權碼",
24+
"status.battery": "電池",
25+
"status.volume": "音量",
26+
"media.play": "播放",
27+
"media.pause": "暫停"
2828
}

‎airsync-mac/Screens/HomeScreen/AppContentView.swift‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ struct AppContentView: View {
113113
Button("Refresh", systemImage: "repeat"){
114114
WebSocketServer.shared.stop()
115115
WebSocketServer.shared.start()
116-
appState.shouldRefreshQR = true
116+
// Delay QR refresh to ensure server has started
117+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
118+
appState.shouldRefreshQR = true
119+
}
117120
}
118121
.help("Refresh server")
119122
}

‎airsync-mac/Screens/HomeScreen/PhoneView/DeviceStatusView.swift‎

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,20 @@ struct DeviceStatusView: View {
9191

9292
if let title = appState.status?.music.title.trimmingCharacters(in: .whitespacesAndNewlines),
9393
!title.isEmpty && showMediaToggle {
94-
GlassButtonView(
95-
label: "Music Player",
96-
systemImage: appState.status?.music.isPlaying == true ? "play.rectangle" : "music.note",
97-
iconOnly: true,
98-
primary: false,
99-
action: {
100-
withAnimation(.easeInOut(duration: 0.28)) {
101-
appState.isMusicCardHidden.toggle()
102-
}
94+
95+
GlassButtonView(
96+
label: "Music Player",
97+
systemImage: appState.status?.music.isPlaying == true ? "play.rectangle" : "music.note",
98+
iconOnly: true,
99+
primary: false,
100+
action: {
101+
withAnimation(.easeInOut(duration: 0.28)) {
102+
appState.isMusicCardHidden.toggle()
103103
}
104-
)
105-
.help("Show player")
106-
.transition(.opacity.combined(with: .scale))
104+
}
105+
)
106+
.help(appState.isMusicCardHidden ? "Show player" : "Hide player")
107+
.transition(.opacity.combined(with: .scale))
107108
}
108109
}
109110
.padding(.bottom, appState.isMusicCardHidden ? 0 : 8)
@@ -115,6 +116,41 @@ struct DeviceStatusView: View {
115116
.easeInOut(duration: 0.25),
116117
value: "\(appState.status?.battery.level ?? 0)-\(appState.status?.music.volume ?? 0)"
117118
)
119+
.onAppear {
120+
// Ensure media card is collapsed at startup if no media is present
121+
checkAndCollapseIfNoMedia()
122+
}
123+
.onChange(of: appState.status?.music.title) { _, newTitle in
124+
// Auto-collapse music card when there's no media playing
125+
checkAndCollapseIfNoMedia()
126+
}
127+
.onChange(of: appState.status?.music.artist) { _, newArtist in
128+
// Also check when artist changes (could become empty)
129+
checkAndCollapseIfNoMedia()
130+
}
131+
}
132+
133+
// Helper function to check if there's valid media info and collapse if not
134+
private func checkAndCollapseIfNoMedia() {
135+
let hasValidMedia = {
136+
// Check if we have status and music info
137+
guard let music = appState.status?.music else {
138+
return false
139+
}
140+
141+
// Check if title and artist are non-empty after trimming
142+
let title = music.title.trimmingCharacters(in: .whitespacesAndNewlines)
143+
let artist = music.artist.trimmingCharacters(in: .whitespacesAndNewlines)
144+
145+
return !title.isEmpty && !artist.isEmpty
146+
}()
147+
148+
// If no valid media info and card is currently expanded, collapse it
149+
if !hasValidMedia && !appState.isMusicCardHidden {
150+
withAnimation(.easeInOut(duration: 0.28)) {
151+
appState.isMusicCardHidden = true
152+
}
153+
}
118154
}
119155

120156
// Battery icon helper

‎airsync-mac/Screens/HomeScreen/PhoneView/TimeView.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ struct TimeView: View {
130130
let minute = String(format: "%02d", components.minute ?? 0)
131131

132132
// Desired size and weight
133-
let fontSize: CGFloat = 90
133+
let fontSize: CGFloat = 85
134134
// Use a rounded NSFont for the liquid glass path
135135
let roundedNSFont = roundedFont(ofSize: fontSize, weight: .black)
136136

‎airsync-mac/Screens/ScannerView/ScannerView.swift‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ struct ScannerView: View {
153153
appState.shouldRefreshQR = false
154154
}
155155
}
156+
.onChange(of: appState.selectedNetworkAdapterName) { _, _ in
157+
// Network adapter changed, regenerate QR with new IP
158+
generateQRAsync()
159+
}
160+
.onChange(of: appState.myDevice?.port) { _, _ in
161+
// Port changed, regenerate QR
162+
generateQRAsync()
163+
}
164+
.onChange(of: appState.myDevice?.name) { _, _ in
165+
// Device name changed, regenerate QR
166+
generateQRAsync()
167+
}
156168

157169
}
158170

‎airsync-mac/Screens/Settings/SettingsView.swift‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ struct SettingsView: View {
66
@State private var deviceName: String = ""
77
@State private var port: String = "6996"
88
@State private var availableAdapters: [(name: String, address: String)] = []
9+
@State private var currentIPAddress: String = "N/A"
910

1011

1112
var body: some View {
@@ -36,20 +37,26 @@ struct SettingsView: View {
3637
}
3738
.onAppear {
3839
availableAdapters = WebSocketServer.shared.getAvailableNetworkAdapters()
40+
currentIPAddress = WebSocketServer.shared.getLocalIPAddress(adapterName: appState.selectedNetworkAdapterName) ?? "N/A"
3941
}
4042
.onChange(of: appState.selectedNetworkAdapterName) { _, _ in
43+
// Update IP address immediately
44+
currentIPAddress = WebSocketServer.shared.getLocalIPAddress(adapterName: appState.selectedNetworkAdapterName) ?? "N/A"
45+
4146
WebSocketServer.shared.stop()
4247
if let port = UInt16(port) {
4348
WebSocketServer.shared.start(port: port)
4449
} else {
4550
WebSocketServer.shared.start()
4651
}
52+
// Refresh QR code since IP address may have changed
53+
appState.shouldRefreshQR = true
4754
}
4855

4956
ConnectionInfoText(
5057
label: "IP Address",
5158
icon: "wifi",
52-
text: WebSocketServer.shared.getLocalIPAddress(adapterName: appState.selectedNetworkAdapterName) ?? "N/A"
59+
text: currentIPAddress
5360
)
5461

5562
HStack {

0 commit comments

Comments
 (0)