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
20 changes: 16 additions & 4 deletions scrobble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
A6A580A82E78DB7F0079DC91 /* MediaRemoteAdapter in Frameworks */ = {isa = PBXBuildFile; productRef = A6A580A72E78DB7F0079DC91 /* MediaRemoteAdapter */; };
A6A580A92E78DB9F0079DC91 /* MediaRemoteAdapter in Embed Frameworks */ = {isa = PBXBuildFile; productRef = A6A580A72E78DB7F0079DC91 /* MediaRemoteAdapter */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
A6CC84552EE8470C00236D47 /* MarqueeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CC84542EE8470C00236D47 /* MarqueeText.swift */; };
A6COMPAT012EFBAA0000001 /* GlassEffect+Compat.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6COMPAT012EFBAA0000002 /* GlassEffect+Compat.swift */; };
A6COMPAT012EFBAA0000003 /* LastFMWebAuthViewLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6COMPAT012EFBAA0000004 /* LastFMWebAuthViewLegacy.swift */; };
A6F213002C8C021800E1D23B /* scrobbleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F212FF2C8C021800E1D23B /* scrobbleApp.swift */; };
A6F213022C8C021800E1D23B /* ScrobblingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F213012C8C021800E1D23B /* ScrobblingView.swift */; };
A6F213042C8C021900E1D23B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A6F213032C8C021900E1D23B /* Assets.xcassets */; };
Expand All @@ -46,6 +48,7 @@
A6F2FEEC2EE79C16003826F7 /* FriendsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F2FEEB2EE79C16003826F7 /* FriendsModel.swift */; };
A6F2FEEF2EE79E47003826F7 /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = A6F2FEED2EE79E47003826F7 /* Secrets.xcconfig */; };
A6F2FEF22EE7A429003826F7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F2FEF12EE7A429003826F7 /* Logger.swift */; };
A6F954FC2EED236000501378 /* LaunchAtLoginSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F954FB2EED236000501378 /* LaunchAtLoginSettingsView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -89,6 +92,8 @@
A6A580A22E78D8480079DC91 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
A6A580A42E78D9340079DC91 /* NowPlayingFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingFetcher.swift; sourceTree = "<group>"; };
A6CC84542EE8470C00236D47 /* MarqueeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarqueeText.swift; sourceTree = "<group>"; };
A6COMPAT012EFBAA0000002 /* GlassEffect+Compat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GlassEffect+Compat.swift"; sourceTree = "<group>"; };
A6COMPAT012EFBAA0000004 /* LastFMWebAuthViewLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastFMWebAuthViewLegacy.swift; sourceTree = "<group>"; };
A6F212FC2C8C021800E1D23B /* scrobble.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = scrobble.app; sourceTree = BUILT_PRODUCTS_DIR; };
A6F212FF2C8C021800E1D23B /* scrobbleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = scrobbleApp.swift; sourceTree = "<group>"; };
A6F213012C8C021800E1D23B /* ScrobblingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrobblingView.swift; sourceTree = "<group>"; };
Expand All @@ -105,6 +110,7 @@
A6F2FEEB2EE79C16003826F7 /* FriendsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsModel.swift; sourceTree = "<group>"; };
A6F2FEED2EE79E47003826F7 /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = "<group>"; };
A6F2FEF12EE7A429003826F7 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
A6F954FB2EED236000501378 /* LaunchAtLoginSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLoginSettingsView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -206,8 +212,10 @@
A6F213012C8C021800E1D23B /* ScrobblingView.swift */,
A64304BA2D52EC87001998B6 /* LastFMAuthSheetView.swift */,
A65800992E78A56E00A30A42 /* LastFMWebAuthView.swift */,
A6COMPAT012EFBAA0000004 /* LastFMWebAuthViewLegacy.swift */,
A6F213102C8C037500E1D23B /* PreferencesView.swift */,
A65671EB2EEADB9F00773C58 /* UpdateView.swift */,
A6F954FB2EED236000501378 /* LaunchAtLoginSettingsView.swift */,
A66926022EE83DE1000921B1 /* ScrobblingServicesView.swift */,
);
path = Views;
Expand Down Expand Up @@ -237,6 +245,7 @@
isa = PBXGroup;
children = (
A6F2FEF12EE7A429003826F7 /* Logger.swift */,
A6COMPAT012EFBAA0000002 /* GlassEffect+Compat.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -317,6 +326,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A6COMPAT012EFBAA0000001 /* GlassEffect+Compat.swift in Sources */,
A6COMPAT012EFBAA0000003 /* LastFMWebAuthViewLegacy.swift in Sources */,
A6F2131C2C8C085900E1D23B /* Music.h in Sources */,
A622991D2EE7D72900592239 /* ServicesStatusView.swift in Sources */,
A64304C82D52FBEC001998B6 /* AuthState.swift in Sources */,
Expand All @@ -326,6 +337,7 @@
A64B5FAE2E7E0AF1000F1BAE /* SupportedMusicApp.swift in Sources */,
A6CC84552EE8470C00236D47 /* MarqueeText.swift in Sources */,
A6F213022C8C021800E1D23B /* ScrobblingView.swift in Sources */,
A6F954FC2EED236000501378 /* LaunchAtLoginSettingsView.swift in Sources */,
A65671EC2EEADB9F00773C58 /* UpdateView.swift in Sources */,
A6A580A52E78D9340079DC91 /* NowPlayingFetcher.swift in Sources */,
A63A2B1B2D271A95000BC92E /* ContentView.swift in Sources */,
Expand Down Expand Up @@ -491,7 +503,7 @@
CODE_SIGN_ENTITLEMENTS = scrobble/scrobble.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 4;
CURRENT_PROJECT_VERSION = 5;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"scrobble/Preview Content\"";
ENABLE_APP_SANDBOX = NO;
Expand All @@ -512,7 +524,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.6;
PRODUCT_BUNDLE_IDENTIFIER = computerdata.scrobble;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -541,7 +553,7 @@
CODE_SIGN_ENTITLEMENTS = scrobble/scrobble.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 4;
CURRENT_PROJECT_VERSION = 5;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"scrobble/Preview Content\"";
ENABLE_APP_SANDBOX = NO;
Expand All @@ -562,7 +574,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.6;
PRODUCT_BUNDLE_IDENTIFIER = computerdata.scrobble;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
9 changes: 9 additions & 0 deletions scrobble/Models/PreferencesManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ class PreferencesManager {
didSet { UserDefaults.standard.set(enableLastFm, forKey: "enableLastFm") }
}

// App settings
var launchAtLogin: Bool = false {
didSet {
UserDefaults.standard.set(launchAtLogin, forKey: "launchAtLogin")
}
}

private var _isInitialized = false

init() {
Expand Down Expand Up @@ -107,6 +114,8 @@ class PreferencesManager {
self.enableCustomScrobbler = UserDefaults.standard.bool(forKey: "enableCustomScrobbler")
self.blueskyHandle = UserDefaults.standard.string(forKey: "blueskyHandle") ?? ""

self.launchAtLogin = UserDefaults.standard.bool(forKey: "launchAtLogin") ?? false

// Initialize Last.fm settings
if UserDefaults.standard.object(forKey: "enableLastFm") != nil {
self.enableLastFm = UserDefaults.standard.bool(forKey: "enableLastFm")
Expand Down
88 changes: 88 additions & 0 deletions scrobble/Utils/GlassEffect+Compat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// GlassEffect+Compat.swift
// scrobble
//
// Created by Brett Henderson on 12/12/25.
// Backwards compatibility layer for glass effects (macOS 15+)
//

import SwiftUI

// MARK: - Adaptive Glass Effect Modifier

extension View {
/// Applies a glass effect on macOS 26+, or an ultrathin material background on macOS 15+
@ViewBuilder
func compatGlass(in shape: RoundedRectangle = RoundedRectangle(cornerRadius: 8)) -> some View {
if #available(macOS 26, *) {
self.glassEffect(in: shape)
} else {
self.background(.ultraThinMaterial, in: shape)
}
}

/// Applies a glass effect with custom corner radius
@ViewBuilder
func compatGlass(cornerRadius: CGFloat) -> some View {
if #available(macOS 26, *) {
self.glassEffect(in: .rect(cornerRadius: cornerRadius))
} else {
self.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
}
}

/// Applies a clear glass effect on macOS 26+, or a subtle overlay on macOS 15+
@ViewBuilder
func compatGlassClear() -> some View {
if #available(macOS 26, *) {
self.glassEffect(.clear)
} else {
self.background(.ultraThinMaterial.opacity(0.3), in: RoundedRectangle(cornerRadius: 8))
}
}

/// Applies scroll edge effect style on macOS 26+, no-op on macOS 15+
@ViewBuilder
func compatScrollEdgeEffectStyle() -> some View {
if #available(macOS 26, *) {
self.scrollEdgeEffectStyle(.soft, for: .all)
} else {
self
}
}
}

