Merge PR #117 (feature/settings) into revamp/essentials for testing & iteration

This commit is contained in:
Hariharan Mudaliar 2025-11-28 00:26:10 +05:30
commit 4090100c2a
3 changed files with 104 additions and 25 deletions

View file

@ -10629,10 +10629,6 @@
}
}
},
"Left button" : {
"comment" : "A label for a picker that lets the user select a control for the left button on a media player.",
"isCommentAutoGenerated" : true
},
"License key" : {
"localizations" : {
"fr" : {
@ -15730,10 +15726,6 @@
}
}
},
"Right button" : {
"comment" : "A label for the right button in the media controls picker.",
"isCommentAutoGenerated" : true
},
"Rotation:" : {
"localizations" : {
"fr" : {

View file

@ -908,16 +908,7 @@ struct Media: View {
}
.disabled(!showShuffleAndRepeat)
if showShuffleAndRepeat {
Picker("Left button", selection: $musicAuxLeftControl) {
ForEach(MusicAuxiliaryControl.allCases) { control in
Text(control.displayName).tag(control)
}
}
Picker("Right button", selection: $musicAuxRightControl) {
ForEach(MusicAuxiliaryControl.allCases) { control in
Text(control.displayName).tag(control)
}
}
mediaControlsGrid
}
} header: {
Text("Media controls")
@ -1034,6 +1025,102 @@ struct Media: View {
.navigationTitle("Media")
}
private var mediaControlsGrid: some View {
VStack(alignment: .leading, spacing: 20) {
mediaControlsRow(title: "Left button", selection: $musicAuxLeftControl, otherSelection: musicAuxRightControl)
mediaControlsRow(title: "Right button", selection: $musicAuxRightControl, otherSelection: musicAuxLeftControl)
}
.padding(.vertical, 12)
}
private func mediaControlsRow(title: String, selection: Binding<MusicAuxiliaryControl>, otherSelection: MusicAuxiliaryControl) -> some View {
VStack(alignment: .leading, spacing: 12) {
Text(title)
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(.white)
HStack(spacing: 16) {
ForEach([MusicAuxiliaryControl.mediaOutput, MusicAuxiliaryControl.repeatMode, MusicAuxiliaryControl.lyrics, MusicAuxiliaryControl.shuffle], id: \.self) { control in
MediaControlButton(
control: control,
isSelected: selection.wrappedValue == control,
disabled: control == otherSelection
) {
selection.wrappedValue = control
}
}
}
}
}
struct MediaControlButton: View {
let control: MusicAuxiliaryControl
let isSelected: Bool
let disabled: Bool
let action: () -> Void
@State private var isHovering = false
var body: some View {
VStack(spacing: 8) {
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(backgroundColor)
.overlay(
RoundedRectangle(cornerRadius: 12)
.strokeBorder(borderColor, lineWidth: isSelected ? 2 : 1)
)
Image(systemName: control.symbolName)
.font(.system(size: 22, weight: .medium))
.foregroundStyle(iconColor)
}
.frame(width: 80, height: 60)
.onHover { hovering in
if !disabled {
withAnimation(.easeInOut(duration: 0.2)) {
isHovering = hovering
}
}
}
Text(control.displayName)
.font(.caption)
.fontWeight(.medium)
.lineLimit(2)
.multilineTextAlignment(.center)
.frame(width: 100)
.foregroundStyle(disabled ? .secondary : .primary)
}
.contentShape(Rectangle())
.onTapGesture {
if !disabled {
action()
}
}
.opacity(disabled ? 0.5 : 1.0)
}
private var backgroundColor: Color {
if disabled { return Color(nsColor: .controlBackgroundColor) }
if isSelected { return Color.accentColor.opacity(0.1) }
if isHovering { return Color.primary.opacity(0.05) }
return Color(nsColor: .controlBackgroundColor)
}
private var borderColor: Color {
if isSelected { return Color.accentColor }
if isHovering { return Color.primary.opacity(0.1) }
return Color.clear
}
private var iconColor: Color {
if disabled { return .secondary }
if isSelected { return .accentColor }
return .primary
}
}
// Only show controller options that are available on this macOS version
private var availableMediaControllers: [MediaControllerType] {
if MusicManager.shared.isNowPlayingDeprecated {
@ -2486,34 +2573,34 @@ struct TimerSettings: View {
let nextIndex = timerPresets.count + 1
let defaultColor = Defaults[.accentColor]
let newPreset = TimerPreset(name: "Preset \(nextIndex)", duration: 5 * 60, color: defaultColor)
_ = withAnimation(.smooth) {
withAnimation(.smooth) {
timerPresets.append(newPreset)
}
}
private func movePresetUp(_ index: Int) {
guard index > timerPresets.startIndex else { return }
_ = withAnimation(.smooth) {
withAnimation(.smooth) {
timerPresets.swapAt(index, index - 1)
}
}
private func movePresetDown(_ index: Int) {
guard index < timerPresets.index(before: timerPresets.endIndex) else { return }
_ = withAnimation(.smooth) {
withAnimation(.smooth) {
timerPresets.swapAt(index, index + 1)
}
}
private func removePreset(_ index: Int) {
guard timerPresets.indices.contains(index) else { return }
_ = withAnimation(.smooth) {
withAnimation(.smooth) {
timerPresets.remove(at: index)
}
}
private func resetPresets() {
_ = withAnimation(.smooth) {
withAnimation(.smooth) {
timerPresets = TimerPreset.defaultPresets
}
}

View file

@ -224,7 +224,7 @@ enum MusicAuxiliaryControl: String, CaseIterable, Identifiable, Defaults.Seriali
case .repeatMode:
return "repeat"
case .mediaOutput:
return "speaker.wave.2"
return "laptopcomputer"
case .lyrics:
return "quote.bubble"
}