From f2b176044771359989b5fcdb3dd46acd5a24cf7a Mon Sep 17 00:00:00 2001 From: Paul Halpin Date: Thu, 12 Oct 2023 15:03:48 +0100 Subject: [PATCH 1/6] fixed position in libraryview --- HalpoPlayer/Views/LibraryView.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/HalpoPlayer/Views/LibraryView.swift b/HalpoPlayer/Views/LibraryView.swift index 7570954..0793dd5 100644 --- a/HalpoPlayer/Views/LibraryView.swift +++ b/HalpoPlayer/Views/LibraryView.swift @@ -19,15 +19,6 @@ struct LibraryView: View { ArtistListView(viewModel: viewModel) case .albums: AlbumListView(viewModel: viewModel) - .onAppear { - Task { - do { - try await viewModel.loadContent(force: true) - } catch { - print(error) - } - } - } } } else { Button { From 8bc9a007d8d7472226c8c22bff384db5c9849257 Mon Sep 17 00:00:00 2001 From: Paul Halpin Date: Thu, 12 Oct 2023 15:55:15 +0100 Subject: [PATCH 2/6] new player view started --- HalpoPlayer/HalpoPlayerApp.swift | 13 +- .../View Models/PlaylistViewModel.swift | 4 - .../View Models/PlaylistsViewModel.swift | 4 - HalpoPlayer/Views/AlbumDetailView.swift | 9 - HalpoPlayer/Views/DownloadsView.swift | 9 - HalpoPlayer/Views/LibraryView.swift | 15 -- HalpoPlayer/Views/MediaControlBar.swift | 153 +----------- HalpoPlayer/Views/NowPlayingView.swift | 221 ++++++++++-------- HalpoPlayer/Views/PlaylistView.swift | 5 - HalpoPlayer/Views/PlaylistsView.swift | 5 - 10 files changed, 144 insertions(+), 294 deletions(-) diff --git a/HalpoPlayer/HalpoPlayerApp.swift b/HalpoPlayer/HalpoPlayerApp.swift index 23745fb..ae60582 100644 --- a/HalpoPlayer/HalpoPlayerApp.swift +++ b/HalpoPlayer/HalpoPlayerApp.swift @@ -14,8 +14,8 @@ struct halpoplayerApp: App { @ObservedObject var downloadsCoordinator = Coordinator() @ObservedObject var playlistsCoordinator = Coordinator() @ObservedObject var searchCoordinator = Coordinator() - @ObservedObject var mediaControlBarMinimized = MediaControlBarMinimized.shared @State var selectedTab: AppTab = .library + @State var presentTEst = false let batteryManager = BatteryManager.shared var body: some Scene { WindowGroup { @@ -51,6 +51,16 @@ struct halpoplayerApp: App { } } MediaControlBar() + .onTapGesture { + self.presentTEst.toggle() + } + .sheet(isPresented: $presentTEst, content: { + NowPlayingView(goToAlbum: { albumId, songId in + if self.coordinatorForTab(tab: self.selectedTab).viewingAlbum != albumId { + self.coordinatorForTab(tab: self.selectedTab).albumTapped(albumId: albumId, scrollToSong: songId) + } + }) + }) HStack { ForEach(AppTab.allCases, id: \.self) { tab in @@ -76,7 +86,6 @@ struct halpoplayerApp: App { .frame(maxWidth: .infinity) } .ignoresSafeArea(.keyboard) - .environmentObject(mediaControlBarMinimized) .environmentObject(coordinatorForTab(tab: selectedTab)) .onAppear { initApp() diff --git a/HalpoPlayer/View Models/PlaylistViewModel.swift b/HalpoPlayer/View Models/PlaylistViewModel.swift index 71f5f83..0b6bf5b 100644 --- a/HalpoPlayer/View Models/PlaylistViewModel.swift +++ b/HalpoPlayer/View Models/PlaylistViewModel.swift @@ -61,10 +61,6 @@ class PlaylistViewModel: ObservableObject { self.player.play(songs: songs, index: 0) } } - func cellDidAppear(song: Song) { - guard MediaControlBarMinimized.shared.isCompact == false else { return } - MediaControlBarMinimized.shared.isCompact = true - } func move(from source: IndexSet, to destination: Int) { guard !reordering else { return } reordering = true diff --git a/HalpoPlayer/View Models/PlaylistsViewModel.swift b/HalpoPlayer/View Models/PlaylistsViewModel.swift index 12ee0f2..3722e5c 100644 --- a/HalpoPlayer/View Models/PlaylistsViewModel.swift +++ b/HalpoPlayer/View Models/PlaylistsViewModel.swift @@ -37,10 +37,6 @@ class PlaylistsViewModel: ObservableObject { func goToPlaylist(playlist: GetPlaylistsResponse.Playlist, coordinator: Coordinator) { coordinator.goToPlaylist(playlist: playlist) } - func cellDidAppear(playlist: GetPlaylistsResponse.Playlist) { - guard MediaControlBarMinimized.shared.isCompact == false else { return } - MediaControlBarMinimized.shared.isCompact = true - } func addSongsToPlaylist(playlistId: String, coordinator: Coordinator) { Task { let songIds = songs.map { $0.id } diff --git a/HalpoPlayer/Views/AlbumDetailView.swift b/HalpoPlayer/Views/AlbumDetailView.swift index a1d6cc6..eaa73a4 100644 --- a/HalpoPlayer/Views/AlbumDetailView.swift +++ b/HalpoPlayer/Views/AlbumDetailView.swift @@ -119,9 +119,6 @@ struct AlbumDetailView: View { } .id(song.id) .listRowSeparator(.hidden) - .onAppear { - self.songAppeared(song: song) - } } } .listStyle(.plain) @@ -160,10 +157,4 @@ struct AlbumDetailView: View { ProgressView() } } - func songAppeared(song: Song) { - guard MediaControlBarMinimized.shared.isCompact == false else { return } - withAnimation { - MediaControlBarMinimized.shared.isCompact = true - } - } } diff --git a/HalpoPlayer/Views/DownloadsView.swift b/HalpoPlayer/Views/DownloadsView.swift index 554cfc7..9d16769 100644 --- a/HalpoPlayer/Views/DownloadsView.swift +++ b/HalpoPlayer/Views/DownloadsView.swift @@ -42,9 +42,6 @@ struct DownloadsView: View { .tint(.blue) } .listRowSeparator(.hidden) - .onAppear { - self.songAppeared(song: file.song) - } } case .albums: ForEach(viewModel.albums) { album in @@ -98,12 +95,6 @@ struct DownloadsView: View { } } } - func songAppeared(song: Song) { - guard MediaControlBarMinimized.shared.isCompact == false else { return } - withAnimation { - MediaControlBarMinimized.shared.isCompact = true - } - } } enum DownloadsType: String, CaseIterable { diff --git a/HalpoPlayer/Views/LibraryView.swift b/HalpoPlayer/Views/LibraryView.swift index 0793dd5..83bbe6c 100644 --- a/HalpoPlayer/Views/LibraryView.swift +++ b/HalpoPlayer/Views/LibraryView.swift @@ -63,11 +63,6 @@ struct AlbumListView: View { } .padding(8) } - .simultaneousGesture(DragGesture().onChanged({ value in - withAnimation { - MediaControlBarMinimized.shared.isCompact = true - } - })) .refreshable { do { try await viewModel.loadContent(force: true) @@ -138,11 +133,6 @@ struct AlbumListView: View { viewModel.albumAppeared(album: album) } } - .simultaneousGesture(DragGesture().onChanged({ value in - withAnimation { - MediaControlBarMinimized.shared.isCompact = true - } - })) .refreshable { do { try await viewModel.loadContent(force: true) @@ -236,11 +226,6 @@ struct ArtistListView: View { } .listRowSeparator(.hidden) } - .simultaneousGesture(DragGesture().onChanged({ value in - withAnimation { - MediaControlBarMinimized.shared.isCompact = true - } - })) .listStyle(.plain) .searchable(text: $viewModel.searchText, prompt: "Search artists") .scrollDismissesKeyboard(.immediately) diff --git a/HalpoPlayer/Views/MediaControlBar.swift b/HalpoPlayer/Views/MediaControlBar.swift index 8fdf424..8bbb000 100644 --- a/HalpoPlayer/Views/MediaControlBar.swift +++ b/HalpoPlayer/Views/MediaControlBar.swift @@ -10,65 +10,25 @@ import SwiftUI struct MediaControlBar: View { @EnvironmentObject var coordinator: Coordinator @ObservedObject var player = AudioManager.shared - @EnvironmentObject var compact: MediaControlBarMinimized @ObservedObject var timeline = TimelineManager.shared @State private var dragAmount = CGSize.zero - var buttonSize: CGFloat { - return compact.isCompact ? 28 : 48 - } + var buttonSize: CGFloat = 28 var body: some View { if player.currentSong != nil { VStack { HStack { - Button { - if compact.isCompact { - withAnimation { - compact.isCompact = false - } - } else if let albumId = player.currentSong?.albumId { - if coordinator.viewingAlbum != albumId { - compact.isCompact = true - coordinator.albumTapped(albumId: albumId, scrollToSong: player.currentSong?.id) - } else { - // scroll to current song? - } - } - } label: { - if let image = player.albumArt { - Image(uiImage: image) - .resizable() - .scaledToFill() - .frame(width: 60, height: 60) - .cornerRadius(8) - } - if compact.isCompact { - Text("\(player.currentSong?.title ?? "")") - .font(.body).bold() - .foregroundColor(.primary) - .lineLimit(1) - } else { - VStack(alignment: .leading) { - Text("\(player.currentSong?.title ?? "")") - .font(.body).bold() - .foregroundColor(.primary) - .multilineTextAlignment(.leading) - Text("\(player.currentSong?.artist ?? "")") - .font(.body) - .foregroundColor(.secondary) - .multilineTextAlignment(.leading) - } - } - Spacer() - if !compact.isCompact { - AirPlayView.shared - .frame(width: 24, height: 24) - .padding(8) - .onTapGesture { - AirPlayView.shared.showAirPlayMenu() - } - } + if let image = player.albumArt { + Image(uiImage: image) + .resizable() + .scaledToFill() + .frame(width: 60, height: 60) + .cornerRadius(8) } - if compact.isCompact { + Text("\(player.currentSong?.title ?? "")") + .font(.body).bold() + .foregroundColor(.primary) + .lineLimit(1) + Spacer() Button { if player.isPlaying { player.queue.pause() @@ -98,84 +58,6 @@ struct MediaControlBar: View { .padding() .disabled(self.player.queue.nextItems.isEmpty) } - } - if !compact.isCompact { - HStack { - Spacer() - Button { - print("back") - do { - try self.player.previousPressed() - } catch { - print(error) - } - } label: { - Image(systemName: "backward.circle") - .font(.system(size: buttonSize)) - } - .padding() - .disabled(self.player.queue.previousItems.isEmpty && self.player.queue.currentTime < 5) - - Button { - if player.isPlaying { - player.queue.pause() - } else { - player.queue.play() - } - } label: { - if player.loading { - ProgressView() - .controlSize(.large) - } else { - if player.isPlaying { - Image(systemName: "pause.circle") - .font(.system(size:buttonSize)) - } else { - Image(systemName: "play.circle") - .font(.system(size:buttonSize)) - } - } - } - .padding() - Button { - try? self.player.queue.next() - } label: { - Image(systemName: "forward.circle") - .font(.system(size:buttonSize)) - } - .padding() - .disabled(self.player.queue.nextItems.isEmpty) - - Spacer() - } - - let upperBound = getUpperBound() - HStack { - Text(timeString(time:TimeInterval(timeline.timeElapsed))) - .foregroundColor(.secondary) - .font(.callout) - .padding(4) - ZStack { - let duration = max(0, min(timeline.duration, upperBound)) - ProgressView(value: duration, total: upperBound) - Slider(value: $timeline.timeElapsed, in: 0...upperBound) { didChange in - player.invalidateSlider = didChange - if didChange { - player.queue.pause() - } else { - player.queue.seek(to: timeline.timeElapsed) - player.queue.play() - } - } - .tint(nil) - .opacity(0.8) - } - Text(timeString(time:TimeInterval(0 - self.getUpperBound() + timeline.timeElapsed))) - .foregroundColor(.secondary) - .font(.callout) - .padding(4) - } - } } .padding() .background { @@ -184,13 +66,6 @@ struct MediaControlBar: View { .gesture( DragGesture(minimumDistance: 30) .onEnded { value in - if !compact.isCompact { - if value.translation.height > 0 { - withAnimation { - compact.isCompact = true - } - } - } } ) } else { @@ -219,7 +94,3 @@ struct MediaControlBar: View { } } -class MediaControlBarMinimized: ObservableObject { - static let shared = MediaControlBarMinimized() - @Published var isCompact = false -} diff --git a/HalpoPlayer/Views/NowPlayingView.swift b/HalpoPlayer/Views/NowPlayingView.swift index cb92b8a..b773a79 100644 --- a/HalpoPlayer/Views/NowPlayingView.swift +++ b/HalpoPlayer/Views/NowPlayingView.swift @@ -8,125 +8,146 @@ import SwiftUI struct NowPlayingView: View { - @EnvironmentObject var coordinator: Coordinator + @Environment(\.dismiss) var dismiss @ObservedObject var player = AudioManager.shared - @EnvironmentObject var compact: MediaControlBarMinimized @ObservedObject var timeline = TimelineManager.shared - var buttonSize: CGFloat { - return compact.isCompact ? 28 : 48 + var goToAlbum: (((albumId: String, songId: String?)) -> Void)? + var buttonSize: CGFloat = 32 + var playButtonName: String { + if player.isPlaying { + return "pause.circle.fill" + } else { + return "play.circle.fill" + } + } + func playButtonPressed() { + if player.isPlaying { + self.player.queue.pause() + } else { + self.player.queue.play() + } } var body: some View { - if player.currentSong != nil { - if compact.isCompact { - // COMPACT - HStack { - - Button { - withAnimation { - compact.isCompact = false - } - } label: { - HStack { - if let image = player.albumArt { - Image(uiImage: image) - .resizable() - .scaledToFill() - .frame(width: 60, height: 60) - .cornerRadius(8) + VStack { + Spacer() + .frame(height: 64) + Button { + if let albumId = player.currentSong?.albumId { + self.dismiss() + self.goToAlbum?((albumId, player.currentSong?.id)) + } + } label: { + if let image = player.albumArt { + HStack { + Spacer() + ZStack { + Image(uiImage: image) + .resizable() + .scaledToFit() + .cornerRadius(8) + .frame(width: 300, height: 300) + .shadow(radius: 8) + if player.loading { + ProgressView() + .controlSize(.large) } - Text("\(player.currentSong?.title ?? "")") - .font(.body).bold() - .foregroundColor(.primary) - .lineLimit(1) } + Spacer() } - - - Button { - if player.isPlaying { + } + } + Spacer() + .frame(height: 16) + VStack { + Text(player.currentSong?.title ?? "") + .font(.title) + .multilineTextAlignment(.center) + Text(player.currentSong?.artist ?? "") + .font(.subheadline) + .multilineTextAlignment(.center) + } + .padding([.leading, .trailing], 16) + Spacer() + let upperBound = getUpperBound() + HStack { + Text(timeString(time:TimeInterval(timeline.timeElapsed))) + .foregroundColor(.secondary) + .font(.callout) + .padding(4) + ZStack { + let duration = max(0, min(timeline.duration, upperBound)) + ProgressView(value: duration, total: upperBound) + Slider(value: $timeline.timeElapsed, in: 0...upperBound) { didChange in + player.invalidateSlider = didChange + if didChange { player.queue.pause() } else { + player.queue.seek(to: timeline.timeElapsed) player.queue.play() } - } label: { - if player.loading { - ProgressView() - } else { - if player.isPlaying { - Image(systemName: "pause.fill") - .font(.system(size:buttonSize)) - } else { - Image(systemName: "play.fill") - .font(.system(size:buttonSize)) - } - } } - .padding() - Button { - try? self.player.queue.next() - } label: { - Image(systemName: "forward.fill") - .font(.system(size:buttonSize)) + .tint(nil) + .opacity(0.8) + } + Text(timeString(time:TimeInterval(0 - self.getUpperBound() + timeline.timeElapsed))) + .foregroundColor(.secondary) + .font(.callout) + .padding(4) + } + .padding(16) + HStack { + Spacer() + Button { + print("back") + do { + try self.player.previousPressed() + } catch { + print(error) } - .padding() - .disabled(self.player.queue.nextItems.isEmpty) + } label: { + Image(systemName: "backward.fill") + .font(.system(size: buttonSize)) } - } else { - // FULL SCREEN - + .padding() + .disabled(self.player.queue.previousItems.isEmpty && self.player.queue.currentTime < 5) - VStack { - - Button { - if let albumId = player.currentSong?.albumId { - if coordinator.viewingAlbum != albumId { - compact.isCompact = true - coordinator.albumTapped(albumId: albumId, scrollToSong: player.currentSong?.id) - } else { - // scroll to current song? - } - } - } label: { - if let image = player.albumArt { - Image(uiImage: image) - .resizable() - .scaledToFill() - .frame(maxWidth: .infinity) - .cornerRadius(8) - .padding(16) - } + Button { + if player.isPlaying { + player.queue.pause() + } else { + player.queue.play() } - - let upperBound = getUpperBound() - HStack { - Text(timeString(time:TimeInterval(timeline.timeElapsed))) - .foregroundColor(.secondary) - .font(.callout) - .padding(4) - ZStack { - let duration = max(0, min(timeline.duration, upperBound)) - ProgressView(value: duration, total: upperBound) - Slider(value: $timeline.timeElapsed, in: 0...upperBound) { didChange in - player.invalidateSlider = didChange - if didChange { - player.queue.pause() - } else { - player.queue.seek(to: timeline.timeElapsed) - player.queue.play() - } - } - .tint(nil) - .opacity(0.8) - } - Text(timeString(time:TimeInterval(0 - self.getUpperBound() + timeline.timeElapsed))) - .foregroundColor(.secondary) - .font(.callout) - .padding(4) + } label: { + if player.isPlaying { + Image(systemName: "pause.fill") + .font(.system(size:buttonSize)) + } else { + Image(systemName: "play.fill") + .font(.system(size:buttonSize)) } } + .padding() + Button { + try? self.player.queue.next() + } label: { + Image(systemName: "forward.fill") + .font(.system(size:buttonSize)) + } + .padding() + .disabled(self.player.queue.nextItems.isEmpty) + + Spacer() + } + HStack { + Spacer() + AirPlayView.shared + .frame(width: 24, height: 24) + .padding(8) + .onTapGesture { + AirPlayView.shared.showAirPlayMenu() + } + Spacer() } - } else { - EmptyView() } } func getUpperBound() -> Double { diff --git a/HalpoPlayer/Views/PlaylistView.swift b/HalpoPlayer/Views/PlaylistView.swift index eae99e0..5cac1b5 100644 --- a/HalpoPlayer/Views/PlaylistView.swift +++ b/HalpoPlayer/Views/PlaylistView.swift @@ -50,11 +50,6 @@ struct PlaylistView: View { SongCell(showAlbumName: true, showTrackNumber: false, song: song) } .listRowSeparator(.hidden) - .onAppear { - withAnimation { - viewModel.cellDidAppear(song: song) - } - } .moveDisabled(viewModel.reordering) } .onMove(perform: viewModel.move) diff --git a/HalpoPlayer/Views/PlaylistsView.swift b/HalpoPlayer/Views/PlaylistsView.swift index f2f7cf1..43abeac 100644 --- a/HalpoPlayer/Views/PlaylistsView.swift +++ b/HalpoPlayer/Views/PlaylistsView.swift @@ -44,11 +44,6 @@ struct PlaylistsView: View { PlaylistCell(showChevron: viewModel.songs.isEmpty, playlist: playlist) } .listRowSeparator(.hidden) - .onAppear { - withAnimation { - viewModel.cellDidAppear(playlist: playlist) - } - } } } .refreshable { From e8aadbea68db5147aa3d65be331c253470de6c8c Mon Sep 17 00:00:00 2001 From: Paul Halpin Date: Thu, 12 Oct 2023 18:01:56 +0100 Subject: [PATCH 3/6] fixed crash when adding port manually --- HalpoPlayer/AudioManager.swift | 31 ++++++++++++++---------- HalpoPlayer/Network/SubsonicClient.swift | 5 ++-- HalpoPlayer/Views/LibraryView.swift | 13 +++++++++- HalpoPlayer/Views/LoginView.swift | 9 ++++++- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/HalpoPlayer/AudioManager.swift b/HalpoPlayer/AudioManager.swift index 73ccdf2..75b70df 100644 --- a/HalpoPlayer/AudioManager.swift +++ b/HalpoPlayer/AudioManager.swift @@ -75,6 +75,7 @@ class AudioManager: ObservableObject { func loadSavedState() { guard !initialLoad else { return } + guard AccountHolder.shared.account != nil else { return } self.play(songs: self.playerState.songs, index: self.playerState.index, paused: true, currentTime: self.playerState.currentTime) initialLoad = true } @@ -119,20 +120,24 @@ class AudioManager: ObservableObject { } func addSongToQueue(song: Song) { - self.songs?.append(song) - let item: DefaultAudioItem - if let cachedURL = Database.shared.retrieveSong(song: song) { - item = DefaultAudioItem(audioUrl: cachedURL.path(), sourceType: .file) - } else { - let url = SubsonicClient.shared.stream(id: song.id, mp3: true).absoluteString - item = DefaultAudioItem(audioUrl: url, sourceType: .stream) + do { + self.songs?.append(song) + let item: DefaultAudioItem + if let cachedURL = Database.shared.retrieveSong(song: song) { + item = DefaultAudioItem(audioUrl: cachedURL.path(), sourceType: .file) + } else { + let url = try SubsonicClient.shared.stream(id: song.id, mp3: true).absoluteString + item = DefaultAudioItem(audioUrl: url, sourceType: .stream) + } + item.albumTitle = song.album + item.artist = song.artist + item.title = song.title + item.artwork = albumArt + try? self.queue.add(item: item) + self.updatePlaylist() + } catch { + print(error) } - item.albumTitle = song.album - item.artist = song.artist - item.title = song.title - item.artwork = albumArt - try? self.queue.add(item: item) - self.updatePlaylist() } func handleAudioPlayerError(fail: AudioPlayer.FailEventData) { diff --git a/HalpoPlayer/Network/SubsonicClient.swift b/HalpoPlayer/Network/SubsonicClient.swift index 9b69634..7f8f5dc 100644 --- a/HalpoPlayer/Network/SubsonicClient.swift +++ b/HalpoPlayer/Network/SubsonicClient.swift @@ -123,9 +123,10 @@ class SubsonicClient { let cachedSong = CachedSong(song: song, album: album.subsonicResponse.album, imageUrl: coverArtUrl, path: "") return (data, response, cachedSong) } - func stream(id: String, mp3: Bool = false) -> URL { + func stream(id: String, mp3: Bool = false) throws -> URL { + guard let currentAddress = currentAddress else { throw HalpoError.noAccount } let api = SubsonicAPI.stream(id: id, mp3: mp3) - let url = URL(string: "\(currentAddress!)/rest/\(api.pathComponent)\(userString)")! + let url = URL(string: "\(currentAddress)/rest/\(api.pathComponent)\(userString)")! return url } func coverArt(albumId: String) async throws -> UIImage { diff --git a/HalpoPlayer/Views/LibraryView.swift b/HalpoPlayer/Views/LibraryView.swift index 83bbe6c..45dda3b 100644 --- a/HalpoPlayer/Views/LibraryView.swift +++ b/HalpoPlayer/Views/LibraryView.swift @@ -11,7 +11,6 @@ struct LibraryView: View { @StateObject var viewModel = LibraryViewModel() @EnvironmentObject var coordinator: Coordinator @ObservedObject var accountHolder = AccountHolder.shared - var body: some View { if accountHolder.account != nil { switch viewModel.viewType { @@ -19,6 +18,18 @@ struct LibraryView: View { ArtistListView(viewModel: viewModel) case .albums: AlbumListView(viewModel: viewModel) + .onAppear { + if viewModel.albums.isEmpty { + Task { + do { + try await viewModel.loadContent(force: true) + } catch { + print(error) + } + } + + } + } } } else { Button { diff --git a/HalpoPlayer/Views/LoginView.swift b/HalpoPlayer/Views/LoginView.swift index 34858c5..a27de8b 100644 --- a/HalpoPlayer/Views/LoginView.swift +++ b/HalpoPlayer/Views/LoginView.swift @@ -70,7 +70,7 @@ struct LoginView: View { Task { do { - let success = try await SubsonicClient.shared.testAddressesForPermission(ad1: "\(address):\(port)", ad2: "\(otherAddress):\(port)") + let success = try await SubsonicClient.shared.testAddressesForPermission(ad1: self.combineAddressWithPort(address: address, port: port), ad2: self.combineAddressWithPort(address: otherAddress, port: port)) if success { let account = Account(username: username, password: password, address: address, otherAddress: otherAddress, port: port) DispatchQueue.main.async { @@ -86,6 +86,13 @@ struct LoginView: View { } dismiss() } + func combineAddressWithPort(address: String, port: String) -> String { + if address.contains(":") { + return address + } else { + return "\(address)\(port)" + } + } func logout() { self.address = "" self.otherAddress = "" From 61b3a823f6dc3170ff4dbbab8a8cc2d923f0cbc9 Mon Sep 17 00:00:00 2001 From: Paul Halpin Date: Fri, 13 Oct 2023 11:36:18 +0100 Subject: [PATCH 4/6] volume slider, fixed library search --- HalpoPlayer.xcodeproj/project.pbxproj | 4 ++++ HalpoPlayer/HalpoPlayerApp.swift | 2 ++ HalpoPlayer/Views/LibraryView.swift | 4 ++-- HalpoPlayer/Views/NowPlayingView.swift | 22 ++++++++++++++++++---- HalpoPlayer/Views/VolumeSlider.swift | 23 +++++++++++++++++++++++ 5 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 HalpoPlayer/Views/VolumeSlider.swift diff --git a/HalpoPlayer.xcodeproj/project.pbxproj b/HalpoPlayer.xcodeproj/project.pbxproj index 7664c0a..d97b3b2 100644 --- a/HalpoPlayer.xcodeproj/project.pbxproj +++ b/HalpoPlayer.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ DA13D7FF2A6D94DD00BADCB6 /* BatteryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA13D7FE2A6D94DD00BADCB6 /* BatteryManager.swift */; }; + DA1FC7DA2AD94F8D00B2DCBA /* VolumeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1FC7D92AD94F8D00B2DCBA /* VolumeSlider.swift */; }; DA26224A2A7940FE006A2CDD /* CreatePlaylistResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2622492A7940FE006A2CDD /* CreatePlaylistResponse.swift */; }; DA3DFF642AB8595900D40D7A /* ViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3DFF632AB8595900D40D7A /* ViewFactory.swift */; }; DA3E7C352A69213900DD5180 /* SideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3E7C342A69213900DD5180 /* SideMenu.swift */; }; @@ -112,6 +113,7 @@ /* Begin PBXFileReference section */ DA13D7FE2A6D94DD00BADCB6 /* BatteryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryManager.swift; sourceTree = ""; }; + DA1FC7D92AD94F8D00B2DCBA /* VolumeSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeSlider.swift; sourceTree = ""; }; DA2622492A7940FE006A2CDD /* CreatePlaylistResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePlaylistResponse.swift; sourceTree = ""; }; DA3DFF632AB8595900D40D7A /* ViewFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewFactory.swift; path = HalpoPlayer/ViewFactory.swift; sourceTree = SOURCE_ROOT; }; DA3E7C342A69213900DD5180 /* SideMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenu.swift; sourceTree = ""; }; @@ -324,6 +326,7 @@ DA3E7C3A2A693C8100DD5180 /* ArtistView.swift */, DA3E7C3E2A69420100DD5180 /* ArtistCell.swift */, DA47D8302A6E74B200CBAAE4 /* NowPlayingView.swift */, + DA1FC7D92AD94F8D00B2DCBA /* VolumeSlider.swift */, ); path = Views; sourceTree = ""; @@ -523,6 +526,7 @@ DA7F1EA42A652E14006D8934 /* Account.swift in Sources */, DA3DFF642AB8595900D40D7A /* ViewFactory.swift in Sources */, DA7F1EA02A652E14006D8934 /* AuthenticationResponse.swift in Sources */, + DA1FC7DA2AD94F8D00B2DCBA /* VolumeSlider.swift in Sources */, DA7F1EA12A652E14006D8934 /* GetIndexesResponse.swift in Sources */, DA47D8312A6E74B200CBAAE4 /* NowPlayingView.swift in Sources */, DA7F1E942A652DF4006D8934 /* Extensions.swift in Sources */, diff --git a/HalpoPlayer/HalpoPlayerApp.swift b/HalpoPlayer/HalpoPlayerApp.swift index ae60582..afd0a66 100644 --- a/HalpoPlayer/HalpoPlayerApp.swift +++ b/HalpoPlayer/HalpoPlayerApp.swift @@ -59,6 +59,8 @@ struct halpoplayerApp: App { if self.coordinatorForTab(tab: self.selectedTab).viewingAlbum != albumId { self.coordinatorForTab(tab: self.selectedTab).albumTapped(albumId: albumId, scrollToSong: songId) } + }, goToArtist: { artistId, artistName in + self.coordinatorForTab(tab: self.selectedTab).goToArtist(artistId: artistId, artistName: artistName) }) }) diff --git a/HalpoPlayer/Views/LibraryView.swift b/HalpoPlayer/Views/LibraryView.swift index 45dda3b..b2f596b 100644 --- a/HalpoPlayer/Views/LibraryView.swift +++ b/HalpoPlayer/Views/LibraryView.swift @@ -119,7 +119,7 @@ struct AlbumListView: View { } } } else { - List(viewModel.albums) { album in + List(viewModel.filteredAlbums) { album in Button { if viewModel.selectMode { if viewModel.selectedAlbums.contains(album) { @@ -229,7 +229,7 @@ struct ArtistListView: View { } } } - List(viewModel.artists) { artist in + List(viewModel.filteredArtists) { artist in Button { coordinator.goToArtist(artistId: artist.id, artistName: artist.name) } label: { diff --git a/HalpoPlayer/Views/NowPlayingView.swift b/HalpoPlayer/Views/NowPlayingView.swift index b773a79..6715588 100644 --- a/HalpoPlayer/Views/NowPlayingView.swift +++ b/HalpoPlayer/Views/NowPlayingView.swift @@ -11,7 +11,9 @@ struct NowPlayingView: View { @Environment(\.dismiss) var dismiss @ObservedObject var player = AudioManager.shared @ObservedObject var timeline = TimelineManager.shared + @State var volume: Float = AudioManager.shared.queue.volume var goToAlbum: (((albumId: String, songId: String?)) -> Void)? + var goToArtist: (((artistId: String, artistName: String)) -> Void)? var buttonSize: CGFloat = 32 var playButtonName: String { if player.isPlaying { @@ -58,13 +60,22 @@ struct NowPlayingView: View { } Spacer() .frame(height: 16) - VStack { + VStack(spacing: 8) { Text(player.currentSong?.title ?? "") .font(.title) .multilineTextAlignment(.center) - Text(player.currentSong?.artist ?? "") - .font(.subheadline) - .multilineTextAlignment(.center) + Button { + if let artistId = player.currentSong?.artistId, + let artistName = player.currentSong?.artist { + self.dismiss() + self.goToArtist?((artistId, artistName)) + } + } label: { + Text(player.currentSong?.artist ?? "") + .font(.body) + .multilineTextAlignment(.center) + } + .disabled(player.currentSong?.artistId == nil) } .padding([.leading, .trailing], 16) Spacer() @@ -138,6 +149,9 @@ struct NowPlayingView: View { Spacer() } + VolumeSlider() + .frame(height: 40) + .padding(.horizontal) HStack { Spacer() AirPlayView.shared diff --git a/HalpoPlayer/Views/VolumeSlider.swift b/HalpoPlayer/Views/VolumeSlider.swift new file mode 100644 index 0000000..6f60cbc --- /dev/null +++ b/HalpoPlayer/Views/VolumeSlider.swift @@ -0,0 +1,23 @@ +// +// VolumeSlider.swift +// HalpoPlayer +// +// Created by paul on 13/10/2023. +// + +import SwiftUI +import MediaPlayer +import UIKit + +struct VolumeSlider: UIViewRepresentable { + func makeUIView(context: Context) -> MPVolumeView { + let slider = MPVolumeView(frame: .zero) + for v in slider.subviews where v is UISlider { + let slider = v as? UISlider + slider?.thumbTintColor = .clear + } + return slider + } + + func updateUIView(_ view: MPVolumeView, context: Context) {} +} From b9bb694f5f6b749297d886e02ccdae03ee76037c Mon Sep 17 00:00:00 2001 From: Paul Halpin Date: Mon, 16 Oct 2023 12:11:11 +0100 Subject: [PATCH 5/6] artist song shuffle fix --- HalpoPlayer/View Models/ArtistViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/HalpoPlayer/View Models/ArtistViewModel.swift b/HalpoPlayer/View Models/ArtistViewModel.swift index a3f401b..0e07b55 100644 --- a/HalpoPlayer/View Models/ArtistViewModel.swift +++ b/HalpoPlayer/View Models/ArtistViewModel.swift @@ -88,6 +88,7 @@ class ArtistViewModel: ObservableObject { let respones = try await SubsonicClient.shared.getAlbum(id: album.id) songs.append(contentsOf: respones.subsonicResponse.album.song) } + songs = songs.shuffled() let finalSongs = songs DispatchQueue.main.async { self.player.play(songs:finalSongs, index: 0) From 08921947ebc70072447fec64c34e4bec02880a54 Mon Sep 17 00:00:00 2001 From: Paul Halpin Date: Mon, 16 Oct 2023 13:52:50 +0100 Subject: [PATCH 6/6] volume slider fix --- HalpoPlayer/Views/NowPlayingView.swift | 12 +++++++++--- HalpoPlayer/Views/VolumeSlider.swift | 22 +++++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/HalpoPlayer/Views/NowPlayingView.swift b/HalpoPlayer/Views/NowPlayingView.swift index 6715588..0ee5886 100644 --- a/HalpoPlayer/Views/NowPlayingView.swift +++ b/HalpoPlayer/Views/NowPlayingView.swift @@ -149,9 +149,15 @@ struct NowPlayingView: View { Spacer() } - VolumeSlider() - .frame(height: 40) - .padding(.horizontal) + HStack { + Image(systemName: "speaker") + .foregroundStyle(.gray) + VolumeSlider() + Image(systemName: "speaker.wave.3") + .foregroundStyle(.gray) + } + .frame(height: 40) + .padding(.horizontal) HStack { Spacer() AirPlayView.shared diff --git a/HalpoPlayer/Views/VolumeSlider.swift b/HalpoPlayer/Views/VolumeSlider.swift index 6f60cbc..e93c27f 100644 --- a/HalpoPlayer/Views/VolumeSlider.swift +++ b/HalpoPlayer/Views/VolumeSlider.swift @@ -10,8 +10,24 @@ import MediaPlayer import UIKit struct VolumeSlider: UIViewRepresentable { - func makeUIView(context: Context) -> MPVolumeView { - let slider = MPVolumeView(frame: .zero) + + class SystemVolumeView: MPVolumeView { + override func volumeSliderRect(forBounds bounds: CGRect) -> CGRect { + var newBounds = super.volumeSliderRect(forBounds: bounds) + newBounds.origin.y = bounds.origin.y + newBounds.size.height = bounds.size.height + return newBounds + } + override func volumeThumbRect(forBounds bounds: CGRect, volumeSliderRect rect: CGRect, value: Float) -> CGRect { + var newBounds = super.volumeThumbRect(forBounds: bounds, volumeSliderRect: rect, value: value) + newBounds.origin.y = bounds.origin.y + newBounds.size.height = bounds.size.height + return newBounds + } + } + + func makeUIView(context: Context) -> SystemVolumeView { + let slider = SystemVolumeView(frame: .zero) for v in slider.subviews where v is UISlider { let slider = v as? UISlider slider?.thumbTintColor = .clear @@ -19,5 +35,5 @@ struct VolumeSlider: UIViewRepresentable { return slider } - func updateUIView(_ view: MPVolumeView, context: Context) {} + func updateUIView(_ view: SystemVolumeView, context: Context) {} }