Remove music
All checks were successful
Ascently - Docs Deploy / build-and-push (push) Successful in 3m57s

This commit is contained in:
2026-02-01 17:55:51 -07:00
parent aa08892e75
commit 3874703fcb
10 changed files with 346 additions and 626 deletions

View File

@@ -25,9 +25,9 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^9.5.1",
"@astrojs/starlight": "^0.37.2",
"astro": "^5.16.8",
"@astrojs/node": "^9.5.2",
"@astrojs/starlight": "^0.37.5",
"astro": "^5.17.1",
"qrcode": "^1.5.4",
"sharp": "^0.34.5"
},

644
docs/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,7 +44,6 @@ struct ContentView: View {
.tag(4)
}
.environmentObject(dataManager)
.environmentObject(MusicService.shared)
.onChange(of: scenePhase) { _, newPhase in
if newPhase == .active {
// Add slight delay to ensure app is fully loaded

View File

@@ -1,192 +0,0 @@
import AVFoundation
import Combine
import MusicKit
import SwiftUI
@MainActor
class MusicService: ObservableObject {
static let shared = MusicService()
@Published var isAuthorized = false
@Published var playlists: MusicItemCollection<Playlist> = []
@Published var selectedPlaylistId: String? {
didSet {
UserDefaults.standard.set(selectedPlaylistId, forKey: "ascently_selected_playlist_id")
}
}
@Published var isMusicEnabled: Bool {
didSet {
UserDefaults.standard.set(isMusicEnabled, forKey: "ascently_music_enabled")
if !isMusicEnabled {
// Genuinely unsure what I want to do with this but we should account for it at some point
}
}
}
@Published var isAutoPlayEnabled: Bool {
didSet {
UserDefaults.standard.set(isAutoPlayEnabled, forKey: "ascently_music_autoplay_enabled")
}
}
@Published var isAutoStopEnabled: Bool {
didSet {
UserDefaults.standard.set(isAutoStopEnabled, forKey: "ascently_music_autostop_enabled")
}
}
@Published var isPlaying = false
private var cancellables = Set<AnyCancellable>()
private var hasStartedSessionPlayback = false
private var currentPlaylistTrackIds: Set<MusicItemID> = []
private init() {
self.selectedPlaylistId = UserDefaults.standard.string(forKey: "ascently_selected_playlist_id")
self.isMusicEnabled = UserDefaults.standard.bool(forKey: "ascently_music_enabled")
self.isAutoPlayEnabled = UserDefaults.standard.bool(forKey: "ascently_music_autoplay_enabled")
self.isAutoStopEnabled = UserDefaults.standard.bool(forKey: "ascently_music_autostop_enabled")
if isMusicEnabled {
Task {
await checkAuthorizationStatus()
}
}
setupObservers()
}
private func setupObservers() {
SystemMusicPlayer.shared.state.objectWillChange
.sink { [weak self] _ in
self?.updatePlaybackStatus()
}
.store(in: &cancellables)
SystemMusicPlayer.shared.queue.objectWillChange
.sink { [weak self] _ in
self?.checkQueueConsistency()
}
.store(in: &cancellables)
}
private func updatePlaybackStatus() {
isPlaying = SystemMusicPlayer.shared.state.playbackStatus == .playing
}
private func checkQueueConsistency() {
guard hasStartedSessionPlayback else { return }
if let currentEntry = SystemMusicPlayer.shared.queue.currentEntry,
let item = currentEntry.item {
if !currentPlaylistTrackIds.isEmpty && !currentPlaylistTrackIds.contains(item.id) {
hasStartedSessionPlayback = false
}
}
}
func toggleMusicEnabled(_ enabled: Bool) {
isMusicEnabled = enabled
if enabled {
Task {
await checkAuthorizationStatus()
}
}
}
func checkAuthorizationStatus() async {
let status = await MusicAuthorization.request()
self.isAuthorized = status == .authorized
if isAuthorized {
await fetchPlaylists()
}
}
func fetchPlaylists() async {
guard isAuthorized else { return }
do {
var request = MusicLibraryRequest<Playlist>()
request.sort(by: \.name, ascending: true)
let response = try await request.response()
self.playlists = response.items
} catch {
print("Error fetching playlists: \(error)")
}
}
func playSelectedPlaylistIfHeadphonesConnected() {
guard isMusicEnabled, isAutoPlayEnabled, let playlistId = selectedPlaylistId else { return }
if isHeadphonesConnected() {
playPlaylist(id: playlistId)
}
}
func resetSessionPlaybackState() {
hasStartedSessionPlayback = false
currentPlaylistTrackIds.removeAll()
}
func playPlaylist(id: String) {
print("Attempting to play playlist \(id)")
Task {
do {
if playlists.isEmpty {
await fetchPlaylists()
}
var targetPlaylist: Playlist?
if let playlist = playlists.first(where: { $0.id.rawValue == id }) {
targetPlaylist = playlist
} else {
var request = MusicLibraryRequest<Playlist>()
request.filter(matching: \.id, equalTo: MusicItemID(id))
let response = try await request.response()
targetPlaylist = response.items.first
}
if let playlist = targetPlaylist {
let detailedPlaylist = try await playlist.with([.tracks])
if let tracks = detailedPlaylist.tracks {
self.currentPlaylistTrackIds = Set(tracks.map { $0.id })
}
SystemMusicPlayer.shared.queue = [playlist]
try await SystemMusicPlayer.shared.play()
hasStartedSessionPlayback = true
}
} catch {
print("Error playing playlist: \(error)")
}
}
}
func stopPlaybackIfEnabled() {
guard isMusicEnabled, isAutoStopEnabled else { return }
SystemMusicPlayer.shared.stop()
}
func togglePlayback() {
Task {
if isPlaying {
SystemMusicPlayer.shared.pause()
} else {
if let playlistId = selectedPlaylistId, !hasStartedSessionPlayback {
playPlaylist(id: playlistId)
} else {
try? await SystemMusicPlayer.shared.play()
}
}
}
}
private func isHeadphonesConnected() -> Bool {
let route = AVAudioSession.sharedInstance().currentRoute
return route.outputs.contains { port in
port.portType == .headphones ||
port.portType == .bluetoothA2DP ||
port.portType == .bluetoothLE ||
port.portType == .bluetoothHFP ||
port.portType == .usbAudio ||
port.portType == .airPlay
}
}
}

View File

@@ -38,7 +38,6 @@ class ClimbingDataManager: ObservableObject {
let syncService = SyncService()
let healthKitService = HealthKitService.shared
let musicService = MusicService.shared
@Published var isSyncing = false
private enum Keys {
@@ -427,8 +426,7 @@ class ClimbingDataManager: ObservableObject {
gymName: gym.name)
}
musicService.resetSessionPlaybackState()
musicService.playSelectedPlaylistIfHeadphonesConnected()
if healthKitService.isEnabled {
do {
@@ -470,10 +468,7 @@ class ClimbingDataManager: ObservableObject {
await LiveActivityManager.shared.endLiveActivity()
if UserDefaults.standard.bool(forKey: "isAutoStopMusicEnabled") {
musicService.stopPlaybackIfEnabled()
}
musicService.stopPlaybackIfEnabled()
if healthKitService.isEnabled {
do {

View File

@@ -58,7 +58,6 @@ struct CalendarView: View {
let gym = dataManager.gym(withId: activeSession.gymId)
{
ActiveSessionBanner(session: activeSession, gym: gym)
.environmentObject(MusicService.shared)
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 16)

View File

@@ -1,12 +1,10 @@
import Combine
import MusicKit
import SwiftUI
struct SessionDetailView: View {
let sessionId: UUID
@EnvironmentObject var dataManager: ClimbingDataManager
@EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var musicService: MusicService
@Environment(\.dismiss) private var dismiss
@State private var showingDeleteAlert = false
@State private var showingAddAttempt = false
@@ -47,12 +45,7 @@ struct SessionDetailView: View {
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
if session.status == .active && musicService.isMusicEnabled && musicService.isAuthorized {
MusicControlCard()
.listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
}
SessionStatsCard(stats: sessionStats)
.listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
@@ -449,52 +442,7 @@ struct SessionStats {
let uniqueProblemsCompleted: Int
}
struct MusicControlCard: View {
@EnvironmentObject var musicService: MusicService
var body: some View {
HStack {
Image(systemName: "music.note")
.font(.title2)
.foregroundColor(.pink)
.frame(width: 40, height: 40)
.background(Color.pink.opacity(0.1))
.clipShape(Circle())
VStack(alignment: .leading, spacing: 2) {
Text("Music")
.font(.headline)
if let playlistId = musicService.selectedPlaylistId,
let playlist = musicService.playlists.first(where: { $0.id.rawValue == playlistId }) {
Text(playlist.name)
.font(.caption)
.foregroundColor(.secondary)
} else {
Text("No playlist selected")
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
Button(action: {
musicService.togglePlayback()
}) {
Image(systemName: musicService.isPlaying ? "pause.circle.fill" : "play.circle.fill")
.font(.system(size: 32))
.foregroundColor(.pink)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.secondarySystemGroupedBackground))
.shadow(color: .black.opacity(0.05), radius: 5, x: 0, y: 2)
)
}
}
#Preview {
NavigationView {

View File

@@ -122,7 +122,6 @@ struct SessionsList: View {
{
Section {
ActiveSessionBanner(session: activeSession, gym: gym)
.environmentObject(MusicService.shared)
.padding(.horizontal, 16)
.listRowInsets(EdgeInsets(top: 16, leading: 0, bottom: 24, trailing: 0))
.listRowBackground(Color.clear)
@@ -184,7 +183,7 @@ struct ActiveSessionBanner: View {
let session: ClimbSession
let gym: Gym
@EnvironmentObject var dataManager: ClimbingDataManager
@EnvironmentObject var musicService: MusicService
@State private var navigateToDetail = false
var body: some View {
@@ -210,18 +209,7 @@ struct ActiveSessionBanner: View {
.monospacedDigit()
}
if musicService.isMusicEnabled && musicService.isAuthorized {
Button(action: {
musicService.togglePlayback()
}) {
HStack(spacing: 4) {
Image(systemName: musicService.isPlaying ? "pause.fill" : "play.fill")
.font(.caption)
}
.foregroundColor(.pink)
.padding(.top, 2)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())

View File

@@ -1,5 +1,4 @@
import HealthKit
import MusicKit
import SwiftUI
import UIKit
import UniformTypeIdentifiers
@@ -24,8 +23,7 @@ struct SettingsView: View {
HealthKitSection()
.environmentObject(dataManager.healthKitService)
MusicSection()
.environmentObject(dataManager.musicService)
AppearanceSection()
@@ -1282,52 +1280,7 @@ struct HealthKitSection: View {
}
}
struct MusicSection: View {
@EnvironmentObject var musicService: MusicService
var body: some View {
Section {
Toggle(isOn: Binding(
get: { musicService.isMusicEnabled },
set: { musicService.toggleMusicEnabled($0) }
)) {
HStack {
Image(systemName: "music.note")
.foregroundColor(.pink)
Text("Apple Music Integration")
}
}
if musicService.isMusicEnabled {
if !musicService.isAuthorized {
Button("Connect Apple Music") {
Task {
await musicService.checkAuthorizationStatus()
}
}
} else {
Toggle("Auto-Play on Session Start", isOn: $musicService.isAutoPlayEnabled)
Toggle("Stop Music on Session End", isOn: $musicService.isAutoStopEnabled)
Picker("Playlist", selection: $musicService.selectedPlaylistId) {
Text("None").tag(nil as String?)
ForEach(musicService.playlists, id: \.id) { playlist in
Text(playlist.name).tag(playlist.id.rawValue as String?)
}
}
if musicService.isAutoPlayEnabled {
Text("Music will only auto-play if headphones are connected when you start a session.")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
} header: {
Text("Music")
}
}
}
#Preview {
SettingsView()