// MARK: - Adaptive Glass Button Style

/// On macOS 15, provides a material-based fallback for glass buttons.
struct CompatGlassButtonStyleLegacy: ButtonStyle {
var isSelected: Bool = false

func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background {
RoundedRectangle(cornerRadius: 8)
.fill(isSelected ? Color.accentColor.opacity(0.2) : Color.clear)
}
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8))
.opacity(configuration.isPressed ? 0.8 : 1.0)
}
}

// MARK: - Button Style Extensions

extension View {
/// Applies glass button styling with backwards compatibility
@ViewBuilder
func compatGlassButtonStyle(selected: Bool = false) -> some View {
if #available(macOS 26, *) {
self
.tint(selected ? .accentColor : .clear)
.buttonStyle(selected ? .glass(.regular.tint(.accentColor)) : .glass)
} else {
self.buttonStyle(CompatGlassButtonStyleLegacy(isSelected: selected))
}
}
}
22 changes: 12 additions & 10 deletions scrobble/Views/AppSelectionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,20 @@ struct AppSelectionView: View {

if !runningApps.isEmpty {
VStack(alignment: .leading, spacing: 4) {
HStack {
HStack(alignment: .center) {
Image(systemName: "circle.fill")
.foregroundStyle(.green)
.font(.caption)
Text("Currently Running")
Text("Currently Running: ")
.font(.caption)
.foregroundStyle(.secondary)

Text(runningApps.map { $0.displayName }.joined(separator: ", "))
.font(.caption2)
.foregroundStyle(.secondary)
}

Text(runningApps.map { $0.displayName }.joined(separator: ", "))
.font(.caption2)
.foregroundStyle(.secondary)

}
.padding(.horizontal)
}
Expand Down Expand Up @@ -92,7 +94,7 @@ struct AppSelectionView: View {
}
.padding(8)
.background(Color.accentColor.opacity(0.1))
.glassEffect(in: .rect(cornerRadius: 8))
.compatGlass(cornerRadius: 8)
}
.padding(.horizontal)

Expand Down Expand Up @@ -145,7 +147,7 @@ struct AppSelectionButton: View {
ZStack {
Image(systemName: app.icon)
.font(.title2)
.foregroundStyle(isSelected ? .white : .primary)
.foregroundStyle(isSelected ? .white : .secondary)

if isRunning {
Circle()
Expand All @@ -158,16 +160,16 @@ struct AppSelectionButton: View {
Text(app.displayName)
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(isSelected ? .white : .primary)
.foregroundStyle(isSelected ? .white : .secondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.padding(.horizontal, 8)

}
.buttonStyle(isSelected ? .glass(.identity.tint(.accentColor)) : .glass)
.tint(isSelected ? .accentColor : .none)
.compatGlassButtonStyle(selected: isSelected)

}
}

Expand Down
2 changes: 1 addition & 1 deletion scrobble/Views/FriendCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct FriendCardView: View {
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.glassEffect(in: .rect(cornerRadius: 10))
.compatGlass(cornerRadius: 10)
.shadow(radius: 1)

}
Expand Down
7 changes: 4 additions & 3 deletions scrobble/Views/FriendsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ struct FriendsView: View {
Image(systemName: "arrow.clockwise")
}
.disabled(model.isLoading)
.buttonStyle(.glass)
.compatGlassButtonStyle()
}
.background(.clear)
.padding(.horizontal)
.padding(.top)
.scrollEdgeEffectStyle(.soft, for: .bottom)
.compatScrollEdgeEffectStyle()

if model.isLoading && model.friends.isEmpty {
ProgressView()
Expand All @@ -51,7 +52,7 @@ struct FriendsView: View {
}
.padding(.horizontal)
}
.scrollEdgeEffectStyle(.soft, for: .all)
.compatScrollEdgeEffectStyle()
.refreshable {
model.refreshData()
}
Expand Down
9 changes: 7 additions & 2 deletions scrobble/Views/LastFMAuthSheetView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ struct LastFMAuthSheetView: View {
.padding()
.frame(width: 400, height: 200)
} else {
LastFMWebAuthView(lastFmManager: lastFmManager)
.environment(authState)
if #available(macOS 26, *) {
LastFMWebAuthView(lastFmManager: lastFmManager)
.environment(authState)
} else {
LastFMWebAuthViewLegacy(lastFmManager: lastFmManager)
.environment(authState)
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions scrobble/Views/LastFMWebAuthView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SwiftUI
import WebKit
import Observation

@available(macOS 26, *)
struct LastFMWebAuthView: View {
var lastFmManager: LastFmDesktopManager
@Environment(AuthState.self) var authState
Expand Down Expand Up @@ -180,6 +181,7 @@ struct LastFMWebAuthView: View {
}
}

@available(macOS 26, *)
#Preview {
let authState = AuthState()
let manager = LastFmDesktopManager(apiKey: "test", apiSecret: "test", username: "test", authState: authState)
Expand Down
Loading