From a80c778150580f2e01ec933e5a5e0ba47d512d91 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sun, 19 Nov 2023 17:22:52 +0900 Subject: [PATCH 01/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20UI=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=ED=98=84=EC=9E=AC=20=EC=9E=AC=EC=83=9D=EC=A4=91=EC=9D=B8=20?= =?UTF-8?q?=EA=B3=A1=20UI=20=EC=B6=94=EA=B0=80=20-=20=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EA=B7=B8=20=EC=8B=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=ED=94=8C=EB=A0=88=EC=9D=B4=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=84=B0=EC=B9=98=EC=98=81=EC=97=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=ED=94=8C=EB=A0=88=EC=9D=B4=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=9C=EC=84=9C=20=EC=B4=88=EA=B8=B0=EA=B0=92=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/PlaylistView.swift | 29 +++++++++++++------ .../SongDetail/PlaylistViewModel.swift | 11 ++++++- .../Features/SongDetail/SongDetailView.swift | 3 ++ .../SongDetail/SongDetailViewModel.swift | 7 +++-- Halmap/Persistence.swift | 25 ++++++++++++++++ Halmap/View/Component/Progressbar.swift | 4 +++ 6 files changed, 66 insertions(+), 13 deletions(-) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 584c0e7..5e1be99 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -8,6 +8,7 @@ import SwiftUI struct PlaylistView: View { + @AppStorage("currentSongId") var currentSongId: String = "" @StateObject var viewModel: PlaylistViewModel @Binding var song: SongInfo @Binding var isScrolled: Bool @@ -21,6 +22,7 @@ struct PlaylistView: View { List { ForEach(collectedSongs, id: \.self) { playListSong in getPlayListRowView(song: playListSong) + .background(Color.white.opacity(0.001)) .onTapGesture { self.song = viewModel.didTappedSongCell(song: playListSong) } @@ -31,8 +33,9 @@ struct PlaylistView: View { to: destination, based: collectedSongs) } - .listRowBackground(Color.clear) - Color.clear.frame(height:50) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .listRowBackground(Color("\(song.team)Sub")) + Color.clear.frame(height:70) .listRowBackground(Color.clear) } .listStyle(.plain) @@ -62,15 +65,22 @@ struct PlaylistView: View { @ViewBuilder private func getPlayListRowView(song: CollectedSong) -> some View { HStack{ - Image(viewModel.getAlbumImage(with: song)) - .resizable() - .frame(width: 40, height: 40) - .cornerRadius(8) + if currentSongId == song.id { + Image(systemName: "waveform") + .font(.system(size: 24)) + .foregroundStyle(Color(viewModel.getSongTeamBackgroundColor(with: song))) + .frame(width: 40, height: 40) + } else { + Image(viewModel.getAlbumImage(with: song)) + .resizable() + .frame(width: 40, height: 40) + .cornerRadius(8) + } VStack(alignment: .leading, spacing: 8) { Text(viewModel.getSongTitle(song: song)) .font(Font.Halmap.CustomBodyMedium) .foregroundStyle(Color.white) - Text(viewModel.getSongTitle(song: song)) + Text(viewModel.getTeamNameKr(song: song)) .font(Font.Halmap.CustomCaptionMedium) .foregroundStyle(Color.customGray) }.padding(.horizontal, 16) @@ -80,8 +90,9 @@ struct PlaylistView: View { .frame(width: 18, height: 18) .foregroundStyle(Color.customGray) } - .padding(.horizontal, 20) // 40 -> 20 값 조정 - .frame(height: 50) // 70 -> 50값 조정 + .padding(.horizontal, 40) + .frame(height: 70) + .background(currentSongId == song.id ? Color.white.opacity(0.2) : Color.clear) } private struct ViewOffsetKey: PreferenceKey { diff --git a/Halmap/Features/SongDetail/PlaylistViewModel.swift b/Halmap/Features/SongDetail/PlaylistViewModel.swift index bf82a47..6f0f07d 100644 --- a/Halmap/Features/SongDetail/PlaylistViewModel.swift +++ b/Halmap/Features/SongDetail/PlaylistViewModel.swift @@ -30,13 +30,22 @@ final class PlaylistViewModel: ObservableObject { func getSongTitle(song: CollectedSong) -> String { song.title ?? "" } - + + func getTeamNameKr(song: CollectedSong) -> String { + TeamName(rawValue: song.safeTeam)?.fetchTeamNameKr() ?? "" + } + + func getSongTeamBackgroundColor(with song: CollectedSong) -> String { + "\(convertSongToSongInfo(song: song).team)Background" + } + func getSongTeamName(with song: CollectedSong) -> String { let song = convertSongToSongInfo(song: song) return TeamName(rawValue: song.team)?.fetchTeamNameKr() ?? "." } func didTappedSongCell(song: CollectedSong) -> SongInfo { + print("song: ", song.safeTitle, "order: ", song.order) let song = convertSongToSongInfo(song: song) print(#function, song.title) self.song = song diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index c246ec7..411f093 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -168,6 +168,7 @@ private struct FavoriteButton: View { sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlayListFilter(filter: "favorite").predicate, animation: .default) private var favoriteSongs: FetchedResults + @AppStorage("currentSongId") var currentSongId: String = "" var body: some View { Button { @@ -177,11 +178,13 @@ private struct FavoriteButton: View { .foregroundStyle(viewModel.isFavorite ? Color("\(viewModel.song.team)Point") : Color.white) } .onAppear() { + currentSongId = viewModel.song.id if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { viewModel.isFavorite = true } } .onChange(of: viewModel.song.id) { _ in + currentSongId = viewModel.song.id if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { viewModel.isFavorite = true } else { diff --git a/Halmap/Features/SongDetail/SongDetailViewModel.swift b/Halmap/Features/SongDetail/SongDetailViewModel.swift index f97c536..ec7d7d8 100644 --- a/Halmap/Features/SongDetail/SongDetailViewModel.swift +++ b/Halmap/Features/SongDetail/SongDetailViewModel.swift @@ -62,9 +62,10 @@ final class SongDetailViewModel: ObservableObject { func addDefaultPlaylist(defaultPlaylistSongs: FetchedResults) { if !defaultPlaylistSongs.contains(where: {$0.id == self.song.id}) { - let collectedSong = persistence.createCollectedSong(song: song, playListTitle: "bufferPlaylist") - persistence.resetBufferList(song: collectedSong) - persistence.saveSongs(song: song, playListTitle: "defaultPlaylist") + //추후 하프모달 사용 시 다시 이용 +// let collectedSong = persistence.createCollectedSong(song: song, playListTitle: "bufferPlaylist") +// persistence.resetBufferList(song: collectedSong) + persistence.saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(defaultPlaylistSongs.count)) } } diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index ba57050..3e575ab 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -53,6 +53,31 @@ struct PersistenceController { } } + func saveSongs(song: SongInfo, playListTitle: String?, order: Int64) { + let context = container.viewContext + let collectedSong = CollectedSong(context: context) + + collectedSong.id = song.id + collectedSong.title = song.title + collectedSong.info = song.info + collectedSong.lyrics = song.lyrics + collectedSong.url = song.url + collectedSong.type = song.type + collectedSong.playListTitle = playListTitle + collectedSong.team = song.team + collectedSong.date = Date() + collectedSong.order = order + + if context.hasChanges { + do { + try context.save() + } catch { + let nsError = error as NSError + fatalError("error \(nsError), \(nsError.userInfo)") + } + } + } + func saveSongs(song: SongInfo, playListTitle: String?, menuType: MenuType, collectedSongs: FetchedResults) { let context = container.viewContext let collectedSong = CollectedSong(context: context) diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index 942e7d7..1031e14 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -27,6 +27,10 @@ struct Progressbar: View { } .tint(Color("\(team)Point")) .padding(.horizontal, isThumbActive ? 5 : 0) + .onChange(of: team) { _ in + let thumbImage = makeThumbView(isThumbActive: isThumbActive) + UISlider.appearance().setThumbImage(thumbImage, for: .normal) + } // .disabled(!isThumbActive) if isThumbActive { HStack { From f562d697413253123f59e4ae65aca1c9274e9f6d Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Thu, 23 Nov 2023 12:33:07 +0900 Subject: [PATCH 02/56] =?UTF-8?q?[Feat]=20=EC=9D=B4=EC=A0=84=20/=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=8C=20=EB=B2=84=ED=8A=BC=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PlayList -> Playlist - coredata 정렬 방식 변경 --- Halmap.xcodeproj/project.pbxproj | 8 +- Halmap/Data/AudioManager.swift | 237 ++++++++++-------- Halmap/Data/DataManager.swift | 4 +- Halmap/Features/SongDetail/PlaylistView.swift | 6 +- .../Features/SongDetail/SongDetailView.swift | 75 ++++-- .../SongDetail/SongDetailViewModel.swift | 11 + .../SongList/MainSongListTabView.swift | 3 - .../SongStorage/ScalingHeaderView.swift | 2 +- .../TeamSelection/TeamSelectionView.swift | 3 + Halmap/Persistence.swift | 29 +-- ...yListFilter.swift => PlaylistFilter.swift} | 4 +- 11 files changed, 218 insertions(+), 164 deletions(-) rename Halmap/{PlayListFilter.swift => PlaylistFilter.swift} (81%) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 92e86d4..4d39147 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -63,7 +63,7 @@ F98F623129B59CE60025F50E /* TeamName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98F623029B59CE60025F50E /* TeamName.swift */; }; F98F8D5D29D85B0C00F9C956 /* RequestSongView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98F8D5C29D85B0C00F9C956 /* RequestSongView.swift */; }; F9A178222A1BF5CB003E8E4C /* HalfSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A178212A1BF5CB003E8E4C /* HalfSheetView.swift */; }; - F9A178262A1C8B01003E8E4C /* PlayListFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A178252A1C8B01003E8E4C /* PlayListFilter.swift */; }; + F9A178262A1C8B01003E8E4C /* PlaylistFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A178252A1C8B01003E8E4C /* PlaylistFilter.swift */; }; F9A2DAA62A07DE9A008B84A9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F9A2DAA52A07DE9A008B84A9 /* Preview Assets.xcassets */; }; F9A925A02AA60A5100EF8BC9 /* MainTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A9259F2AA60A5100EF8BC9 /* MainTabViewModel.swift */; }; F9B3B7B529B7397600232BB8 /* MapName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9B3B7B429B7397600232BB8 /* MapName.swift */; }; @@ -125,7 +125,7 @@ F98F623029B59CE60025F50E /* TeamName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamName.swift; sourceTree = ""; }; F98F8D5C29D85B0C00F9C956 /* RequestSongView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSongView.swift; sourceTree = ""; }; F9A178212A1BF5CB003E8E4C /* HalfSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HalfSheetView.swift; sourceTree = ""; }; - F9A178252A1C8B01003E8E4C /* PlayListFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListFilter.swift; sourceTree = ""; }; + F9A178252A1C8B01003E8E4C /* PlaylistFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistFilter.swift; sourceTree = ""; }; F9A2DAA52A07DE9A008B84A9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; F9A9259F2AA60A5100EF8BC9 /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = ""; }; F9B3B7B429B7397600232BB8 /* MapName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapName.swift; sourceTree = ""; }; @@ -252,7 +252,7 @@ A81FFC7D28F15D9C00B0FC7C /* Assets.xcassets */, A81FFC8028F15D9C00B0FC7C /* Preview Assets.xcassets */, A81FFC8228F15D9C00B0FC7C /* Persistence.swift */, - F9A178252A1C8B01003E8E4C /* PlayListFilter.swift */, + F9A178252A1C8B01003E8E4C /* PlaylistFilter.swift */, A81FFC8428F15D9C00B0FC7C /* Halmap.xcdatamodeld */, A81FFC7F28F15D9C00B0FC7C /* Preview Content */, F95CE98A2917A8EF00FFE213 /* Settings.bundle */, @@ -527,7 +527,7 @@ F97BE1242AC011DF00B37C76 /* Halmap+CollectedSong.swift in Sources */, F9B3B7B529B7397600232BB8 /* MapName.swift in Sources */, 206F9F6D2AD29611004DAD73 /* MainSongListViewModel.swift in Sources */, - F9A178262A1C8B01003E8E4C /* PlayListFilter.swift in Sources */, + F9A178262A1C8B01003E8E4C /* PlaylistFilter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Halmap/Data/AudioManager.swift b/Halmap/Data/AudioManager.swift index 1129ed9..0c66914 100644 --- a/Halmap/Data/AudioManager.swift +++ b/Halmap/Data/AudioManager.swift @@ -22,7 +22,19 @@ final class AudioManager: NSObject, ObservableObject { } @Published var progressValue: Float = 0 //player.seek 에서 사용 + { + didSet { + print("progressValue: ", progressValue) + } + } @Published var currentTime: Double = 0 //재생바 현재 시간 표시에서 사용 + { + didSet { + print("currentTime: ", currentTime) + } + } + @Published var currentIndex: Int = 0 //현재 재생중인 곡 index + var duration: Double { return player?.currentItem?.duration.seconds ?? 0 } @@ -38,115 +50,6 @@ final class AudioManager: NSObject, ObservableObject { var song: SongInfo? var selectedTeam = "" - // MARK: - Media Player Setting.. - - private func setupNowPlayingInfo(title: String, albumArt: UIImage?) { - var nowPlayingInfo = [String: Any]() - nowPlayingInfo[MPMediaItemPropertyTitle] = title - - if let albumArt = albumArt { - nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: albumArt.size) { _ in albumArt } - } - - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds - nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player?.currentItem?.duration.seconds - nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate - - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - } - - private func updateNowPlayingPlaybackRate() { - if var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo { - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds - nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - } - } - - private func setupRemoteTransportControls() { - let commandCenter = MPRemoteCommandCenter.shared() - - commandCenter.playCommand.addTarget { [unowned self] event in - if self.player?.rate == 0.0 { - player?.play() - isPlaying = true - - updateNowPlayingPlaybackRate() - - return .success - } - return .commandFailed - } - - commandCenter.pauseCommand.addTarget { [unowned self] event in - if self.player?.rate == 1.0 { - AMstop() - return .success - } - return .commandFailed - } - - commandCenter.changePlaybackPositionCommand.addTarget { [unowned self] event in - if let positionTime = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime { - let seekTime = CMTime(value: Int64(positionTime), timescale: 1) - self.currentTime = seekTime.seconds - self.progressValue = self.calculateProgress(currentTime: seekTime.seconds) - self.player?.seek(to: seekTime) - // AMseek(to: progressValue) - } - return .success - } - } - - //시작 상태 감지를 위한 observer -> 음원이 준비 된 경우 미디어 플레이어 셋팅 - override func observeValue(forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey : Any]?, - context: UnsafeMutableRawPointer?) { - - // Only handle observations for the playerItemContext - guard context == &playerItemContext else { - super.observeValue(forKeyPath: keyPath, - of: object, - change: change, - context: context) - return - } - - if keyPath == #keyPath(AVPlayerItem.status) { - let status: AVPlayerItem.Status - if let statusNumber = change?[.newKey] as? NSNumber { - status = AVPlayerItem.Status(rawValue: statusNumber.intValue)! - } else { - status = .unknown - } - - // Switch over status value - switch status { - case .readyToPlay: - // Player item is ready to play. - let title = song?.title ?? "unknown title" - let albumArt = UIImage(named: "\(selectedTeam)Album") - self.setupNowPlayingInfo(title: title, albumArt: albumArt) - - break - case .failed: - // Player item failed. See error. - print("failed") - if let playerItem = object as? AVPlayerItem { - print("Player item error: \(String(describing: playerItem.error?.localizedDescription))") - } - break - case .unknown: - // Player item is not yet ready. - print("unknown") - break - @unknown default: - print("default") - break - } - } - } // MARK: - AM Properties @@ -290,6 +193,7 @@ final class AudioManager: NSObject, ObservableObject { return Float(currentTime / duration) } + //재생바 이동 시 func didSliderChanged(_ didChange: Bool) { acceptProgressUpdates = !didChange if didChange { @@ -299,7 +203,7 @@ final class AudioManager: NSObject, ObservableObject { self.AMplay() } } - + //재생바 설정 private func setupPeriodicObservation(for player: AVPlayer) { let timeScale = CMTimeScale(NSEC_PER_SEC) let time = CMTime(seconds: 0.5, preferredTimescale: timeScale) @@ -325,3 +229,116 @@ final class AudioManager: NSObject, ObservableObject { context: &playerItemContext) } } + +// MARK: - Media Player Setting.. +extension AudioManager { + + private func setupNowPlayingInfo(title: String, albumArt: UIImage?) { + var nowPlayingInfo = [String: Any]() + nowPlayingInfo[MPMediaItemPropertyTitle] = title + + if let albumArt = albumArt { + nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: albumArt.size) { _ in albumArt } + } + + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds + nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player?.currentItem?.duration.seconds + nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate + + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + + private func updateNowPlayingPlaybackRate() { + if var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo { + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds + nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + } + + private func setupRemoteTransportControls() { + let commandCenter = MPRemoteCommandCenter.shared() + + commandCenter.playCommand.addTarget { [unowned self] event in + if self.player?.rate == 0.0 { + player?.play() + isPlaying = true + + updateNowPlayingPlaybackRate() + + return .success + } + return .commandFailed + } + + commandCenter.pauseCommand.addTarget { [unowned self] event in + if self.player?.rate == 1.0 { + AMstop() + return .success + } + return .commandFailed + } + + commandCenter.changePlaybackPositionCommand.addTarget { [unowned self] event in + if let positionTime = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime { + let seekTime = CMTime(value: Int64(positionTime), timescale: 1) + self.currentTime = seekTime.seconds + self.progressValue = self.calculateProgress(currentTime: seekTime.seconds) + self.player?.seek(to: seekTime) + // AMseek(to: progressValue) + } + return .success + } + } + + //시작 상태 감지를 위한 observer -> 음원이 준비 된 경우 미디어 플레이어 셋팅 + override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { + + // Only handle observations for the playerItemContext + guard context == &playerItemContext else { + super.observeValue(forKeyPath: keyPath, + of: object, + change: change, + context: context) + return + } + + if keyPath == #keyPath(AVPlayerItem.status) { + let status: AVPlayerItem.Status + if let statusNumber = change?[.newKey] as? NSNumber { + status = AVPlayerItem.Status(rawValue: statusNumber.intValue)! + } else { + status = .unknown + } + + // Switch over status value + switch status { + case .readyToPlay: + // Player item is ready to play. + let title = song?.title ?? "unknown title" + let albumArt = UIImage(named: "\(selectedTeam)Album") + self.setupNowPlayingInfo(title: title, albumArt: albumArt) + + break + case .failed: + // Player item failed. See error. + print("failed") + if let playerItem = object as? AVPlayerItem { + print("Player item error: \(String(describing: playerItem.error?.localizedDescription))") + } + break + case .unknown: + // Player item is not yet ready. + print("unknown") + break + @unknown default: + print("default") + break + } + } + } + +} diff --git a/Halmap/Data/DataManager.swift b/Halmap/Data/DataManager.swift index 39242fa..cbf3341 100644 --- a/Halmap/Data/DataManager.swift +++ b/Halmap/Data/DataManager.swift @@ -30,8 +30,8 @@ class DataManager: ObservableObject { @Published var playerList: [Player] = [] @Published var playerSongs: [Song] = [] @Published var teamSongs: [Song] = [] - @Published var favoriteSongs = PersistenceController.shared.fetchFavoriteSong() - @Published var playListSongs = PersistenceController.shared.fetchPlayListSong() +// @Published var favoriteSongs = PersistenceController.shared.fetchCollectedSong() +// @Published var playListSongs = PersistenceController.shared.fetchPlayListSong() @Published var playerSongsAll = [[Song]](repeating: [], count: 10) @Published var teamSongsAll = [[Song]](repeating: [], count: 10) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 5e1be99..469b98f 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -14,14 +14,14 @@ struct PlaylistView: View { @Binding var isScrolled: Bool let persistence = PersistenceController.shared - @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlayListFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults + @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults var body: some View { ZStack{ if !collectedSongs.isEmpty { List { ForEach(collectedSongs, id: \.self) { playListSong in - getPlayListRowView(song: playListSong) + getPlaylistRowView(song: playListSong) .background(Color.white.opacity(0.001)) .onTapGesture { self.song = viewModel.didTappedSongCell(song: playListSong) @@ -63,7 +63,7 @@ struct PlaylistView: View { } @ViewBuilder - private func getPlayListRowView(song: CollectedSong) -> some View { + private func getPlaylistRowView(song: CollectedSong) -> some View { HStack{ if currentSongId == song.id { Image(systemName: "waveform") diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 411f093..6720130 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -11,18 +11,19 @@ struct SongDetailView: View { @StateObject var viewModel: SongDetailViewModel @FetchRequest( entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], - predicate: PlayListFilter(filter: "defaultPlaylist").predicate, + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults - @State var isPlayListView = false + @State var isPlaylistView = false + @State var currentIndex = 0 var body: some View { ZStack { Color("\(viewModel.song.team)Sub") .ignoresSafeArea() - if isPlayListView { + if isPlaylistView { VStack { PlaylistView(viewModel: PlaylistViewModel(viewModel: viewModel), song: $viewModel.song, isScrolled: $viewModel.isScrolled) .padding(.top, 10) @@ -43,7 +44,7 @@ struct SongDetailView: View { playlistButton } - PlayBar(viewModel: viewModel) + PlayBar(viewModel: viewModel, currentIndex: $currentIndex) } .ignoresSafeArea() } @@ -57,11 +58,12 @@ struct SongDetailView: View { .onAppear() { viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) } + .onChange(of: self.currentIndex) { _ in + self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[currentIndex]) + self.viewModel.getAudioManager().AMset(song: self.viewModel.song) + } } - - private func playSong() { - - } + @ViewBuilder private func gradientRectangle(isTop: Bool) -> some View { Rectangle() @@ -72,15 +74,15 @@ struct SongDetailView: View { } var playlistButton: some View { - // PlayListButton + // PlaylistButton HStack(){ Spacer() Button(action: { - isPlayListView.toggle() + isPlaylistView.toggle() }, label: { ZStack { Circle().foregroundColor(Color("\(viewModel.song.team)Background")).frame(width: 43, height: 43) - Image(systemName: isPlayListView ? "quote.bubble.fill" : "list.bullet").foregroundColor(.white) + Image(systemName: isPlaylistView ? "quote.bubble.fill" : "list.bullet").foregroundColor(.white) } }) @@ -132,18 +134,53 @@ private struct Lyric: View { private struct PlayBar: View { @StateObject var viewModel: SongDetailViewModel - + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, + animation: .default) private var defaultPlaylistSongs: FetchedResults + @Binding var currentIndex: Int var body: some View { VStack(spacing: 0) { Progressbar(team: $viewModel.song.team, isThumbActive: true) HStack(spacing: 52) { Button { - viewModel.handlePlayButtonTap() + //이전곡 재생 기능 + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index - 1 < 0 { + currentIndex = defaultPlaylistSongs.count - 1 + } else { + currentIndex = index - 1 + } + print(currentIndex) + } + } label: { + Image(systemName: "backward.end.fill") + .font(.system(size: 28, weight: .regular)) + .foregroundColor(.customGray) + } + Button { + viewModel.handlePlayButtonTap() + } label: { + Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") + .font(.system(size: 60, weight: .medium)) + .foregroundStyle(Color.customGray) + } + Button { + //다음곡 재생 기능 + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index + 1 > defaultPlaylistSongs.count - 1 { + currentIndex = 0 + } else { + currentIndex = index + 1 + } + print(currentIndex) + } } label: { - Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") - .font(.system(size: 60, weight: .medium)) - .foregroundStyle(Color.customGray) + Image(systemName: "forward.end.fill") + .font(.system(size: 28, weight: .regular)) + .foregroundColor(.customGray) } } .padding(.bottom, 54) @@ -165,8 +202,8 @@ private struct FavoriteButton: View { @StateObject var viewModel: SongDetailViewModel @FetchRequest( entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], - predicate: PlayListFilter(filter: "favorite").predicate, + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], + predicate: PlaylistFilter(filter: "favorite").predicate, animation: .default) private var favoriteSongs: FetchedResults @AppStorage("currentSongId") var currentSongId: String = "" diff --git a/Halmap/Features/SongDetail/SongDetailViewModel.swift b/Halmap/Features/SongDetail/SongDetailViewModel.swift index ec7d7d8..dac6bd2 100644 --- a/Halmap/Features/SongDetail/SongDetailViewModel.swift +++ b/Halmap/Features/SongDetail/SongDetailViewModel.swift @@ -68,6 +68,17 @@ final class SongDetailViewModel: ObservableObject { persistence.saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(defaultPlaylistSongs.count)) } } + func convertSongToSongInfo(song: CollectedSong) -> SongInfo { + SongInfo( + id: song.id ?? "", + team: song.team ?? "", + type: song.type, + title: song.title ?? "", + lyrics: song.lyrics ?? "", + info: song.info ?? "", + url: song.url ?? "" + ) + } // MARK: - initailize playlist view model func getAudioManager() -> AudioManager { diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 312e7bf..6f2b456 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -155,9 +155,6 @@ struct MainSongListTabView: View { .background(Color.systemBackground) .sheet(isPresented: $viewModel.showingTeamChangingView) { TeamSelectionView(viewModel: TeamSelectionViewModel(dataManager: dataManager), isShowing: $viewModel.showingTeamChangingView) - .onDisappear{ - Color.setColor(selectedTeam) - } } .navigationBarHidden(true) } diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index 35292c2..eb43c3e 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -12,7 +12,7 @@ struct ScalingHeaderView: View { @EnvironmentObject var audioManager: AudioManager let persistence = PersistenceController.shared - @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], predicate: PlayListFilter(filter: "favorite").predicate, animation: .default) private var collectedSongs: FetchedResults + @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], predicate: PlaylistFilter(filter: "favorite").predicate, animation: .default) private var collectedSongs: FetchedResults @StateObject var viewModel: SongStorageViewModel diff --git a/Halmap/Features/TeamSelection/TeamSelectionView.swift b/Halmap/Features/TeamSelection/TeamSelectionView.swift index a19a8da..31f007f 100644 --- a/Halmap/Features/TeamSelection/TeamSelectionView.swift +++ b/Halmap/Features/TeamSelection/TeamSelectionView.swift @@ -61,5 +61,8 @@ struct TeamSelectionView: View { .disabled(!viewModel.isChangedSelectedTeam()) } .padding(.horizontal, 20) + .onDisappear{ + Color.setColor(selectedTeamName) + } } } diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index 3e575ab..72d4dca 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -9,8 +9,7 @@ import CoreData import SwiftUI struct PersistenceController { - @AppStorage("selectedTeam") var selectedTeam = "Hanwha" - @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], animation: .default) private var collectedSongs: FetchedResults + @AppStorage("selectedTeam") var selectedTeam = "" static let shared = PersistenceController() @@ -124,7 +123,7 @@ struct PersistenceController { } } - /// index를 이용하여 PlayListd에서 곡을 지우는 함수. + /// index를 이용하여 Playlist에서 곡을 지우는 함수. func deleteSong(at indexs: IndexSet, from results: FetchedResults) { for index in indexs { @@ -140,7 +139,7 @@ struct PersistenceController { } } - /// defaultPlayList 순서를 변경하는 함수 + /// defaultPlaylist 순서를 변경하는 함수 func moveDefaultPlaylistSong(from source: IndexSet, to destination: Int, based results: FetchedResults){ guard let itemToMove = source.first else { return } @@ -179,7 +178,7 @@ struct PersistenceController { } - func fetchFavoriteSong() -> [CollectedSong] { + func fetchCollectedSong() -> [CollectedSong] { let fetchRequest: NSFetchRequest = CollectedSong.fetchRequest() do{ @@ -189,7 +188,7 @@ struct PersistenceController { } } - func findFavoriteSong(song: Song, collectedSongs: FetchedResults) -> CollectedSong { + func fincCollectedSong(song: SongInfo, collectedSongs: FetchedResults) -> CollectedSong { if let index = collectedSongs.firstIndex(where: {song.id == $0.id}) { return collectedSongs[index] } else { @@ -197,21 +196,11 @@ struct PersistenceController { } } - func fetchPlayListSong() -> [CollectedSong] { - let fetchRequest: NSFetchRequest = CollectedSong.fetchRequest() - - do{ - return try container.viewContext.fetch(fetchRequest) - }catch { - return [] - } - } - - func findPlayListSong(song: Song, collectedSongs: FetchedResults) -> CollectedSong { + func findCollectedSongIndex(song: SongInfo, collectedSongs: FetchedResults) -> Int { if let index = collectedSongs.firstIndex(where: {song.id == $0.id}) { - return collectedSongs[index] + return index } else { - return CollectedSong() + return 0 } } @@ -234,7 +223,7 @@ struct PersistenceController { /// CollectedSong을 생성하기 위해 BufferList에 넣은 곡을 지우는 함수. func resetBufferList(song: CollectedSong){ - if song.playListTitle == "bufferPlayList" { + if song.playListTitle == "bufferPlaylist" { container.viewContext.delete(song) } diff --git a/Halmap/PlayListFilter.swift b/Halmap/PlaylistFilter.swift similarity index 81% rename from Halmap/PlayListFilter.swift rename to Halmap/PlaylistFilter.swift index fcebd96..d188edc 100644 --- a/Halmap/PlayListFilter.swift +++ b/Halmap/PlaylistFilter.swift @@ -1,5 +1,5 @@ // -// PlayListFilter.swift +// PlaylistFilter.swift // Halmap // // Created by 전지민 on 2023/05/23. @@ -7,7 +7,7 @@ import Foundation -struct PlayListFilter: Equatable { +struct PlaylistFilter: Equatable { var filter: String? var predicate: NSPredicate? { guard let filter = filter else { return nil } From 9b0f57ca6abb761c9b30dd2af0a1a744c6050569 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Fri, 24 Nov 2023 15:04:18 +0900 Subject: [PATCH 03/56] =?UTF-8?q?[Feat]=20playlist=20move=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Persistence.swift | 6 +- Halmap/View/Component/Progressbar.swift | 191 +++++++++++++++++++----- Halmap/View/Component/Utility.swift | 8 + 3 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 Halmap/View/Component/Utility.swift diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index 72d4dca..c0ccb91 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -150,7 +150,7 @@ struct PersistenceController { var startOrder = results[itemToMove].order while startIndex <= endIndex{ results[startIndex].order = startOrder - startOrder = startOrder + 1 + startOrder = startOrder - 1 startIndex = startIndex + 1 } results[itemToMove].order = startOrder @@ -159,11 +159,11 @@ struct PersistenceController { else if destination < itemToMove{ var startIndex = destination let endIndex = itemToMove - 1 - var startOrder = results[destination].order + 1 + var startOrder = results[destination].order - 1 let newOrder = results[destination].order while startIndex <= endIndex{ results[startIndex].order = startOrder - startOrder = startOrder + 1 + startOrder = startOrder - 1 startIndex = startIndex + 1 } results[itemToMove].order = newOrder diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index 1031e14..095db6e 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -1,18 +1,19 @@ // -// Progressbar.swift +// Progress.swift // Halmap // -// Created by JeonJimin on 2023/06/29. +// Created by JeonJimin on 11/24/23. // - import SwiftUI +import AVFoundation -struct Progressbar: View { - @EnvironmentObject var audioManager: AudioManager +struct ProgressBar: View { + let player: AVPlayer @Binding var team: String let isThumbActive: Bool - init(team: Binding, isThumbActive: Bool) { + init(player: AVPlayer, team: Binding, isThumbActive: Bool) { + self.player = player self.isThumbActive = isThumbActive self._team = team let thumbImage = makeThumbView(isThumbActive: isThumbActive) @@ -21,32 +22,26 @@ struct Progressbar: View { } var body: some View { - VStack(spacing: 0) { - Slider(value: $audioManager.progressValue) { editing in - audioManager.didSliderChanged(editing) - } - .tint(Color("\(team)Point")) - .padding(.horizontal, isThumbActive ? 5 : 0) - .onChange(of: team) { _ in - let thumbImage = makeThumbView(isThumbActive: isThumbActive) - UISlider.appearance().setThumbImage(thumbImage, for: .normal) - } -// .disabled(!isThumbActive) - if isThumbActive { - HStack { - Text(formatTime(audioManager.currentTime)) - Spacer() - Text(formatTime(audioManager.duration)) - } - .font(Font.Halmap.CustomCaptionMedium) - .foregroundColor(.customGray) - } + VStack { + AudioPlayerControlsView(player: player, + timeObserver: PlayerTimeObserver(player: player), + durationObserver: PlayerDurationObserver(player: player), + itemObserver: PlayerItemObserver(player: player), + isThumbActive: isThumbActive) + + } + .tint(Color("\(team)Point")) + .padding(.horizontal, isThumbActive ? 5 : 0) + .onChange(of: team) { _ in + let thumbImage = makeThumbView(isThumbActive: isThumbActive) + UISlider.appearance().setThumbImage(thumbImage, for: .normal) + } + .onDisappear { + self.player.replaceCurrentItem(with: nil) } - - } - func makeThumbView(isThumbActive: Bool) -> UIImage { + private func makeThumbView(isThumbActive: Bool) -> UIImage { lazy var thumbView: UIView = { let thumb = UIView() thumb.backgroundColor = UIColor(isThumbActive ? Color("\(team)Point") : Color.clear) @@ -62,15 +57,139 @@ struct Progressbar: View { return image } +} + +struct AudioPlayerControlsView: View { + private enum PlaybackState: Int { + case pause + case buffering + case playing + } + + let player: AVPlayer + let timeObserver: PlayerTimeObserver + let durationObserver: PlayerDurationObserver + let itemObserver: PlayerItemObserver + @State private var currentTime: TimeInterval = 0 + @State private var currentDuration: TimeInterval = 0 + @State private var state = PlaybackState.pause + + let isThumbActive: Bool + + var body: some View { + VStack { + Slider(value: $currentTime, + in: 0...currentDuration, + onEditingChanged: sliderEditingChanged) + .disabled(state != .playing) + .onReceive(timeObserver.publisher) { time in + self.currentTime = time + if time > 0 { + self.state = .playing + } + } + .onReceive(durationObserver.publisher) { duration in + // Update the local var + self.currentDuration = duration + } + .onReceive(itemObserver.publisher) { hasItem in + self.state = hasItem ? .buffering : .pause + self.currentTime = 0 + self.currentDuration = 0 + } + + if isThumbActive { + HStack { + Text(Utility.formatSecondsToHMS(currentTime)) + Spacer() + Text(Utility.formatSecondsToHMS(currentDuration)) + } + .font(Font.Halmap.CustomCaptionMedium) + .foregroundColor(.customGray) + } + } + } - func formatTime(_ time: TimeInterval) -> String { - if time.isNaN { - return "00:00" - } else { - let minutes = Int(time) == 0 ? 0 : Int(time) / 60 - let seconds = Int(time) == 0 ? 0 : Int(time) % 60 - return String(format: "%02d:%02d", minutes, seconds) + // MARK: Private functions + private func sliderEditingChanged(editingStarted: Bool) { + if editingStarted { + timeObserver.pause(true) } + else { + state = .buffering + let targetTime = CMTime(seconds: currentTime, + preferredTimescale: 600) + player.seek(to: targetTime) { _ in + self.timeObserver.pause(false) + self.state = .playing + } + } + } +} + +import Combine +class PlayerTimeObserver { + let publisher = PassthroughSubject() + private weak var player: AVPlayer? + private var timeObservation: Any? + private var paused = false + + init(player: AVPlayer) { + self.player = player + timeObservation = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.5, preferredTimescale: 600), queue: nil) { [weak self] time in + guard let self = self else { return } + guard !self.paused else { return } + self.publisher.send(time.seconds) + } + } + + deinit { + if let player = player, + let observer = timeObservation { + player.removeTimeObserver(observer) + } + } + + func pause(_ pause: Bool) { + paused = pause + } +} + +class PlayerItemObserver { + let publisher = PassthroughSubject() + private var itemObservation: NSKeyValueObservation? + + init(player: AVPlayer) { + // Observe the current item changing + itemObservation = player.observe(\.currentItem) { [weak self] player, change in + guard let self = self else { return } + // Publish whether the player has an item or not + self.publisher.send(player.currentItem != nil) + } + } + + deinit { + if let observer = itemObservation { + observer.invalidate() + } + } +} + +class PlayerDurationObserver { + let publisher = PassthroughSubject() + private var cancellable: AnyCancellable? + + init(player: AVPlayer) { + let durationKeyPath: KeyPath = \.currentItem?.duration + cancellable = player.publisher(for: durationKeyPath).sink { duration in + guard let duration = duration else { return } + guard duration.isNumeric else { return } + self.publisher.send(duration.seconds) + } + } + + deinit { + cancellable?.cancel() } } diff --git a/Halmap/View/Component/Utility.swift b/Halmap/View/Component/Utility.swift new file mode 100644 index 0000000..633ef35 --- /dev/null +++ b/Halmap/View/Component/Utility.swift @@ -0,0 +1,8 @@ +// +// Utility.swift +// Halmap +// +// Created by JeonJimin on 11/24/23. +// + +import Foundation From 5a1c21cd91108679653bd01120f39a303535e5c1 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Fri, 24 Nov 2023 15:07:57 +0900 Subject: [PATCH 04/56] =?UTF-8?q?[Feat]=20progressbar=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EB=B0=8F=20=EB=8B=A4=EC=9D=8C=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20-?= =?UTF-8?q?=20player=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20.replaceCurrentIte?= =?UTF-8?q?m()=20=EC=9D=B4=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20player=20=EC=98=B5=EC=85=94=EB=84=90=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20-=20audioManager=EC=97=90=EC=84=9C=20progr?= =?UTF-8?q?essbar=20=EA=B4=80=EB=A0=A8=20=EB=B3=80=EC=88=98,=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 12 +- Halmap/Data/AudioManager.swift | 135 ++++-------------- .../Features/SongDetail/SongDetailView.swift | 9 +- Halmap/View/Component/Progressbar.swift | 30 +++- Halmap/View/Component/Utility.swift | 21 +++ 5 files changed, 90 insertions(+), 117 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 4d39147..569dc1f 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -50,12 +50,13 @@ F9174B2A2A2B2F5F00B1CE87 /* SeasonSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9174B292A2B2F5F00B1CE87 /* SeasonSong.swift */; }; F91BE5FB29B18AF800F7E488 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F91BE5FA29B18AF800F7E488 /* MainTabView.swift */; }; F91BE5FD29B18D1E00F7E488 /* MainSongListTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F91BE5FC29B18D1E00F7E488 /* MainSongListTabView.swift */; }; + F9305CA22B102E0600A0F7C9 /* Progressbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9305CA12B102E0600A0F7C9 /* Progressbar.swift */; }; + F9305CA42B1038BE00A0F7C9 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9305CA32B1038BE00A0F7C9 /* Utility.swift */; }; F939EBBA29D58FB2005ED8CA /* StorageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939EBB929D58FB2005ED8CA /* StorageContentView.swift */; }; F939EBBC29D5920F005ED8CA /* OffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939EBBB29D5920F005ED8CA /* OffsetModifier.swift */; }; F941FA7F2A7F78500034F72D /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */; }; F949284F28F2AFB800901F27 /* Music.json in Resources */ = {isa = PBXBuildFile; fileRef = F949284E28F2AFB800901F27 /* Music.json */; }; F95CE98B2917A8EF00FFE213 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F95CE98A2917A8EF00FFE213 /* Settings.bundle */; }; - F95F26C32A4C9BFD00B534EB /* Progressbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95F26C22A4C9BFD00B534EB /* Progressbar.swift */; }; F97BE1222AC011B100B37C76 /* SongStorageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */; }; F97BE1242AC011DF00B37C76 /* Halmap+CollectedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */; }; F98F622929B4C9BD0025F50E /* Themes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98F622829B4C9BD0025F50E /* Themes.swift */; }; @@ -112,12 +113,13 @@ F9174B292A2B2F5F00B1CE87 /* SeasonSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonSong.swift; sourceTree = ""; }; F91BE5FA29B18AF800F7E488 /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; F91BE5FC29B18D1E00F7E488 /* MainSongListTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSongListTabView.swift; sourceTree = ""; }; + F9305CA12B102E0600A0F7C9 /* Progressbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Progressbar.swift; sourceTree = ""; }; + F9305CA32B1038BE00A0F7C9 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; F939EBB929D58FB2005ED8CA /* StorageContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageContentView.swift; sourceTree = ""; }; F939EBBB29D5920F005ED8CA /* OffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetModifier.swift; sourceTree = ""; }; F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; F949284E28F2AFB800901F27 /* Music.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Music.json; sourceTree = ""; }; F95CE98A2917A8EF00FFE213 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; - F95F26C22A4C9BFD00B534EB /* Progressbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Progressbar.swift; sourceTree = ""; }; F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongStorageViewModel.swift; sourceTree = ""; }; F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Halmap+CollectedSong.swift"; sourceTree = ""; }; F98F622829B4C9BD0025F50E /* Themes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themes.swift; sourceTree = ""; }; @@ -334,7 +336,8 @@ F95F26C12A4C9BF000B534EB /* Component */ = { isa = PBXGroup; children = ( - F95F26C22A4C9BFD00B534EB /* Progressbar.swift */, + F9305CA12B102E0600A0F7C9 /* Progressbar.swift */, + F9305CA32B1038BE00A0F7C9 /* Utility.swift */, ); path = Component; sourceTree = ""; @@ -495,6 +498,7 @@ 8774038C2ABD2B7700C94D35 /* SongSearchViewModel.swift in Sources */, F98F8D5D29D85B0C00F9C956 /* RequestSongView.swift in Sources */, A81FFC7C28F15D9900B0FC7C /* ContentView.swift in Sources */, + F9305CA42B1038BE00A0F7C9 /* Utility.swift in Sources */, A81FFC8628F15D9C00B0FC7C /* Halmap.xcdatamodeld in Sources */, F90190812B038BDF00C9BFF6 /* PlaylistViewModel.swift in Sources */, 87F6ECF72916BB44004533C4 /* Halmap+UIView.swift in Sources */, @@ -508,9 +512,9 @@ F9DB6D1F2A76744A00B9469D /* LaunchScreenView.swift in Sources */, F9174B2A2A2B2F5F00B1CE87 /* SeasonSong.swift in Sources */, EEBC26A028F1CAE900BD5B3D /* TabBarItemView.swift in Sources */, - F95F26C32A4C9BFD00B534EB /* Progressbar.swift in Sources */, A81FFCA728F1B84A00B0FC7C /* Halmap+Color.swift in Sources */, F98F622929B4C9BD0025F50E /* Themes.swift in Sources */, + F9305CA22B102E0600A0F7C9 /* Progressbar.swift in Sources */, A81FFC7A28F15D9900B0FC7C /* HalmapApp.swift in Sources */, F91BE5FD29B18D1E00F7E488 /* MainSongListTabView.swift in Sources */, F97BE1222AC011B100B37C76 /* SongStorageViewModel.swift in Sources */, diff --git a/Halmap/Data/AudioManager.swift b/Halmap/Data/AudioManager.swift index 0c66914..961dd75 100644 --- a/Halmap/Data/AudioManager.swift +++ b/Halmap/Data/AudioManager.swift @@ -12,31 +12,16 @@ import MediaPlayer final class AudioManager: NSObject, ObservableObject { static let instance = AudioManager() - var player: AVPlayer? + var player: AVPlayer = AVPlayer() var item: AVPlayerItem? - @Published private(set) var isPlaying: Bool = false { - didSet{ - print("isPlaying", isPlaying) - } - } - - @Published var progressValue: Float = 0 //player.seek 에서 사용 - { - didSet { - print("progressValue: ", progressValue) - } - } - @Published var currentTime: Double = 0 //재생바 현재 시간 표시에서 사용 - { - didSet { - print("currentTime: ", currentTime) - } - } + @Published private(set) var isPlaying: Bool = false +// @Published var progressValue: Float = 0 //player.seek 에서 사용 +// @Published var currentTime: Double = 0 //재생바 현재 시간 표시에서 사용 @Published var currentIndex: Int = 0 //현재 재생중인 곡 index var duration: Double { - return player?.currentItem?.duration.seconds ?? 0 + return player.currentItem?.duration.seconds ?? 0 } //progressbar @@ -54,13 +39,11 @@ final class AudioManager: NSObject, ObservableObject { // MARK: - AM Properties private var AMduration: Double { - return player?.currentItem?.duration.seconds ?? 0 + return player.currentItem?.duration.seconds ?? 0 } func AMset(song: SongInfo) { self.song = song - self.progressValue = 0 - self.currentTime = 0 let changedTitle = "\(self.selectedTeam) \(song.title)" // 로컬에 해당 노래가 이미 저장되어 있는지 확인 @@ -117,22 +100,18 @@ final class AudioManager: NSObject, ObservableObject { // 노래 재생 private func setupPlayer() { - self.item?.addObserver(self, - forKeyPath: #keyPath(AVPlayerItem.status), - options: [.old, .new], - context: &playerItemContext) - - player = AVPlayer(playerItem: item) +// self.item?.addObserver(self, +// forKeyPath: #keyPath(AVPlayerItem.status), +// options: [.old, .new], +// context: &playerItemContext) + player.replaceCurrentItem(with: item) do { try AVAudioSession.sharedInstance().setCategory(.playback) } catch(let error) { print(error.localizedDescription) } - if let player = player { - setupPeriodicObservation(for: player) - AMplay() - } + AMplay() } // MARK: - AM Functions @@ -142,7 +121,7 @@ final class AudioManager: NSObject, ObservableObject { setupRemoteTransportControls() - player?.play() + player.play() isPlaying = true updateNowPlayingPlaybackRate() @@ -151,79 +130,22 @@ final class AudioManager: NSObject, ObservableObject { func AMstop() { updateNowPlayingPlaybackRate() - - guard let player = player else { - print("Instance of audio player not found") - return - } player.pause() isPlaying = false } - @objc func AMplayEnd() { - AMstop() - player?.seek(to: .zero) - NotificationCenter.default.removeObserver(self) - } - - func AMseek(to time: CMTime){ - guard let player = player else { return } - player.seek(to: time) - } - - func AMseek(to percentage: Float) { - guard let player = player else { return } - let time = AMconvertFloatToCMTime(percentage) - player.seek(to: time) - } func removePlayer() { AMstop() } - //MARK: - progressbar - private func AMconvertFloatToCMTime(_ percentage: Float) -> CMTime { - return CMTime(seconds: AMduration * Double(percentage), preferredTimescale: CMTimeScale(NSEC_PER_SEC)) - } - - private func AMcalculateProgress(currentTime: Double) -> Float { - return Float(currentTime / AMduration) - } - - private func calculateProgress(currentTime: Double) -> Float { - return Float(currentTime / duration) - } - - //재생바 이동 시 - func didSliderChanged(_ didChange: Bool) { - acceptProgressUpdates = !didChange - if didChange { - self.AMstop() - } else { - self.AMseek(to: progressValue) - self.AMplay() - } - } - //재생바 설정 - private func setupPeriodicObservation(for player: AVPlayer) { - let timeScale = CMTimeScale(NSEC_PER_SEC) - let time = CMTime(seconds: 0.5, preferredTimescale: timeScale) - - playerPeriodicObserver = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] (time) in - guard let `self` = self else { return } - self.currentTime = time.seconds - let progress = self.calculateProgress(currentTime: time.seconds) - self.progressValue = progress - - self.currentProgressPublisher.send(progress) - self.currentTimePublisher.send(time.seconds) - - updateNowPlayingPlaybackRate() - } + @objc func AMplayEnd() { + AMstop() + player.replaceCurrentItem(with: nil) + NotificationCenter.default.removeObserver(self) } deinit { - player = nil - + player.replaceCurrentItem(with: nil) self.item?.removeObserver(self as NSObject, forKeyPath: #keyPath(AVPlayerItem.status), context: &playerItemContext) @@ -241,17 +163,17 @@ extension AudioManager { nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: albumArt.size) { _ in albumArt } } - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds - nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player?.currentItem?.duration.seconds - nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentItem?.currentTime().seconds + nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player.currentItem?.duration.seconds + nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } private func updateNowPlayingPlaybackRate() { if var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo { - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds - nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentItem?.currentTime().seconds + nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } } @@ -260,8 +182,8 @@ extension AudioManager { let commandCenter = MPRemoteCommandCenter.shared() commandCenter.playCommand.addTarget { [unowned self] event in - if self.player?.rate == 0.0 { - player?.play() + if self.player.rate == 0.0 { + player.play() isPlaying = true updateNowPlayingPlaybackRate() @@ -272,7 +194,7 @@ extension AudioManager { } commandCenter.pauseCommand.addTarget { [unowned self] event in - if self.player?.rate == 1.0 { + if self.player.rate == 1.0 { AMstop() return .success } @@ -282,10 +204,7 @@ extension AudioManager { commandCenter.changePlaybackPositionCommand.addTarget { [unowned self] event in if let positionTime = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime { let seekTime = CMTime(value: Int64(positionTime), timescale: 1) - self.currentTime = seekTime.seconds - self.progressValue = self.calculateProgress(currentTime: seekTime.seconds) - self.player?.seek(to: seekTime) - // AMseek(to: progressValue) + self.player.seek(to: seekTime) } return .success } diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 6720130..db4e67d 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -5,6 +5,7 @@ // Created by JeonJimin on 10/8/23. // import SwiftUI +import AVFoundation struct SongDetailView: View { @@ -142,8 +143,11 @@ private struct PlayBar: View { @Binding var currentIndex: Int var body: some View { VStack(spacing: 0) { - Progressbar(team: $viewModel.song.team, isThumbActive: true) - + Progressbar( + player: viewModel.getAudioManager().player, + currentIndex: $currentIndex , + team: $viewModel.song.team, + isThumbActive: true) HStack(spacing: 52) { Button { //이전곡 재생 기능 @@ -171,6 +175,7 @@ private struct PlayBar: View { //다음곡 재생 기능 if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { + print("재생목록이 처음으로 돌아갑니다.") currentIndex = 0 } else { currentIndex = index + 1 diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index 095db6e..f0c00fb 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -7,14 +7,16 @@ import SwiftUI import AVFoundation -struct ProgressBar: View { +struct Progressbar: View { let player: AVPlayer @Binding var team: String + @Binding var currentIndex: Int let isThumbActive: Bool - init(player: AVPlayer, team: Binding, isThumbActive: Bool) { + init(player: AVPlayer, currentIndex: Binding, team: Binding, isThumbActive: Bool) { self.player = player self.isThumbActive = isThumbActive + self._currentIndex = currentIndex self._team = team let thumbImage = makeThumbView(isThumbActive: isThumbActive) UISlider.appearance().setThumbImage(thumbImage, for: .normal) @@ -26,13 +28,15 @@ struct ProgressBar: View { AudioPlayerControlsView(player: player, timeObserver: PlayerTimeObserver(player: player), durationObserver: PlayerDurationObserver(player: player), - itemObserver: PlayerItemObserver(player: player), + itemObserver: PlayerItemObserver(player: player), + currentIndex: $currentIndex, isThumbActive: isThumbActive) } .tint(Color("\(team)Point")) .padding(.horizontal, isThumbActive ? 5 : 0) .onChange(of: team) { _ in + print("Team: \(team)") let thumbImage = makeThumbView(isThumbActive: isThumbActive) UISlider.appearance().setThumbImage(thumbImage, for: .normal) } @@ -74,6 +78,14 @@ struct AudioPlayerControlsView: View { @State private var currentDuration: TimeInterval = 0 @State private var state = PlaybackState.pause + @Binding var currentIndex: Int + + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, + animation: .default) private var defaultPlaylistSongs: FetchedResults + let isThumbActive: Bool var body: some View { @@ -96,6 +108,18 @@ struct AudioPlayerControlsView: View { self.state = hasItem ? .buffering : .pause self.currentTime = 0 self.currentDuration = 0 + + if self.state == .pause { + if currentIndex + 1 < defaultPlaylistSongs.count { + currentIndex += 1 + } else { + print("재생목록이 처음으로 돌아갑니다.") + currentIndex = 0 + } + } + } + .onChange(of: self.state) { _ in + print("State: \(state)") } if isThumbActive { diff --git a/Halmap/View/Component/Utility.swift b/Halmap/View/Component/Utility.swift index 633ef35..93c7919 100644 --- a/Halmap/View/Component/Utility.swift +++ b/Halmap/View/Component/Utility.swift @@ -6,3 +6,24 @@ // import Foundation + +class Utility: NSObject { + + private static var timeHMSFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .positional + formatter.allowedUnits = [.minute, .second] + formatter.zeroFormattingBehavior = [.pad] + return formatter + }() + + static func formatSecondsToHMS(_ seconds: Double) -> String { + guard !seconds.isNaN, + let text = timeHMSFormatter.string(from: seconds) else { + return "00:00" + } + + return text + } + +} From 9856bfce32739e8310806967f9bdfaec53659410 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Fri, 24 Nov 2023 15:17:44 +0900 Subject: [PATCH 05/56] =?UTF-8?q?[Feat]=20=EB=AF=B8=EB=94=94=EC=96=B4=20?= =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=88=98=EC=A0=95=20-?= =?UTF-8?q?=20selectedTeam=20->=20song.team=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20=EB=AF=B8=EB=94=94=EC=96=B4=20=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=84=A4=EC=A0=95=20=EB=B3=B5?= =?UTF-8?q?=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Data/AudioManager.swift | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Halmap/Data/AudioManager.swift b/Halmap/Data/AudioManager.swift index 961dd75..0879f0b 100644 --- a/Halmap/Data/AudioManager.swift +++ b/Halmap/Data/AudioManager.swift @@ -16,8 +16,6 @@ final class AudioManager: NSObject, ObservableObject { var item: AVPlayerItem? @Published private(set) var isPlaying: Bool = false -// @Published var progressValue: Float = 0 //player.seek 에서 사용 -// @Published var currentTime: Double = 0 //재생바 현재 시간 표시에서 사용 @Published var currentIndex: Int = 0 //현재 재생중인 곡 index var duration: Double { @@ -33,8 +31,6 @@ final class AudioManager: NSObject, ObservableObject { private var playerItemContext = 0 var song: SongInfo? - var selectedTeam = "" - // MARK: - AM Properties @@ -44,7 +40,7 @@ final class AudioManager: NSObject, ObservableObject { func AMset(song: SongInfo) { self.song = song - let changedTitle = "\(self.selectedTeam) \(song.title)" + let changedTitle = "\(song.team) \(song.title)" // 로컬에 해당 노래가 이미 저장되어 있는지 확인 @@ -100,10 +96,11 @@ final class AudioManager: NSObject, ObservableObject { // 노래 재생 private func setupPlayer() { -// self.item?.addObserver(self, -// forKeyPath: #keyPath(AVPlayerItem.status), -// options: [.old, .new], -// context: &playerItemContext) + //미디어 플레이어 설정 + self.item?.addObserver(self, + forKeyPath: #keyPath(AVPlayerItem.status), + options: [.old, .new], + context: &playerItemContext) player.replaceCurrentItem(with: item) do { @@ -238,7 +235,7 @@ extension AudioManager { case .readyToPlay: // Player item is ready to play. let title = song?.title ?? "unknown title" - let albumArt = UIImage(named: "\(selectedTeam)Album") + let albumArt = UIImage(named: "\(song?.team ?? "")Album") self.setupNowPlayingInfo(title: title, albumArt: albumArt) break From e5b2e693c9d97fda7ff95611d2047afd2ce43932 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 27 Nov 2023 18:55:23 +0900 Subject: [PATCH 06/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=20=EB=B3=80=EA=B2=BD=20-=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=EA=B3=A1=EC=9D=B4=20=EB=A7=A8=20=EB=A7=88=EC=A7=80=EB=A7=89?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20-=20move=20=ED=95=A8=EC=88=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/PlaylistView.swift | 2 +- Halmap/Features/SongDetail/SongDetailView.swift | 4 ++-- Halmap/Persistence.swift | 6 +++--- Halmap/View/Component/Progressbar.swift | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 469b98f..d686bd4 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -14,7 +14,7 @@ struct PlaylistView: View { @Binding var isScrolled: Bool let persistence = PersistenceController.shared - @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults + @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults var body: some View { ZStack{ diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index db4e67d..0c04a6e 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -12,7 +12,7 @@ struct SongDetailView: View { @StateObject var viewModel: SongDetailViewModel @FetchRequest( entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults @@ -137,7 +137,7 @@ private struct PlayBar: View { @StateObject var viewModel: SongDetailViewModel @FetchRequest( entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults @Binding var currentIndex: Int diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index c0ccb91..72d4dca 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -150,7 +150,7 @@ struct PersistenceController { var startOrder = results[itemToMove].order while startIndex <= endIndex{ results[startIndex].order = startOrder - startOrder = startOrder - 1 + startOrder = startOrder + 1 startIndex = startIndex + 1 } results[itemToMove].order = startOrder @@ -159,11 +159,11 @@ struct PersistenceController { else if destination < itemToMove{ var startIndex = destination let endIndex = itemToMove - 1 - var startOrder = results[destination].order - 1 + var startOrder = results[destination].order + 1 let newOrder = results[destination].order while startIndex <= endIndex{ results[startIndex].order = startOrder - startOrder = startOrder - 1 + startOrder = startOrder + 1 startIndex = startIndex + 1 } results[itemToMove].order = newOrder diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index f0c00fb..d881cbf 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -82,7 +82,7 @@ struct AudioPlayerControlsView: View { @FetchRequest( entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: false)], + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults From 3dfaaf0038642c634e513034f855b1fa55130bd0 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 27 Nov 2023 19:17:49 +0900 Subject: [PATCH 07/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20=EA=B3=A1=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20order=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Persistence.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index 72d4dca..3f2c3c2 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -129,6 +129,10 @@ struct PersistenceController { for index in indexs { let song = results[index] container.viewContext.delete(song) + + for i in index.. Date: Mon, 27 Nov 2023 21:08:37 +0900 Subject: [PATCH 08/56] =?UTF-8?q?[Feat]=20=EC=9E=AC=EC=83=9D=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=97=90=20=EC=9D=B4=EB=AF=B8=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B3=A1=20=EB=88=84=EB=A5=B8=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 맨 마지막곡 재생 중 위로 한칸 올릴 경우 index 변화 감지 안되는 오류 존재 --- Halmap/Features/SongDetail/PlaylistView.swift | 2 ++ .../Features/SongDetail/SongDetailView.swift | 24 +++++++-------- .../SongDetail/SongDetailViewModel.swift | 13 +++++---- Halmap/Persistence.swift | 13 +++++++++ Halmap/View/Component/Progressbar.swift | 29 +++++++++---------- 5 files changed, 47 insertions(+), 34 deletions(-) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index d686bd4..f445b5a 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -12,6 +12,7 @@ struct PlaylistView: View { @StateObject var viewModel: PlaylistViewModel @Binding var song: SongInfo @Binding var isScrolled: Bool + @Binding var currentIndex: Int let persistence = PersistenceController.shared @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults @@ -25,6 +26,7 @@ struct PlaylistView: View { .background(Color.white.opacity(0.001)) .onTapGesture { self.song = viewModel.didTappedSongCell(song: playListSong) + self.currentIndex = Int(playListSong.order) } }.onDelete { indexSet in persistence.deleteSong(at: indexSet, from: collectedSongs) diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 0c04a6e..00ec46f 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -17,7 +17,6 @@ struct SongDetailView: View { animation: .default) private var defaultPlaylistSongs: FetchedResults @State var isPlaylistView = false - @State var currentIndex = 0 var body: some View { ZStack { @@ -26,7 +25,7 @@ struct SongDetailView: View { if isPlaylistView { VStack { - PlaylistView(viewModel: PlaylistViewModel(viewModel: viewModel), song: $viewModel.song, isScrolled: $viewModel.isScrolled) + PlaylistView(viewModel: PlaylistViewModel(viewModel: viewModel), song: $viewModel.song, isScrolled: $viewModel.isScrolled, currentIndex: $viewModel.currentIndex) .padding(.top, 10) .padding(.bottom, 150) } @@ -45,7 +44,7 @@ struct SongDetailView: View { playlistButton } - PlayBar(viewModel: viewModel, currentIndex: $currentIndex) + PlayBar(viewModel: viewModel) } .ignoresSafeArea() } @@ -59,8 +58,8 @@ struct SongDetailView: View { .onAppear() { viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) } - .onChange(of: self.currentIndex) { _ in - self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[currentIndex]) + .onChange(of: self.viewModel.currentIndex) { _ in + self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[self.viewModel.currentIndex]) self.viewModel.getAudioManager().AMset(song: self.viewModel.song) } } @@ -140,24 +139,22 @@ private struct PlayBar: View { sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults - @Binding var currentIndex: Int var body: some View { VStack(spacing: 0) { Progressbar( player: viewModel.getAudioManager().player, - currentIndex: $currentIndex , - team: $viewModel.song.team, + currentIndex: $viewModel.currentIndex, + song: $viewModel.song, isThumbActive: true) HStack(spacing: 52) { Button { //이전곡 재생 기능 if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index - 1 < 0 { - currentIndex = defaultPlaylistSongs.count - 1 + viewModel.currentIndex = defaultPlaylistSongs.count - 1 } else { - currentIndex = index - 1 + viewModel.currentIndex = index - 1 } - print(currentIndex) } } label: { Image(systemName: "backward.end.fill") @@ -176,11 +173,10 @@ private struct PlayBar: View { if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { print("재생목록이 처음으로 돌아갑니다.") - currentIndex = 0 + viewModel.currentIndex = 0 } else { - currentIndex = index + 1 + viewModel.currentIndex = index + 1 } - print(currentIndex) } } label: { Image(systemName: "forward.end.fill") diff --git a/Halmap/Features/SongDetail/SongDetailViewModel.swift b/Halmap/Features/SongDetail/SongDetailViewModel.swift index dac6bd2..671699c 100644 --- a/Halmap/Features/SongDetail/SongDetailViewModel.swift +++ b/Halmap/Features/SongDetail/SongDetailViewModel.swift @@ -16,6 +16,8 @@ final class SongDetailViewModel: ObservableObject { @Published var isScrolled = false @Published var isFavorite = false @Published var isPlaying = false + + @Published var currentIndex = 0 private var cancellables = Set() @@ -59,15 +61,16 @@ final class SongDetailViewModel: ObservableObject { func getAudioIsPlaying() -> Bool { audioManager.isPlaying } - + func addDefaultPlaylist(defaultPlaylistSongs: FetchedResults) { - if !defaultPlaylistSongs.contains(where: {$0.id == self.song.id}) { - //추후 하프모달 사용 시 다시 이용 -// let collectedSong = persistence.createCollectedSong(song: song, playListTitle: "bufferPlaylist") -// persistence.resetBufferList(song: collectedSong) + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == self.song.id}) { + persistence.reorderSelectedSong(index: index, results: defaultPlaylistSongs) + self.currentIndex = defaultPlaylistSongs.count - 1 + } else { persistence.saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(defaultPlaylistSongs.count)) } } + func convertSongToSongInfo(song: CollectedSong) -> SongInfo { SongInfo( id: song.id ?? "", diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index 3f2c3c2..d35689b 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -238,4 +238,17 @@ struct PersistenceController { print(error.localizedDescription) } } + + func reorderSelectedSong(index: Int, results: FetchedResults) { + results[index].order = Int64(results.count-1) + for i in index+1.., team: Binding, isThumbActive: Bool) { + init(player: AVPlayer, currentIndex: Binding, song: Binding, isThumbActive: Bool) { self.player = player self.isThumbActive = isThumbActive self._currentIndex = currentIndex - self._team = team + self._song = song let thumbImage = makeThumbView(isThumbActive: isThumbActive) UISlider.appearance().setThumbImage(thumbImage, for: .normal) UISlider.appearance().maximumTrackTintColor = UIColor(Color.customGray.opacity(0.2)) @@ -30,16 +31,12 @@ struct Progressbar: View { durationObserver: PlayerDurationObserver(player: player), itemObserver: PlayerItemObserver(player: player), currentIndex: $currentIndex, + song: $song, isThumbActive: isThumbActive) } - .tint(Color("\(team)Point")) + .tint(Color("\(song.team)Point")) .padding(.horizontal, isThumbActive ? 5 : 0) - .onChange(of: team) { _ in - print("Team: \(team)") - let thumbImage = makeThumbView(isThumbActive: isThumbActive) - UISlider.appearance().setThumbImage(thumbImage, for: .normal) - } .onDisappear { self.player.replaceCurrentItem(with: nil) } @@ -48,7 +45,7 @@ struct Progressbar: View { private func makeThumbView(isThumbActive: Bool) -> UIImage { lazy var thumbView: UIView = { let thumb = UIView() - thumb.backgroundColor = UIColor(isThumbActive ? Color("\(team)Point") : Color.clear) + thumb.backgroundColor = UIColor(isThumbActive ? Color("\(song.team)Point") : Color.clear) return thumb }() let radius:CGFloat = 10 @@ -79,6 +76,7 @@ struct AudioPlayerControlsView: View { @State private var state = PlaybackState.pause @Binding var currentIndex: Int + @Binding var song: SongInfo @FetchRequest( entity: CollectedSong.entity(), @@ -110,11 +108,12 @@ struct AudioPlayerControlsView: View { self.currentDuration = 0 if self.state == .pause { - if currentIndex + 1 < defaultPlaylistSongs.count { - currentIndex += 1 - } else { - print("재생목록이 처음으로 돌아갑니다.") - currentIndex = 0 + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == song.id}) { + if index + 1 < defaultPlaylistSongs.count { + currentIndex = Int(defaultPlaylistSongs[index].order + 1) + } else { + currentIndex = 0 + } } } } From 17d7fc224ac4b0e4c6b9dbd630a60843dc792034 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Tue, 28 Nov 2023 20:10:51 +0900 Subject: [PATCH 09/56] =?UTF-8?q?[Feat]=20=ED=98=84=EC=9E=AC=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=EC=A4=91=EC=9D=B8=20=EA=B3=A1=20=EC=A7=80=EC=9A=B0?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추후 메인화면으로 나가는 동작 추가 필요 --- Halmap/Features/SongDetail/PlaylistView.swift | 17 +++++++++++++++++ .../Features/SongDetail/PlaylistViewModel.swift | 4 ++++ .../SongDetail/SongDetailViewModel.swift | 1 + 3 files changed, 22 insertions(+) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index f445b5a..9ef01d7 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -29,6 +29,23 @@ struct PlaylistView: View { self.currentIndex = Int(playListSong.order) } }.onDelete { indexSet in + for index in indexSet { + if collectedSongs.count - 1 == 0 { + // TODO: 메인화면으로 나가는 동작 + print("메인화면으로") + viewModel.stopPlayer() + } else { + if let songIndex = collectedSongs.firstIndex(where: {$0.id == song.id}) { + if collectedSongs[songIndex].order == index { + if index + 1 < collectedSongs.count { + currentIndex = Int(collectedSongs[songIndex].order) + } else { + currentIndex = 0 + } + } + } + } + } persistence.deleteSong(at: indexSet, from: collectedSongs) }.onMove { indexSet, destination in persistence.moveDefaultPlaylistSong(from: indexSet, diff --git a/Halmap/Features/SongDetail/PlaylistViewModel.swift b/Halmap/Features/SongDetail/PlaylistViewModel.swift index 6f0f07d..d9c2c13 100644 --- a/Halmap/Features/SongDetail/PlaylistViewModel.swift +++ b/Halmap/Features/SongDetail/PlaylistViewModel.swift @@ -65,4 +65,8 @@ final class PlaylistViewModel: ObservableObject { url: song.url ?? "" ) } + + func stopPlayer() { + audioManager.AMplayEnd() + } } diff --git a/Halmap/Features/SongDetail/SongDetailViewModel.swift b/Halmap/Features/SongDetail/SongDetailViewModel.swift index 671699c..cd0ea7c 100644 --- a/Halmap/Features/SongDetail/SongDetailViewModel.swift +++ b/Halmap/Features/SongDetail/SongDetailViewModel.swift @@ -68,6 +68,7 @@ final class SongDetailViewModel: ObservableObject { self.currentIndex = defaultPlaylistSongs.count - 1 } else { persistence.saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(defaultPlaylistSongs.count)) + self.currentIndex = defaultPlaylistSongs.count } } From 5cc0360adbbb1fa090b67f0b96c3b6a8cdbdbc89 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 29 Nov 2023 16:13:12 +0900 Subject: [PATCH 10/56] =?UTF-8?q?[Feat]=20toast=20message=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추후 애니메이션 연결 필요 --- Halmap.xcodeproj/project.pbxproj | 4 + .../Features/SongDetail/SongDetailView.swift | 12 ++- Halmap/View/Component/Progressbar.swift | 9 +- Halmap/View/Component/ToastView.swift | 99 +++++++++++++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 Halmap/View/Component/ToastView.swift diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 569dc1f..3180aee 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ F939EBBC29D5920F005ED8CA /* OffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939EBBB29D5920F005ED8CA /* OffsetModifier.swift */; }; F941FA7F2A7F78500034F72D /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */; }; F949284F28F2AFB800901F27 /* Music.json in Resources */ = {isa = PBXBuildFile; fileRef = F949284E28F2AFB800901F27 /* Music.json */; }; + F949421D2B16044F00075DE1 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F949421C2B16044F00075DE1 /* ToastView.swift */; }; F95CE98B2917A8EF00FFE213 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F95CE98A2917A8EF00FFE213 /* Settings.bundle */; }; F97BE1222AC011B100B37C76 /* SongStorageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */; }; F97BE1242AC011DF00B37C76 /* Halmap+CollectedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */; }; @@ -119,6 +120,7 @@ F939EBBB29D5920F005ED8CA /* OffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetModifier.swift; sourceTree = ""; }; F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; F949284E28F2AFB800901F27 /* Music.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Music.json; sourceTree = ""; }; + F949421C2B16044F00075DE1 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; F95CE98A2917A8EF00FFE213 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongStorageViewModel.swift; sourceTree = ""; }; F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Halmap+CollectedSong.swift"; sourceTree = ""; }; @@ -338,6 +340,7 @@ children = ( F9305CA12B102E0600A0F7C9 /* Progressbar.swift */, F9305CA32B1038BE00A0F7C9 /* Utility.swift */, + F949421C2B16044F00075DE1 /* ToastView.swift */, ); path = Component; sourceTree = ""; @@ -485,6 +488,7 @@ buildActionMask = 2147483647; files = ( F9EB19C129168E45002DFE46 /* Song.swift in Sources */, + F949421D2B16044F00075DE1 /* ToastView.swift in Sources */, F9A925A02AA60A5100EF8BC9 /* MainTabViewModel.swift in Sources */, F9E4642329E8089A009E12FC /* ScalingHeaderView.swift in Sources */, A81FFC8328F15D9C00B0FC7C /* Persistence.swift in Sources */, diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 00ec46f..25e096a 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -17,6 +17,7 @@ struct SongDetailView: View { animation: .default) private var defaultPlaylistSongs: FetchedResults @State var isPlaylistView = false + @State private var toast: Toast? = nil var body: some View { ZStack { @@ -43,8 +44,9 @@ struct SongDetailView: View { gradientRectangle(isTop: false) playlistButton } + .toastView(toast: $toast) - PlayBar(viewModel: viewModel) + PlayBar(viewModel: viewModel, toast: $toast) } .ignoresSafeArea() } @@ -139,12 +141,16 @@ private struct PlayBar: View { sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults + + @Binding var toast: Toast? + var body: some View { VStack(spacing: 0) { Progressbar( player: viewModel.getAudioManager().player, currentIndex: $viewModel.currentIndex, - song: $viewModel.song, + song: $viewModel.song, + toast: $toast, isThumbActive: true) HStack(spacing: 52) { Button { @@ -172,7 +178,7 @@ private struct PlayBar: View { //다음곡 재생 기능 if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { - print("재생목록이 처음으로 돌아갑니다.") + toast = Toast(message: "재생목록이 처음으로 돌아갑니다.") viewModel.currentIndex = 0 } else { viewModel.currentIndex = index + 1 diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index 2d0a037..d5f79f6 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -10,15 +10,19 @@ import AVFoundation struct Progressbar: View { @EnvironmentObject var audioManager: AudioManager let player: AVPlayer + @Binding var song: SongInfo @Binding var currentIndex: Int + @Binding var toast: Toast? + let isThumbActive: Bool - init(player: AVPlayer, currentIndex: Binding, song: Binding, isThumbActive: Bool) { + init(player: AVPlayer, currentIndex: Binding, song: Binding, toast: Binding, isThumbActive: Bool) { self.player = player self.isThumbActive = isThumbActive self._currentIndex = currentIndex self._song = song + self._toast = toast let thumbImage = makeThumbView(isThumbActive: isThumbActive) UISlider.appearance().setThumbImage(thumbImage, for: .normal) UISlider.appearance().maximumTrackTintColor = UIColor(Color.customGray.opacity(0.2)) @@ -32,6 +36,7 @@ struct Progressbar: View { itemObserver: PlayerItemObserver(player: player), currentIndex: $currentIndex, song: $song, + toast: $toast, isThumbActive: isThumbActive) } @@ -77,6 +82,7 @@ struct AudioPlayerControlsView: View { @Binding var currentIndex: Int @Binding var song: SongInfo + @Binding var toast: Toast? @FetchRequest( entity: CollectedSong.entity(), @@ -113,6 +119,7 @@ struct AudioPlayerControlsView: View { currentIndex = Int(defaultPlaylistSongs[index].order + 1) } else { currentIndex = 0 + toast = Toast(message: "재생목록이 처음으로 돌아갑니다.") } } } diff --git a/Halmap/View/Component/ToastView.swift b/Halmap/View/Component/ToastView.swift new file mode 100644 index 0000000..6666936 --- /dev/null +++ b/Halmap/View/Component/ToastView.swift @@ -0,0 +1,99 @@ +// +// ToastView.swift +// Halmap +// +// Created by JeonJimin on 11/28/23. +// + +import SwiftUI + +//https://ondrej-kvasnovsky.medium.com/how-to-build-a-simple-toast-message-view-in-swiftui-b2e982340bd +struct Toast: Equatable { + var message: String + var duration: Double = 3 + var width: Double = .infinity +} + +struct ToastModifier: ViewModifier { + @Binding var toast: Toast? + @State private var workItem: DispatchWorkItem? + + func body(content: Content) -> some View { + content + .frame(maxWidth: .infinity) + .overlay( + ZStack { + mainToastView() + .offset(y: 32) + }.animation(.spring(), value: toast) + ) + .onChange(of: toast) { value in + showToast() + } + } + + @ViewBuilder + func mainToastView() -> some View { + if let toast { + VStack { + ToastView(message: toast.message) { + dismissToast() + } + } + } + } + + private func showToast() { + guard let toast else { return } + + UIImpactFeedbackGenerator(style: .light) + .impactOccurred() + + if toast.duration > 0 { + workItem?.cancel() + + let task = DispatchWorkItem { + dismissToast() + } + + workItem = task + DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration, execute: task) + } + } + + private func dismissToast() { + withAnimation { + toast = nil + } + workItem?.cancel() + workItem = nil + } +} + +struct ToastView: View { + @AppStorage("selectedTeam") var selectedTeamName: String = "" + var message = "재생 목록의 처음으로 돌아갑니다." + var width = CGFloat.infinity + var onCancelTapped: (() -> Void) + var body: some View { + HStack(alignment: .center, spacing: 18) { + Image(systemName: "text.insert") + Text(message) + .font(Font.Halmap.CustomBodyBold) + } + .foregroundStyle(Color("\(selectedTeamName)Background")) + .padding(EdgeInsets(top: 17, leading: 28, bottom: 17, trailing: 28)) + .background(Color.customGray) + .cornerRadius(10) + .padding(.bottom, 20) + .onTapGesture { + onCancelTapped() + } + } +} + +extension View { + func toastView(toast: Binding) -> some View { + self.modifier(ToastModifier(toast: toast)) + } +} From 7fa11ed87edc7f835eef8b810c353e5efe002406 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 29 Nov 2023 19:24:13 +0900 Subject: [PATCH 11/56] =?UTF-8?q?[Feat]=20=EB=A1=9C=ED=8B=B0=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isPlaying값에 맞춰서 애니메이션 재생 및 정지 - toast message 글자 색상 변경 --- Halmap.xcodeproj/project.pbxproj | 29 +++++++++++++++++++ Halmap/Features/SongDetail/PlaylistView.swift | 14 +++++++-- .../Features/SongDetail/SongDetailView.swift | 9 ++++-- Halmap/Resource/waveform.json | 1 + Halmap/View/Component/Progressbar.swift | 2 +- Halmap/View/Component/ToastView.swift | 7 +++-- 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 Halmap/Resource/waveform.json diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 3180aee..1caebf4 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -57,6 +57,8 @@ F941FA7F2A7F78500034F72D /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */; }; F949284F28F2AFB800901F27 /* Music.json in Resources */ = {isa = PBXBuildFile; fileRef = F949284E28F2AFB800901F27 /* Music.json */; }; F949421D2B16044F00075DE1 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F949421C2B16044F00075DE1 /* ToastView.swift */; }; + F94942202B17373C00075DE1 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = F949421F2B17373C00075DE1 /* Lottie */; }; + F94942252B17398100075DE1 /* waveform.json in Resources */ = {isa = PBXBuildFile; fileRef = F94942242B17398100075DE1 /* waveform.json */; }; F95CE98B2917A8EF00FFE213 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F95CE98A2917A8EF00FFE213 /* Settings.bundle */; }; F97BE1222AC011B100B37C76 /* SongStorageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */; }; F97BE1242AC011DF00B37C76 /* Halmap+CollectedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */; }; @@ -121,6 +123,7 @@ F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; F949284E28F2AFB800901F27 /* Music.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Music.json; sourceTree = ""; }; F949421C2B16044F00075DE1 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; + F94942242B17398100075DE1 /* waveform.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = waveform.json; sourceTree = ""; }; F95CE98A2917A8EF00FFE213 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongStorageViewModel.swift; sourceTree = ""; }; F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Halmap+CollectedSong.swift"; sourceTree = ""; }; @@ -151,6 +154,7 @@ 20A687E82A711C48005F18FA /* FirebaseStorage in Frameworks */, 20A687DA2A711C48005F18FA /* FirebaseAnalyticsSwift in Frameworks */, 20A687E42A711C48005F18FA /* FirebaseFirestoreSwift in Frameworks */, + F94942202B17373C00075DE1 /* Lottie in Frameworks */, 20A687DE2A711C48005F18FA /* FirebaseDatabase in Frameworks */, 20A687D82A711C48005F18FA /* FirebaseAnalytics in Frameworks */, 20A687E22A711C48005F18FA /* FirebaseFirestore in Frameworks */, @@ -245,6 +249,7 @@ A81FFC7828F15D9900B0FC7C /* Halmap */ = { isa = PBXGroup; children = ( + F94942232B17396E00075DE1 /* Resource */, 877403852AB71FD900C94D35 /* Features */, A81FFC8E28F1770500B0FC7C /* View */, B2699E0B28F1CB5100267A4F /* Info.plist */, @@ -335,6 +340,14 @@ name = "Recovered References"; sourceTree = ""; }; + F94942232B17396E00075DE1 /* Resource */ = { + isa = PBXGroup; + children = ( + F94942242B17398100075DE1 /* waveform.json */, + ); + path = Resource; + sourceTree = ""; + }; F95F26C12A4C9BF000B534EB /* Component */ = { isa = PBXGroup; children = ( @@ -399,6 +412,7 @@ 20A687E32A711C48005F18FA /* FirebaseFirestoreSwift */, 20A687E52A711C48005F18FA /* FirebaseRemoteConfig */, 20A687E72A711C48005F18FA /* FirebaseStorage */, + F949421F2B17373C00075DE1 /* Lottie */, ); productName = Halmap; productReference = A81FFC7628F15D9900B0FC7C /* Halmap.app */; @@ -430,6 +444,7 @@ mainGroup = A81FFC6D28F15D9900B0FC7C; packageReferences = ( 20A687D62A711C48005F18FA /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + F949421E2B17373C00075DE1 /* XCRemoteSwiftPackageReference "lottie-ios" */, ); productRefGroup = A81FFC7728F15D9900B0FC7C /* Products */; projectDirPath = ""; @@ -449,6 +464,7 @@ F9A2DAA62A07DE9A008B84A9 /* Preview Assets.xcassets in Resources */, A81FFC8128F15D9C00B0FC7C /* Preview Assets.xcassets in Resources */, B2699E0D28F1CB7E00267A4F /* Pretendard-Bold.otf in Resources */, + F94942252B17398100075DE1 /* waveform.json in Resources */, A81FFC7E28F15D9C00B0FC7C /* Assets.xcassets in Resources */, B2699E1028F1CB9700267A4F /* Pretendard-Medium.otf in Resources */, F949284F28F2AFB800901F27 /* Music.json in Resources */, @@ -767,6 +783,14 @@ minimumVersion = 10.0.0; }; }; + F949421E2B17373C00075DE1 /* XCRemoteSwiftPackageReference "lottie-ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/airbnb/lottie-ios"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.3.3; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -815,6 +839,11 @@ package = 20A687D62A711C48005F18FA /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseStorage; }; + F949421F2B17373C00075DE1 /* Lottie */ = { + isa = XCSwiftPackageProductDependency; + package = F949421E2B17373C00075DE1 /* XCRemoteSwiftPackageReference "lottie-ios" */; + productName = Lottie; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 9ef01d7..4ab531b 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -5,6 +5,7 @@ // Created by JeonJimin on 11/14/23. // import SwiftUI +import Lottie struct PlaylistView: View { @@ -13,6 +14,7 @@ struct PlaylistView: View { @Binding var song: SongInfo @Binding var isScrolled: Bool @Binding var currentIndex: Int + @Binding var isPlaying: Bool let persistence = PersistenceController.shared @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults @@ -85,8 +87,16 @@ struct PlaylistView: View { private func getPlaylistRowView(song: CollectedSong) -> some View { HStack{ if currentSongId == song.id { - Image(systemName: "waveform") - .font(.system(size: 24)) + LottieView(animation: .named("waveform")) + .playing(loopMode: .loop) + .configure({ lottie in + if isPlaying { + lottie.loopMode = .loop + lottie.play() + } else { + lottie.stop() + } + }) .foregroundStyle(Color(viewModel.getSongTeamBackgroundColor(with: song))) .frame(width: 40, height: 40) } else { diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 25e096a..74df8fa 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -26,7 +26,12 @@ struct SongDetailView: View { if isPlaylistView { VStack { - PlaylistView(viewModel: PlaylistViewModel(viewModel: viewModel), song: $viewModel.song, isScrolled: $viewModel.isScrolled, currentIndex: $viewModel.currentIndex) + PlaylistView( + viewModel: PlaylistViewModel(viewModel: viewModel), + song: $viewModel.song, + isScrolled: $viewModel.isScrolled, + currentIndex: $viewModel.currentIndex, + isPlaying: $viewModel.isPlaying) .padding(.top, 10) .padding(.bottom, 150) } @@ -178,7 +183,7 @@ private struct PlayBar: View { //다음곡 재생 기능 if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { - toast = Toast(message: "재생목록이 처음으로 돌아갑니다.") + toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") viewModel.currentIndex = 0 } else { viewModel.currentIndex = index + 1 diff --git a/Halmap/Resource/waveform.json b/Halmap/Resource/waveform.json new file mode 100644 index 0000000..2750bd6 --- /dev/null +++ b/Halmap/Resource/waveform.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":60,"ip":0,"op":66,"w":196,"h":196,"nm":"wave ","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"wave","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[98,98,0],"ix":2,"l":2},"a":{"a":0,"k":[-728.814,789.468,0],"ix":1,"l":2},"s":{"a":0,"k":[40,40,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":35,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,813.998],[-858.42,801.733],[-858.42,777.203],[-870.685,764.938],[-882.95,777.203],[-882.95,801.733]],"c":true}]},{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":45,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,828.061],[-858.42,815.796],[-858.42,763.141],[-870.685,750.875],[-882.95,763.141],[-882.95,815.796]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":55,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,810.092],[-858.42,797.827],[-858.42,781.891],[-870.685,769.625],[-882.95,781.891],[-882.95,797.827]],"c":true}]},{"t":65,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,813.998],[-858.42,801.733],[-858.42,777.203],[-870.685,764.938],[-882.95,777.203],[-882.95,801.733]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-593.185,789.468],"ix":2},"a":{"a":0,"k":[-870.685,789.468],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":25,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,813.998],[-858.42,801.733],[-858.42,777.203],[-870.685,764.938],[-882.95,777.203],[-882.95,801.733]],"c":true}]},{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":35,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,828.061],[-858.42,815.796],[-858.42,763.141],[-870.685,750.875],[-882.95,763.141],[-882.95,815.796]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":45,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,810.092],[-858.42,797.827],[-858.42,781.891],[-870.685,769.625],[-882.95,781.891],[-882.95,797.827]],"c":true}]},{"t":55,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,813.998],[-858.42,801.733],[-858.42,777.203],[-870.685,764.938],[-882.95,777.203],[-882.95,801.733]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-680.685,789.468],"ix":2},"a":{"a":0,"k":[-870.685,789.468],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":0,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,813.998],[-858.42,801.733],[-858.42,777.203],[-870.685,764.938],[-882.95,777.203],[-882.95,801.733]],"c":true}]},{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":10,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,828.061],[-858.42,815.796],[-858.42,763.141],[-870.685,750.875],[-882.95,763.141],[-882.95,815.796]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":20,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,810.092],[-858.42,797.827],[-858.42,781.891],[-870.685,769.625],[-882.95,781.891],[-882.95,797.827]],"c":true}]},{"t":30,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-870.685,813.998],[-858.42,801.733],[-858.42,777.203],[-870.685,764.938],[-882.95,777.203],[-882.95,801.733]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-870.685,789.468],"ix":2},"a":{"a":0,"k":[-870.685,789.468],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":5,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,863.059],[-809.359,850.794],[-809.359,728.143],[-821.624,715.878],[-833.89,728.143],[-833.89,850.794]],"c":true}]},{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":15,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,847.434],[-809.359,835.169],[-809.359,745.33],[-821.624,733.065],[-833.89,745.33],[-833.89,835.169]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":25,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,877.121],[-809.359,864.856],[-809.359,714.08],[-821.624,701.815],[-833.89,714.08],[-833.89,864.856]],"c":true}]},{"t":35,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,863.059],[-809.359,850.794],[-809.359,728.143],[-821.624,715.878],[-833.89,728.143],[-833.89,850.794]],"c":true}]}],"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-639.749,789.468],"ix":2},"a":{"a":0,"k":[-821.624,789.468],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":30,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,863.059],[-809.359,850.794],[-809.359,728.143],[-821.624,715.878],[-833.89,728.143],[-833.89,850.794]],"c":true}]},{"i":{"x":0.28,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,847.434],[-809.359,835.169],[-809.359,745.33],[-821.624,733.065],[-833.89,745.33],[-833.89,835.169]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":50,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,877.121],[-809.359,864.856],[-809.359,714.08],[-821.624,701.815],[-833.89,714.08],[-833.89,864.856]],"c":true}]},{"t":60,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-821.624,863.059],[-809.359,850.794],[-809.359,728.143],[-821.624,715.878],[-833.89,728.143],[-833.89,850.794]],"c":true}]}],"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-821.624,789.468],"ix":2},"a":{"a":0,"k":[-821.624,789.468],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-772.564,912.119],[-760.299,899.854],[-760.299,679.082],[-772.564,666.817],[-784.829,679.082],[-784.829,899.854]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-772.564,832.432],[-760.299,820.166],[-760.299,735.332],[-772.564,723.067],[-784.829,735.332],[-784.829,820.166]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-772.564,879.307],[-760.299,867.041],[-760.299,700.957],[-772.564,688.692],[-784.829,700.957],[-784.829,867.041]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-772.564,876.182],[-760.299,863.916],[-760.299,710.332],[-772.564,698.067],[-784.829,710.332],[-784.829,863.916]],"c":true}]},{"t":50,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-772.564,912.119],[-760.299,899.854],[-760.299,679.082],[-772.564,666.817],[-784.829,679.082],[-784.829,899.854]],"c":true}]}],"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-772.564,789.468],"ix":2},"a":{"a":0,"k":[-772.564,789.468],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":15,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-723.504,863.059],[-711.239,850.794],[-711.239,728.143],[-723.504,715.878],[-735.769,728.143],[-735.769,850.794]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-723.504,842.746],[-711.239,830.481],[-711.239,745.33],[-723.504,733.065],[-735.769,745.33],[-735.769,830.481]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-723.504,853.684],[-711.239,841.419],[-711.239,732.83],[-723.504,720.565],[-735.769,732.83],[-735.769,841.419]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-723.504,836.496],[-711.239,824.231],[-711.239,754.705],[-723.504,742.44],[-735.769,754.705],[-735.769,824.231]],"c":true}]},{"t":55,"s":[{"i":[[-6.746,0],[0,6.746],[0,0],[6.746,0],[0,-6.746],[0,0]],"o":[[6.746,0],[0,0],[0,-6.746],[-6.746,0],[0,0],[0,6.746]],"v":[[-723.504,863.059],[-711.239,850.794],[-711.239,728.143],[-723.504,715.878],[-735.769,728.143],[-735.769,850.794]],"c":true}]}],"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-723.504,789.468],"ix":2},"a":{"a":0,"k":[-723.504,789.468],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[255,255,255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":8,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":374,"st":0,"ct":1,"bm":0}],"markers":[]} diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index d5f79f6..f72afae 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -119,7 +119,7 @@ struct AudioPlayerControlsView: View { currentIndex = Int(defaultPlaylistSongs[index].order + 1) } else { currentIndex = 0 - toast = Toast(message: "재생목록이 처음으로 돌아갑니다.") + toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") } } } diff --git a/Halmap/View/Component/ToastView.swift b/Halmap/View/Component/ToastView.swift index 6666936..56d7816 100644 --- a/Halmap/View/Component/ToastView.swift +++ b/Halmap/View/Component/ToastView.swift @@ -9,6 +9,7 @@ import SwiftUI //https://ondrej-kvasnovsky.medium.com/how-to-build-a-simple-toast-message-view-in-swiftui-b2e982340bd struct Toast: Equatable { + var team: String var message: String var duration: Double = 3 var width: Double = .infinity @@ -36,7 +37,7 @@ struct ToastModifier: ViewModifier { func mainToastView() -> some View { if let toast { VStack { - ToastView(message: toast.message) { + ToastView(team: toast.team, message: toast.message) { dismissToast() } } @@ -71,7 +72,7 @@ struct ToastModifier: ViewModifier { } struct ToastView: View { - @AppStorage("selectedTeam") var selectedTeamName: String = "" + let team: String var message = "재생 목록의 처음으로 돌아갑니다." var width = CGFloat.infinity var onCancelTapped: (() -> Void) @@ -81,7 +82,7 @@ struct ToastView: View { Text(message) .font(Font.Halmap.CustomBodyBold) } - .foregroundStyle(Color("\(selectedTeamName)Background")) + .foregroundStyle(Color("\(team)Background")) .padding(EdgeInsets(top: 17, leading: 28, bottom: 17, trailing: 28)) .background(Color.customGray) .cornerRadius(10) From aa89aac92dc5afb6bf5d52499d3feb35d79f83cf Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 29 Nov 2023 19:29:32 +0900 Subject: [PATCH 12/56] [Feat] currentIndex onChange -> onReceive --- Halmap/Features/SongDetail/SongDetailView.swift | 8 +++++--- Halmap/Features/SongDetail/SongDetailViewModel.swift | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 74df8fa..e57ba8b 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -65,9 +65,11 @@ struct SongDetailView: View { .onAppear() { viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) } - .onChange(of: self.viewModel.currentIndex) { _ in - self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[self.viewModel.currentIndex]) - self.viewModel.getAudioManager().AMset(song: self.viewModel.song) + .onReceive(viewModel.$currentIndex) { output in + if output >= 0 { + self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[self.viewModel.currentIndex]) + self.viewModel.getAudioManager().AMset(song: self.viewModel.song) + } } } diff --git a/Halmap/Features/SongDetail/SongDetailViewModel.swift b/Halmap/Features/SongDetail/SongDetailViewModel.swift index cd0ea7c..ffba8df 100644 --- a/Halmap/Features/SongDetail/SongDetailViewModel.swift +++ b/Halmap/Features/SongDetail/SongDetailViewModel.swift @@ -17,7 +17,7 @@ final class SongDetailViewModel: ObservableObject { @Published var isFavorite = false @Published var isPlaying = false - @Published var currentIndex = 0 + @Published var currentIndex = -1 private var cancellables = Set() From af09e7df2c8a73e5a076e5a3560d8d980b45649e Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Thu, 30 Nov 2023 14:38:32 +0900 Subject: [PATCH 13/56] =?UTF-8?q?[Feat]=20toast=20message=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/View/Component/ToastView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Halmap/View/Component/ToastView.swift b/Halmap/View/Component/ToastView.swift index 56d7816..36d5b88 100644 --- a/Halmap/View/Component/ToastView.swift +++ b/Halmap/View/Component/ToastView.swift @@ -25,8 +25,9 @@ struct ToastModifier: ViewModifier { .overlay( ZStack { mainToastView() - .offset(y: 32) - }.animation(.spring(), value: toast) + .transition(.move(edge: .bottom)) + .offset(y: toast != nil ? 0 : 40) + }.animation(.smooth(duration: 0.2), value: toast) ) .onChange(of: toast) { value in showToast() @@ -86,7 +87,6 @@ struct ToastView: View { .padding(EdgeInsets(top: 17, leading: 28, bottom: 17, trailing: 28)) .background(Color.customGray) .cornerRadius(10) - .padding(.bottom, 20) .onTapGesture { onCancelTapped() } From f4605c479d238a981a94a4f980f6e66fa367ad12 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Thu, 30 Nov 2023 18:06:31 +0900 Subject: [PATCH 14/56] =?UTF-8?q?wip=20-=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=B3=80=EC=88=98=20=EC=A0=9C=EA=B1=B0=20-=20?= =?UTF-8?q?=EB=AF=B8=EB=94=94=EC=96=B4=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=EC=85=8B=EC=97=85=20=EC=9C=84=EC=B9=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(AMPlayer=20->=20init)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Data/AudioManager.swift | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Halmap/Data/AudioManager.swift b/Halmap/Data/AudioManager.swift index 0879f0b..125b815 100644 --- a/Halmap/Data/AudioManager.swift +++ b/Halmap/Data/AudioManager.swift @@ -18,19 +18,13 @@ final class AudioManager: NSObject, ObservableObject { @Published private(set) var isPlaying: Bool = false @Published var currentIndex: Int = 0 //현재 재생중인 곡 index - var duration: Double { - return player.currentItem?.duration.seconds ?? 0 - } - - //progressbar - var currentTimePublisher: PassthroughSubject = .init() - var currentProgressPublisher: PassthroughSubject = .init() - private var playerPeriodicObserver: Any? - var acceptProgressUpdates = true - + var song: SongInfo? private var playerItemContext = 0 - var song: SongInfo? + override init() { + super.init() + setupRemoteTransportControls() + } // MARK: - AM Properties @@ -115,9 +109,6 @@ final class AudioManager: NSObject, ObservableObject { func AMplay() { NotificationCenter.default.addObserver(self, selector: #selector(self.AMplayEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil) - - setupRemoteTransportControls() - player.play() isPlaying = true @@ -198,6 +189,16 @@ extension AudioManager { return .commandFailed } + commandCenter.nextTrackCommand.addTarget { [unowned self] event in + print("다음 \(currentIndex+1)") + return .success + } + + commandCenter.previousTrackCommand.addTarget { [unowned self] event in + print("이전 \(currentIndex-1)") + return .success + } + commandCenter.changePlaybackPositionCommand.addTarget { [unowned self] event in if let positionTime = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime { let seekTime = CMTime(value: Int64(positionTime), timescale: 1) From 30d82e33dea2043b90da7a5a636c0385837e4219 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Thu, 30 Nov 2023 19:32:19 +0900 Subject: [PATCH 15/56] =?UTF-8?q?wip=20-=EC=A4=91=EB=B3=B5=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=ED=95=A8=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/PlaylistViewModel.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Halmap/Features/SongDetail/PlaylistViewModel.swift b/Halmap/Features/SongDetail/PlaylistViewModel.swift index d9c2c13..274e4d3 100644 --- a/Halmap/Features/SongDetail/PlaylistViewModel.swift +++ b/Halmap/Features/SongDetail/PlaylistViewModel.swift @@ -50,7 +50,6 @@ final class PlaylistViewModel: ObservableObject { print(#function, song.title) self.song = song audioManager.removePlayer() - audioManager.AMset(song: song) return song } From 1718b628a611d4fc081d2033ae54d24e12ab0dff Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Thu, 30 Nov 2023 19:55:37 +0900 Subject: [PATCH 16/56] =?UTF-8?q?[Feat]=20=EC=9E=AC=EC=83=9D=EB=B0=94=20th?= =?UTF-8?q?umb=20image=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - introspect 패키지로 해결 - 재생바 안에서 AudioManager 직접선언으로 player전달방식 수정 --- Halmap.xcodeproj/project.pbxproj | 17 +++++ .../Features/SongDetail/SongDetailView.swift | 1 - Halmap/View/Component/Progressbar.swift | 62 +++++++++---------- 3 files changed, 47 insertions(+), 33 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 1caebf4..20ac57d 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ F9B3B7B529B7397600232BB8 /* MapName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9B3B7B429B7397600232BB8 /* MapName.swift */; }; F9B95BB928F271A100728048 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9B95BB828F271A100728048 /* DataManager.swift */; }; F9B95BBB28F271B800728048 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9B95BBA28F271B800728048 /* Model.swift */; }; + F9CB6A5A2B18A104005DF514 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F9CB6A592B18A104005DF514 /* SwiftUIIntrospect */; }; F9DB6D1D2A763B7400B9469D /* Traffic.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9DB6D1C2A763B7400B9469D /* Traffic.swift */; }; F9DB6D1F2A76744A00B9469D /* LaunchScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9DB6D1E2A76744A00B9469D /* LaunchScreenView.swift */; }; F9E4642329E8089A009E12FC /* ScalingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E4642229E8089A009E12FC /* ScalingHeaderView.swift */; }; @@ -154,6 +155,7 @@ 20A687E82A711C48005F18FA /* FirebaseStorage in Frameworks */, 20A687DA2A711C48005F18FA /* FirebaseAnalyticsSwift in Frameworks */, 20A687E42A711C48005F18FA /* FirebaseFirestoreSwift in Frameworks */, + F9CB6A5A2B18A104005DF514 /* SwiftUIIntrospect in Frameworks */, F94942202B17373C00075DE1 /* Lottie in Frameworks */, 20A687DE2A711C48005F18FA /* FirebaseDatabase in Frameworks */, 20A687D82A711C48005F18FA /* FirebaseAnalytics in Frameworks */, @@ -413,6 +415,7 @@ 20A687E52A711C48005F18FA /* FirebaseRemoteConfig */, 20A687E72A711C48005F18FA /* FirebaseStorage */, F949421F2B17373C00075DE1 /* Lottie */, + F9CB6A592B18A104005DF514 /* SwiftUIIntrospect */, ); productName = Halmap; productReference = A81FFC7628F15D9900B0FC7C /* Halmap.app */; @@ -445,6 +448,7 @@ packageReferences = ( 20A687D62A711C48005F18FA /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, F949421E2B17373C00075DE1 /* XCRemoteSwiftPackageReference "lottie-ios" */, + F9CB6A582B18A104005DF514 /* XCRemoteSwiftPackageReference "swiftui-introspect" */, ); productRefGroup = A81FFC7728F15D9900B0FC7C /* Products */; projectDirPath = ""; @@ -791,6 +795,14 @@ minimumVersion = 4.3.3; }; }; + F9CB6A582B18A104005DF514 /* XCRemoteSwiftPackageReference "swiftui-introspect" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/siteline/swiftui-introspect"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.1; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -844,6 +856,11 @@ package = F949421E2B17373C00075DE1 /* XCRemoteSwiftPackageReference "lottie-ios" */; productName = Lottie; }; + F9CB6A592B18A104005DF514 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + package = F9CB6A582B18A104005DF514 /* XCRemoteSwiftPackageReference "swiftui-introspect" */; + productName = SwiftUIIntrospect; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index e57ba8b..8c4c7b2 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -154,7 +154,6 @@ private struct PlayBar: View { var body: some View { VStack(spacing: 0) { Progressbar( - player: viewModel.getAudioManager().player, currentIndex: $viewModel.currentIndex, song: $viewModel.song, toast: $toast, diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index f72afae..274cb67 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -6,10 +6,10 @@ // import SwiftUI import AVFoundation +import SwiftUIIntrospect struct Progressbar: View { @EnvironmentObject var audioManager: AudioManager - let player: AVPlayer @Binding var song: SongInfo @Binding var currentIndex: Int @@ -17,52 +17,32 @@ struct Progressbar: View { let isThumbActive: Bool - init(player: AVPlayer, currentIndex: Binding, song: Binding, toast: Binding, isThumbActive: Bool) { - self.player = player + init(currentIndex: Binding, song: Binding, toast: Binding, isThumbActive: Bool) { self.isThumbActive = isThumbActive self._currentIndex = currentIndex self._song = song self._toast = toast - let thumbImage = makeThumbView(isThumbActive: isThumbActive) - UISlider.appearance().setThumbImage(thumbImage, for: .normal) UISlider.appearance().maximumTrackTintColor = UIColor(Color.customGray.opacity(0.2)) } var body: some View { VStack { - AudioPlayerControlsView(player: player, - timeObserver: PlayerTimeObserver(player: player), - durationObserver: PlayerDurationObserver(player: player), - itemObserver: PlayerItemObserver(player: player), - currentIndex: $currentIndex, + AudioPlayerControlsView(player: audioManager.player, + timeObserver: PlayerTimeObserver(player: audioManager.player), + durationObserver: PlayerDurationObserver(player: audioManager.player), + itemObserver: PlayerItemObserver(player: audioManager.player), + isThumbActive: isThumbActive, + currentIndex: $currentIndex, song: $song, - toast: $toast, - isThumbActive: isThumbActive) + toast: $toast) } .tint(Color("\(song.team)Point")) .padding(.horizontal, isThumbActive ? 5 : 0) .onDisappear { - self.player.replaceCurrentItem(with: nil) + audioManager.AMstop() } } - - private func makeThumbView(isThumbActive: Bool) -> UIImage { - lazy var thumbView: UIView = { - let thumb = UIView() - thumb.backgroundColor = UIColor(isThumbActive ? Color("\(song.team)Point") : Color.clear) - return thumb - }() - let radius:CGFloat = 10 - thumbView.frame = CGRect(x: 0, y: radius / 2, width: radius, height: radius) - thumbView.layer.cornerRadius = radius / 2 - let renderer = UIGraphicsImageRenderer(bounds: thumbView.bounds) - let image = renderer.image { rendererContext in - thumbView.layer.render(in: rendererContext.cgContext) - } - - return image - } } struct AudioPlayerControlsView: View { @@ -76,6 +56,7 @@ struct AudioPlayerControlsView: View { let timeObserver: PlayerTimeObserver let durationObserver: PlayerDurationObserver let itemObserver: PlayerItemObserver + let isThumbActive: Bool @State private var currentTime: TimeInterval = 0 @State private var currentDuration: TimeInterval = 0 @State private var state = PlaybackState.pause @@ -90,13 +71,14 @@ struct AudioPlayerControlsView: View { predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults - let isThumbActive: Bool - var body: some View { VStack { Slider(value: $currentTime, in: 0...currentDuration, onEditingChanged: sliderEditingChanged) + .introspect(.slider, on: .iOS(.v15,.v16,.v17), customize: { slider in + slider.setThumbImage(makeThumbView(isThumbActive: isThumbActive), for: .normal) + }) .disabled(state != .playing) .onReceive(timeObserver.publisher) { time in self.currentTime = time @@ -155,6 +137,22 @@ struct AudioPlayerControlsView: View { } } } + private func makeThumbView(isThumbActive: Bool) -> UIImage { + lazy var thumbView: UIView = { + let thumb = UIView() + thumb.backgroundColor = UIColor(isThumbActive ? Color("\(song.team)Point") : Color.clear) + return thumb + }() + let radius:CGFloat = 10 + thumbView.frame = CGRect(x: 0, y: radius / 2, width: radius, height: radius) + thumbView.layer.cornerRadius = radius / 2 + let renderer = UIGraphicsImageRenderer(bounds: thumbView.bounds) + let image = renderer.image { rendererContext in + thumbView.layer.render(in: rendererContext.cgContext) + } + + return image + } } import Combine From 3a3f80c0b50ba2a7e35f7ad200dd0ce006d48fd5 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Fri, 1 Dec 2023 15:15:25 +0900 Subject: [PATCH 17/56] =?UTF-8?q?[Feat]=20=EB=8B=A4=EC=9D=8C=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=99=20=EC=9E=AC=EC=83=9D=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20currentIndex=20->=20song.id=20(songDetailView)?= =?UTF-8?q?=20-=20currentSongId=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/PlaylistView.swift | 15 ++++----------- .../SongDetail/PlaylistViewModel.swift | 16 ++++++++++------ .../Features/SongDetail/SongDetailView.swift | 19 +++++++------------ .../SongDetail/SongDetailViewModel.swift | 3 --- Halmap/View/Component/Progressbar.swift | 12 ++++-------- Halmap/View/Component/Utility.swift | 12 ++++++++++++ 6 files changed, 37 insertions(+), 40 deletions(-) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 4ab531b..d106ec1 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -9,11 +9,9 @@ import Lottie struct PlaylistView: View { - @AppStorage("currentSongId") var currentSongId: String = "" @StateObject var viewModel: PlaylistViewModel @Binding var song: SongInfo @Binding var isScrolled: Bool - @Binding var currentIndex: Int @Binding var isPlaying: Bool let persistence = PersistenceController.shared @@ -28,7 +26,6 @@ struct PlaylistView: View { .background(Color.white.opacity(0.001)) .onTapGesture { self.song = viewModel.didTappedSongCell(song: playListSong) - self.currentIndex = Int(playListSong.order) } }.onDelete { indexSet in for index in indexSet { @@ -40,9 +37,9 @@ struct PlaylistView: View { if let songIndex = collectedSongs.firstIndex(where: {$0.id == song.id}) { if collectedSongs[songIndex].order == index { if index + 1 < collectedSongs.count { - currentIndex = Int(collectedSongs[songIndex].order) + self.song = Utility.convertSongToSongInfo(song: collectedSongs[Int(collectedSongs[songIndex].order + 1)]) } else { - currentIndex = 0 + self.song = Utility.convertSongToSongInfo(song: collectedSongs[0]) } } } @@ -86,7 +83,7 @@ struct PlaylistView: View { @ViewBuilder private func getPlaylistRowView(song: CollectedSong) -> some View { HStack{ - if currentSongId == song.id { + if self.song.id == song.id { LottieView(animation: .named("waveform")) .playing(loopMode: .loop) .configure({ lottie in @@ -121,7 +118,7 @@ struct PlaylistView: View { } .padding(.horizontal, 40) .frame(height: 70) - .background(currentSongId == song.id ? Color.white.opacity(0.2) : Color.clear) + .background(self.song.id == song.id ? Color.white.opacity(0.2) : Color.clear) } private struct ViewOffsetKey: PreferenceKey { @@ -144,7 +141,3 @@ struct ListBackgroundModifier: ViewModifier { } } } - -//#Preview { -// PlaylistView(song: .constant(SongInfo(id: "",team: "NC", type: false, title: "test", lyrics: "test", info: "test", url: "test")), isScrolled: .constant(false)) -//} diff --git a/Halmap/Features/SongDetail/PlaylistViewModel.swift b/Halmap/Features/SongDetail/PlaylistViewModel.swift index 274e4d3..6759885 100644 --- a/Halmap/Features/SongDetail/PlaylistViewModel.swift +++ b/Halmap/Features/SongDetail/PlaylistViewModel.swift @@ -46,13 +46,17 @@ final class PlaylistViewModel: ObservableObject { func didTappedSongCell(song: CollectedSong) -> SongInfo { print("song: ", song.safeTitle, "order: ", song.order) - let song = convertSongToSongInfo(song: song) - print(#function, song.title) - self.song = song - audioManager.removePlayer() - return song + if song.id != self.song.id { + let song = convertSongToSongInfo(song: song) + print(#function, song.title) + self.song = song + audioManager.removePlayer() + return song + } else { + return self.song + } } - + private func convertSongToSongInfo(song: CollectedSong) -> SongInfo { SongInfo( id: song.id ?? "", diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 8c4c7b2..4a5eb81 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -30,7 +30,6 @@ struct SongDetailView: View { viewModel: PlaylistViewModel(viewModel: viewModel), song: $viewModel.song, isScrolled: $viewModel.isScrolled, - currentIndex: $viewModel.currentIndex, isPlaying: $viewModel.isPlaying) .padding(.top, 10) .padding(.bottom, 150) @@ -65,9 +64,9 @@ struct SongDetailView: View { .onAppear() { viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) } - .onReceive(viewModel.$currentIndex) { output in - if output >= 0 { - self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[self.viewModel.currentIndex]) + .onChange(of: viewModel.song.id) { _ in + if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == viewModel.song.id }) { + self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[index]) self.viewModel.getAudioManager().AMset(song: self.viewModel.song) } } @@ -154,7 +153,6 @@ private struct PlayBar: View { var body: some View { VStack(spacing: 0) { Progressbar( - currentIndex: $viewModel.currentIndex, song: $viewModel.song, toast: $toast, isThumbActive: true) @@ -163,9 +161,9 @@ private struct PlayBar: View { //이전곡 재생 기능 if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index - 1 < 0 { - viewModel.currentIndex = defaultPlaylistSongs.count - 1 + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) } else { - viewModel.currentIndex = index - 1 + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) } } } label: { @@ -185,9 +183,9 @@ private struct PlayBar: View { if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") - viewModel.currentIndex = 0 + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) } else { - viewModel.currentIndex = index + 1 + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) } } } label: { @@ -218,7 +216,6 @@ private struct FavoriteButton: View { sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], predicate: PlaylistFilter(filter: "favorite").predicate, animation: .default) private var favoriteSongs: FetchedResults - @AppStorage("currentSongId") var currentSongId: String = "" var body: some View { Button { @@ -228,13 +225,11 @@ private struct FavoriteButton: View { .foregroundStyle(viewModel.isFavorite ? Color("\(viewModel.song.team)Point") : Color.white) } .onAppear() { - currentSongId = viewModel.song.id if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { viewModel.isFavorite = true } } .onChange(of: viewModel.song.id) { _ in - currentSongId = viewModel.song.id if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { viewModel.isFavorite = true } else { diff --git a/Halmap/Features/SongDetail/SongDetailViewModel.swift b/Halmap/Features/SongDetail/SongDetailViewModel.swift index ffba8df..be43f9a 100644 --- a/Halmap/Features/SongDetail/SongDetailViewModel.swift +++ b/Halmap/Features/SongDetail/SongDetailViewModel.swift @@ -17,7 +17,6 @@ final class SongDetailViewModel: ObservableObject { @Published var isFavorite = false @Published var isPlaying = false - @Published var currentIndex = -1 private var cancellables = Set() @@ -65,10 +64,8 @@ final class SongDetailViewModel: ObservableObject { func addDefaultPlaylist(defaultPlaylistSongs: FetchedResults) { if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == self.song.id}) { persistence.reorderSelectedSong(index: index, results: defaultPlaylistSongs) - self.currentIndex = defaultPlaylistSongs.count - 1 } else { persistence.saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(defaultPlaylistSongs.count)) - self.currentIndex = defaultPlaylistSongs.count } } diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index 274cb67..9183b32 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -12,14 +12,12 @@ struct Progressbar: View { @EnvironmentObject var audioManager: AudioManager @Binding var song: SongInfo - @Binding var currentIndex: Int @Binding var toast: Toast? let isThumbActive: Bool - init(currentIndex: Binding, song: Binding, toast: Binding, isThumbActive: Bool) { + init(song: Binding, toast: Binding, isThumbActive: Bool) { self.isThumbActive = isThumbActive - self._currentIndex = currentIndex self._song = song self._toast = toast UISlider.appearance().maximumTrackTintColor = UIColor(Color.customGray.opacity(0.2)) @@ -31,8 +29,7 @@ struct Progressbar: View { timeObserver: PlayerTimeObserver(player: audioManager.player), durationObserver: PlayerDurationObserver(player: audioManager.player), itemObserver: PlayerItemObserver(player: audioManager.player), - isThumbActive: isThumbActive, - currentIndex: $currentIndex, + isThumbActive: isThumbActive, song: $song, toast: $toast) @@ -61,7 +58,6 @@ struct AudioPlayerControlsView: View { @State private var currentDuration: TimeInterval = 0 @State private var state = PlaybackState.pause - @Binding var currentIndex: Int @Binding var song: SongInfo @Binding var toast: Toast? @@ -98,9 +94,9 @@ struct AudioPlayerControlsView: View { if self.state == .pause { if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == song.id}) { if index + 1 < defaultPlaylistSongs.count { - currentIndex = Int(defaultPlaylistSongs[index].order + 1) + self.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[Int(defaultPlaylistSongs[index].order + 1)]) } else { - currentIndex = 0 + self.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[0]) toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") } } diff --git a/Halmap/View/Component/Utility.swift b/Halmap/View/Component/Utility.swift index 93c7919..6871254 100644 --- a/Halmap/View/Component/Utility.swift +++ b/Halmap/View/Component/Utility.swift @@ -26,4 +26,16 @@ class Utility: NSObject { return text } + static func convertSongToSongInfo(song: CollectedSong) -> SongInfo { + SongInfo( + id: song.id ?? "", + team: song.team ?? "", + type: song.type, + title: song.title ?? "", + lyrics: song.lyrics ?? "", + info: song.info ?? "", + url: song.url ?? "" + ) + } + } From fd8d12574a61d2ce12e907c7d0745a75b1be04d0 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Fri, 1 Dec 2023 16:08:02 +0900 Subject: [PATCH 18/56] =?UTF-8?q?[Feat]=20=EB=AF=B8=EB=94=94=EC=96=B4=20?= =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=EC=96=B4=20=EB=8B=A4=EC=9D=8C=20?= =?UTF-8?q?=ED=8A=B8=EB=9E=99=20=EC=9E=AC=EC=83=9D=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Data/AudioManager.swift | 15 +---------- .../Features/SongDetail/SongDetailView.swift | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Halmap/Data/AudioManager.swift b/Halmap/Data/AudioManager.swift index 125b815..c04d90d 100644 --- a/Halmap/Data/AudioManager.swift +++ b/Halmap/Data/AudioManager.swift @@ -16,9 +16,7 @@ final class AudioManager: NSObject, ObservableObject { var item: AVPlayerItem? @Published private(set) var isPlaying: Bool = false - @Published var currentIndex: Int = 0 //현재 재생중인 곡 index - - var song: SongInfo? + @Published var song: SongInfo? private var playerItemContext = 0 override init() { @@ -131,7 +129,6 @@ final class AudioManager: NSObject, ObservableObject { player.replaceCurrentItem(with: nil) NotificationCenter.default.removeObserver(self) } - deinit { player.replaceCurrentItem(with: nil) self.item?.removeObserver(self as NSObject, @@ -189,16 +186,6 @@ extension AudioManager { return .commandFailed } - commandCenter.nextTrackCommand.addTarget { [unowned self] event in - print("다음 \(currentIndex+1)") - return .success - } - - commandCenter.previousTrackCommand.addTarget { [unowned self] event in - print("이전 \(currentIndex-1)") - return .success - } - commandCenter.changePlaybackPositionCommand.addTarget { [unowned self] event in if let positionTime = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime { let seekTime = CMTime(value: Int64(positionTime), timescale: 1) diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index 4a5eb81..aee2c3d 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -6,6 +6,7 @@ // import SwiftUI import AVFoundation +import MediaPlayer struct SongDetailView: View { @@ -63,6 +64,7 @@ struct SongDetailView: View { } .onAppear() { viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) + setMediaPlayerNextTrack() } .onChange(of: viewModel.song.id) { _ in if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == viewModel.song.id }) { @@ -96,6 +98,31 @@ struct SongDetailView: View { }) }.padding(20) } + + private func setMediaPlayerNextTrack() { + let commandCenter = MPRemoteCommandCenter.shared() + commandCenter.nextTrackCommand.addTarget { _ in + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index + 1 > defaultPlaylistSongs.count - 1 { + toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) + } else { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) + } + } + return .success + } + commandCenter.previousTrackCommand.addTarget { _ in + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index - 1 < 0 { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) + } else { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) + } + } + return .success + } + } } private struct Lyric: View { From 472c561693945faa611ce26b4287f075763f131d Mon Sep 17 00:00:00 2001 From: JIWON1923 Date: Fri, 1 Dec 2023 16:30:21 +0900 Subject: [PATCH 19/56] =?UTF-8?q?[Feat]=20=ED=95=98=ED=94=84=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SongList/MainSongListTabView.swift | 82 +++++++++++++++++-- Halmap/View/HalfSheetView.swift | 7 +- 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 6f2b456..cfc66cc 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -12,8 +12,13 @@ struct MainSongListTabView: View { @AppStorage("selectedTeam") var selectedTeam: String = "Hanwha" @EnvironmentObject var dataManager: DataManager @EnvironmentObject var audioManager: AudioManager + @Environment(\.managedObjectContext) private var viewContext @StateObject var viewModel = MainSongListTabViewModel() + @State private var isShowingHalfSheet: Bool = false + @State private var isActiveNavigatioinLink: Bool = false + @State private var selectedSong: SongInfo? + let persistence = PersistenceController.shared var body: some View { @@ -47,15 +52,17 @@ struct MainSongListTabView: View { info: song.info, url: song.url) - NavigationLink(destination: SongDetailView(viewModel: SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: songInfo))) { + ZStack { HStack(spacing: 16) { Image(viewModel.getSongImage(for: songInfo)) .resizable() .frame(width: 40, height: 40) .cornerRadius(8) - VStack(alignment: .leading, spacing: 6){ + + VStack(alignment: .leading, spacing: 6) { Text(song.title) .font(Font.Halmap.CustomBodyMedium) + if !song.info.isEmpty { Text(song.info) .font(Font.Halmap.CustomCaptionMedium) @@ -64,17 +71,56 @@ struct MainSongListTabView: View { } .frame(maxWidth: .infinity, alignment: .leading) .lineLimit(1) + Spacer() + + Image(systemName: "ellipsis") + .foregroundColor(.customDarkGray) + .onTapGesture { + selectedSong = songInfo + isShowingHalfSheet.toggle() + } + .frame(width: 35, height: 35) + } + + if let selectedSong { + NavigationLink(destination: SongDetailView(viewModel: SongDetailViewModel(audioManager: audioManager, + dataManager: dataManager, + persistence: persistence, + song: selectedSong)), + isActive: $isActiveNavigatioinLink) { + EmptyView() + } + .opacity(0) + .disabled(true) } + + } + .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) + + .listRowBackground(Color.systemBackground) + .listRowSeparatorTint(Color.customGray) + .background(Color.systemBackground) + .onTapGesture { + selectedSong = songInfo + isActiveNavigatioinLink.toggle() } + } - .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) - .listRowBackground(Color.systemBackground) - .listRowSeparatorTint(Color.customGray) RequestSongView(buttonColor: Color.HalmacPoint) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) .listRowBackground(Color.systemBackground) .listRowSeparatorTint(Color.customGray) } + .sheet(isPresented: $isShowingHalfSheet) { + if let selectedSong { + HalfSheet { + HalfSheetView(collectedSong: CollectedSong(context: viewContext), + songInfo: selectedSong, + team: selectedTeam, + showSheet: $isShowingHalfSheet) + } + } + } .padding(.horizontal, 20) .listStyle(.plain) .tag(0) @@ -89,7 +135,7 @@ struct MainSongListTabView: View { info: song.info, url: song.url) - NavigationLink(destination: SongDetailView(viewModel: SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: songInfo))) { + ZStack { HStack(spacing: 16) { Image(viewModel.getPlayerImage(for: songInfo)) .resizable() @@ -106,7 +152,31 @@ struct MainSongListTabView: View { } .frame(maxWidth: .infinity, alignment: .leading) .lineLimit(1) + Spacer() + + Image(systemName: "ellipsis") + .foregroundColor(.customDarkGray) + .onTapGesture { + selectedSong = songInfo + isShowingHalfSheet.toggle() + } + .frame(width: 35, height: 35) + } + + .onTapGesture { + selectedSong = songInfo + isActiveNavigatioinLink.toggle() + } + + NavigationLink(destination: SongDetailView(viewModel: SongDetailViewModel(audioManager: audioManager, + dataManager: dataManager, + persistence: persistence, + song: songInfo)), + isActive: $isActiveNavigatioinLink) { + EmptyView() } + .opacity(0) + .disabled(true) } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) diff --git a/Halmap/View/HalfSheetView.swift b/Halmap/View/HalfSheetView.swift index 2cde47e..cd5e540 100644 --- a/Halmap/View/HalfSheetView.swift +++ b/Halmap/View/HalfSheetView.swift @@ -11,19 +11,18 @@ struct HalfSheetView: View { @Environment(\.managedObjectContext) var managedObjectContext let persistence = PersistenceController.shared @ObservedObject var collectedSong: CollectedSong - @State var songData: Song - @State var song: CollectedSong = CollectedSong() + @State var songInfo: SongInfo var team: String @Binding var showSheet: Bool var body: some View { VStack(spacing: 0) { HStack(spacing: 16) { - Image("\(team)SongListImage") + Image("\(songInfo.team)SongListImage") .frame(width: 52, height: 52) .shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4) VStack(alignment: .leading, spacing: 6) { - Text(songData.title ) + Text(songInfo.title ) .font(Font.Halmap.CustomBodyBold) .foregroundColor(.customBlack) Text(TeamName(rawValue: team )?.fetchTeamNameKr() ?? ".") From 79cc48dc2e10cef65ea511fe766b498c37d4dad1 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sat, 2 Dec 2023 00:40:55 +0900 Subject: [PATCH 20/56] =?UTF-8?q?[Feat]=20=ED=95=98=ED=94=84=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20-=EC=A0=84=EC=B2=B4=20=EC=9E=AC=EC=83=9D?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20UI=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/PlaylistView.swift | 1 + .../Features/SongDetail/SongDetailView.swift | 3 ++ .../SongList/MainSongListTabView.swift | 18 ++++---- .../SongStorage/ScalingHeaderView.swift | 40 +++++++++++++++-- .../SongStorage/SongStorageViewModel.swift | 3 ++ Halmap/HalfModalPresentationController.swift | 2 +- Halmap/Persistence.swift | 43 +++++++++++-------- Halmap/View/Component/Utility.swift | 9 ++++ Halmap/View/HalfSheetView.swift | 42 ++++++++++++------ 9 files changed, 117 insertions(+), 44 deletions(-) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index d106ec1..36f4451 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -25,6 +25,7 @@ struct PlaylistView: View { getPlaylistRowView(song: playListSong) .background(Color.white.opacity(0.001)) .onTapGesture { + print("self.song", playListSong.safeTitle, playListSong.order) self.song = viewModel.didTappedSongCell(song: playListSong) } }.onDelete { indexSet in diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index aee2c3d..ce55602 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -17,6 +17,7 @@ struct SongDetailView: View { predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var defaultPlaylistSongs: FetchedResults + @AppStorage("currentSongId") var currentSongId: String = "" @State var isPlaylistView = false @State private var toast: Toast? = nil @@ -63,10 +64,12 @@ struct SongDetailView: View { } } .onAppear() { + self.currentSongId = viewModel.song.id viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) setMediaPlayerNextTrack() } .onChange(of: viewModel.song.id) { _ in + self.currentSongId = viewModel.song.id if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == viewModel.song.id }) { self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[index]) self.viewModel.getAudioManager().AMset(song: self.viewModel.song) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index cfc66cc..b8e903a 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -18,6 +18,7 @@ struct MainSongListTabView: View { @State private var isShowingHalfSheet: Bool = false @State private var isActiveNavigatioinLink: Bool = false @State private var selectedSong: SongInfo? + @State var collectedSong: CollectedSong? let persistence = PersistenceController.shared @@ -75,11 +76,13 @@ struct MainSongListTabView: View { Image(systemName: "ellipsis") .foregroundColor(.customDarkGray) + .frame(maxWidth: 35, maxHeight: .infinity) + .background(Color.white.opacity(0.001)) .onTapGesture { + collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") selectedSong = songInfo isShowingHalfSheet.toggle() } - .frame(width: 35, height: 35) } if let selectedSong { @@ -112,12 +115,9 @@ struct MainSongListTabView: View { .listRowSeparatorTint(Color.customGray) } .sheet(isPresented: $isShowingHalfSheet) { - if let selectedSong { - HalfSheet { - HalfSheetView(collectedSong: CollectedSong(context: viewContext), - songInfo: selectedSong, - team: selectedTeam, - showSheet: $isShowingHalfSheet) + if let collectedSong { + HalfSheet{ + HalfSheetView(collectedSongData: collectedSong, showSheet: $isShowingHalfSheet) } } } @@ -156,11 +156,13 @@ struct MainSongListTabView: View { Image(systemName: "ellipsis") .foregroundColor(.customDarkGray) + .frame(maxWidth: 35, maxHeight: .infinity) + .background(Color.white.opacity(0.001)) .onTapGesture { + collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") selectedSong = songInfo isShowingHalfSheet.toggle() } - .frame(width: 35, height: 35) } .onTapGesture { diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index eb43c3e..e055bb5 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -8,6 +8,7 @@ import SwiftUI struct ScalingHeaderView: View { + @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var dataManager: DataManager @EnvironmentObject var audioManager: AudioManager let persistence = PersistenceController.shared @@ -15,6 +16,8 @@ struct ScalingHeaderView: View { @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], predicate: PlaylistFilter(filter: "favorite").predicate, animation: .default) private var collectedSongs: FetchedResults @StateObject var viewModel: SongStorageViewModel + @State var collectedSongData: CollectedSong? + @State var isShowSheet = false var body: some View { ScrollView(.vertical, showsIndicators: false) { @@ -33,6 +36,19 @@ struct ScalingHeaderView: View { .font(Font.Halmap.CustomCaptionBold) .foregroundColor(.customDarkGray) Spacer() + Button { + print("전체 재생하기") + } label: { + HStack(spacing: 5) { + Image(systemName: "play.circle.fill") + .foregroundColor(.mainGreen) + .font(.system(size: 20)) + Text("전체 재생하기") + .font(Font.Halmap.CustomCaptionBold) + .foregroundColor(.mainGreen) + } + .opacity(viewModel.checkScrollRequirement(listCount: collectedSongs.count)) + } } .padding(.horizontal, 20) .padding(.vertical, UIScreen.getHeight(17)) @@ -82,12 +98,14 @@ struct ScalingHeaderView: View { } .frame(maxWidth: .infinity, alignment: .leading) .lineLimit(1) - + Button { - viewModel.deleteSong(favoriteSong: favoriteSong) + self.collectedSongData = favoriteSong + isShowSheet.toggle() } label: { - Image(systemName: "heart.fill") - .foregroundColor(.mainGreen) + Image(systemName: "ellipsis") + .foregroundColor(.customDarkGray) + .frame(maxWidth: 35, maxHeight: .infinity) } } .padding(.horizontal, 20) @@ -106,6 +124,13 @@ struct ScalingHeaderView: View { } .coordinateSpace(name: "StorageScroll") .background(Color.systemBackground) + .sheet(isPresented: $isShowSheet) { + if let collectedSongData { + HalfSheet { + HalfSheetView(collectedSongData: collectedSongData, showSheet: $isShowSheet) + } + } + } } var defaultHeader: some View { @@ -116,6 +141,13 @@ struct ScalingHeaderView: View { Text("보관함") .font(Font.Halmap.CustomLargeTitle) Spacer() + Button { + print("전체 재생") + } label: { + Image(systemName: "play.circle.fill") + .foregroundColor(.mainGreen) + .font(.system(size: 50)) + } } .padding(EdgeInsets(top: 0, leading: 20, bottom: 20, trailing: 20)) } diff --git a/Halmap/Features/SongStorage/SongStorageViewModel.swift b/Halmap/Features/SongStorage/SongStorageViewModel.swift index 9d698ce..6336d1e 100644 --- a/Halmap/Features/SongStorage/SongStorageViewModel.swift +++ b/Halmap/Features/SongStorage/SongStorageViewModel.swift @@ -77,4 +77,7 @@ final class SongStorageViewModel: ObservableObject { isDraged ? (59 + topEdge) : maxHeight + offset } + func checkScrollRequirement(listCount: Int) -> Double{ + UIScreen.screenHeight - (59 + topEdge) - CGFloat(75 * listCount) <= 0 && self.isDraged ? 1 : 0 + } } diff --git a/Halmap/HalfModalPresentationController.swift b/Halmap/HalfModalPresentationController.swift index 24ac540..720f7f5 100644 --- a/Halmap/HalfModalPresentationController.swift +++ b/Halmap/HalfModalPresentationController.swift @@ -14,7 +14,7 @@ class HalfSheetController: UIHostingController where Content : if let presentation = sheetPresentationController { presentation.detents = [.medium()] - presentation.prefersGrabberVisible = false + presentation.prefersGrabberVisible = true presentation.largestUndimmedDetentIdentifier = .none } } diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index d35689b..3505c81 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -77,29 +77,34 @@ struct PersistenceController { } } - func saveSongs(song: SongInfo, playListTitle: String?, menuType: MenuType, collectedSongs: FetchedResults) { + func saveSongs(collectedSong: CollectedSong, playListTitle: String?, menuType: MenuType, collectedSongs: FetchedResults) { let context = container.viewContext - let collectedSong = CollectedSong(context: context) + var order = Int64(collectedSongs.count) - switch menuType { - case .cancelLiked: - break - case .playNext: - // TODO: 현재 곡 다음순서로 넣는 로직 필요 - collectedSong.order = Int64(collectedSongs.count) - case .playLast: - collectedSong.order = Int64(collectedSongs.count) + if let currentIndex = collectedSongs.firstIndex(where: {$0.id == UserDefaults.standard.string(forKey: "currentSongId")}) { + switch menuType { + case .playNext: + if let index = collectedSongs.firstIndex(where: { $0.id == collectedSong.id }) { + + } else { + + } + + break + case .playLast: + if let index = collectedSongs.firstIndex(where: { $0.id == collectedSong.id }) { + + } + break + default: + resetBufferList(song: collectedSong) + break + } } - collectedSong.id = song.id - collectedSong.title = song.title - collectedSong.info = song.info - collectedSong.lyrics = song.lyrics - collectedSong.url = song.url - collectedSong.type = song.type - collectedSong.playListTitle = playListTitle - collectedSong.team = song.team - collectedSong.date = Date() +// collectedSong.playListTitle = playListTitle +// collectedSong.date = Date() +// collectedSong.order = order if context.hasChanges { do { diff --git a/Halmap/View/Component/Utility.swift b/Halmap/View/Component/Utility.swift index 6871254..ce34474 100644 --- a/Halmap/View/Component/Utility.swift +++ b/Halmap/View/Component/Utility.swift @@ -38,4 +38,13 @@ class Utility: NSObject { ) } + static func getAlbumImage(with song: CollectedSong, seasonSongs: [[String]]) -> String { + let song = Utility.convertSongToSongInfo(song: song) + if seasonSongs[TeamName(rawValue: song.team)?.fetchTeamIndex() ?? 0].contains(song.title) { + return "\(song.team)23" + } else { + return "\(song.team)\(song.type ? "Player" : "Album")" + } + } + } diff --git a/Halmap/View/HalfSheetView.swift b/Halmap/View/HalfSheetView.swift index cd5e540..d9c870c 100644 --- a/Halmap/View/HalfSheetView.swift +++ b/Halmap/View/HalfSheetView.swift @@ -8,24 +8,29 @@ import SwiftUI struct HalfSheetView: View { + @EnvironmentObject var dataManager: DataManager @Environment(\.managedObjectContext) var managedObjectContext - let persistence = PersistenceController.shared - @ObservedObject var collectedSong: CollectedSong - @State var songInfo: SongInfo - var team: String + @State var collectedSongData: CollectedSong @Binding var showSheet: Bool + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], + predicate: PlaylistFilter(filter: "favorite").predicate, + animation: .default) private var favoriteSongs: FetchedResults var body: some View { VStack(spacing: 0) { HStack(spacing: 16) { - Image("\(songInfo.team)SongListImage") + Image(Utility.getAlbumImage(with: collectedSongData, seasonSongs: dataManager.seasonSongs)) + .resizable() + .cornerRadius(10) .frame(width: 52, height: 52) .shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4) VStack(alignment: .leading, spacing: 6) { - Text(songInfo.title ) + Text(collectedSongData.safeTitle) .font(Font.Halmap.CustomBodyBold) .foregroundColor(.customBlack) - Text(TeamName(rawValue: team )?.fetchTeamNameKr() ?? ".") + Text(TeamName(rawValue: collectedSongData.safeTeam)?.fetchTeamNameKr() ?? ".") .font(Font.Halmap.CustomCaptionMedium) .foregroundColor(.customDarkGray) } @@ -36,10 +41,15 @@ struct HalfSheetView: View { Divider() .overlay(Color.customGray.opacity(0.6)) .padding(EdgeInsets(top: 20, leading: 0, bottom: 29, trailing: 0)) - MenuItem(menuType: .cancelLiked, collectedSong: collectedSong, showSheet: $showSheet) - MenuItem(menuType: .playNext, collectedSong: collectedSong, showSheet: $showSheet) - MenuItem(menuType: .playLast, collectedSong: collectedSong, showSheet: $showSheet) + if let index = favoriteSongs.firstIndex(where: { $0.id == collectedSongData.id}) { + MenuItem(menuType: .cancelLiked, collectedSong: favoriteSongs[index], showSheet: $showSheet) + } else { + MenuItem(menuType: .liked, collectedSong: collectedSongData, showSheet: $showSheet) + } + MenuItem(menuType: .playNext, collectedSong: collectedSongData, showSheet: $showSheet) + MenuItem(menuType: .playLast, collectedSong: collectedSongData, showSheet: $showSheet) Spacer() + } } } @@ -48,10 +58,11 @@ struct MenuItem: View { let menuType: MenuType @ObservedObject var collectedSong: CollectedSong @Binding var showSheet: Bool + @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults var body: some View { Button { - menuType.fetchFunction(collectedSong: collectedSong) + menuType.fetchFunction(collectedSong: collectedSong, playlists: collectedSongs) showSheet.toggle() } label: { HStack(spacing: 24) { @@ -69,12 +80,15 @@ struct MenuItem: View { } enum MenuType { + case liked case cancelLiked case playNext case playLast func fetchMenuTitle() -> String { switch self { + case .liked: + return "좋아요" case .cancelLiked: return "좋아요 취소" case .playNext: @@ -85,6 +99,8 @@ enum MenuType { } func fetchImage() -> String { switch self { + case .liked: + return "heart" case .cancelLiked: return "heart.slash.fill" case .playNext: @@ -94,9 +110,11 @@ enum MenuType { } } - func fetchFunction(collectedSong: CollectedSong) { + func fetchFunction(collectedSong: CollectedSong, playlists: FetchedResults) { let persistence = PersistenceController.shared switch self { + case .liked: + return persistence.saveSongs(song: Utility.convertSongToSongInfo(song: collectedSong), playListTitle: "favorite") case .cancelLiked: return persistence.deleteSongs(song: collectedSong) case .playNext: From 3d2199a2bb992703d45f0c8e0d7e58a3b2d4afa8 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sat, 2 Dec 2023 01:29:39 +0900 Subject: [PATCH 21/56] =?UTF-8?q?[Feat]=20=EC=A0=84=EC=B2=B4=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=EC=9D=8C=EC=95=85=20=EC=9E=AC=EC=83=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=ED=8A=B8=EB=A6=AC=EA=B1=B0=20song=20->=20?= =?UTF-8?q?currentSongId=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC?= =?UTF-8?q?=20=EC=97=AC=EB=9F=AC=20=EB=B7=B0=EC=97=90=EC=84=9C=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/SongDetail/SongDetailView.swift | 2 + .../SongList/MainSongListTabView.swift | 32 ++++++----- .../SongStorage/ScalingHeaderView.swift | 44 +++++++++------ Halmap/Persistence.swift | 55 +++++++++++++++++-- 4 files changed, 99 insertions(+), 34 deletions(-) diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index ce55602..e168620 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -70,6 +70,8 @@ struct SongDetailView: View { } .onChange(of: viewModel.song.id) { _ in self.currentSongId = viewModel.song.id + } + .onChange(of: currentSongId) { _ in if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == viewModel.song.id }) { self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[index]) self.viewModel.getAudioManager().AMset(song: self.viewModel.song) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index b8e903a..8ea84dc 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -86,10 +86,12 @@ struct MainSongListTabView: View { } if let selectedSong { - NavigationLink(destination: SongDetailView(viewModel: SongDetailViewModel(audioManager: audioManager, - dataManager: dataManager, - persistence: persistence, - song: selectedSong)), + NavigationLink(destination: + SongDetailView(viewModel: SongDetailViewModel( + audioManager: audioManager, + dataManager: dataManager, + persistence: persistence, + song: selectedSong)), isActive: $isActiveNavigatioinLink) { EmptyView() } @@ -160,7 +162,6 @@ struct MainSongListTabView: View { .background(Color.white.opacity(0.001)) .onTapGesture { collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") - selectedSong = songInfo isShowingHalfSheet.toggle() } } @@ -170,15 +171,20 @@ struct MainSongListTabView: View { isActiveNavigatioinLink.toggle() } - NavigationLink(destination: SongDetailView(viewModel: SongDetailViewModel(audioManager: audioManager, - dataManager: dataManager, - persistence: persistence, - song: songInfo)), - isActive: $isActiveNavigatioinLink) { - EmptyView() + if let selectedSong { + NavigationLink(destination: + SongDetailView(viewModel: SongDetailViewModel( + audioManager: audioManager, + dataManager: dataManager, + persistence: persistence, + song: selectedSong)), + isActive: $isActiveNavigatioinLink) { + EmptyView() + } + .opacity(0) + .disabled(true) } - .opacity(0) - .disabled(true) + } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index e055bb5..9a61ac1 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -19,6 +19,8 @@ struct ScalingHeaderView: View { @State var collectedSongData: CollectedSong? @State var isShowSheet = false + @AppStorage("currentSongId") var currentSongId: String = "" + var body: some View { ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 15) { @@ -36,18 +38,22 @@ struct ScalingHeaderView: View { .font(Font.Halmap.CustomCaptionBold) .foregroundColor(.customDarkGray) Spacer() - Button { - print("전체 재생하기") - } label: { - HStack(spacing: 5) { - Image(systemName: "play.circle.fill") - .foregroundColor(.mainGreen) - .font(.system(size: 20)) - Text("전체 재생하기") - .font(Font.Halmap.CustomCaptionBold) - .foregroundColor(.mainGreen) + if collectedSongs.count > 0 { + Button { + print("전체 재생하기") + persistence.fetchPlaylistAll() + currentSongId = collectedSongs.first!.safeId + } label: { + HStack(spacing: 5) { + Image(systemName: "play.circle.fill") + .foregroundColor(.mainGreen) + .font(.system(size: 20)) + Text("전체 재생하기") + .font(Font.Halmap.CustomCaptionBold) + .foregroundColor(.mainGreen) + } + .opacity(viewModel.checkScrollRequirement(listCount: collectedSongs.count)) } - .opacity(viewModel.checkScrollRequirement(listCount: collectedSongs.count)) } } .padding(.horizontal, 20) @@ -141,12 +147,16 @@ struct ScalingHeaderView: View { Text("보관함") .font(Font.Halmap.CustomLargeTitle) Spacer() - Button { - print("전체 재생") - } label: { - Image(systemName: "play.circle.fill") - .foregroundColor(.mainGreen) - .font(.system(size: 50)) + if collectedSongs.count > 0 { + Button { + //TODO: 첫번째 곡 재생 -> SongDetailView song + persistence.fetchPlaylistAll() + self.currentSongId = collectedSongs.first!.safeId + } label: { + Image(systemName: "play.circle.fill") + .foregroundColor(.mainGreen) + .font(.system(size: 50)) + } } } .padding(EdgeInsets(top: 0, leading: 20, bottom: 20, trailing: 20)) diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index 3505c81..e0b75bb 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -77,6 +77,22 @@ struct PersistenceController { } } + private func saveSongs(song: CollectedSong, playListTitle: String, order: Int64) { + let context = container.viewContext + let collectedSong = CollectedSong(context: context) + + collectedSong.id = song.id + collectedSong.title = song.title + collectedSong.info = song.info + collectedSong.lyrics = song.lyrics + collectedSong.url = song.url + collectedSong.type = song.type + collectedSong.playListTitle = playListTitle + collectedSong.team = song.team + collectedSong.date = Date() + collectedSong.order = order + } + func saveSongs(collectedSong: CollectedSong, playListTitle: String?, menuType: MenuType, collectedSongs: FetchedResults) { let context = container.viewContext var order = Int64(collectedSongs.count) @@ -102,10 +118,6 @@ struct PersistenceController { } } -// collectedSong.playListTitle = playListTitle -// collectedSong.date = Date() -// collectedSong.order = order - if context.hasChanges { do { try context.save() @@ -148,6 +160,41 @@ struct PersistenceController { } } + func fetchPlaylistAll() { + let fetchRequest = CollectedSong.fetchRequest() + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)] + fetchRequest.predicate = PlaylistFilter(filter: "defaultPlaylist").predicate + + do { + let old = try container.viewContext.fetch(fetchRequest) + + for song in old { + container.viewContext.delete(song) + } + + try container.viewContext.save() + } catch { + print("Failed to fetch and delete songs: \(error)") + } + + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)] + fetchRequest.predicate = PlaylistFilter(filter: "favorite").predicate + + do { + let new = try container.viewContext.fetch(fetchRequest) + + var order = 0 + for song in new { + print("***", song.safeTitle, order) + saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(order)) + order += 1 + } + try container.viewContext.save() + } catch { + print("Failed to fetch and delete songs: \(error)") + } + } + /// defaultPlaylist 순서를 변경하는 함수 func moveDefaultPlaylistSong(from source: IndexSet, to destination: Int, based results: FetchedResults){ From 0de4f489ffc5adaf4184fae2bb7320a350f230df Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sat, 2 Dec 2023 02:18:49 +0900 Subject: [PATCH 22/56] =?UTF-8?q?[Feat]=20playlist=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=20=EC=8B=9C=20gradient=20=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/PlaylistView.swift | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 36f4451..e3bc748 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -28,6 +28,12 @@ struct PlaylistView: View { print("self.song", playListSong.safeTitle, playListSong.order) self.song = viewModel.didTappedSongCell(song: playListSong) } + .background( + playListSong.order == 0 ? GeometryReader{ + Color.clear.preference( + key: ViewOffsetKey.self, + value: -$0.frame(in: .named("list")).origin.y) + } : nil ) }.onDelete { indexSet in for index in indexSet { if collectedSongs.count - 1 == 0 { @@ -54,31 +60,27 @@ struct PlaylistView: View { } .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) .listRowBackground(Color("\(song.team)Sub")) + .onPreferenceChange(ViewOffsetKey.self) { + if $0 > -112 { + withAnimation { + isScrolled = true + } + } else { + withAnimation { + isScrolled = false + } + } + } Color.clear.frame(height:70) .listRowBackground(Color.clear) } + .coordinateSpace(name: "list") .listStyle(.plain) .modifier(ListBackgroundModifier()) + .padding(.top, 5) } - } .background(Color("\(song.team)Sub")) - .background(GeometryReader{ - Color.clear.preference( - key: ViewOffsetKey.self, - value: -$0.frame(in: .named("scroll")).origin.y) - }) - .onPreferenceChange(ViewOffsetKey.self) { - if $0 > 0 { - withAnimation { - isScrolled = true - } - } else { - withAnimation { - isScrolled = false - } - } - } } @ViewBuilder From a83ef99bb1dc263f0f8029d8990777cf41e92297 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sat, 2 Dec 2023 17:04:05 +0900 Subject: [PATCH 23/56] =?UTF-8?q?[Style]=20=EB=B3=B4=EA=B4=80=ED=95=A8=20?= =?UTF-8?q?=EC=85=80=20=EC=97=AC=EB=B0=B1=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongStorage/ScalingHeaderView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index 9a61ac1..d2e03af 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -94,7 +94,7 @@ struct ScalingHeaderView: View { .resizable() .frame(width: 40, height: 40) .cornerRadius(8) - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: 6) { Text(favoriteSong.safeTitle) .font(Font.Halmap.CustomBodyMedium) .foregroundColor(.black) @@ -115,7 +115,7 @@ struct ScalingHeaderView: View { } } .padding(.horizontal, 20) - .padding(.vertical, 10) + .padding(.vertical, 15) } } .background(Color.systemBackground) From f952c01f9310935516a3d1f214e78cb52795009d Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sat, 2 Dec 2023 17:23:25 +0900 Subject: [PATCH 24/56] =?UTF-8?q?[Feat]=20=EB=A9=94=EC=9D=B8=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=84=EC=B2=B4=20=EC=9E=AC=EC=83=9D=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SongList/MainSongListTabView.swift | 18 +++++- .../SongStorage/ScalingHeaderView.swift | 1 - Halmap/Persistence.swift | 55 ++++++++++++++++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 8ea84dc..ab83919 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -10,6 +10,7 @@ import SwiftUI struct MainSongListTabView: View { @AppStorage("selectedTeam") var selectedTeam: String = "Hanwha" + @AppStorage("currentSongId") var currentSongId: String = "" @EnvironmentObject var dataManager: DataManager @EnvironmentObject var audioManager: AudioManager @Environment(\.managedObjectContext) private var viewContext @@ -34,9 +35,24 @@ struct MainSongListTabView: View { .font(Font.Halmap.CustomCaptionBold) .foregroundColor(.customDarkGray) Spacer() + Button { + let currentSongs = viewModel.index == 0 ? dataManager.teamSongs : dataManager.playerSongs + persistence.fetchPlaylistAllMain(newSongs: currentSongs) + currentSongId = currentSongs.first!.id + + } label: { + HStack(spacing: 5) { + Image(systemName: "play.circle.fill") + .foregroundColor(.HalmacPoint) + .font(.system(size: 20)) + Text("전체 재생하기") + .font(Font.Halmap.CustomCaptionBold) + .foregroundColor(.HalmacPoint) + } + } } .padding(EdgeInsets(top: 20, leading: 20, bottom: 15, trailing: 20)) - .padding(.top, UIScreen.getHeight(27)) + .padding(.top, UIScreen.getHeight(48)) Divider() .overlay(Color.customGray.opacity(0.6)) diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index d2e03af..ae4392e 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -40,7 +40,6 @@ struct ScalingHeaderView: View { Spacer() if collectedSongs.count > 0 { Button { - print("전체 재생하기") persistence.fetchPlaylistAll() currentSongId = collectedSongs.first!.safeId } label: { diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index e0b75bb..28dcaba 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -77,6 +77,31 @@ struct PersistenceController { } } + func saveSongs(song: Song, playListTitle: String?, order: Int64) { + let context = container.viewContext + let collectedSong = CollectedSong(context: context) + + collectedSong.id = song.id + collectedSong.title = song.title + collectedSong.info = song.info + collectedSong.lyrics = song.lyrics + collectedSong.url = song.url + collectedSong.type = song.type + collectedSong.playListTitle = playListTitle + collectedSong.team = UserDefaults.standard.string(forKey: "selectedTeam") + collectedSong.date = Date() + collectedSong.order = order + + if context.hasChanges { + do { + try context.save() + } catch { + let nsError = error as NSError + fatalError("error \(nsError), \(nsError.userInfo)") + } + } + } + private func saveSongs(song: CollectedSong, playListTitle: String, order: Int64) { let context = container.viewContext let collectedSong = CollectedSong(context: context) @@ -185,7 +210,35 @@ struct PersistenceController { var order = 0 for song in new { - print("***", song.safeTitle, order) + saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(order)) + order += 1 + } + try container.viewContext.save() + } catch { + print("Failed to fetch and delete songs: \(error)") + } + } + + func fetchPlaylistAllMain(newSongs: [Song]) { + let fetchRequest = CollectedSong.fetchRequest() + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)] + fetchRequest.predicate = PlaylistFilter(filter: "defaultPlaylist").predicate + + do { + let old = try container.viewContext.fetch(fetchRequest) + + for song in old { + container.viewContext.delete(song) + } + + try container.viewContext.save() + } catch { + print("Failed to fetch and delete songs: \(error)") + } + + do { + var order = 0 + for song in newSongs { saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(order)) order += 1 } From f6c91160e5580cddf9402c992fc5baa59dfa9796 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sat, 2 Dec 2023 22:29:02 +0900 Subject: [PATCH 25/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B0=94=EB=A1=9C=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=EC=97=90=20=EC=9E=AC=EC=83=9D,=20=EB=A7=88=EC=A7=80?= =?UTF-8?q?=EB=A7=89=EC=97=90=20=EC=9E=AC=EC=83=9D=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Persistence.swift | 65 +++++++++++++++++++++++++-------- Halmap/View/HalfSheetView.swift | 4 +- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index 28dcaba..6566811 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -12,9 +12,9 @@ struct PersistenceController { @AppStorage("selectedTeam") var selectedTeam = "" static let shared = PersistenceController() - + let container: NSPersistentContainer - + init(inMemory: Bool = false) { container = NSPersistentContainer(name: "Halmap") if inMemory { @@ -120,27 +120,62 @@ struct PersistenceController { func saveSongs(collectedSong: CollectedSong, playListTitle: String?, menuType: MenuType, collectedSongs: FetchedResults) { let context = container.viewContext - var order = Int64(collectedSongs.count) + let count = collectedSongs.count - if let currentIndex = collectedSongs.firstIndex(where: {$0.id == UserDefaults.standard.string(forKey: "currentSongId")}) { - switch menuType { - case .playNext: + switch menuType { + case .playNext: + if let currentIndex = collectedSongs.firstIndex(where: {$0.id == UserDefaults.standard.string(forKey: "currentSongId")}) { if let index = collectedSongs.firstIndex(where: { $0.id == collectedSong.id }) { - + if index < currentIndex { + var startOrder = collectedSongs[index].order + for i in index+1...currentIndex { + print(collectedSongs[i].safeTitle, collectedSongs[i].order, startOrder) + collectedSongs[i].order = startOrder + startOrder += 1 + } + collectedSongs[index].order = startOrder + } else if index > currentIndex { + let newOrder = collectedSongs[currentIndex+1].order + var startOrder = collectedSongs[currentIndex+1].order+1 + for i in currentIndex+1.. Date: Sun, 3 Dec 2023 20:58:48 +0900 Subject: [PATCH 26/56] =?UTF-8?q?[Feat]=20=EA=B7=B8=EB=9D=BC=EB=94=94?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EA=B8=B0=EC=A4=80=20=EC=99=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/PlaylistView.swift | 2 +- Halmap/Features/SongDetail/SongDetailView.swift | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index e3bc748..abade01 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -61,7 +61,7 @@ struct PlaylistView: View { .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) .listRowBackground(Color("\(song.team)Sub")) .onPreferenceChange(ViewOffsetKey.self) { - if $0 > -112 { + if $0 > -(UIScreen.getHeight(90)) { withAnimation { isScrolled = true } diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift index e168620..99ddb06 100644 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ b/Halmap/Features/SongDetail/SongDetailView.swift @@ -27,15 +27,13 @@ struct SongDetailView: View { .ignoresSafeArea() if isPlaylistView { - VStack { - PlaylistView( - viewModel: PlaylistViewModel(viewModel: viewModel), - song: $viewModel.song, - isScrolled: $viewModel.isScrolled, - isPlaying: $viewModel.isPlaying) - .padding(.top, 10) - .padding(.bottom, 150) - } + PlaylistView( + viewModel: PlaylistViewModel(viewModel: viewModel), + song: $viewModel.song, + isScrolled: $viewModel.isScrolled, + isPlaying: $viewModel.isPlaying) + .padding(.top, 10) + .padding(.bottom, 150) } else { Lyric(viewModel: viewModel) } From de349782c9a435c963368c6f178b47edc259d41c Mon Sep 17 00:00:00 2001 From: JIWON1923 Date: Mon, 4 Dec 2023 19:31:50 +0900 Subject: [PATCH 27/56] =?UTF-8?q?[Fix]=20=EC=84=A0=EC=88=98=20=EC=9D=91?= =?UTF-8?q?=EC=9B=90=EA=B0=80=20=EB=B9=88=20=EB=AA=A8=EB=8B=AC=20=EB=9C=A8?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongList/MainSongListTabView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index ab83919..2d25d0d 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -178,6 +178,7 @@ struct MainSongListTabView: View { .background(Color.white.opacity(0.001)) .onTapGesture { collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") + selectedSong = songInfo isShowingHalfSheet.toggle() } } From a8dad781d2ab48b3f0023c92c8a367ad38d50abb Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Tue, 12 Dec 2023 23:26:22 +0900 Subject: [PATCH 28/56] =?UTF-8?q?=EB=AF=B8=EB=8B=88=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=9E=84=ED=8F=AC=ED=8A=B8=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 8 + .../Features/SongDetail/MiniPlayerView.swift | 354 ++++++++++++++++++ .../SongDetail/MiniPlayerViewModel.swift | 45 +++ .../SongList/MainSongListTabView.swift | 1 + 4 files changed, 408 insertions(+) create mode 100644 Halmap/Features/SongDetail/MiniPlayerView.swift create mode 100644 Halmap/Features/SongDetail/MiniPlayerViewModel.swift diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 20ac57d..88076e3 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 2051C1E32B28A1DB00842AA3 /* MiniPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2051C1E22B28A1DB00842AA3 /* MiniPlayerView.swift */; }; + 2051C1E52B28A20F00842AA3 /* MiniPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2051C1E42B28A20F00842AA3 /* MiniPlayerViewModel.swift */; }; 206F9F6D2AD29611004DAD73 /* MainSongListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 206F9F6C2AD29611004DAD73 /* MainSongListViewModel.swift */; }; 20A687D82A711C48005F18FA /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 20A687D72A711C48005F18FA /* FirebaseAnalytics */; }; 20A687DA2A711C48005F18FA /* FirebaseAnalyticsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 20A687D92A711C48005F18FA /* FirebaseAnalyticsSwift */; }; @@ -81,6 +83,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2051C1E22B28A1DB00842AA3 /* MiniPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniPlayerView.swift; sourceTree = ""; }; + 2051C1E42B28A20F00842AA3 /* MiniPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniPlayerViewModel.swift; sourceTree = ""; }; 206F9F6C2AD29611004DAD73 /* MainSongListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSongListViewModel.swift; sourceTree = ""; }; 8744D0822AC5FBCC002F1D3A /* TeamSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamSelectionViewModel.swift; sourceTree = ""; }; 8744D0842AC60D22002F1D3A /* OnBoardingStartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingStartView.swift; sourceTree = ""; }; @@ -224,6 +228,8 @@ F90190802B038BDF00C9BFF6 /* PlaylistViewModel.swift */, F90190822B038BFF00C9BFF6 /* SongDetailView.swift */, F90190842B038C1600C9BFF6 /* SongDetailViewModel.swift */, + 2051C1E22B28A1DB00842AA3 /* MiniPlayerView.swift */, + 2051C1E42B28A20F00842AA3 /* MiniPlayerViewModel.swift */, ); path = SongDetail; sourceTree = ""; @@ -530,6 +536,7 @@ F9B95BB928F271A100728048 /* DataManager.swift in Sources */, F9168B292A5A81AD00DCF649 /* NotificationView.swift in Sources */, A81FFC9928F1777B00B0FC7C /* TeamSelectionView.swift in Sources */, + 2051C1E52B28A20F00842AA3 /* MiniPlayerViewModel.swift in Sources */, F98F622B29B4CB450025F50E /* ThemeManager.swift in Sources */, F9168B2B2A5A81BC00DCF649 /* Notification.swift in Sources */, A81FFC9128F1775000B0FC7C /* SongSearchView.swift in Sources */, @@ -545,6 +552,7 @@ 8744D0852AC60D22002F1D3A /* OnBoardingStartView.swift in Sources */, F939EBBA29D58FB2005ED8CA /* StorageContentView.swift in Sources */, F90190832B038BFF00C9BFF6 /* SongDetailView.swift in Sources */, + 2051C1E32B28A1DB00842AA3 /* MiniPlayerView.swift in Sources */, F9168B322A5AA1FB00DCF649 /* HalfModalPresentationController.swift in Sources */, 8744D0832AC5FBCC002F1D3A /* TeamSelectionViewModel.swift in Sources */, A8F8BA9929C8CE5B00FCB229 /* AudioManager.swift in Sources */, diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift new file mode 100644 index 0000000..c55eff0 --- /dev/null +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -0,0 +1,354 @@ +// +// MiniPlayerView.swift +// Halmap +// +// Created by Kyubo Shim on 12/12/23. +// + +import SwiftUI +import AVFoundation +import MediaPlayer + +struct MiniPlayerView: View { + @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel + @StateObject var viewModel: SongDetailViewModel + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, + animation: .default) private var defaultPlaylistSongs: FetchedResults + + @AppStorage("currentSongId") var currentSongId: String = "" + @State var isPlaylistView = false + @State private var toast: Toast? = nil + + var body: some View { + VStack(spacing: 0){ + HStack(spacing: 11){ + if !miniPlayerViewModel.isMiniPlayerActivate { + Button(action: { + withAnimation{ + miniPlayerViewModel.showPlayer = false + miniPlayerViewModel.hideTabBar = false + miniPlayerViewModel.isMiniPlayerActivate = true + } + }, label: { + Image(systemName: "chevron.down") + .resizable() + .scaledToFit() + .frame(width: 24) + .foregroundColor(Color.white) + + }) + + } + + VStack(alignment: .leading){ + Text("\(viewModel.song.title)") + .font(Font.Halmap.CustomBodyBold) + .foregroundColor(Color.white) + Text("\(viewModel.song.team)") + .font(Font.Halmap.CustomCaptionMedium) + .foregroundColor(Color.customGray) + } + Spacer() + + if miniPlayerViewModel.isMiniPlayerActivate { + HStack{ + Button(action: { + viewModel.handlePlayButtonTap() + }, label: { + Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") + .font(.system(size: 30, weight: .medium)) + .foregroundStyle(Color.white) + }) + } + } + + if !miniPlayerViewModel.isMiniPlayerActivate { + FavoriteButton(viewModel: viewModel) + } + } + .padding(.horizontal, 20) + .frame(height: 60) + + GeometryReader{ reader in + ZStack { + if isPlaylistView { + PlaylistView( + viewModel: PlaylistViewModel(viewModel: viewModel), + song: $viewModel.song, + isScrolled: $viewModel.isScrolled, + isPlaying: $viewModel.isPlaying) + .padding(.top, 10) + .padding(.bottom, 150) + } else { + Lyric(viewModel: viewModel) + } + + VStack(spacing: 0) { +// Rectangle() +// .frame(height: UIScreen.getHeight(108)) +// .foregroundColor(Color("\(viewModel.song.team)Sub")) +// gradientRectangle(isTop: true) + Spacer() + ZStack(alignment: .bottom) { + gradientRectangle(isTop: false) + playlistButton + } + .toastView(toast: $toast) + + PlayBar(viewModel: viewModel, toast: $toast) + } + .ignoresSafeArea() + } + .onAppear() { + self.currentSongId = viewModel.song.id + viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) + setMediaPlayerNextTrack() + } + .onChange(of: viewModel.song.id) { _ in + self.currentSongId = viewModel.song.id + } + .onChange(of: currentSongId) { _ in + if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == viewModel.song.id }) { + self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[index]) + self.viewModel.getAudioManager().AMset(song: self.viewModel.song) + } + } + } + .background(Color("\(viewModel.song.team)Sub")) + .ignoresSafeArea() + .opacity(miniPlayerViewModel.isMiniPlayerActivate ? 0 : getOpacity()) + .frame(height: miniPlayerViewModel.isMiniPlayerActivate ? 0 : nil) + } + .background( + Color("\(viewModel.song.team)Sub") + .cornerRadius(8) + .ignoresSafeArea(.keyboard) + .onTapGesture { + withAnimation{ + miniPlayerViewModel.width = UIScreen.main.bounds.width + miniPlayerViewModel.isMiniPlayerActivate.toggle() + miniPlayerViewModel.hideTabBar = true + } + } + + ) + .onAppear { + print("\(viewModel.song.title)") + } + } + + func getOpacity()->Double{ + + let progress = miniPlayerViewModel.offset / (miniPlayerViewModel.height) + if progress <= 1{ + return Double(1 - progress) + } + return 1 + } + + @ViewBuilder + private func gradientRectangle(isTop: Bool) -> some View { + Rectangle() + .frame(height: 120) + .foregroundColor(Color(UIColor.clear)) + .background(isTop ? (viewModel.isScrolled ? Color.fetchTopGradient(color: Color("\(viewModel.song.team)Sub")) : nil ) : Color.fetchBottomGradient(color: Color("\(viewModel.song.team)Sub"))) + .allowsHitTesting(false) + } + + var playlistButton: some View { + // PlaylistButton + HStack(){ + Spacer() + Button(action: { + isPlaylistView.toggle() + }, label: { + ZStack { + Circle().foregroundColor(Color("\(viewModel.song.team)Background")).frame(width: 43, height: 43) + Image(systemName: isPlaylistView ? "quote.bubble.fill" : "list.bullet").foregroundColor(.white) + + } + }) + }.padding(20) + } + + private func setMediaPlayerNextTrack() { + let commandCenter = MPRemoteCommandCenter.shared() + commandCenter.nextTrackCommand.addTarget { _ in + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index + 1 > defaultPlaylistSongs.count - 1 { + toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) + } else { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) + } + } + return .success + } + commandCenter.previousTrackCommand.addTarget { _ in + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index - 1 < 0 { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) + } else { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) + } + } + return .success + } + } +} + +private struct Lyric: View { + + @StateObject var viewModel: SongDetailViewModel + + var body: some View { + ScrollView(showsIndicators: true) { + VStack { + Text("\(viewModel.song.lyrics.replacingOccurrences(of: "\\n", with: "\n"))") + .foregroundColor(.white.opacity(0.8)) + .font(.Halmap.CustomHeadline) + .lineSpacing(20) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(EdgeInsets(top: 40, leading: 40, bottom: 230, trailing: 40)) + } + .background(GeometryReader{ + Color.clear.preference( + key: ViewOffsetKey.self, + value: -$0.frame(in: .named("scroll")).origin.y) + }) + .onPreferenceChange(ViewOffsetKey.self) { + if $0 > 0 { + withAnimation { + viewModel.isScrolled = true + } + } else { + withAnimation { + viewModel.isScrolled = false + } + } + } + } + .coordinateSpace(name: "scroll") + } + private struct ViewOffsetKey: PreferenceKey { + static var defaultValue = CGFloat.zero + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value += nextValue() + } + } +} + +private struct PlayBar: View { + + @StateObject var viewModel: SongDetailViewModel + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, + animation: .default) private var defaultPlaylistSongs: FetchedResults + + @Binding var toast: Toast? + + var body: some View { + VStack(spacing: 0) { + Progressbar( + song: $viewModel.song, + toast: $toast, + isThumbActive: true) + HStack(spacing: 52) { + Button { + //이전곡 재생 기능 + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index - 1 < 0 { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) + } else { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) + } + } + } label: { + Image(systemName: "backward.end.fill") + .font(.system(size: 28, weight: .regular)) + .foregroundColor(.customGray) + } + Button { + viewModel.handlePlayButtonTap() + } label: { + Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") + .font(.system(size: 60, weight: .medium)) + .foregroundStyle(Color.customGray) + } + Button { + //다음곡 재생 기능 + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if index + 1 > defaultPlaylistSongs.count - 1 { + toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) + } else { + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) + } + } + } label: { + Image(systemName: "forward.end.fill") + .font(.system(size: 28, weight: .regular)) + .foregroundColor(.customGray) + } + } + .padding(.bottom, 54) + } + .padding(.horizontal, 45) + .frame(maxWidth: .infinity) + .background(Color("\(viewModel.song.team)Sub")) + .onDisappear(){ + viewModel.removePlayer() + } + .onAppear(){ + viewModel.setPlayer() + } + } +} + +private struct FavoriteButton: View { + + @StateObject var viewModel: SongDetailViewModel + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], + predicate: PlaylistFilter(filter: "favorite").predicate, + animation: .default) private var favoriteSongs: FetchedResults + + var body: some View { + Button { + viewModel.handleLikeButtonTap(deleteSong: findFavoriteSong()) + } label: { + Image(systemName: viewModel.isFavorite ? "heart.fill" : "heart") + .foregroundStyle(viewModel.isFavorite ? Color("\(viewModel.song.team)Point") : Color.white) + } + .onAppear() { + if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { + viewModel.isFavorite = true + } + } + .onChange(of: viewModel.song.id) { _ in + if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { + viewModel.isFavorite = true + } else { + viewModel.isFavorite = false + } + } + } + + private func findFavoriteSong() -> CollectedSong { + if let index = favoriteSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + return favoriteSongs[index] + } else { + return CollectedSong() + } + } +} + +//#Preview { +// MiniPlayerView() +//} diff --git a/Halmap/Features/SongDetail/MiniPlayerViewModel.swift b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift new file mode 100644 index 0000000..5fabd74 --- /dev/null +++ b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift @@ -0,0 +1,45 @@ +// +// MiniPlayerViewModel.swift +// Halmap +// +// Created by Kyubo Shim on 12/12/23. +// + +import Foundation +import SwiftUI + +class MiniPlayerViewModel: ObservableObject { + @Published var showPlayer = false + + @Published var offset: CGFloat = 0 + @Published var width: CGFloat = UIScreen.main.bounds.width + @Published var height : CGFloat = 0 + @Published var isMiniPlayerActivate = false + @Published var hideTabBar = false + +// func convertTeamNameToKor(teamName: String) -> String { +// switch teamName{ +// case "doosan": +// return "두산 베어스" +// case "hanwha": +// return "한화 이글스" +// case "samsung": +// return "삼성 라이온즈" +// case "lotte": +// return "롯데 자이언츠" +// case .: +// return "엘지 트윈스" +// case .ssg: +// return "쓱 랜더스" +// case .kt: +// return "케이티 위즈" +// case .nc: +// return "엔씨 다이노스" +// case .kiwoom: +// return "키움 히어로즈" +// case .kia: +// return "기아 타이거즈" +// } +// } +} + diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 2d25d0d..cefe384 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -15,6 +15,7 @@ struct MainSongListTabView: View { @EnvironmentObject var audioManager: AudioManager @Environment(\.managedObjectContext) private var viewContext @StateObject var viewModel = MainSongListTabViewModel() + @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel @State private var isShowingHalfSheet: Bool = false @State private var isActiveNavigatioinLink: Bool = false From 3424b24a568376649ecab1a6ecc8ded9d3a3800a Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Tue, 12 Dec 2023 23:59:06 +0900 Subject: [PATCH 29/56] =?UTF-8?q?=EB=AF=B8=EB=8B=88=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EB=A9=94=EC=9D=B8=ED=83=AD=20=EC=9E=84?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SongList/MainSongListTabView.swift | 82 +++++++++++++++---- Halmap/View/MainTabView.swift | 8 +- Halmap/View/OnBoardingStartView.swift | 78 +++++++++++++++--- 3 files changed, 137 insertions(+), 31 deletions(-) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index cefe384..0e3e1c8 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -21,6 +21,7 @@ struct MainSongListTabView: View { @State private var isActiveNavigatioinLink: Bool = false @State private var selectedSong: SongInfo? @State var collectedSong: CollectedSong? + @Binding var songInfo: SongInfo let persistence = PersistenceController.shared @@ -71,6 +72,47 @@ struct MainSongListTabView: View { url: song.url) ZStack { +// Button(action: { +// SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() +// self.songInfo = songInfo +// SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() +// withAnimation{ +// miniPlayerViewModel.showPlayer = true +// miniPlayerViewModel.hideTabBar = true +// miniPlayerViewModel.isMiniPlayerActivate = false +// } +// }, label: { +// HStack(spacing: 16) { +// Image(viewModel.getSongImage(for: songInfo)) +// .resizable() +// .frame(width: 40, height: 40) +// .cornerRadius(8) +// +// VStack(alignment: .leading, spacing: 6) { +// Text(song.title) +// .font(Font.Halmap.CustomBodyMedium) +// +// if !song.info.isEmpty { +// Text(song.info) +// .font(Font.Halmap.CustomCaptionMedium) +// .foregroundColor(.customDarkGray) +// } +// } +// .frame(maxWidth: .infinity, alignment: .leading) +// .lineLimit(1) +// Spacer() +// +// Image(systemName: "ellipsis") +// .foregroundColor(.customDarkGray) +// .frame(maxWidth: 35, maxHeight: .infinity) +// .background(Color.white.opacity(0.001)) +// .onTapGesture { +// collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") +// selectedSong = songInfo +// isShowingHalfSheet.toggle() +// } +// } +// }) HStack(spacing: 16) { Image(viewModel.getSongImage(for: songInfo)) .resizable() @@ -102,19 +144,19 @@ struct MainSongListTabView: View { } } - if let selectedSong { - NavigationLink(destination: - SongDetailView(viewModel: SongDetailViewModel( - audioManager: audioManager, - dataManager: dataManager, - persistence: persistence, - song: selectedSong)), - isActive: $isActiveNavigatioinLink) { - EmptyView() - } - .opacity(0) - .disabled(true) - } +// if let selectedSong { +// NavigationLink(destination: +// SongDetailView(viewModel: SongDetailViewModel( +// audioManager: audioManager, +// dataManager: dataManager, +// persistence: persistence, +// song: selectedSong)), +// isActive: $isActiveNavigatioinLink) { +// EmptyView() +// } +// .opacity(0) +// .disabled(true) +// } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) @@ -123,8 +165,16 @@ struct MainSongListTabView: View { .listRowSeparatorTint(Color.customGray) .background(Color.systemBackground) .onTapGesture { - selectedSong = songInfo - isActiveNavigatioinLink.toggle() + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() + self.songInfo = songInfo + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + selectedSong = songInfo + isActiveNavigatioinLink.toggle() + } } } @@ -258,6 +308,6 @@ struct MainSongListTabView: View { struct MainSongListTabView_Previews: PreviewProvider { static var previews: some View { - MainSongListTabView() + MainSongListTabView(songInfo: .constant(SongInfo(id: "", team: "Lotte", type: true, title: "", lyrics: "",info: "", url: ""))) } } diff --git a/Halmap/View/MainTabView.swift b/Halmap/View/MainTabView.swift index 4898503..c95933b 100644 --- a/Halmap/View/MainTabView.swift +++ b/Halmap/View/MainTabView.swift @@ -12,8 +12,10 @@ struct MainTabView: View { @EnvironmentObject var dataManager: DataManager @AppStorage("selectedTeam") var selectedTeam = "Hanwha" @StateObject var viewModel = MainTabViewModel() + @Binding var songInfo: SongInfo - init() { + init(songInfo: Binding) { + self._songInfo = songInfo Color.setColor(selectedTeam) } @@ -22,7 +24,7 @@ struct MainTabView: View { VStack(spacing: 0) { switch viewModel.state { case .home: - MainSongListTabView() + MainSongListTabView(songInfo: $songInfo) case .search: SongSearchView(viewModel: SongSearchViewModel(dataManager: dataManager)) case .storage: @@ -59,6 +61,6 @@ struct MainTabView: View { struct MainTabView_Previews: PreviewProvider { static var previews: some View { - MainTabView() + MainTabView(songInfo: .constant(SongInfo(id: "", team: "Lotte", type: true, title: "", lyrics: "", info: "", url: ""))) } } diff --git a/Halmap/View/OnBoardingStartView.swift b/Halmap/View/OnBoardingStartView.swift index f062d6b..a042cc0 100644 --- a/Halmap/View/OnBoardingStartView.swift +++ b/Halmap/View/OnBoardingStartView.swift @@ -13,27 +13,81 @@ struct OnBoardingStartView: View { @AppStorage("isShouldShowNotification") var isShouldShowNotification = false @AppStorage("isShouldShowTraffic") var isShouldShowTraffic = false @AppStorage("latestVersion") var latestVersion = "1.0.0" - + @EnvironmentObject var audioManager: AudioManager @EnvironmentObject var dataManager: DataManager + @StateObject var miniPlayerViewModel = MiniPlayerViewModel() + @State var songInfo = SongInfo(id: "", team: "Lotte", type: true, title: "", lyrics: "", info: "", url: "") + @GestureState var gestureOffset: CGFloat = 0 + @Namespace var animation + let persistence = PersistenceController.shared + var body: some View { if !isFirstLaunching { - ForEach(Array(TeamName.allCases.enumerated()), id: \.offset) { index, team in - if Themes.themes[index] == TeamName(rawValue: selectedTeam) { - MainTabView() - .sheet(isPresented: $isShouldShowTraffic) { - HalfSheet { - NotificationView(type: .traffic) + ZStack{ + ForEach(Array(TeamName.allCases.enumerated()), id: \.offset) { index, team in + if Themes.themes[index] == TeamName(rawValue: selectedTeam) { + MainTabView(songInfo: $songInfo) + .environmentObject(miniPlayerViewModel) + .sheet(isPresented: $isShouldShowTraffic) { + HalfSheet { + NotificationView(type: .traffic) + } } - } - .sheet(isPresented: $isShouldShowNotification) { - HalfSheet { - NotificationView(type: .version) + .sheet(isPresented: $isShouldShowNotification) { + HalfSheet { + NotificationView(type: .version) + } } - } + } + } + VStack{ + Spacer() + if miniPlayerViewModel.showPlayer { + MiniPlayerView(viewModel: SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: songInfo)) + .transition(.move(edge: .bottom)) + .offset(y: miniPlayerViewModel.offset) + .gesture(DragGesture().updating($gestureOffset, body: { (value, state, _) in + + state = value.translation.height + }) + .onEnded(onEnd(value:))) + .padding(.bottom, miniPlayerViewModel.hideTabBar ? 0 : 57) + .ignoresSafeArea(.keyboard) + .ignoresSafeArea() + } } } + .onChange(of: gestureOffset, perform: { value in + onChanged() + }) + .environmentObject(miniPlayerViewModel) } else { TeamSelectionView(viewModel: TeamSelectionViewModel(dataManager: dataManager), isShowing: $isFirstLaunching) } } + + func onChanged(){ + if gestureOffset > 0 && !miniPlayerViewModel.isMiniPlayerActivate && miniPlayerViewModel.offset + 200 <= miniPlayerViewModel.height{ + miniPlayerViewModel.offset = gestureOffset + } + } + + func onEnd(value: DragGesture.Value){ + withAnimation(.smooth){ + + if !miniPlayerViewModel.isMiniPlayerActivate{ + miniPlayerViewModel.offset = 0 + + // Closing View... + if value.translation.height > UIScreen.main.bounds.height / 3{ + miniPlayerViewModel.hideTabBar = false + miniPlayerViewModel.isMiniPlayerActivate = true + } + else{ + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + } + } + } + } } From 84dcc078baeecca92f07124acd478af1b0ba0f0c Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Wed, 13 Dec 2023 00:14:10 +0900 Subject: [PATCH 30/56] =?UTF-8?q?=EB=AF=B8=EB=8B=88=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EB=A9=94=EC=9D=B8=ED=83=AD=20=EC=9E=84?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SongList/MainSongListTabView.swift | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 0e3e1c8..d281fb2 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -235,23 +235,31 @@ struct MainSongListTabView: View { } .onTapGesture { - selectedSong = songInfo - isActiveNavigatioinLink.toggle() - } - - if let selectedSong { - NavigationLink(destination: - SongDetailView(viewModel: SongDetailViewModel( - audioManager: audioManager, - dataManager: dataManager, - persistence: persistence, - song: selectedSong)), - isActive: $isActiveNavigatioinLink) { - EmptyView() + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() + self.songInfo = songInfo + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + selectedSong = songInfo + isActiveNavigatioinLink.toggle() + } } - .opacity(0) - .disabled(true) - } + +// if let selectedSong { +// NavigationLink(destination: +// SongDetailView(viewModel: SongDetailViewModel( +// audioManager: audioManager, +// dataManager: dataManager, +// persistence: persistence, +// song: selectedSong)), +// isActive: $isActiveNavigatioinLink) { +// EmptyView() +// } +// .opacity(0) +// .disabled(true) +// } } } From 70a2d04c3d6138ca3fdf50e0402c7ec37dcc89ae Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Wed, 13 Dec 2023 00:45:36 +0900 Subject: [PATCH 31/56] =?UTF-8?q?=EA=B2=80=EC=83=89,=20=EB=B3=B4=EA=B4=80?= =?UTF-8?q?=ED=95=A8=EC=97=90=20=EB=AF=B8=EB=8B=88=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/SongDetail/MiniPlayerView.swift | 2 +- .../Features/SongSearch/SongSearchView.swift | 65 +++++++++++++---- .../SongStorage/ScalingHeaderView.swift | 72 ++++++++++++++----- .../SongStorage/StorageContentView.swift | 14 ++-- Halmap/View/MainTabView.swift | 5 +- 5 files changed, 118 insertions(+), 40 deletions(-) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index c55eff0..b5f8764 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -11,7 +11,7 @@ import MediaPlayer struct MiniPlayerView: View { @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel - @StateObject var viewModel: SongDetailViewModel + @ObservedObject var viewModel: SongDetailViewModel @FetchRequest( entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index 80ec097..af2e2ea 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -11,9 +11,10 @@ struct SongSearchView: View { @EnvironmentObject var dataManager: DataManager @EnvironmentObject var audioManager: AudioManager let persistence = PersistenceController.shared - + @ObservedObject var miniPlayerViewModel: MiniPlayerViewModel @StateObject var viewModel: SongSearchViewModel @FocusState private var isFocused: Bool + @Binding var songInfo: SongInfo var body: some View { @@ -104,14 +105,48 @@ struct SongSearchView: View { case .result: List { ForEach(viewModel.autoComplete, id: \.id) { song in - NavigationLink { - SongDetailView(viewModel: SongDetailViewModel( - audioManager: audioManager, - dataManager: dataManager, - persistence: persistence, - song: song)) - } label: { - HStack { + let songInfo = SongInfo(id: song.id, + team: song.team, + type: song.type, + title: song.title, + lyrics: song.lyrics, + info: song.info, + url: song.url) +// NavigationLink { +// SongDetailView(viewModel: SongDetailViewModel( +// audioManager: audioManager, +// dataManager: dataManager, +// persistence: persistence, +// song: song)) +// } label: { +// HStack { +// Image(viewModel.getAlbumImage(with: song)) +// .resizable() +// .frame(width: 40, height: 40) +// .cornerRadius(8) +// VStack(alignment: .leading, spacing: 8) { +// Text(song.title) +// .font(Font.Halmap.CustomBodyMedium) +// .foregroundColor(.black) +// Text(viewModel.getTeamName(with: song)) +// .font(Font.Halmap.CustomCaptionMedium) +// .foregroundColor(.customDarkGray) +// } +// .frame(height: 45) +// .lineLimit(1) +// } +// } + Button(action: { + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() + self.songInfo = song + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + } + }, label: { + HStack(spacing: 16) { Image(viewModel.getAlbumImage(with: song)) .resizable() .frame(width: 40, height: 40) @@ -127,7 +162,7 @@ struct SongSearchView: View { .frame(height: 45) .lineLimit(1) } - } + }) .listRowBackground(Color(UIColor.clear)) .listRowInsets((EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))) .listRowSeparatorTint(Color.customGray) @@ -142,8 +177,8 @@ struct SongSearchView: View { // MARK: Previews -struct SongSearchView_Previews: PreviewProvider { - static var previews: some View { - SongSearchView(viewModel: SongSearchViewModel(dataManager: DataManager())) - } -} +//struct SongSearchView_Previews: PreviewProvider { +// static var previews: some View { +// SongSearchView(viewModel: SongSearchViewModel(dataManager: DataManager())) +// } +//} diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index ae4392e..7347c58 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -20,6 +20,8 @@ struct ScalingHeaderView: View { @State var isShowSheet = false @AppStorage("currentSongId") var currentSongId: String = "" + @ObservedObject var miniPlayerViewModel: MiniPlayerViewModel + @Binding var songInfo: SongInfo var body: some View { ScrollView(.vertical, showsIndicators: false) { @@ -79,15 +81,16 @@ struct ScalingHeaderView: View { let song = viewModel.makeSong(favoriteSong: favoriteSong) VStack(spacing: 0) { - NavigationLink { - SongDetailView( - viewModel: SongDetailViewModel( - audioManager: audioManager, - dataManager: dataManager, - persistence: persistence, - song: song - )) - } label: { + Button(action: { + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() + self.songInfo = songInfo + SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + } + }, label: { HStack(spacing: 16) { Image(viewModel.fetchSongImageName(favoriteSong: favoriteSong)) .resizable() @@ -103,7 +106,7 @@ struct ScalingHeaderView: View { } .frame(maxWidth: .infinity, alignment: .leading) .lineLimit(1) - + Button { self.collectedSongData = favoriteSong isShowSheet.toggle() @@ -115,7 +118,44 @@ struct ScalingHeaderView: View { } .padding(.horizontal, 20) .padding(.vertical, 15) - } + }) +// NavigationLink { +// SongDetailView( +// viewModel: SongDetailViewModel( +// audioManager: audioManager, +// dataManager: dataManager, +// persistence: persistence, +// song: song +// )) +// } label: { +// HStack(spacing: 16) { +// Image(viewModel.fetchSongImageName(favoriteSong: favoriteSong)) +// .resizable() +// .frame(width: 40, height: 40) +// .cornerRadius(8) +// VStack(alignment: .leading, spacing: 6) { +// Text(favoriteSong.safeTitle) +// .font(Font.Halmap.CustomBodyMedium) +// .foregroundColor(.black) +// Text(TeamName(rawValue: favoriteSong.safeTeam)?.fetchTeamNameKr() ?? ".") +// .font(Font.Halmap.CustomCaptionMedium) +// .foregroundColor(.customDarkGray) +// } +// .frame(maxWidth: .infinity, alignment: .leading) +// .lineLimit(1) +// +// Button { +// self.collectedSongData = favoriteSong +// isShowSheet.toggle() +// } label: { +// Image(systemName: "ellipsis") +// .foregroundColor(.customDarkGray) +// .frame(maxWidth: 35, maxHeight: .infinity) +// } +// } +// .padding(.horizontal, 20) +// .padding(.vertical, 15) +// } } .background(Color.systemBackground) } @@ -179,8 +219,8 @@ struct ScalingHeaderView: View { } } -struct ScalingHeaderView_Previews: PreviewProvider { - static var previews: some View { - StorageContentView() - } -} +//struct ScalingHeaderView_Previews: PreviewProvider { +// static var previews: some View { +// StorageContentView() +// } +//} diff --git a/Halmap/Features/SongStorage/StorageContentView.swift b/Halmap/Features/SongStorage/StorageContentView.swift index 14b3a74..9cdec69 100644 --- a/Halmap/Features/SongStorage/StorageContentView.swift +++ b/Halmap/Features/SongStorage/StorageContentView.swift @@ -10,19 +10,21 @@ import SwiftUI struct StorageContentView: View { @EnvironmentObject var dataManager: DataManager let persistence = PersistenceController.shared + @ObservedObject var miniPlayerViewModel: MiniPlayerViewModel + @Binding var songInfo: SongInfo var body: some View { GeometryReader { proxy in let topEdge = proxy.safeAreaInsets.top - ScalingHeaderView(viewModel: SongStorageViewModel(dataManager: dataManager, persistence: persistence, topEdge: topEdge)) + ScalingHeaderView(viewModel: SongStorageViewModel(dataManager: dataManager, persistence: persistence, topEdge: topEdge), miniPlayerViewModel: miniPlayerViewModel, songInfo: $songInfo) .ignoresSafeArea(.all, edges: .top) } } } -struct StorageContentView_Previews: PreviewProvider { - static var previews: some View { - StorageContentView() - } -} +//struct StorageContentView_Previews: PreviewProvider { +// static var previews: some View { +// StorageContentView() +// } +//} diff --git a/Halmap/View/MainTabView.swift b/Halmap/View/MainTabView.swift index c95933b..6da8316 100644 --- a/Halmap/View/MainTabView.swift +++ b/Halmap/View/MainTabView.swift @@ -10,6 +10,7 @@ import SwiftUI struct MainTabView: View { @EnvironmentObject var dataManager: DataManager + @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel @AppStorage("selectedTeam") var selectedTeam = "Hanwha" @StateObject var viewModel = MainTabViewModel() @Binding var songInfo: SongInfo @@ -26,9 +27,9 @@ struct MainTabView: View { case .home: MainSongListTabView(songInfo: $songInfo) case .search: - SongSearchView(viewModel: SongSearchViewModel(dataManager: dataManager)) + SongSearchView(miniPlayerViewModel: miniPlayerViewModel, viewModel: SongSearchViewModel(dataManager: dataManager), songInfo: $songInfo) case .storage: - StorageContentView() + StorageContentView(miniPlayerViewModel: miniPlayerViewModel, songInfo: $songInfo) } HStack { From 2cfc88695a6d0a6cf70eb00f937130be0d61c565 Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Wed, 13 Dec 2023 17:42:18 +0900 Subject: [PATCH 32/56] =?UTF-8?q?=EB=AF=B8=EB=8B=88=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=95=8C=ED=8C=8C=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/SongDetail/MiniPlayerView.swift | 17 +++-- .../SongList/MainSongListTabView.swift | 75 +------------------ .../Features/SongSearch/SongSearchView.swift | 25 +------ Halmap/View/OnBoardingStartView.swift | 2 +- 4 files changed, 16 insertions(+), 103 deletions(-) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index b5f8764..ce228c5 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -11,7 +11,7 @@ import MediaPlayer struct MiniPlayerView: View { @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel - @ObservedObject var viewModel: SongDetailViewModel + @StateObject var viewModel: SongDetailViewModel @FetchRequest( entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], @@ -21,6 +21,7 @@ struct MiniPlayerView: View { @AppStorage("currentSongId") var currentSongId: String = "" @State var isPlaylistView = false @State private var toast: Toast? = nil + @Binding var selectedSongInfo: SongInfo var body: some View { VStack(spacing: 0){ @@ -122,6 +123,14 @@ struct MiniPlayerView: View { .opacity(miniPlayerViewModel.isMiniPlayerActivate ? 0 : getOpacity()) .frame(height: miniPlayerViewModel.isMiniPlayerActivate ? 0 : nil) } + .onChange(of: miniPlayerViewModel.isMiniPlayerActivate) { isActivated in + if !isActivated { + viewModel.song = selectedSongInfo + self.currentSongId = viewModel.song.id + viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) + setMediaPlayerNextTrack() + } + } .background( Color("\(viewModel.song.team)Sub") .cornerRadius(8) @@ -131,13 +140,11 @@ struct MiniPlayerView: View { miniPlayerViewModel.width = UIScreen.main.bounds.width miniPlayerViewModel.isMiniPlayerActivate.toggle() miniPlayerViewModel.hideTabBar = true + print("\(viewModel.song.title)") } } ) - .onAppear { - print("\(viewModel.song.title)") - } } func getOpacity()->Double{ @@ -281,7 +288,7 @@ private struct PlayBar: View { .foregroundStyle(Color.customGray) } Button { - //다음곡 재생 기능 + // - MARK: 다음곡 재생 기능 if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index d281fb2..59a02a5 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -22,7 +22,6 @@ struct MainSongListTabView: View { @State private var selectedSong: SongInfo? @State var collectedSong: CollectedSong? @Binding var songInfo: SongInfo - let persistence = PersistenceController.shared var body: some View { @@ -72,47 +71,6 @@ struct MainSongListTabView: View { url: song.url) ZStack { -// Button(action: { -// SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() -// self.songInfo = songInfo -// SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() -// withAnimation{ -// miniPlayerViewModel.showPlayer = true -// miniPlayerViewModel.hideTabBar = true -// miniPlayerViewModel.isMiniPlayerActivate = false -// } -// }, label: { -// HStack(spacing: 16) { -// Image(viewModel.getSongImage(for: songInfo)) -// .resizable() -// .frame(width: 40, height: 40) -// .cornerRadius(8) -// -// VStack(alignment: .leading, spacing: 6) { -// Text(song.title) -// .font(Font.Halmap.CustomBodyMedium) -// -// if !song.info.isEmpty { -// Text(song.info) -// .font(Font.Halmap.CustomCaptionMedium) -// .foregroundColor(.customDarkGray) -// } -// } -// .frame(maxWidth: .infinity, alignment: .leading) -// .lineLimit(1) -// Spacer() -// -// Image(systemName: "ellipsis") -// .foregroundColor(.customDarkGray) -// .frame(maxWidth: 35, maxHeight: .infinity) -// .background(Color.white.opacity(0.001)) -// .onTapGesture { -// collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") -// selectedSong = songInfo -// isShowingHalfSheet.toggle() -// } -// } -// }) HStack(spacing: 16) { Image(viewModel.getSongImage(for: songInfo)) .resizable() @@ -143,20 +101,6 @@ struct MainSongListTabView: View { isShowingHalfSheet.toggle() } } - -// if let selectedSong { -// NavigationLink(destination: -// SongDetailView(viewModel: SongDetailViewModel( -// audioManager: audioManager, -// dataManager: dataManager, -// persistence: persistence, -// song: selectedSong)), -// isActive: $isActiveNavigatioinLink) { -// EmptyView() -// } -// .opacity(0) -// .disabled(true) -// } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) @@ -165,9 +109,8 @@ struct MainSongListTabView: View { .listRowSeparatorTint(Color.customGray) .background(Color.systemBackground) .onTapGesture { - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() - self.songInfo = songInfo - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() + self.songInfo = songInfo + print(songInfo.title) withAnimation{ miniPlayerViewModel.showPlayer = true miniPlayerViewModel.hideTabBar = true @@ -247,20 +190,6 @@ struct MainSongListTabView: View { } } -// if let selectedSong { -// NavigationLink(destination: -// SongDetailView(viewModel: SongDetailViewModel( -// audioManager: audioManager, -// dataManager: dataManager, -// persistence: persistence, -// song: selectedSong)), -// isActive: $isActiveNavigatioinLink) { -// EmptyView() -// } -// .opacity(0) -// .disabled(true) -// } - } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index af2e2ea..44fd5e7 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -112,30 +112,7 @@ struct SongSearchView: View { lyrics: song.lyrics, info: song.info, url: song.url) -// NavigationLink { -// SongDetailView(viewModel: SongDetailViewModel( -// audioManager: audioManager, -// dataManager: dataManager, -// persistence: persistence, -// song: song)) -// } label: { -// HStack { -// Image(viewModel.getAlbumImage(with: song)) -// .resizable() -// .frame(width: 40, height: 40) -// .cornerRadius(8) -// VStack(alignment: .leading, spacing: 8) { -// Text(song.title) -// .font(Font.Halmap.CustomBodyMedium) -// .foregroundColor(.black) -// Text(viewModel.getTeamName(with: song)) -// .font(Font.Halmap.CustomCaptionMedium) -// .foregroundColor(.customDarkGray) -// } -// .frame(height: 45) -// .lineLimit(1) -// } -// } + Button(action: { SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() self.songInfo = song diff --git a/Halmap/View/OnBoardingStartView.swift b/Halmap/View/OnBoardingStartView.swift index a042cc0..e659bb2 100644 --- a/Halmap/View/OnBoardingStartView.swift +++ b/Halmap/View/OnBoardingStartView.swift @@ -43,7 +43,7 @@ struct OnBoardingStartView: View { VStack{ Spacer() if miniPlayerViewModel.showPlayer { - MiniPlayerView(viewModel: SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: songInfo)) + MiniPlayerView(viewModel: SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: songInfo), selectedSongInfo: $songInfo) .transition(.move(edge: .bottom)) .offset(y: miniPlayerViewModel.offset) .gesture(DragGesture().updating($gestureOffset, body: { (value, state, _) in From 4bd8f4625fef1f93f495841b517e5c737ca00fa1 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 13 Dec 2023 21:31:25 +0900 Subject: [PATCH 33/56] =?UTF-8?q?[Feat]=20=EC=A0=84=EC=B2=B4=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=ED=95=98=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20-=20SongDetailView=20/=20SongDetailViewModel=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20-=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 8 - Halmap/Data/DataManager.swift | 1 + .../Features/SongDetail/MiniPlayerView.swift | 76 ++--- .../SongDetail/MiniPlayerViewModel.swift | 122 ++++++-- Halmap/Features/SongDetail/PlaylistView.swift | 4 +- .../SongDetail/PlaylistViewModel.swift | 2 +- .../Features/SongDetail/SongDetailView.swift | 278 ------------------ .../SongDetail/SongDetailViewModel.swift | 96 ------ .../SongList/MainSongListTabView.swift | 48 +-- .../Features/SongSearch/SongSearchView.swift | 17 +- .../SongStorage/ScalingHeaderView.swift | 78 ++--- .../SongStorage/StorageContentView.swift | 5 +- Halmap/HalmapApp.swift | 4 +- Halmap/View/Component/Utility.swift | 7 + Halmap/View/MainTabView.swift | 13 +- Halmap/View/OnBoardingStartView.swift | 8 +- 16 files changed, 212 insertions(+), 555 deletions(-) delete mode 100644 Halmap/Features/SongDetail/SongDetailView.swift delete mode 100644 Halmap/Features/SongDetail/SongDetailViewModel.swift diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 88076e3..72b773d 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -44,8 +44,6 @@ EEBC26A128F1CAE900BD5B3D /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBC269F28F1CAE800BD5B3D /* TabBarView.swift */; }; F901907F2B038BC200C9BFF6 /* PlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F901907E2B038BC200C9BFF6 /* PlaylistView.swift */; }; F90190812B038BDF00C9BFF6 /* PlaylistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90190802B038BDF00C9BFF6 /* PlaylistViewModel.swift */; }; - F90190832B038BFF00C9BFF6 /* SongDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90190822B038BFF00C9BFF6 /* SongDetailView.swift */; }; - F90190852B038C1600C9BFF6 /* SongDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90190842B038C1600C9BFF6 /* SongDetailViewModel.swift */; }; F9168B292A5A81AD00DCF649 /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9168B282A5A81AD00DCF649 /* NotificationView.swift */; }; F9168B2B2A5A81BC00DCF649 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9168B2A2A5A81BC00DCF649 /* Notification.swift */; }; F9168B322A5AA1FB00DCF649 /* HalfModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9168B312A5AA1FB00DCF649 /* HalfModalPresentationController.swift */; }; @@ -113,8 +111,6 @@ EEBC269F28F1CAE800BD5B3D /* TabBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = ""; }; F901907E2B038BC200C9BFF6 /* PlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistView.swift; sourceTree = ""; }; F90190802B038BDF00C9BFF6 /* PlaylistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistViewModel.swift; sourceTree = ""; }; - F90190822B038BFF00C9BFF6 /* SongDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongDetailView.swift; sourceTree = ""; }; - F90190842B038C1600C9BFF6 /* SongDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongDetailViewModel.swift; sourceTree = ""; }; F9168B282A5A81AD00DCF649 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; F9168B2A2A5A81BC00DCF649 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; F9168B312A5AA1FB00DCF649 /* HalfModalPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HalfModalPresentationController.swift; sourceTree = ""; }; @@ -226,8 +222,6 @@ children = ( F901907E2B038BC200C9BFF6 /* PlaylistView.swift */, F90190802B038BDF00C9BFF6 /* PlaylistViewModel.swift */, - F90190822B038BFF00C9BFF6 /* SongDetailView.swift */, - F90190842B038C1600C9BFF6 /* SongDetailViewModel.swift */, 2051C1E22B28A1DB00842AA3 /* MiniPlayerView.swift */, 2051C1E42B28A20F00842AA3 /* MiniPlayerViewModel.swift */, ); @@ -551,14 +545,12 @@ F97BE1222AC011B100B37C76 /* SongStorageViewModel.swift in Sources */, 8744D0852AC60D22002F1D3A /* OnBoardingStartView.swift in Sources */, F939EBBA29D58FB2005ED8CA /* StorageContentView.swift in Sources */, - F90190832B038BFF00C9BFF6 /* SongDetailView.swift in Sources */, 2051C1E32B28A1DB00842AA3 /* MiniPlayerView.swift in Sources */, F9168B322A5AA1FB00DCF649 /* HalfModalPresentationController.swift in Sources */, 8744D0832AC5FBCC002F1D3A /* TeamSelectionViewModel.swift in Sources */, A8F8BA9929C8CE5B00FCB229 /* AudioManager.swift in Sources */, F9B95BBB28F271B800728048 /* Model.swift in Sources */, F901907F2B038BC200C9BFF6 /* PlaylistView.swift in Sources */, - F90190852B038C1600C9BFF6 /* SongDetailViewModel.swift in Sources */, F98F623129B59CE60025F50E /* TeamName.swift in Sources */, F97BE1242AC011DF00B37C76 /* Halmap+CollectedSong.swift in Sources */, F9B3B7B529B7397600232BB8 /* MapName.swift in Sources */, diff --git a/Halmap/Data/DataManager.swift b/Halmap/Data/DataManager.swift index cbf3341..ac6ccf2 100644 --- a/Halmap/Data/DataManager.swift +++ b/Halmap/Data/DataManager.swift @@ -11,6 +11,7 @@ import FirebaseRemoteConfig import FirebaseFirestoreSwift class DataManager: ObservableObject { + static let instance = DataManager() private let db = Firestore.firestore() diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index ce228c5..e48f751 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -11,7 +11,6 @@ import MediaPlayer struct MiniPlayerView: View { @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel - @StateObject var viewModel: SongDetailViewModel @FetchRequest( entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], @@ -21,7 +20,6 @@ struct MiniPlayerView: View { @AppStorage("currentSongId") var currentSongId: String = "" @State var isPlaylistView = false @State private var toast: Toast? = nil - @Binding var selectedSongInfo: SongInfo var body: some View { VStack(spacing: 0){ @@ -45,10 +43,10 @@ struct MiniPlayerView: View { } VStack(alignment: .leading){ - Text("\(viewModel.song.title)") + Text("\(miniPlayerViewModel.song.title)") .font(Font.Halmap.CustomBodyBold) .foregroundColor(Color.white) - Text("\(viewModel.song.team)") + Text("\(miniPlayerViewModel.song.team)") .font(Font.Halmap.CustomCaptionMedium) .foregroundColor(Color.customGray) } @@ -57,9 +55,9 @@ struct MiniPlayerView: View { if miniPlayerViewModel.isMiniPlayerActivate { HStack{ Button(action: { - viewModel.handlePlayButtonTap() + miniPlayerViewModel.handlePlayButtonTap() }, label: { - Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") + Image(systemName: miniPlayerViewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") .font(.system(size: 30, weight: .medium)) .foregroundStyle(Color.white) }) @@ -67,7 +65,7 @@ struct MiniPlayerView: View { } if !miniPlayerViewModel.isMiniPlayerActivate { - FavoriteButton(viewModel: viewModel) + FavoriteButton(viewModel: miniPlayerViewModel) } } .padding(.horizontal, 20) @@ -77,21 +75,17 @@ struct MiniPlayerView: View { ZStack { if isPlaylistView { PlaylistView( - viewModel: PlaylistViewModel(viewModel: viewModel), - song: $viewModel.song, - isScrolled: $viewModel.isScrolled, - isPlaying: $viewModel.isPlaying) + viewModel: PlaylistViewModel(viewModel: miniPlayerViewModel), + song: $miniPlayerViewModel.song, + isScrolled: $miniPlayerViewModel.isScrolled, + isPlaying: $miniPlayerViewModel.isPlaying) .padding(.top, 10) .padding(.bottom, 150) } else { - Lyric(viewModel: viewModel) + Lyric(viewModel: miniPlayerViewModel) } VStack(spacing: 0) { -// Rectangle() -// .frame(height: UIScreen.getHeight(108)) -// .foregroundColor(Color("\(viewModel.song.team)Sub")) -// gradientRectangle(isTop: true) Spacer() ZStack(alignment: .bottom) { gradientRectangle(isTop: false) @@ -99,40 +93,32 @@ struct MiniPlayerView: View { } .toastView(toast: $toast) - PlayBar(viewModel: viewModel, toast: $toast) + PlayBar(viewModel: miniPlayerViewModel, toast: $toast) } .ignoresSafeArea() } - .onAppear() { - self.currentSongId = viewModel.song.id - viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) - setMediaPlayerNextTrack() - } - .onChange(of: viewModel.song.id) { _ in - self.currentSongId = viewModel.song.id + .onChange(of: miniPlayerViewModel.song.id) { _ in + self.currentSongId = miniPlayerViewModel.song.id } .onChange(of: currentSongId) { _ in - if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == viewModel.song.id }) { - self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[index]) - self.viewModel.getAudioManager().AMset(song: self.viewModel.song) + if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == miniPlayerViewModel.song.id }) { + self.miniPlayerViewModel.song = miniPlayerViewModel.convertSongToSongInfo(song: defaultPlaylistSongs[index]) + self.miniPlayerViewModel.setPlayer() } } } - .background(Color("\(viewModel.song.team)Sub")) + .background(Color("\(miniPlayerViewModel.song.team)Sub")) .ignoresSafeArea() .opacity(miniPlayerViewModel.isMiniPlayerActivate ? 0 : getOpacity()) .frame(height: miniPlayerViewModel.isMiniPlayerActivate ? 0 : nil) } .onChange(of: miniPlayerViewModel.isMiniPlayerActivate) { isActivated in if !isActivated { - viewModel.song = selectedSongInfo - self.currentSongId = viewModel.song.id - viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) setMediaPlayerNextTrack() } } .background( - Color("\(viewModel.song.team)Sub") + Color("\(miniPlayerViewModel.song.team)Sub") .cornerRadius(8) .ignoresSafeArea(.keyboard) .onTapGesture { @@ -140,7 +126,7 @@ struct MiniPlayerView: View { miniPlayerViewModel.width = UIScreen.main.bounds.width miniPlayerViewModel.isMiniPlayerActivate.toggle() miniPlayerViewModel.hideTabBar = true - print("\(viewModel.song.title)") + print("\(miniPlayerViewModel.song.title)") } } @@ -161,7 +147,7 @@ struct MiniPlayerView: View { Rectangle() .frame(height: 120) .foregroundColor(Color(UIColor.clear)) - .background(isTop ? (viewModel.isScrolled ? Color.fetchTopGradient(color: Color("\(viewModel.song.team)Sub")) : nil ) : Color.fetchBottomGradient(color: Color("\(viewModel.song.team)Sub"))) + .background(isTop ? (miniPlayerViewModel.isScrolled ? Color.fetchTopGradient(color: Color("\(miniPlayerViewModel.song.team)Sub")) : nil ) : Color.fetchBottomGradient(color: Color("\(miniPlayerViewModel.song.team)Sub"))) .allowsHitTesting(false) } @@ -173,7 +159,7 @@ struct MiniPlayerView: View { isPlaylistView.toggle() }, label: { ZStack { - Circle().foregroundColor(Color("\(viewModel.song.team)Background")).frame(width: 43, height: 43) + Circle().foregroundColor(Color("\(miniPlayerViewModel.song.team)Background")).frame(width: 43, height: 43) Image(systemName: isPlaylistView ? "quote.bubble.fill" : "list.bullet").foregroundColor(.white) } @@ -184,22 +170,22 @@ struct MiniPlayerView: View { private func setMediaPlayerNextTrack() { let commandCenter = MPRemoteCommandCenter.shared() commandCenter.nextTrackCommand.addTarget { _ in - if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == miniPlayerViewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) + miniPlayerViewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) } else { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) + miniPlayerViewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) } } return .success } commandCenter.previousTrackCommand.addTarget { _ in - if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == miniPlayerViewModel.song.id}) { if index - 1 < 0 { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) + miniPlayerViewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) } else { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) + miniPlayerViewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) } } return .success @@ -209,7 +195,7 @@ struct MiniPlayerView: View { private struct Lyric: View { - @StateObject var viewModel: SongDetailViewModel + @StateObject var viewModel: MiniPlayerViewModel var body: some View { ScrollView(showsIndicators: true) { @@ -219,7 +205,7 @@ private struct Lyric: View { .font(.Halmap.CustomHeadline) .lineSpacing(20) .frame(maxWidth: .infinity, alignment: .leading) - .padding(EdgeInsets(top: 40, leading: 40, bottom: 230, trailing: 40)) + .padding(EdgeInsets(top: 40, leading: 40, bottom: 300, trailing: 40)) } .background(GeometryReader{ Color.clear.preference( @@ -250,7 +236,7 @@ private struct Lyric: View { private struct PlayBar: View { - @StateObject var viewModel: SongDetailViewModel + @StateObject var viewModel: MiniPlayerViewModel @FetchRequest( entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], @@ -319,7 +305,7 @@ private struct PlayBar: View { private struct FavoriteButton: View { - @StateObject var viewModel: SongDetailViewModel + @StateObject var viewModel: MiniPlayerViewModel @FetchRequest( entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], diff --git a/Halmap/Features/SongDetail/MiniPlayerViewModel.swift b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift index 5fabd74..1bd156a 100644 --- a/Halmap/Features/SongDetail/MiniPlayerViewModel.swift +++ b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift @@ -5,10 +5,24 @@ // Created by Kyubo Shim on 12/12/23. // -import Foundation import SwiftUI +import Combine class MiniPlayerViewModel: ObservableObject { + static let instance = MiniPlayerViewModel(song: SongInfo(id: "", team: "", type: false, title: "", lyrics: "", info: "", url: "")) + + private let audioManager = AudioManager.instance + private let dataManager = DataManager.instance + private let persistence = PersistenceController.shared + + @Published var song: SongInfo + @Published var isScrolled = false + @Published var isFavorite = false + @Published var isPlaying = false + + private var cancellables = Set() + + //미니 플레이어 @Published var showPlayer = false @Published var offset: CGFloat = 0 @@ -17,29 +31,87 @@ class MiniPlayerViewModel: ObservableObject { @Published var isMiniPlayerActivate = false @Published var hideTabBar = false -// func convertTeamNameToKor(teamName: String) -> String { -// switch teamName{ -// case "doosan": -// return "두산 베어스" -// case "hanwha": -// return "한화 이글스" -// case "samsung": -// return "삼성 라이온즈" -// case "lotte": -// return "롯데 자이언츠" -// case .: -// return "엘지 트윈스" -// case .ssg: -// return "쓱 랜더스" -// case .kt: -// return "케이티 위즈" -// case .nc: -// return "엔씨 다이노스" -// case .kiwoom: -// return "키움 히어로즈" -// case .kia: -// return "기아 타이거즈" -// } -// } + init(song: SongInfo) { + self.song = song + + audioManager.$isPlaying + .sink { [weak self] in self?.isPlaying = $0 } + .store(in: &cancellables) + } + + func handleLikeButtonTap(deleteSong: CollectedSong) { + if isFavorite { + persistence.deleteSongs(song: deleteSong) + } else { + persistence.saveSongs(song: song, playListTitle: "favorite") + } + isFavorite.toggle() + } + + //AudioManager + func removePlayer() { + self.audioManager.removePlayer() + } + + func setPlayer() { + self.audioManager.AMset(song: song) + } + + func handlePlayButtonTap() { + if !audioManager.isPlaying { + audioManager.AMplay() + } else { + audioManager.AMstop() + } + } + + func getAudioIsPlaying() -> Bool { + audioManager.isPlaying + } + + func addDefaultPlaylist(defaultPlaylistSongs: FetchedResults) { + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == self.song.id}) { + persistence.reorderSelectedSong(index: index, results: defaultPlaylistSongs) + } else { + persistence.saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(defaultPlaylistSongs.count)) + } + } + + func convertSongToSongInfo(song: CollectedSong) -> SongInfo { + SongInfo( + id: song.id ?? "", + team: song.team ?? "", + type: song.type, + title: song.title ?? "", + lyrics: song.lyrics ?? "", + info: song.info ?? "", + url: song.url ?? "" + ) + } + + func convertSongToSongInfo(song: Song) -> SongInfo { + SongInfo( + id: song.id, + team: UserDefaults.standard.string(forKey: "selectedTeam") ?? "", + type: song.type, + title: song.title, + lyrics: song.lyrics, + info: song.info, + url: song.url + ) + } + + // MARK: - initailize playlist view model + func getAudioManager() -> AudioManager { + self.audioManager + } + + func getdataManager() -> DataManager { + self.dataManager + } + + func getSongInfo() -> SongInfo { + self.song + } } diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index abade01..62356fc 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -60,6 +60,7 @@ struct PlaylistView: View { } .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) .listRowBackground(Color("\(song.team)Sub")) + .listRowSeparator(.hidden) .onPreferenceChange(ViewOffsetKey.self) { if $0 > -(UIScreen.getHeight(90)) { withAnimation { @@ -71,8 +72,9 @@ struct PlaylistView: View { } } } - Color.clear.frame(height:70) + Color.clear.frame(height:100) .listRowBackground(Color.clear) + .listRowSeparator(.hidden) } .coordinateSpace(name: "list") .listStyle(.plain) diff --git a/Halmap/Features/SongDetail/PlaylistViewModel.swift b/Halmap/Features/SongDetail/PlaylistViewModel.swift index 6759885..f54c645 100644 --- a/Halmap/Features/SongDetail/PlaylistViewModel.swift +++ b/Halmap/Features/SongDetail/PlaylistViewModel.swift @@ -12,7 +12,7 @@ final class PlaylistViewModel: ObservableObject { private let dataManager: DataManager private var song: SongInfo - init(viewModel: SongDetailViewModel) { + init(viewModel: MiniPlayerViewModel) { audioManager = viewModel.getAudioManager() dataManager = viewModel.getdataManager() song = viewModel.getSongInfo() diff --git a/Halmap/Features/SongDetail/SongDetailView.swift b/Halmap/Features/SongDetail/SongDetailView.swift deleted file mode 100644 index 99ddb06..0000000 --- a/Halmap/Features/SongDetail/SongDetailView.swift +++ /dev/null @@ -1,278 +0,0 @@ -// -// SongDetailView.swift -// Halmap -// -// Created by JeonJimin on 10/8/23. -// -import SwiftUI -import AVFoundation -import MediaPlayer - -struct SongDetailView: View { - - @StateObject var viewModel: SongDetailViewModel - @FetchRequest( - entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], - predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, - animation: .default) private var defaultPlaylistSongs: FetchedResults - - @AppStorage("currentSongId") var currentSongId: String = "" - @State var isPlaylistView = false - @State private var toast: Toast? = nil - - var body: some View { - ZStack { - Color("\(viewModel.song.team)Sub") - .ignoresSafeArea() - - if isPlaylistView { - PlaylistView( - viewModel: PlaylistViewModel(viewModel: viewModel), - song: $viewModel.song, - isScrolled: $viewModel.isScrolled, - isPlaying: $viewModel.isPlaying) - .padding(.top, 10) - .padding(.bottom, 150) - } else { - Lyric(viewModel: viewModel) - } - - VStack(spacing: 0) { - Rectangle() - .frame(height: UIScreen.getHeight(108)) - .foregroundColor(Color("\(viewModel.song.team)Sub")) - gradientRectangle(isTop: true) - Spacer() - ZStack(alignment: .bottom) { - gradientRectangle(isTop: false) - playlistButton - } - .toastView(toast: $toast) - - PlayBar(viewModel: viewModel, toast: $toast) - } - .ignoresSafeArea() - } - .navigationTitle(viewModel.song.title) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - FavoriteButton(viewModel: viewModel) - } - } - .onAppear() { - self.currentSongId = viewModel.song.id - viewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) - setMediaPlayerNextTrack() - } - .onChange(of: viewModel.song.id) { _ in - self.currentSongId = viewModel.song.id - } - .onChange(of: currentSongId) { _ in - if let index = defaultPlaylistSongs.firstIndex(where: { $0.id == viewModel.song.id }) { - self.viewModel.song = viewModel.convertSongToSongInfo(song: defaultPlaylistSongs[index]) - self.viewModel.getAudioManager().AMset(song: self.viewModel.song) - } - } - } - - @ViewBuilder - private func gradientRectangle(isTop: Bool) -> some View { - Rectangle() - .frame(height: 120) - .foregroundColor(Color(UIColor.clear)) - .background(isTop ? (viewModel.isScrolled ? Color.fetchTopGradient(color: Color("\(viewModel.song.team)Sub")) : nil ) : Color.fetchBottomGradient(color: Color("\(viewModel.song.team)Sub"))) - .allowsHitTesting(false) - } - - var playlistButton: some View { - // PlaylistButton - HStack(){ - Spacer() - Button(action: { - isPlaylistView.toggle() - }, label: { - ZStack { - Circle().foregroundColor(Color("\(viewModel.song.team)Background")).frame(width: 43, height: 43) - Image(systemName: isPlaylistView ? "quote.bubble.fill" : "list.bullet").foregroundColor(.white) - - } - }) - }.padding(20) - } - - private func setMediaPlayerNextTrack() { - let commandCenter = MPRemoteCommandCenter.shared() - commandCenter.nextTrackCommand.addTarget { _ in - if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { - if index + 1 > defaultPlaylistSongs.count - 1 { - toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) - } else { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) - } - } - return .success - } - commandCenter.previousTrackCommand.addTarget { _ in - if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { - if index - 1 < 0 { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) - } else { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) - } - } - return .success - } - } -} - -private struct Lyric: View { - - @StateObject var viewModel: SongDetailViewModel - - var body: some View { - ScrollView(showsIndicators: true) { - VStack { - Text("\(viewModel.song.lyrics.replacingOccurrences(of: "\\n", with: "\n"))") - .foregroundColor(.white.opacity(0.8)) - .font(.Halmap.CustomHeadline) - .lineSpacing(20) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(EdgeInsets(top: 40, leading: 40, bottom: 230, trailing: 40)) - } - .background(GeometryReader{ - Color.clear.preference( - key: ViewOffsetKey.self, - value: -$0.frame(in: .named("scroll")).origin.y) - }) - .onPreferenceChange(ViewOffsetKey.self) { - if $0 > 0 { - withAnimation { - viewModel.isScrolled = true - } - } else { - withAnimation { - viewModel.isScrolled = false - } - } - } - } - .coordinateSpace(name: "scroll") - } - private struct ViewOffsetKey: PreferenceKey { - static var defaultValue = CGFloat.zero - static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { - value += nextValue() - } - } -} - -private struct PlayBar: View { - - @StateObject var viewModel: SongDetailViewModel - @FetchRequest( - entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], - predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, - animation: .default) private var defaultPlaylistSongs: FetchedResults - - @Binding var toast: Toast? - - var body: some View { - VStack(spacing: 0) { - Progressbar( - song: $viewModel.song, - toast: $toast, - isThumbActive: true) - HStack(spacing: 52) { - Button { - //이전곡 재생 기능 - if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { - if index - 1 < 0 { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.last!) - } else { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index - 1]) - } - } - } label: { - Image(systemName: "backward.end.fill") - .font(.system(size: 28, weight: .regular)) - .foregroundColor(.customGray) - } - Button { - viewModel.handlePlayButtonTap() - } label: { - Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") - .font(.system(size: 60, weight: .medium)) - .foregroundStyle(Color.customGray) - } - Button { - //다음곡 재생 기능 - if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { - if index + 1 > defaultPlaylistSongs.count - 1 { - toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) - } else { - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) - } - } - } label: { - Image(systemName: "forward.end.fill") - .font(.system(size: 28, weight: .regular)) - .foregroundColor(.customGray) - } - } - .padding(.bottom, 54) - } - .padding(.horizontal, 45) - .frame(maxWidth: .infinity) - .background(Color("\(viewModel.song.team)Sub")) - .onDisappear(){ - viewModel.removePlayer() - } - .onAppear(){ - viewModel.setPlayer() - } - } -} - -private struct FavoriteButton: View { - - @StateObject var viewModel: SongDetailViewModel - @FetchRequest( - entity: CollectedSong.entity(), - sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], - predicate: PlaylistFilter(filter: "favorite").predicate, - animation: .default) private var favoriteSongs: FetchedResults - - var body: some View { - Button { - viewModel.handleLikeButtonTap(deleteSong: findFavoriteSong()) - } label: { - Image(systemName: viewModel.isFavorite ? "heart.fill" : "heart") - .foregroundStyle(viewModel.isFavorite ? Color("\(viewModel.song.team)Point") : Color.white) - } - .onAppear() { - if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { - viewModel.isFavorite = true - } - } - .onChange(of: viewModel.song.id) { _ in - if favoriteSongs.contains(where: {$0.id == viewModel.song.id}) { - viewModel.isFavorite = true - } else { - viewModel.isFavorite = false - } - } - } - - private func findFavoriteSong() -> CollectedSong { - if let index = favoriteSongs.firstIndex(where: {$0.id == viewModel.song.id}) { - return favoriteSongs[index] - } else { - return CollectedSong() - } - } -} diff --git a/Halmap/Features/SongDetail/SongDetailViewModel.swift b/Halmap/Features/SongDetail/SongDetailViewModel.swift deleted file mode 100644 index be43f9a..0000000 --- a/Halmap/Features/SongDetail/SongDetailViewModel.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// SongDetailViewModel.swift -// Halmap -// -// Created by JeonJimin on 10/8/23. -// -import SwiftUI -import Combine - -final class SongDetailViewModel: ObservableObject { - @ObservedObject private var audioManager: AudioManager - private let dataManager: DataManager - private let persistence: PersistenceController - - @Published var song: SongInfo - @Published var isScrolled = false - @Published var isFavorite = false - @Published var isPlaying = false - - - private var cancellables = Set() - - init(audioManager: AudioManager, dataManager: DataManager, persistence: PersistenceController, song: SongInfo) { - self.audioManager = audioManager - self.dataManager = dataManager - self.persistence = persistence - self.song = song - - audioManager.$isPlaying - .sink { [weak self] in self?.isPlaying = $0 } - .store(in: &cancellables) - } - - func handleLikeButtonTap(deleteSong: CollectedSong) { - if isFavorite { - persistence.deleteSongs(song: deleteSong) - } else { - persistence.saveSongs(song: song, playListTitle: "favorite") - } - isFavorite.toggle() - } - - //AudioManager - func removePlayer() { - self.audioManager.removePlayer() - } - - func setPlayer() { - self.audioManager.AMset(song: song) - } - - func handlePlayButtonTap() { - if !audioManager.isPlaying { - audioManager.AMplay() - } else { - audioManager.AMstop() - } - } - - func getAudioIsPlaying() -> Bool { - audioManager.isPlaying - } - - func addDefaultPlaylist(defaultPlaylistSongs: FetchedResults) { - if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == self.song.id}) { - persistence.reorderSelectedSong(index: index, results: defaultPlaylistSongs) - } else { - persistence.saveSongs(song: song, playListTitle: "defaultPlaylist", order: Int64(defaultPlaylistSongs.count)) - } - } - - func convertSongToSongInfo(song: CollectedSong) -> SongInfo { - SongInfo( - id: song.id ?? "", - team: song.team ?? "", - type: song.type, - title: song.title ?? "", - lyrics: song.lyrics ?? "", - info: song.info ?? "", - url: song.url ?? "" - ) - } - - // MARK: - initailize playlist view model - func getAudioManager() -> AudioManager { - self.audioManager - } - - func getdataManager() -> DataManager { - self.dataManager - } - - func getSongInfo() -> SongInfo { - self.song - } -} diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 59a02a5..1d7b803 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -18,10 +18,15 @@ struct MainSongListTabView: View { @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel @State private var isShowingHalfSheet: Bool = false - @State private var isActiveNavigatioinLink: Bool = false @State private var selectedSong: SongInfo? @State var collectedSong: CollectedSong? - @Binding var songInfo: SongInfo + + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, + animation: .default) private var defaultPlaylistSongs: FetchedResults + let persistence = PersistenceController.shared var body: some View { @@ -39,8 +44,14 @@ struct MainSongListTabView: View { Button { let currentSongs = viewModel.index == 0 ? dataManager.teamSongs : dataManager.playerSongs persistence.fetchPlaylistAllMain(newSongs: currentSongs) - currentSongId = currentSongs.first!.id - + miniPlayerViewModel.removePlayer() + self.miniPlayerViewModel.song = miniPlayerViewModel.convertSongToSongInfo(song: currentSongs[0]) + miniPlayerViewModel.setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + } } label: { HStack(spacing: 5) { Image(systemName: "play.circle.fill") @@ -109,17 +120,16 @@ struct MainSongListTabView: View { .listRowSeparatorTint(Color.customGray) .background(Color.systemBackground) .onTapGesture { - self.songInfo = songInfo + self.miniPlayerViewModel.song = songInfo print(songInfo.title) withAnimation{ miniPlayerViewModel.showPlayer = true miniPlayerViewModel.hideTabBar = true miniPlayerViewModel.isMiniPlayerActivate = false selectedSong = songInfo - isActiveNavigatioinLink.toggle() } + miniPlayerViewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) } - } RequestSongView(buttonColor: Color.HalmacPoint) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) @@ -176,20 +186,18 @@ struct MainSongListTabView: View { isShowingHalfSheet.toggle() } } - .onTapGesture { - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() - self.songInfo = songInfo - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() - withAnimation{ - miniPlayerViewModel.showPlayer = true - miniPlayerViewModel.hideTabBar = true - miniPlayerViewModel.isMiniPlayerActivate = false - selectedSong = songInfo - isActiveNavigatioinLink.toggle() - } + miniPlayerViewModel.removePlayer() + self.miniPlayerViewModel.song = songInfo + miniPlayerViewModel.setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + selectedSong = songInfo } - + miniPlayerViewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) + } } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) @@ -245,6 +253,6 @@ struct MainSongListTabView: View { struct MainSongListTabView_Previews: PreviewProvider { static var previews: some View { - MainSongListTabView(songInfo: .constant(SongInfo(id: "", team: "Lotte", type: true, title: "", lyrics: "",info: "", url: ""))) + MainSongListTabView() } } diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index 44fd5e7..59d6efd 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -11,10 +11,9 @@ struct SongSearchView: View { @EnvironmentObject var dataManager: DataManager @EnvironmentObject var audioManager: AudioManager let persistence = PersistenceController.shared - @ObservedObject var miniPlayerViewModel: MiniPlayerViewModel + @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel @StateObject var viewModel: SongSearchViewModel @FocusState private var isFocused: Bool - @Binding var songInfo: SongInfo var body: some View { @@ -114,9 +113,9 @@ struct SongSearchView: View { url: song.url) Button(action: { - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() - self.songInfo = song - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() + miniPlayerViewModel.removePlayer() + self.miniPlayerViewModel.song = song + miniPlayerViewModel.setPlayer() withAnimation{ miniPlayerViewModel.showPlayer = true miniPlayerViewModel.hideTabBar = true @@ -151,11 +150,3 @@ struct SongSearchView: View { } } } - - -// MARK: Previews -//struct SongSearchView_Previews: PreviewProvider { -// static var previews: some View { -// SongSearchView(viewModel: SongSearchViewModel(dataManager: DataManager())) -// } -//} diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index 7347c58..2c0699d 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -15,13 +15,18 @@ struct ScalingHeaderView: View { @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.date, ascending: true)], predicate: PlaylistFilter(filter: "favorite").predicate, animation: .default) private var collectedSongs: FetchedResults + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, + animation: .default) private var defaultPlaylistSongs: FetchedResults + @StateObject var viewModel: SongStorageViewModel @State var collectedSongData: CollectedSong? @State var isShowSheet = false @AppStorage("currentSongId") var currentSongId: String = "" - @ObservedObject var miniPlayerViewModel: MiniPlayerViewModel - @Binding var songInfo: SongInfo + @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel var body: some View { ScrollView(.vertical, showsIndicators: false) { @@ -43,7 +48,14 @@ struct ScalingHeaderView: View { if collectedSongs.count > 0 { Button { persistence.fetchPlaylistAll() - currentSongId = collectedSongs.first!.safeId + miniPlayerViewModel.removePlayer() + self.miniPlayerViewModel.song = miniPlayerViewModel.convertSongToSongInfo(song: collectedSongs[0]) + miniPlayerViewModel.setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + } } label: { HStack(spacing: 5) { Image(systemName: "play.circle.fill") @@ -82,14 +94,15 @@ struct ScalingHeaderView: View { VStack(spacing: 0) { Button(action: { - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).removePlayer() - self.songInfo = songInfo - SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: self.songInfo).setPlayer() + miniPlayerViewModel.removePlayer() + self.miniPlayerViewModel.song = song + miniPlayerViewModel.setPlayer() withAnimation{ miniPlayerViewModel.showPlayer = true miniPlayerViewModel.hideTabBar = true miniPlayerViewModel.isMiniPlayerActivate = false } + miniPlayerViewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) }, label: { HStack(spacing: 16) { Image(viewModel.fetchSongImageName(favoriteSong: favoriteSong)) @@ -119,43 +132,6 @@ struct ScalingHeaderView: View { .padding(.horizontal, 20) .padding(.vertical, 15) }) -// NavigationLink { -// SongDetailView( -// viewModel: SongDetailViewModel( -// audioManager: audioManager, -// dataManager: dataManager, -// persistence: persistence, -// song: song -// )) -// } label: { -// HStack(spacing: 16) { -// Image(viewModel.fetchSongImageName(favoriteSong: favoriteSong)) -// .resizable() -// .frame(width: 40, height: 40) -// .cornerRadius(8) -// VStack(alignment: .leading, spacing: 6) { -// Text(favoriteSong.safeTitle) -// .font(Font.Halmap.CustomBodyMedium) -// .foregroundColor(.black) -// Text(TeamName(rawValue: favoriteSong.safeTeam)?.fetchTeamNameKr() ?? ".") -// .font(Font.Halmap.CustomCaptionMedium) -// .foregroundColor(.customDarkGray) -// } -// .frame(maxWidth: .infinity, alignment: .leading) -// .lineLimit(1) -// -// Button { -// self.collectedSongData = favoriteSong -// isShowSheet.toggle() -// } label: { -// Image(systemName: "ellipsis") -// .foregroundColor(.customDarkGray) -// .frame(maxWidth: 35, maxHeight: .infinity) -// } -// } -// .padding(.horizontal, 20) -// .padding(.vertical, 15) -// } } .background(Color.systemBackground) } @@ -188,9 +164,15 @@ struct ScalingHeaderView: View { Spacer() if collectedSongs.count > 0 { Button { - //TODO: 첫번째 곡 재생 -> SongDetailView song persistence.fetchPlaylistAll() - self.currentSongId = collectedSongs.first!.safeId + miniPlayerViewModel.removePlayer() + self.miniPlayerViewModel.song = miniPlayerViewModel.convertSongToSongInfo(song: collectedSongs[0]) + miniPlayerViewModel.setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + } } label: { Image(systemName: "play.circle.fill") .foregroundColor(.mainGreen) @@ -218,9 +200,3 @@ struct ScalingHeaderView: View { } } } - -//struct ScalingHeaderView_Previews: PreviewProvider { -// static var previews: some View { -// StorageContentView() -// } -//} diff --git a/Halmap/Features/SongStorage/StorageContentView.swift b/Halmap/Features/SongStorage/StorageContentView.swift index 9cdec69..906e8f9 100644 --- a/Halmap/Features/SongStorage/StorageContentView.swift +++ b/Halmap/Features/SongStorage/StorageContentView.swift @@ -10,14 +10,13 @@ import SwiftUI struct StorageContentView: View { @EnvironmentObject var dataManager: DataManager let persistence = PersistenceController.shared - @ObservedObject var miniPlayerViewModel: MiniPlayerViewModel - @Binding var songInfo: SongInfo + @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel var body: some View { GeometryReader { proxy in let topEdge = proxy.safeAreaInsets.top - ScalingHeaderView(viewModel: SongStorageViewModel(dataManager: dataManager, persistence: persistence, topEdge: topEdge), miniPlayerViewModel: miniPlayerViewModel, songInfo: $songInfo) + ScalingHeaderView(viewModel: SongStorageViewModel(dataManager: dataManager, persistence: persistence, topEdge: topEdge)) .ignoresSafeArea(.all, edges: .top) } } diff --git a/Halmap/HalmapApp.swift b/Halmap/HalmapApp.swift index 7d84d8b..4e82581 100644 --- a/Halmap/HalmapApp.swift +++ b/Halmap/HalmapApp.swift @@ -24,8 +24,8 @@ struct HalmapApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate - @StateObject var dataManager = DataManager() - @StateObject var audioManager = AudioManager() + @StateObject var dataManager = DataManager.instance + @StateObject var audioManager = AudioManager.instance let persistenceController = PersistenceController.shared diff --git a/Halmap/View/Component/Utility.swift b/Halmap/View/Component/Utility.swift index ce34474..3c3561f 100644 --- a/Halmap/View/Component/Utility.swift +++ b/Halmap/View/Component/Utility.swift @@ -46,5 +46,12 @@ class Utility: NSObject { return "\(song.team)\(song.type ? "Player" : "Album")" } } + static func getAlbumImage(with song: SongInfo, seasonSongs: [[String]]) -> String { + if seasonSongs[TeamName(rawValue: song.team)?.fetchTeamIndex() ?? 0].contains(song.title) { + return "\(song.team)23" + } else { + return "\(song.team)\(song.type ? "Player" : "Album")" + } + } } diff --git a/Halmap/View/MainTabView.swift b/Halmap/View/MainTabView.swift index 6da8316..a213d55 100644 --- a/Halmap/View/MainTabView.swift +++ b/Halmap/View/MainTabView.swift @@ -11,12 +11,11 @@ struct MainTabView: View { @EnvironmentObject var dataManager: DataManager @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel + @AppStorage("selectedTeam") var selectedTeam = "Hanwha" @StateObject var viewModel = MainTabViewModel() - @Binding var songInfo: SongInfo - init(songInfo: Binding) { - self._songInfo = songInfo + init() { Color.setColor(selectedTeam) } @@ -25,11 +24,11 @@ struct MainTabView: View { VStack(spacing: 0) { switch viewModel.state { case .home: - MainSongListTabView(songInfo: $songInfo) + MainSongListTabView() case .search: - SongSearchView(miniPlayerViewModel: miniPlayerViewModel, viewModel: SongSearchViewModel(dataManager: dataManager), songInfo: $songInfo) + SongSearchView(viewModel: SongSearchViewModel(dataManager: dataManager)) case .storage: - StorageContentView(miniPlayerViewModel: miniPlayerViewModel, songInfo: $songInfo) + StorageContentView() } HStack { @@ -62,6 +61,6 @@ struct MainTabView: View { struct MainTabView_Previews: PreviewProvider { static var previews: some View { - MainTabView(songInfo: .constant(SongInfo(id: "", team: "Lotte", type: true, title: "", lyrics: "", info: "", url: ""))) + MainTabView() } } diff --git a/Halmap/View/OnBoardingStartView.swift b/Halmap/View/OnBoardingStartView.swift index e659bb2..7c1edf2 100644 --- a/Halmap/View/OnBoardingStartView.swift +++ b/Halmap/View/OnBoardingStartView.swift @@ -15,8 +15,7 @@ struct OnBoardingStartView: View { @AppStorage("latestVersion") var latestVersion = "1.0.0" @EnvironmentObject var audioManager: AudioManager @EnvironmentObject var dataManager: DataManager - @StateObject var miniPlayerViewModel = MiniPlayerViewModel() - @State var songInfo = SongInfo(id: "", team: "Lotte", type: true, title: "", lyrics: "", info: "", url: "") + @StateObject var miniPlayerViewModel = MiniPlayerViewModel.instance @GestureState var gestureOffset: CGFloat = 0 @Namespace var animation let persistence = PersistenceController.shared @@ -26,8 +25,7 @@ struct OnBoardingStartView: View { ZStack{ ForEach(Array(TeamName.allCases.enumerated()), id: \.offset) { index, team in if Themes.themes[index] == TeamName(rawValue: selectedTeam) { - MainTabView(songInfo: $songInfo) - .environmentObject(miniPlayerViewModel) + MainTabView() .sheet(isPresented: $isShouldShowTraffic) { HalfSheet { NotificationView(type: .traffic) @@ -43,7 +41,7 @@ struct OnBoardingStartView: View { VStack{ Spacer() if miniPlayerViewModel.showPlayer { - MiniPlayerView(viewModel: SongDetailViewModel(audioManager: audioManager, dataManager: dataManager, persistence: persistence, song: songInfo), selectedSongInfo: $songInfo) + MiniPlayerView() .transition(.move(edge: .bottom)) .offset(y: miniPlayerViewModel.offset) .gesture(DragGesture().updating($gestureOffset, body: { (value, state, _) in From 75efad5a79cac4452c08dbd0b5d91eeff03c273d Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 13 Dec 2023 21:37:25 +0900 Subject: [PATCH 34/56] =?UTF-8?q?[Feat]=20=EA=B2=80=EC=83=89=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=9D=8C=EC=95=85=20=EC=9E=AC=EC=83=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongSearch/SongSearchView.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index 59d6efd..6a41c25 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -14,6 +14,12 @@ struct SongSearchView: View { @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel @StateObject var viewModel: SongSearchViewModel @FocusState private var isFocused: Bool + + @FetchRequest( + entity: CollectedSong.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], + predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, + animation: .default) private var defaultPlaylistSongs: FetchedResults var body: some View { @@ -121,6 +127,7 @@ struct SongSearchView: View { miniPlayerViewModel.hideTabBar = true miniPlayerViewModel.isMiniPlayerActivate = false } + miniPlayerViewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) }, label: { HStack(spacing: 16) { Image(viewModel.getAlbumImage(with: song)) From 1019ea81cc810015aac900a21f7799988d2239dc Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 13 Dec 2023 21:48:54 +0900 Subject: [PATCH 35/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=ED=99=94=EB=A9=B4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=8C=80=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/MiniPlayerView.swift | 16 ++++++++++------ .../SongDetail/MiniPlayerViewModel.swift | 8 ++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index e48f751..d43ecb2 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -23,7 +23,7 @@ struct MiniPlayerView: View { var body: some View { VStack(spacing: 0){ - HStack(spacing: 11){ + HStack(spacing: 16){ if !miniPlayerViewModel.isMiniPlayerActivate { Button(action: { withAnimation{ @@ -33,20 +33,24 @@ struct MiniPlayerView: View { } }, label: { Image(systemName: "chevron.down") - .resizable() - .scaledToFit() - .frame(width: 24) + .font(.system(size: 24)) .foregroundColor(Color.white) }) - + } + if !miniPlayerViewModel.isMiniPlayerActivate { + Image(miniPlayerViewModel.fetchImage()) + .resizable() + .cornerRadius(10) + .frame(width: 52, height: 52) + .shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4) } VStack(alignment: .leading){ Text("\(miniPlayerViewModel.song.title)") .font(Font.Halmap.CustomBodyBold) .foregroundColor(Color.white) - Text("\(miniPlayerViewModel.song.team)") + Text(miniPlayerViewModel.getTeamNameKr()) .font(Font.Halmap.CustomCaptionMedium) .foregroundColor(Color.customGray) } diff --git a/Halmap/Features/SongDetail/MiniPlayerViewModel.swift b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift index 1bd156a..257f48e 100644 --- a/Halmap/Features/SongDetail/MiniPlayerViewModel.swift +++ b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift @@ -113,5 +113,13 @@ class MiniPlayerViewModel: ObservableObject { func getSongInfo() -> SongInfo { self.song } + + func fetchImage() -> String { + Utility.getAlbumImage(with: self.song, seasonSongs: dataManager.seasonSongs) + } + + func getTeamNameKr() -> String { + TeamName(rawValue: song.team)?.fetchTeamNameKr() ?? "" + } } From 0422817f1bf576361740f154b0185ac17cf81019 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 13 Dec 2023 22:05:52 +0900 Subject: [PATCH 36/56] =?UTF-8?q?[Feat]=20=EB=AF=B8=EB=8B=88=20=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/SongDetail/MiniPlayerView.swift | 29 +++++++++++++------ .../SongList/MainSongListTabView.swift | 2 +- .../SongStorage/StorageContentView.swift | 6 ---- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index d43ecb2..1f5516d 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -46,7 +46,7 @@ struct MiniPlayerView: View { .shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4) } - VStack(alignment: .leading){ + VStack(alignment: .leading, spacing: 4){ Text("\(miniPlayerViewModel.song.title)") .font(Font.Halmap.CustomBodyBold) .foregroundColor(Color.white) @@ -57,12 +57,25 @@ struct MiniPlayerView: View { Spacer() if miniPlayerViewModel.isMiniPlayerActivate { - HStack{ + HStack(spacing: 21) { Button(action: { miniPlayerViewModel.handlePlayButtonTap() }, label: { - Image(systemName: miniPlayerViewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") - .font(.system(size: 30, weight: .medium)) + Image(systemName: miniPlayerViewModel.isPlaying ? "pause.fill" : "play.fill") + .font(.system(size: 20, weight: .medium)) + .foregroundStyle(Color.white) + }) + Button(action: { + if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == miniPlayerViewModel.song.id}) { + if index + 1 > defaultPlaylistSongs.count - 1 { + miniPlayerViewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) + } else { + miniPlayerViewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) + } + } + }, label: { + Image(systemName: "forward.end.fill") + .font(.system(size: 20, weight: .medium)) .foregroundStyle(Color.white) }) } @@ -101,6 +114,9 @@ struct MiniPlayerView: View { } .ignoresSafeArea() } + .onAppear() { + setMediaPlayerNextTrack() + } .onChange(of: miniPlayerViewModel.song.id) { _ in self.currentSongId = miniPlayerViewModel.song.id } @@ -116,11 +132,6 @@ struct MiniPlayerView: View { .opacity(miniPlayerViewModel.isMiniPlayerActivate ? 0 : getOpacity()) .frame(height: miniPlayerViewModel.isMiniPlayerActivate ? 0 : nil) } - .onChange(of: miniPlayerViewModel.isMiniPlayerActivate) { isActivated in - if !isActivated { - setMediaPlayerNextTrack() - } - } .background( Color("\(miniPlayerViewModel.song.team)Sub") .cornerRadius(8) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 1d7b803..c60af09 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -64,7 +64,7 @@ struct MainSongListTabView: View { } } .padding(EdgeInsets(top: 20, leading: 20, bottom: 15, trailing: 20)) - .padding(.top, UIScreen.getHeight(48)) + .padding(.top, UIScreen.getHeight(32)) Divider() .overlay(Color.customGray.opacity(0.6)) diff --git a/Halmap/Features/SongStorage/StorageContentView.swift b/Halmap/Features/SongStorage/StorageContentView.swift index 906e8f9..6325ef7 100644 --- a/Halmap/Features/SongStorage/StorageContentView.swift +++ b/Halmap/Features/SongStorage/StorageContentView.swift @@ -21,9 +21,3 @@ struct StorageContentView: View { } } } - -//struct StorageContentView_Previews: PreviewProvider { -// static var previews: some View { -// StorageContentView() -// } -//} From e0bd04a32dacd0170574adb09233c08242214dfa Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Sun, 17 Dec 2023 15:45:16 +0900 Subject: [PATCH 37/56] =?UTF-8?q?4=EB=B2=88=EB=B9=8C=EB=93=9C=20qa=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 18 ++---- .../NCAlbum.imageset/Contents.json" | 2 +- .../Features/SongDetail/MiniPlayerView.swift | 59 +++++++++++++++---- .../SongList/MainSongListTabView.swift | 1 + .../SongStorage/ScalingHeaderView.swift | 1 + Halmap/View/OnBoardingStartView.swift | 35 +---------- 6 files changed, 57 insertions(+), 59 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 72b773d..cf2551c 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -682,13 +682,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -702,11 +700,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -722,12 +719,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -741,11 +736,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git "a/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" "b/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" index a0647a8..afd4eba 100644 --- "a/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" +++ "b/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "NcAlbum.png", + "filename" : "NCAlbum.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index 1f5516d..de18a26 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -20,6 +20,7 @@ struct MiniPlayerView: View { @AppStorage("currentSongId") var currentSongId: String = "" @State var isPlaylistView = false @State private var toast: Toast? = nil + @GestureState var gestureOffset: CGFloat = 0 var body: some View { VStack(spacing: 0){ @@ -27,7 +28,6 @@ struct MiniPlayerView: View { if !miniPlayerViewModel.isMiniPlayerActivate { Button(action: { withAnimation{ - miniPlayerViewModel.showPlayer = false miniPlayerViewModel.hideTabBar = false miniPlayerViewModel.isMiniPlayerActivate = true } @@ -86,7 +86,25 @@ struct MiniPlayerView: View { } } .padding(.horizontal, 20) - .frame(height: 60) + .frame(height: miniPlayerViewModel.isMiniPlayerActivate ? 60 : 80) + .contentShape(Rectangle()) + .gesture(DragGesture().updating($gestureOffset, body: { (value, state, _) in + + state = value.translation.height + }) + .onEnded(onEnd(value:))) + .onChange(of: gestureOffset, perform: { value in + onChanged() + }) + .onTapGesture { + if miniPlayerViewModel.isMiniPlayerActivate { + withAnimation{ + miniPlayerViewModel.width = UIScreen.main.bounds.width + miniPlayerViewModel.isMiniPlayerActivate.toggle() + miniPlayerViewModel.hideTabBar = true + } + } + } GeometryReader{ reader in ZStack { @@ -135,19 +153,36 @@ struct MiniPlayerView: View { .background( Color("\(miniPlayerViewModel.song.team)Sub") .cornerRadius(8) - .ignoresSafeArea(.keyboard) - .onTapGesture { - withAnimation{ - miniPlayerViewModel.width = UIScreen.main.bounds.width - miniPlayerViewModel.isMiniPlayerActivate.toggle() - miniPlayerViewModel.hideTabBar = true - print("\(miniPlayerViewModel.song.title)") - } - } - ) } + func onChanged(){ + if gestureOffset > 0 && !miniPlayerViewModel.isMiniPlayerActivate && miniPlayerViewModel.offset + 200 <= miniPlayerViewModel.height{ + miniPlayerViewModel.offset = gestureOffset + } + } + + + func onEnd(value: DragGesture.Value){ + withAnimation(.smooth){ + + if !miniPlayerViewModel.isMiniPlayerActivate{ + miniPlayerViewModel.offset = 0 + + // Closing View... + if value.translation.height > UIScreen.main.bounds.height / 3{ + miniPlayerViewModel.hideTabBar = false + miniPlayerViewModel.isMiniPlayerActivate = true + } + else{ + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + } + } + } + } + + func getOpacity()->Double{ let progress = miniPlayerViewModel.offset / (miniPlayerViewModel.height) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index c60af09..822ddc6 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -216,6 +216,7 @@ struct MainSongListTabView: View { .tabViewStyle(.page(indexDisplayMode: .never)) } .edgesIgnoringSafeArea(.top) + .padding(.bottom, miniPlayerViewModel.showPlayer ? 50 : 0) //상단 탭바 TabBarView(currentTab: $viewModel.index) diff --git a/Halmap/Features/SongStorage/ScalingHeaderView.swift b/Halmap/Features/SongStorage/ScalingHeaderView.swift index 2c0699d..1b75dda 100644 --- a/Halmap/Features/SongStorage/ScalingHeaderView.swift +++ b/Halmap/Features/SongStorage/ScalingHeaderView.swift @@ -137,6 +137,7 @@ struct ScalingHeaderView: View { } } } + .padding(.bottom, 80) } .modifier(OffsetModifier(offset: $viewModel.offset)) .onChange(of: viewModel.offset) { _ in diff --git a/Halmap/View/OnBoardingStartView.swift b/Halmap/View/OnBoardingStartView.swift index 7c1edf2..d406bd1 100644 --- a/Halmap/View/OnBoardingStartView.swift +++ b/Halmap/View/OnBoardingStartView.swift @@ -16,7 +16,6 @@ struct OnBoardingStartView: View { @EnvironmentObject var audioManager: AudioManager @EnvironmentObject var dataManager: DataManager @StateObject var miniPlayerViewModel = MiniPlayerViewModel.instance - @GestureState var gestureOffset: CGFloat = 0 @Namespace var animation let persistence = PersistenceController.shared @@ -44,48 +43,16 @@ struct OnBoardingStartView: View { MiniPlayerView() .transition(.move(edge: .bottom)) .offset(y: miniPlayerViewModel.offset) - .gesture(DragGesture().updating($gestureOffset, body: { (value, state, _) in - - state = value.translation.height - }) - .onEnded(onEnd(value:))) .padding(.bottom, miniPlayerViewModel.hideTabBar ? 0 : 57) .ignoresSafeArea(.keyboard) .ignoresSafeArea() } } } - .onChange(of: gestureOffset, perform: { value in - onChanged() - }) .environmentObject(miniPlayerViewModel) + .ignoresSafeArea(.keyboard) } else { TeamSelectionView(viewModel: TeamSelectionViewModel(dataManager: dataManager), isShowing: $isFirstLaunching) } } - - func onChanged(){ - if gestureOffset > 0 && !miniPlayerViewModel.isMiniPlayerActivate && miniPlayerViewModel.offset + 200 <= miniPlayerViewModel.height{ - miniPlayerViewModel.offset = gestureOffset - } - } - - func onEnd(value: DragGesture.Value){ - withAnimation(.smooth){ - - if !miniPlayerViewModel.isMiniPlayerActivate{ - miniPlayerViewModel.offset = 0 - - // Closing View... - if value.translation.height > UIScreen.main.bounds.height / 3{ - miniPlayerViewModel.hideTabBar = false - miniPlayerViewModel.isMiniPlayerActivate = true - } - else{ - miniPlayerViewModel.hideTabBar = true - miniPlayerViewModel.isMiniPlayerActivate = false - } - } - } - } } From 66bbbc4550c395f1f17fb006dd616c0111c44e1d Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Sun, 17 Dec 2023 19:48:54 +0900 Subject: [PATCH 38/56] =?UTF-8?q?QA=205=EB=B2=88=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 3 ++- Halmap/Features/SongDetail/PlaylistView.swift | 6 ++++-- Halmap/Features/SongSearch/SongSearchView.swift | 1 + Halmap/Info.plist | 2 -- Halmap/View/OnBoardingStartView.swift | 1 + 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index cf2551c..f5f645a 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -438,11 +438,12 @@ }; buildConfigurationList = A81FFC7128F15D9900B0FC7C /* Build configuration list for PBXProject "Halmap" */; compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; + developmentRegion = ko; hasScannedForEncodings = 0; knownRegions = ( en, Base, + ko, ); mainGroup = A81FFC6D28F15D9900B0FC7C; packageReferences = ( diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 62356fc..163c75e 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -34,7 +34,8 @@ struct PlaylistView: View { key: ViewOffsetKey.self, value: -$0.frame(in: .named("list")).origin.y) } : nil ) - }.onDelete { indexSet in + } + .onDelete { indexSet in for index in indexSet { if collectedSongs.count - 1 == 0 { // TODO: 메인화면으로 나가는 동작 @@ -53,7 +54,8 @@ struct PlaylistView: View { } } persistence.deleteSong(at: indexSet, from: collectedSongs) - }.onMove { indexSet, destination in + } + .onMove { indexSet, destination in persistence.moveDefaultPlaylistSong(from: indexSet, to: destination, based: collectedSongs) diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index 6a41c25..5ac1c12 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -32,6 +32,7 @@ struct SongSearchView: View { .padding(.top, 22) resultView + .padding(.bottom, miniPlayerViewModel.showPlayer ? 50 : 0) } .background(Color.systemBackground) diff --git a/Halmap/Info.plist b/Halmap/Info.plist index 9780be4..4e8e353 100644 --- a/Halmap/Info.plist +++ b/Halmap/Info.plist @@ -2,8 +2,6 @@ - - BGTaskSchedulerPermittedIdentifiers $(PRODUCT_BUNDLE_IDENTIFIER) diff --git a/Halmap/View/OnBoardingStartView.swift b/Halmap/View/OnBoardingStartView.swift index d406bd1..4254148 100644 --- a/Halmap/View/OnBoardingStartView.swift +++ b/Halmap/View/OnBoardingStartView.swift @@ -48,6 +48,7 @@ struct OnBoardingStartView: View { .ignoresSafeArea() } } + .background(Color.clear) } .environmentObject(miniPlayerViewModel) .ignoresSafeArea(.keyboard) From bfabd20dc96be8c34100013d84433aa08f27557c Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sun, 17 Dec 2023 20:40:55 +0900 Subject: [PATCH 39/56] =?UTF-8?q?[Fix]=20=EB=B9=88=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongList/MainSongListTabView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 822ddc6..3a5f69f 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -115,7 +115,6 @@ struct MainSongListTabView: View { } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) - .listRowBackground(Color.systemBackground) .listRowSeparatorTint(Color.customGray) .background(Color.systemBackground) @@ -136,7 +135,10 @@ struct MainSongListTabView: View { .listRowBackground(Color.systemBackground) .listRowSeparatorTint(Color.customGray) } - .sheet(isPresented: $isShowingHalfSheet) { + .sheet(isPresented: Binding( + get: { isShowingHalfSheet }, + set: { isShowingHalfSheet = $0 } + )) { if let collectedSong { HalfSheet{ HalfSheetView(collectedSongData: collectedSong, showSheet: $isShowingHalfSheet) From 53e1fccb60c772c000bf7a8215386e1ea6aa92bb Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sun, 17 Dec 2023 21:09:50 +0900 Subject: [PATCH 40/56] =?UTF-8?q?[Style]=20=EB=AF=B8=EB=8B=88=20=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=83=81=EB=8B=A8=20=EC=97=AC?= =?UTF-8?q?=EB=B0=B1=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/Halmap+SafeAreaInsetsKey.swift | 28 +++++++++++++++++++ .../Features/SongDetail/MiniPlayerView.swift | 3 ++ Halmap/View/OnBoardingStartView.swift | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 Halmap/Extensions/Halmap+SafeAreaInsetsKey.swift diff --git a/Halmap/Extensions/Halmap+SafeAreaInsetsKey.swift b/Halmap/Extensions/Halmap+SafeAreaInsetsKey.swift new file mode 100644 index 0000000..d5e02bf --- /dev/null +++ b/Halmap/Extensions/Halmap+SafeAreaInsetsKey.swift @@ -0,0 +1,28 @@ +// +// Halmap.swift +// Halmap +// +// Created by JeonJimin on 12/17/23. +// + +import SwiftUI + +private struct SafeAreaInsetsKey: EnvironmentKey { + static var defaultValue: EdgeInsets { + (UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets ?? .zero).insets + } +} + +extension EnvironmentValues { + + var safeAreaInsets: EdgeInsets { + self[SafeAreaInsetsKey.self] + } +} + +private extension UIEdgeInsets { + + var insets: EdgeInsets { + EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right) + } +} diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index de18a26..86e6e82 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -11,6 +11,8 @@ import MediaPlayer struct MiniPlayerView: View { @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel + @Environment(\.safeAreaInsets) private var safeAreaInsets + @FetchRequest( entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], @@ -87,6 +89,7 @@ struct MiniPlayerView: View { } .padding(.horizontal, 20) .frame(height: miniPlayerViewModel.isMiniPlayerActivate ? 60 : 80) + .padding(.top, miniPlayerViewModel.isMiniPlayerActivate ? 0 : safeAreaInsets.top) .contentShape(Rectangle()) .gesture(DragGesture().updating($gestureOffset, body: { (value, state, _) in diff --git a/Halmap/View/OnBoardingStartView.swift b/Halmap/View/OnBoardingStartView.swift index 4254148..c14ba9a 100644 --- a/Halmap/View/OnBoardingStartView.swift +++ b/Halmap/View/OnBoardingStartView.swift @@ -48,6 +48,8 @@ struct OnBoardingStartView: View { .ignoresSafeArea() } } + .ignoresSafeArea() + .padding(.bottom, miniPlayerViewModel.isMiniPlayerActivate ? UIScreen.getHeight(10) : 0 ) .background(Color.clear) } .environmentObject(miniPlayerViewModel) From 794164a652e195191d04f8601e160045a739f0dd Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sun, 17 Dec 2023 22:33:23 +0900 Subject: [PATCH 41/56] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index f5f645a..5fc48c0 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ F94942202B17373C00075DE1 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = F949421F2B17373C00075DE1 /* Lottie */; }; F94942252B17398100075DE1 /* waveform.json in Resources */ = {isa = PBXBuildFile; fileRef = F94942242B17398100075DE1 /* waveform.json */; }; F95CE98B2917A8EF00FFE213 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F95CE98A2917A8EF00FFE213 /* Settings.bundle */; }; + F97B5BBB2B2F189E007C9754 /* Halmap+SafeAreaInsetsKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97B5BBA2B2F189E007C9754 /* Halmap+SafeAreaInsetsKey.swift */; }; F97BE1222AC011B100B37C76 /* SongStorageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */; }; F97BE1242AC011DF00B37C76 /* Halmap+CollectedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */; }; F98F622929B4C9BD0025F50E /* Themes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98F622829B4C9BD0025F50E /* Themes.swift */; }; @@ -126,6 +127,7 @@ F949421C2B16044F00075DE1 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; F94942242B17398100075DE1 /* waveform.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = waveform.json; sourceTree = ""; }; F95CE98A2917A8EF00FFE213 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; + F97B5BBA2B2F189E007C9754 /* Halmap+SafeAreaInsetsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Halmap+SafeAreaInsetsKey.swift"; sourceTree = ""; }; F97BE1212AC011B100B37C76 /* SongStorageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongStorageViewModel.swift; sourceTree = ""; }; F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Halmap+CollectedSong.swift"; sourceTree = ""; }; F98F622829B4C9BD0025F50E /* Themes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themes.swift; sourceTree = ""; }; @@ -306,6 +308,7 @@ 87F6ECF42916B331004533C4 /* Halmap+UINavigationController.swift */, 87F6ECF62916BB44004533C4 /* Halmap+UIView.swift */, F97BE1232AC011DF00B37C76 /* Halmap+CollectedSong.swift */, + F97B5BBA2B2F189E007C9754 /* Halmap+SafeAreaInsetsKey.swift */, ); path = Extensions; sourceTree = ""; @@ -544,6 +547,7 @@ A81FFC7A28F15D9900B0FC7C /* HalmapApp.swift in Sources */, F91BE5FD29B18D1E00F7E488 /* MainSongListTabView.swift in Sources */, F97BE1222AC011B100B37C76 /* SongStorageViewModel.swift in Sources */, + F97B5BBB2B2F189E007C9754 /* Halmap+SafeAreaInsetsKey.swift in Sources */, 8744D0852AC60D22002F1D3A /* OnBoardingStartView.swift in Sources */, F939EBBA29D58FB2005ED8CA /* StorageContentView.swift in Sources */, 2051C1E32B28A1DB00842AA3 /* MiniPlayerView.swift in Sources */, @@ -683,11 +687,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -705,6 +711,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -720,10 +727,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -741,6 +750,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From dfb654bb01ffb8dc9a828bf7043bb910cbbc86c6 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 18 Dec 2023 02:42:17 +0900 Subject: [PATCH 42/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B3=A1=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=82=98=EA=B0=80=EA=B8=B0=20-=20=EB=AF=B8=EB=8B=88=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EB=8F=99=EC=9E=91=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=20-=20=EB=AF=B8=EB=8B=88=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=EB=B2=84=ED=8A=BC=20=EC=83=89=EC=83=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=94=84=EB=A6=B0=ED=8A=B8=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NCAlbum.imageset/Contents.json" | 2 +- .../Features/SongDetail/MiniPlayerView.swift | 27 ++++++++++++------- Halmap/Features/SongDetail/PlaylistView.swift | 8 ++++-- .../SongList/MainSongListTabView.swift | 1 - 4 files changed, 24 insertions(+), 14 deletions(-) diff --git "a/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" "b/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" index afd4eba..a0647a8 100644 --- "a/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" +++ "b/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "NCAlbum.png", + "filename" : "NcAlbum.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index 86e6e82..f1cd41d 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -48,14 +48,21 @@ struct MiniPlayerView: View { .shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4) } - VStack(alignment: .leading, spacing: 4){ - Text("\(miniPlayerViewModel.song.title)") - .font(Font.Halmap.CustomBodyBold) - .foregroundColor(Color.white) - Text(miniPlayerViewModel.getTeamNameKr()) - .font(Font.Halmap.CustomCaptionMedium) - .foregroundColor(Color.customGray) + if defaultPlaylistSongs.count > 0 { + VStack(alignment: .leading, spacing: 4){ + Text("\(miniPlayerViewModel.song.title)") + .font(Font.Halmap.CustomBodyBold) + .foregroundColor(Color.systemBackground) + Text(miniPlayerViewModel.getTeamNameKr()) + .font(Font.Halmap.CustomCaptionMedium) + .foregroundColor(Color.customGray) + } + } else { + Text("재생 중인 곡이 없습니다.") + .foregroundStyle(Color.systemBackground.opacity(0.6)) + .font(.Halmap.CustomBodyBold) } + Spacer() if miniPlayerViewModel.isMiniPlayerActivate { @@ -65,7 +72,6 @@ struct MiniPlayerView: View { }, label: { Image(systemName: miniPlayerViewModel.isPlaying ? "pause.fill" : "play.fill") .font(.system(size: 20, weight: .medium)) - .foregroundStyle(Color.white) }) Button(action: { if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == miniPlayerViewModel.song.id}) { @@ -78,9 +84,10 @@ struct MiniPlayerView: View { }, label: { Image(systemName: "forward.end.fill") .font(.system(size: 20, weight: .medium)) - .foregroundStyle(Color.white) }) } + .disabled(defaultPlaylistSongs.count == 0) + .foregroundStyle(defaultPlaylistSongs.count > 0 ? Color.systemBackground : Color.systemBackground.opacity(0.2)) } if !miniPlayerViewModel.isMiniPlayerActivate { @@ -100,7 +107,7 @@ struct MiniPlayerView: View { onChanged() }) .onTapGesture { - if miniPlayerViewModel.isMiniPlayerActivate { + if miniPlayerViewModel.isMiniPlayerActivate && defaultPlaylistSongs.count > 0 { withAnimation{ miniPlayerViewModel.width = UIScreen.main.bounds.width miniPlayerViewModel.isMiniPlayerActivate.toggle() diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 163c75e..fa2a018 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -9,6 +9,7 @@ import Lottie struct PlaylistView: View { + @EnvironmentObject var miniPlayerViewModel: MiniPlayerViewModel @StateObject var viewModel: PlaylistViewModel @Binding var song: SongInfo @Binding var isScrolled: Bool @@ -38,8 +39,11 @@ struct PlaylistView: View { .onDelete { indexSet in for index in indexSet { if collectedSongs.count - 1 == 0 { - // TODO: 메인화면으로 나가는 동작 - print("메인화면으로") + withAnimation{ + miniPlayerViewModel.hideTabBar = false + miniPlayerViewModel.isMiniPlayerActivate = true + } + miniPlayerViewModel.song = SongInfo(id: "", team: song.team, type: false, title: "", lyrics: "", info: "", url: "") viewModel.stopPlayer() } else { if let songIndex = collectedSongs.firstIndex(where: {$0.id == song.id}) { diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 3a5f69f..6a78f10 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -120,7 +120,6 @@ struct MainSongListTabView: View { .background(Color.systemBackground) .onTapGesture { self.miniPlayerViewModel.song = songInfo - print(songInfo.title) withAnimation{ miniPlayerViewModel.showPlayer = true miniPlayerViewModel.hideTabBar = true From 95dd00fb0ba6043ea916fb677f4fc8a6b4f6fe12 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 18 Dec 2023 03:38:05 +0900 Subject: [PATCH 43/56] =?UTF-8?q?[Style]=20=EB=AF=B8=EB=8B=88=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=97=AC=EB=B0=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(QA6=EB=B2=88)=20-=20=EB=AF=B8=EB=8B=88=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=A2=8C=EC=9A=B0,=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=20=EC=97=AC=EB=B0=B1=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=A0=84=EC=B2=B4=20=EC=9E=AC=EC=83=9D?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=EC=95=84=EB=9E=98=20=EC=97=AC?= =?UTF-8?q?=EB=B0=B1=20=EC=88=98=EC=A0=95=20-=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=8A=A4=ED=83=9D=20=EB=B0=8F=20ignore=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SongList/MainSongListTabView.swift | 130 +++++++++--------- Halmap/View/OnBoardingStartView.swift | 2 +- 2 files changed, 64 insertions(+), 68 deletions(-) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 6a78f10..7a3d065 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -63,8 +63,8 @@ struct MainSongListTabView: View { } } } - .padding(EdgeInsets(top: 20, leading: 20, bottom: 15, trailing: 20)) - .padding(.top, UIScreen.getHeight(32)) + .padding(EdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20)) + .padding(.top, UIScreen.getHeight(24)) Divider() .overlay(Color.customGray.opacity(0.6)) @@ -81,38 +81,35 @@ struct MainSongListTabView: View { info: song.info, url: song.url) - ZStack { - HStack(spacing: 16) { - Image(viewModel.getSongImage(for: songInfo)) - .resizable() - .frame(width: 40, height: 40) - .cornerRadius(8) + HStack(spacing: 16) { + Image(viewModel.getSongImage(for: songInfo)) + .resizable() + .frame(width: 40, height: 40) + .cornerRadius(8) + + VStack(alignment: .leading, spacing: 6) { + Text(song.title) + .font(Font.Halmap.CustomBodyMedium) - VStack(alignment: .leading, spacing: 6) { - Text(song.title) - .font(Font.Halmap.CustomBodyMedium) - - if !song.info.isEmpty { - Text(song.info) - .font(Font.Halmap.CustomCaptionMedium) - .foregroundColor(.customDarkGray) - } + if !song.info.isEmpty { + Text(song.info) + .font(Font.Halmap.CustomCaptionMedium) + .foregroundColor(.customDarkGray) } - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - Spacer() - - Image(systemName: "ellipsis") - .foregroundColor(.customDarkGray) - .frame(maxWidth: 35, maxHeight: .infinity) - .background(Color.white.opacity(0.001)) - .onTapGesture { - collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") - selectedSong = songInfo - isShowingHalfSheet.toggle() - } } + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + Spacer() + Image(systemName: "ellipsis") + .foregroundColor(.customDarkGray) + .frame(maxWidth: 35, maxHeight: .infinity) + .background(Color.white.opacity(0.001)) + .onTapGesture { + collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") + selectedSong = songInfo + isShowingHalfSheet.toggle() + } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) .listRowBackground(Color.systemBackground) @@ -133,6 +130,7 @@ struct MainSongListTabView: View { .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) .listRowBackground(Color.systemBackground) .listRowSeparatorTint(Color.customGray) + .padding(.bottom, 80) } .sheet(isPresented: Binding( get: { isShowingHalfSheet }, @@ -158,47 +156,45 @@ struct MainSongListTabView: View { info: song.info, url: song.url) - ZStack { - HStack(spacing: 16) { - Image(viewModel.getPlayerImage(for: songInfo)) - .resizable() - .frame(width: 40, height: 40) - .cornerRadius(8) - VStack(alignment: .leading, spacing: 6) { - Text(song.title) - .font(Font.Halmap.CustomBodyMedium) - if !song.info.isEmpty { - Text(song.info) - .font(Font.Halmap.CustomCaptionMedium) - .foregroundColor(.customDarkGray) - } + HStack(spacing: 16) { + Image(viewModel.getPlayerImage(for: songInfo)) + .resizable() + .frame(width: 40, height: 40) + .cornerRadius(8) + VStack(alignment: .leading, spacing: 6) { + Text(song.title) + .font(Font.Halmap.CustomBodyMedium) + if !song.info.isEmpty { + Text(song.info) + .font(Font.Halmap.CustomCaptionMedium) + .foregroundColor(.customDarkGray) } - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - Spacer() - - Image(systemName: "ellipsis") - .foregroundColor(.customDarkGray) - .frame(maxWidth: 35, maxHeight: .infinity) - .background(Color.white.opacity(0.001)) - .onTapGesture { - collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") - selectedSong = songInfo - isShowingHalfSheet.toggle() - } } - .onTapGesture { - miniPlayerViewModel.removePlayer() - self.miniPlayerViewModel.song = songInfo - miniPlayerViewModel.setPlayer() - withAnimation{ - miniPlayerViewModel.showPlayer = true - miniPlayerViewModel.hideTabBar = true - miniPlayerViewModel.isMiniPlayerActivate = false + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + Spacer() + + Image(systemName: "ellipsis") + .foregroundColor(.customDarkGray) + .frame(maxWidth: 35, maxHeight: .infinity) + .background(Color.white.opacity(0.001)) + .onTapGesture { + collectedSong = persistence.createCollectedSong(song: songInfo, playListTitle: "bufferPlayList") selectedSong = songInfo + isShowingHalfSheet.toggle() } - miniPlayerViewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) + } + .onTapGesture { + miniPlayerViewModel.removePlayer() + self.miniPlayerViewModel.song = songInfo + miniPlayerViewModel.setPlayer() + withAnimation{ + miniPlayerViewModel.showPlayer = true + miniPlayerViewModel.hideTabBar = true + miniPlayerViewModel.isMiniPlayerActivate = false + selectedSong = songInfo } + miniPlayerViewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) } } .listRowInsets(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0)) @@ -209,6 +205,7 @@ struct MainSongListTabView: View { .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) .listRowBackground(Color.systemBackground) .listRowSeparatorTint(Color.customGray) + .padding(.bottom, 80) } .padding(.horizontal, 20) .listStyle(.plain) @@ -217,7 +214,6 @@ struct MainSongListTabView: View { .tabViewStyle(.page(indexDisplayMode: .never)) } .edgesIgnoringSafeArea(.top) - .padding(.bottom, miniPlayerViewModel.showPlayer ? 50 : 0) //상단 탭바 TabBarView(currentTab: $viewModel.index) diff --git a/Halmap/View/OnBoardingStartView.swift b/Halmap/View/OnBoardingStartView.swift index c14ba9a..f8b2311 100644 --- a/Halmap/View/OnBoardingStartView.swift +++ b/Halmap/View/OnBoardingStartView.swift @@ -44,13 +44,13 @@ struct OnBoardingStartView: View { .transition(.move(edge: .bottom)) .offset(y: miniPlayerViewModel.offset) .padding(.bottom, miniPlayerViewModel.hideTabBar ? 0 : 57) - .ignoresSafeArea(.keyboard) .ignoresSafeArea() } } .ignoresSafeArea() .padding(.bottom, miniPlayerViewModel.isMiniPlayerActivate ? UIScreen.getHeight(10) : 0 ) .background(Color.clear) + .padding(miniPlayerViewModel.isMiniPlayerActivate ? EdgeInsets(top: 0, leading: 15, bottom: 10, trailing: 15) : EdgeInsets()) } .environmentObject(miniPlayerViewModel) .ignoresSafeArea(.keyboard) From e20c26bc2c34707de00c3f1a3074449a65718db2 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 18 Dec 2023 04:10:04 +0900 Subject: [PATCH 44/56] =?UTF-8?q?[Style]=20=EC=97=86=EC=96=B4=EC=A7=84=20?= =?UTF-8?q?=EA=B7=B8=EB=9D=BC=EB=94=94=EC=96=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/MiniPlayerView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index f1cd41d..74a4644 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -131,6 +131,7 @@ struct MiniPlayerView: View { } VStack(spacing: 0) { + gradientRectangle(isTop: true) Spacer() ZStack(alignment: .bottom) { gradientRectangle(isTop: false) From 92a1380e75329b31a98c74d868d3cf361a05705c Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 18 Dec 2023 04:18:56 +0900 Subject: [PATCH 45/56] =?UTF-8?q?[Style]=20=EC=9E=AC=EC=83=9D=EB=B0=94=20?= =?UTF-8?q?=EC=97=AC=EB=B0=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/MiniPlayerView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index 74a4644..3d0571c 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -352,7 +352,7 @@ private struct PlayBar: View { } .padding(.bottom, 54) } - .padding(.horizontal, 45) + .padding(.horizontal, 40) .frame(maxWidth: .infinity) .background(Color("\(viewModel.song.team)Sub")) .onDisappear(){ From 081ca4fd3793bb20b302a3d6613f9371666bc0db Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 18 Dec 2023 04:28:07 +0900 Subject: [PATCH 46/56] =?UTF-8?q?[Style]=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=85=80=20=ED=84=B0=EC=B9=98=20=EC=98=81=EC=97=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=84=A0=EC=88=98=20=EC=9D=91=EC=9B=90?= =?UTF-8?q?=EA=B0=80=20=ED=84=B0=EC=B9=98=20=EC=98=81=EC=97=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongList/MainSongListTabView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 7a3d065..7891499 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -97,7 +97,7 @@ struct MainSongListTabView: View { .foregroundColor(.customDarkGray) } } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) .lineLimit(1) Spacer() @@ -170,7 +170,8 @@ struct MainSongListTabView: View { .foregroundColor(.customDarkGray) } } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .background(Color.white.opacity(0.0001)) .lineLimit(1) Spacer() From f6bb44bf8770bc0baadd761d638fb29def44c920 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Mon, 18 Dec 2023 17:30:43 +0900 Subject: [PATCH 47/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=9D=98=20=EB=A7=88=EC=A7=80?= =?UTF-8?q?=EB=A7=89=20=EA=B3=A1=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/MiniPlayerView.swift | 3 ++- Halmap/Features/SongDetail/PlaylistView.swift | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index 3d0571c..05e8f66 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -123,7 +123,8 @@ struct MiniPlayerView: View { viewModel: PlaylistViewModel(viewModel: miniPlayerViewModel), song: $miniPlayerViewModel.song, isScrolled: $miniPlayerViewModel.isScrolled, - isPlaying: $miniPlayerViewModel.isPlaying) + isPlaying: $miniPlayerViewModel.isPlaying, + toast: $toast) .padding(.top, 10) .padding(.bottom, 150) } else { diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index fa2a018..9de6cc0 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -14,6 +14,7 @@ struct PlaylistView: View { @Binding var song: SongInfo @Binding var isScrolled: Bool @Binding var isPlaying: Bool + @Binding var toast: Toast? let persistence = PersistenceController.shared @FetchRequest(entity: CollectedSong.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CollectedSong.order, ascending: true)], predicate: PlaylistFilter(filter: "defaultPlaylist").predicate, animation: .default) private var collectedSongs: FetchedResults @@ -52,6 +53,7 @@ struct PlaylistView: View { self.song = Utility.convertSongToSongInfo(song: collectedSongs[Int(collectedSongs[songIndex].order + 1)]) } else { self.song = Utility.convertSongToSongInfo(song: collectedSongs[0]) + toast = Toast(team: collectedSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") } } } From 33bd278c852362e67eba0c3b365d4256f56300ae Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Tue, 19 Dec 2023 00:17:03 +0900 Subject: [PATCH 48/56] =?UTF-8?q?=EA=B2=80=EC=83=89=EB=B7=B0=20=ED=82=A4?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NCAlbum.imageset/Contents.json" | 2 +- .../Features/SongSearch/SongSearchView.swift | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git "a/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" "b/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" index a0647a8..afd4eba 100644 --- "a/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" +++ "b/Halmap/Assets.xcassets/\355\214\200 \354\225\250\353\262\224 \354\273\244\353\262\204/NCAlbum.imageset/Contents.json" @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "NcAlbum.png", + "filename" : "NCAlbum.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index 5ac1c12..412a439 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -39,9 +39,11 @@ struct SongSearchView: View { .navigationBarBackButtonHidden(true) .navigationBarHidden(true) .onAppear { - UIApplication.shared.hideKeyboard() isFocused = true } + .onTapGesture { + hideKeyboard() + } } // MARK: Search Bar @@ -154,7 +156,21 @@ struct SongSearchView: View { } .padding(.horizontal, 20) .listStyle(.plain) + .gesture( + DragGesture().onChanged { value in + if value.translation.height < 0 { + hideKeyboard() + } + } + ) } } } } + +extension View { + func hideKeyboard() { + UIApplication.shared.sendAction( + #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} From 7a2815dce4e6963aedd9bd6832263a9f09b9ada4 Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Wed, 20 Dec 2023 15:43:02 +0900 Subject: [PATCH 49/56] =?UTF-8?q?[BugFix]=20=EC=B5=9C=EC=B4=88=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=EC=8B=9C=20=EB=85=B8=EB=9E=98=EA=B0=80=20=EB=82=98?= =?UTF-8?q?=EC=98=A4=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 14 ++++---------- Halmap/Data/DataManager.swift | 18 ++++++++++++++++++ .../TeamSelection/TeamSelectionViewModel.swift | 5 +++++ Halmap/HalmapApp.swift | 1 - Halmap/View/Component/Progressbar.swift | 1 + 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 5fc48c0..163f5ed 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -687,13 +687,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -711,7 +709,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -727,12 +724,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -750,7 +745,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/Halmap/Data/DataManager.swift b/Halmap/Data/DataManager.swift index ac6ccf2..e559ac8 100644 --- a/Halmap/Data/DataManager.swift +++ b/Halmap/Data/DataManager.swift @@ -50,6 +50,24 @@ class DataManager: ObservableObject { } fetchTrafficNotification() + initData() +// loadData() +// teamLists.forEach { teamName in +// fetchSong(team: teamName.rawValue, type: true) { songs in +// self.playerSongsAll[teamName.fetchTeamIndex()] = songs +// self.setSongList(team: self.selectedTeam) +// } +// fetchSong(team: teamName.rawValue, type: false) { songs in +// self.teamSongsAll[teamName.fetchTeamIndex()] = songs +// } +// } +// +// fetchSeasonData { data in +// self.seasonSongs = data +// } + } + + func initData() { loadData() teamLists.forEach { teamName in fetchSong(team: teamName.rawValue, type: true) { songs in diff --git a/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift b/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift index 80f1971..3aa9793 100644 --- a/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift +++ b/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift @@ -6,8 +6,10 @@ // import Foundation +import SwiftUI final class TeamSelectionViewModel: ObservableObject { + @AppStorage("_isFirstLaunching") var isFirstLaunching: Bool = true @Published var buttonPressed = [Bool](repeating: false, count: 10) @Published var selectedTeam: TeamName? = nil @@ -45,6 +47,9 @@ final class TeamSelectionViewModel: ObservableObject { let teamName = selectedTeam.rawValue UserDefaults.standard.set(teamName, forKey: "selectedTeam") dataManager.setSongList(team: teamName) + if isFirstLaunching { + dataManager.initData() + } return false } diff --git a/Halmap/HalmapApp.swift b/Halmap/HalmapApp.swift index 4e82581..be3a068 100644 --- a/Halmap/HalmapApp.swift +++ b/Halmap/HalmapApp.swift @@ -23,7 +23,6 @@ class AppDelegate: NSObject, UIApplicationDelegate { struct HalmapApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate - @StateObject var dataManager = DataManager.instance @StateObject var audioManager = AudioManager.instance diff --git a/Halmap/View/Component/Progressbar.swift b/Halmap/View/Component/Progressbar.swift index 9183b32..b0c12c7 100644 --- a/Halmap/View/Component/Progressbar.swift +++ b/Halmap/View/Component/Progressbar.swift @@ -91,6 +91,7 @@ struct AudioPlayerControlsView: View { self.currentTime = 0 self.currentDuration = 0 + // MARK: 노래 끝났을때 처리 로직 if self.state == .pause { if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == song.id}) { if index + 1 < defaultPlaylistSongs.count { From 4ed09528e6b103ed4d770676bf3b01fac2ff1af6 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 20 Dec 2023 16:00:03 +0900 Subject: [PATCH 50/56] =?UTF-8?q?[Chore]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 18 +- Halmap/Data/DataManager.swift | 68 +-- Halmap/Data/Music.json | 388 ------------------ .../SongList/MainSongListViewModel.swift | 2 +- 4 files changed, 17 insertions(+), 459 deletions(-) delete mode 100644 Halmap/Data/Music.json diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 163f5ed..ad3dc82 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -55,7 +55,6 @@ F939EBBA29D58FB2005ED8CA /* StorageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939EBB929D58FB2005ED8CA /* StorageContentView.swift */; }; F939EBBC29D5920F005ED8CA /* OffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939EBBB29D5920F005ED8CA /* OffsetModifier.swift */; }; F941FA7F2A7F78500034F72D /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */; }; - F949284F28F2AFB800901F27 /* Music.json in Resources */ = {isa = PBXBuildFile; fileRef = F949284E28F2AFB800901F27 /* Music.json */; }; F949421D2B16044F00075DE1 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F949421C2B16044F00075DE1 /* ToastView.swift */; }; F94942202B17373C00075DE1 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = F949421F2B17373C00075DE1 /* Lottie */; }; F94942252B17398100075DE1 /* waveform.json in Resources */ = {isa = PBXBuildFile; fileRef = F94942242B17398100075DE1 /* waveform.json */; }; @@ -123,7 +122,6 @@ F939EBB929D58FB2005ED8CA /* StorageContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageContentView.swift; sourceTree = ""; }; F939EBBB29D5920F005ED8CA /* OffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetModifier.swift; sourceTree = ""; }; F941FA7E2A7F78500034F72D /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; - F949284E28F2AFB800901F27 /* Music.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Music.json; sourceTree = ""; }; F949421C2B16044F00075DE1 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; F94942242B17398100075DE1 /* waveform.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = waveform.json; sourceTree = ""; }; F95CE98A2917A8EF00FFE213 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; @@ -376,7 +374,6 @@ isa = PBXGroup; children = ( A8D1E92129169DAD00BF242C /* test-song.m4a */, - F949284E28F2AFB800901F27 /* Music.json */, F9B95BB828F271A100728048 /* DataManager.swift */, F9B95BBA28F271B800728048 /* Model.swift */, F9EB19BD29168336002DFE46 /* Song.swift */, @@ -475,7 +472,6 @@ F94942252B17398100075DE1 /* waveform.json in Resources */, A81FFC7E28F15D9C00B0FC7C /* Assets.xcassets in Resources */, B2699E1028F1CB9700267A4F /* Pretendard-Medium.otf in Resources */, - F949284F28F2AFB800901F27 /* Music.json in Resources */, 8764EEEC2ABFEF6A0006F438 /* GoogleService-Info.plist in Resources */, F941FA7F2A7F78500034F72D /* Launch Screen.storyboard in Resources */, A8D1E92229169DAD00BF242C /* test-song.m4a in Resources */, @@ -687,11 +683,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -709,6 +707,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -724,10 +723,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -745,6 +746,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/Halmap/Data/DataManager.swift b/Halmap/Data/DataManager.swift index e559ac8..a1076c2 100644 --- a/Halmap/Data/DataManager.swift +++ b/Halmap/Data/DataManager.swift @@ -26,13 +26,8 @@ class DataManager: ObservableObject { } } - var teams: [Team] = [] - @Published var teamSongList: [TeamSong] = [] - @Published var playerList: [Player] = [] @Published var playerSongs: [Song] = [] @Published var teamSongs: [Song] = [] -// @Published var favoriteSongs = PersistenceController.shared.fetchCollectedSong() -// @Published var playListSongs = PersistenceController.shared.fetchPlayListSong() @Published var playerSongsAll = [[Song]](repeating: [], count: 10) @Published var teamSongsAll = [[Song]](repeating: [], count: 10) @@ -51,24 +46,9 @@ class DataManager: ObservableObject { fetchTrafficNotification() initData() -// loadData() -// teamLists.forEach { teamName in -// fetchSong(team: teamName.rawValue, type: true) { songs in -// self.playerSongsAll[teamName.fetchTeamIndex()] = songs -// self.setSongList(team: self.selectedTeam) -// } -// fetchSong(team: teamName.rawValue, type: false) { songs in -// self.teamSongsAll[teamName.fetchTeamIndex()] = songs -// } -// } -// -// fetchSeasonData { data in -// self.seasonSongs = data -// } } func initData() { - loadData() teamLists.forEach { teamName in fetchSong(team: teamName.rawValue, type: true) { songs in self.playerSongsAll[teamName.fetchTeamIndex()] = songs @@ -84,42 +64,6 @@ class DataManager: ObservableObject { } } - func loadData(){ - let fileNm: String = "Music" - let extensionType = "json" - - guard let fileLocation = Bundle.main.url(forResource: fileNm, withExtension: extensionType) else { return } - guard let jsonData = try? Data(contentsOf: fileLocation) else { return } - - do { - let teamArray = try JSONDecoder().decode(TeamList.self, from: jsonData) - teams = teamArray.teamLists - - // TODO: 데이터 불러오는 위치 다시 생각해보기 - setList(teamName: selectedTeam) - } - catch let error { - print(error) - } - } - - func setList(teamName: String) { - var index = 0 - - switch teamName { - case "Doosan" : - index = 0 - case "Lotte": - index = 1 - case "Hanwha": - index = 2 - default: - index = 0 - } - self.teamSongList = teams[index].teamSongs - self.playerList = teams[index].player - } - func setSongList(team: String) { self.playerSongs = playerSongsAll[TeamName(rawValue: selectedTeam)?.fetchTeamIndex() ?? 0] self.teamSongs = teamSongsAll[TeamName(rawValue: selectedTeam)?.fetchTeamIndex() ?? 0] @@ -128,7 +72,7 @@ class DataManager: ObservableObject { //MARK: 파이어스토어에서 해당하는 팀의 응원가 정보를 가져오는 함수 ///team: 팀이름을 영어로 넣어주세요 ///[Song] 값으로 반환합니다. - func fetchSong(team: String, type: Bool, completionHandler: @escaping ([Song])->()) { + private func fetchSong(team: String, type: Bool, completionHandler: @escaping ([Song])->()) { var songs: [Song] = [] db.collection(team) @@ -156,7 +100,7 @@ class DataManager: ObservableObject { } } - func fetchSeasonData(completionHandler: @escaping ([[String]])->()) { + private func fetchSeasonData(completionHandler: @escaping ([[String]])->()) { db.collection("SeasonSong") .getDocuments { (querySnapshot, error) in if let error { @@ -180,7 +124,7 @@ class DataManager: ObservableObject { } } - func processingSeasonSongData(data: SeasonSong) -> [[String]] { + private func processingSeasonSongData(data: SeasonSong) -> [[String]] { var seasonData = [[String]](repeating: [], count: 10) seasonData[TeamName.doosan.fetchTeamIndex()] = splitData(data: data.doosan) @@ -197,7 +141,7 @@ class DataManager: ObservableObject { return seasonData } - func splitData(data: String) -> [String] { + private func splitData(data: String) -> [String] { return data.split(separator: ",").map{ String($0) } } @@ -205,7 +149,7 @@ class DataManager: ObservableObject { self.seasonSongs[TeamName(rawValue: data.team)?.fetchTeamIndex() ?? 0].contains(data.title) } - func fetchTrafficNotification() { + private func fetchTrafficNotification() { db.collection("Traffic") .order(by: "date", descending: false) .getDocuments() { (querySnapshot, error) in @@ -248,7 +192,7 @@ class DataManager: ObservableObject { } //새로운 버전 알림과 관련된 함수들 - func fetchVersionNotification(completionHandler: @escaping ([Notification]) -> ()) { + private func fetchVersionNotification(completionHandler: @escaping ([Notification]) -> ()) { var notifications: [Notification] = [] db.collection("IOSVersion") .getDocuments() { (querySnapshot, error) in diff --git a/Halmap/Data/Music.json b/Halmap/Data/Music.json deleted file mode 100644 index b5410d2..0000000 --- a/Halmap/Data/Music.json +++ /dev/null @@ -1,388 +0,0 @@ -{ - "teamLists": [ - { - "id": 0, - "teamName": "두산 베어스", - "teamSongs": [ - { - "songTitle": "라인업송", - "lyric": "지금부터 두산 베어스의 \n10번 타자들이여~ 일어나라~ \n\n우리들의 모든 함성을~ \n우리들의 모든 열정을~ \n우리들의 모든 마음을~ \n가자! 최!강!두!산!(x2) \n\n1번 타자 OOO \n2번 타자 OOO \n3번 타자 OOO \n4번 타자 OOO \n5번 타자 OOO \n6번 타자 OOO \n7번 타자 OOO \n8번 타자 OOO \n9번 타자 OOO \n선발 투수 OOO \n그리고! 우리가! 10번 타자! \n\n우리들의 모든 함성을~ \n우리들의 모든 열정을~ \n우리들의 모든 마음을~ \n가자! 최!강!두!산!(x2)", - "songInfo": "" - }, - { - "songTitle": "미라클 두산", - "lyric": "최강 두산 베어스 그대 이름 챔피언 \n하얀 물결 하얀 바람이 그댈 부르고 있어 \n챔피언 두산 베어스 높이 날아올라봐 \n너를 사랑하는 영원한 내가 있어 다 승리하리라 \n미라클 두산 베어스 그대 나의 챔피언 \n그대 안의 뜨거운 열정이 승리를 부른다 \n미라클 두산 베어스 그대 나의 챔피언 \n너를 향한 뜨거운 사랑이 승리를 부른다 \n\n챔피언 두산 베어스 높이 날아올라봐 \n너를 사랑하는 영원한 내가 있어 다 승리하리라 \n미라클 두산 베어스 그대 나의 챔피언 \n그대 안의 뜨거운 열정이 승리를 부른다 \n미라클 두산 베어스 그대 나의 챔피언 \n너를 향한 뜨거운 사랑이 승리를 부른다 \n너를 향한 뜨거운 사랑이 승리를 부른다", - "songInfo": "" - }, - { - "songTitle": "서울 두산 승리하라", - "lyric": "두산 베어스! 두산 베어스! 두산 베어스! 승리 하리라~ 최 강 두 산 ! 서울두산 우리모두 소리쳐라! 서울두산 하나되여 소리쳐라! 서울두산 함께가자 승리위해! 두산베어스 승리 하 리 라! 어제도(어제도) 오늘도(오늘도) 내일도 변함없이 듣고싶은 말! 말! 말! 두산 베어스! 두산 베어스! 두산 베어스! 승리 하리라~ 워어어어어~ 두산 베어스! 두산 베어스! 두산 베어스! 승리 하리라 최 강 두 산 화이팅!", - "songInfo": "내일도는 뒤에 말하지 않는다. 어제도, 오늘도만 복창하면 된다." - }, - { - "songTitle": "승리를 위하여", - "lyric": "최!강!두!산! 최!강!두!산! 오~ 오오오~ 오~ 오오오~ 두산의 승리를 위하여-- 오늘도 힘차게 외쳐라 나가자 싸우자 우리의 베어스 두산의 승리를 위-하여~! 두산의 승리를 위하여-- 오늘도 힘차게 외쳐라 나가자 싸우자 우리의 베어스 두산의 승리를 위-하여~! 두산의 승리를 위하여 오늘도 힘차게 외쳐라 나가자 싸우자 우리의 베어스 두산의 승리를 위-하여~!", - "songInfo": "" - }, - { - "songTitle": "승리의 두산", - "lyric": "두산 그대의 이름은 승리~ (두산! 두산 승리!) 두산 그이름 영원한 베어스~ 가슴을 펴라 (가슴을 펴라) 이제 우리가 간다 (우리가 간다) 영원한 승리를 위하여~ 짝짝 짝짝짝 짝짝짝짝 두산! 짝짝 짝짝짝 짝짝짝짝 두산! 짝짝 짝짝짝 짝짝짝짝 두산! 짝짝 짝짝짝 짝짝짝짝 두산! 두산 그대의 이름은 승리~ (두산! 두산 승리!) 두산 그이름 영원한 베어스~ 가슴을 펴라 (가슴을 펴라) 이제 우리가 간다 (우리가 간다) 영원한 승리를 위하여~ 영원한 승리를 위하여~", - "songInfo": "" - }, - { - "songTitle": "야야야 두산", - "lyric": "짝짝 짝짝짝 짝짝짝짝 두산! 짝짝 짝짝짝 짝짝짝짝 두산! 야 야야 야야야~ (오~) 두산! 야 야야 야야야~ (오~) 두산! 야 야야 야야 야 야야 야야 야 야야 야야야~ (오~) 두산! 달려 달려 나간다 (야!) 으쌰 으쌰 베어스 (야!) 싸워 싸워 이긴다 (야!) 두산 베어스 (야!) 왔어 왔어 우리가 왔어 허슬두! 왔어 왔어 우리가 왔어 허슬두! 야 야야 야야야~ (오~) 두산! 야 야야 야야야~ (오~) 두산! 야 야야 야야 야 야야 야야 야 야야 야야야~ (오~) 두산! 마지막 순간까지 (야!) 우린 해낼수 있다 (야!) 소리 높여 부르자 (야!) 두산 베어스~ (야!) 야야야!", - "songInfo": "" - }, - { - "songTitle": "해야해야", - "lyric": "헤이 헤이 해야 해야 내가 간다 최강두산 승리하러 간다 헤이 헤이 해야 해야 내가 간다 간다 간다 승리하러 간다 (오~오오~오오오) 최!강!두!산! (오~오오~오오오) 최!강!두!산! 헤이 헤이 해야 해야 내가 간다 최강두산 승리하러 간다 헤이 헤이 해야 해야 내가 간다 간다 간다 승리하러 간다 (오~오오~오오오) 최!강!두!산! (오~오오~오오오) 최!강!두!산! 헤이 헤이 해야 해야 내가 간다 최강두산 승리하러 간다 헤이 헤이 해야 해야 내가 간다 간다 간다 승리하러 간다 (오~오오~오오오) 최!강!두!산! (오~오오~오오오) 최!강!두!산!", - "songInfo": "브라보 마이 라이프, 락 투더 두산에서 플래시 응원을 한다면 이 응원가에서는 가지고 온 모든 응원도구를 꺼내서 응원한다." - }, - { - "songTitle": "최강두산 승리하라", - "lyric": "최강두산 오오오~ 승리하라 오오오~ 최강두산 오오오~ 다 하나되어 허슬두 화이팅! 최강두산 오오오~ 승리하라 오오오~ 최강두산 오오오~ 다 하나되어 허슬두 화이팅! 짝짝 짝짝짝 짝짝짝짝 두산! 짝짝 짝짝짝 짝짝짝짝 두산! 짝짝 짝짝짝 짝짝짝짝 두산! 짝짝 짝짝짝 짝짝짝짝 두산! 최강두산 오오오~ 승리하라 오오오~ 최강두산 오오오~ 다 하나되어 허슬두 화이팅! 최강두산 오오오~ 승리하라 오오오~ 최강두산 오오오~ 다 하나되어 허슬두 화이팅! 하나되어 허슬두 화이팅! 하나되어 허슬두 화이팅!", - "songInfo": "" - } - ], - "player": [ - { - "playerName": "김재호", - "songInfo": "", - "lyric": "(안타 칠때까지 계속 부름) \n\n오~김재호 최강두산 김재호 \n오~김재호 두산 김재호 \n\n오~김재호 최강두산 김재호 오~김재호 두산 김재호" - }, - { - "playerName": "허경민", - "songInfo": "", - "lyric": "최강두산 허경민 안타 날려줘요~ \n최강두산 허경민 안타 날려줘요~ \n허!경!민! 허!경!민! \n안~~타! 허!경!민! \n\n최강두산 허경민 안타 날려줘요~ \n최강두산 허경민 안타 날려줘요~ \n허!경!민! 허!경!민! \n안~~타! 허!경!민!" - }, - { - "playerName": "김재환", - "songInfo": "", - "lyric": "두산! 승리를 위해 다같이 김재환! \n두산! 승리를 위해 워워워~워워워~ \n\n두산! 승리를 위해 다같이 김재환! \n두산! 승리를 위해 워워워~워워워~" - }, - { - "playerName": "박세혁", - "songInfo": "", - "lyric": "박세혁 안~타! 박세혁 안~타! \n안타! 안타! 날려버려 박세혁 \n 워어어어어어~ 워어어어어어~ \n박!세!혁! \n\n박세혁 안~타! 박세혁 안~타! \n안타! 안타! 날려버려 박세혁 \n 워어어어어어~ 워어어어어어~ \n박!세!혁!" - }, - { - "playerName": "김인태", - "songInfo": "", - "lyric": "날려라 날려라 날려라 \n두산의 김인태 \n안타 안타 김인태 \n최강 두산 김인태 \n\n날려라 날려라 날려라 \n두산의 김인태 \n안타 안타 김인태 \n최강 두산 김인태" - }, - { - "playerName": "장승현", - "songInfo": "", - "lyric": "오오오 두~산의 장승현 \n오오오 두~산의 장승현 \n\n힘차게 날~려라 오 장승현 \n오오오 두~산의 장승현~~" - }, - { - "playerName": "정수빈", - "songInfo": "", - "lyric": "수빈! 두산의 정수빈 \n수빈! 승리를 위하여 \n수빈! 힘차게 치고 달려 \n최강두산 정!수!빈! \n\n수빈! 두산의 정수빈 \n수빈! 승리를 위하여 \n수빈! 힘차게 치고 달려 \n최강두산 정!수!빈!" - }, - { - "playerName": "페르난데스", - "songInfo": "", - "lyric": "두산의 페르난데스 \n두산의 페르난데스 \n두산의 페르난데스 \n\n안~타를 날려버려 날려버려 \n페!르!난!데!스! \n\n두산의 페르난데스 \n두산의 페르난데스 \n두산의 페르난데스 \n\n안~타를 날려버려 날려버려 \n페!르!난!데!스!" - }, - { - "playerName": "조수행", - "songInfo": "", - "lyric": "조수행 조수행 \n조수행 조수행 \n안타치고 도루하고 \n라라라라~라라라라라!(조!수!행!) \n\n조수행 조수행 \n조수행 조수행 \n안타치고 도루하고 \n라라라라~라라라라라!(조!수!행!)" - }, - { - "playerName": "양석환", - "songInfo": "", - "lyric": "날려라 안타 양석환 오오오오 \n안타 양석환 오오오오 \n안타 양석환 오오오오 \n양! 석! 환! \n\n날려라 안타 양석환 오오오오 \n안타 양석환 오오오오 \n안타 양석환 오오오오 \n양! 석! 환!" - }, - { - "playerName": "박계범", - "songInfo": "", - "lyric": "안타를 날려주세요 \n두산의 안타 박계범 \n 안타를 날려주세요 \n저 멀리 안타 박계범 \n\n최!강!두!산! 박!계!범! \n\n안타를 날려주세요 \n두산의 안타 박계범" - }, - { - "playerName": "강승호", - "songInfo": "", - "lyric": "강승호 안타! 강승호 안타! \n최강 두산 강승호~ \n강승호~ 두산의 강승호~ \n강!승!호 \n\n강승호 안타! 강승호 안타! \n최강 두산 강승호~ \n강승호~ 두산의 강승호~ \n강!승!호" - }, - { - "playerName": "안권수", - "songInfo": "", - "lyric": "최강 두산 승리 위해 \n안권수 안타 \n치고 달려 베어스의 \n안권수 안타 \n\n최강 두산 승리 위해 \n안권수 안타 \n치고 달려 베어스의 \n안권수 안타" - }, - { - "playerName": "김대한", - "songInfo": "", - "lyric": "두산의 김!대한 안!타! \n안!타! 워~어어어 \n두산의 김!대한 안!타! \n안!타! 워~어어어 \n\n두산의 김!대한 안!타! \n안!타! 워~어어어 \n두산의 김!대한 안!타! \n안!타! 워~어어어" - }, - { - "playerName": "강진성", - "songInfo": "", - "lyric": "두산 베어스 강진성 \n안타를 날려버려라 \n저멀리 하늘끝까지 (날려버려 강진성) \n\n두산 베어스 강진성 \n안타를 날려버려라 \n저멀리 하늘끝까지 (날려버려 강진성)" - }, - { - "playerName": "최용제", - "songInfo": "", - "lyric": "오~오오~오오~ \n오오오오~ 최!용!제! \n오~오오~오오~ \n오오오오~ 최!용!제! \n\n오~오오~오오~ \n오오오오~ 최!용!제! \n오~오오~오오~ \n오오오오~ 최!용!제!" - }, - { - "playerName": "오재원", - "songInfo": "", - "lyric": "(가사없음)" - }, - { - "playerName": "안재석", - "songInfo": "", - "lyric": "Go! Go! 두산의 안재석 \nGo! Go! 힘차게 달려라 \nGo! Go! 승리를 위하여 \n워어어~ 안!재!석! \n\nGo! Go! 두산의 안재석 \nGo! Go! 힘차게 달려라 \nGo! Go! 승리를 위하여 \n워어어~ 안!재!석!" - } - ] - }, - { - "id": 1, - "teamName": "롯데자이언츠", - "teamSongs": [ - { - "songTitle": "롯데의 승리를 외치자", - "lyric": "오오 오오 오오오 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오 승리의 그이름 최강 롯데 자이언츠 우리는 언제나 외친다 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오 최강롯데(최강롯데!) 승리한다(승리한다!) 최강롯데(최강롯데!) 승리한다(승리한다!) 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오", - "songInfo": "" - }, - { - "songTitle": "바닷새", - "lyric": "(롯데! 롯데! 최강롯데!) 어두운 바닷가 홀로나는 새야(새야) 갈곳을 잃었나 하얀 바다새야(오오) 힘없는 소리로 홀로우는 새야(새야) 내짝을 잃었나 하얀 바다새야(오오) (롯데! 최!강!롯!데!) 모두다 가고없는데 바다도 잠이 드는데 새는 왜에 날개짓하며 저렇게 날아만 다닐까 새야~ 해지고 어두운데 새야~ 어디로 떠나갈까 새야~ 날마저 기우는데 새야~ 아픈 맘 어이하나 아아아아 새야 아아아아 새야 우우우 새야 우우우 새야~ 새야~", - "songInfo": "" - }, - { - "songTitle": "소리높여 외쳐보자", - "lyric": "자~ 롯데의 승리위해 소리높여 외쳐보자 최~강 롯데 자이언츠 최~강 롯데 자이언츠 아 롯데는 승리한다 최! 강! 롯! 데! 최~강 롯데 자이언츠 최~강 롯데 자이언츠 아~ 롯데는 승리하리라~ (x2)", - "songInfo": "" - }, - { - "songTitle": "승리는 누구", - "lyric": "워워워 워워워 워 워워워 이 자리 이 곳에서 다 같이 일어나 롯데의 승리 위하여 힘차게 외치자 언제나 변함없이 다함께 힘모아 롯데의 승리 항하여 힘차게 나가자 고난과 역경이 오더라도 언제나 변함없이 우리는 이 곳 이 자리에서 승리를 외친다 우리는 누구 (최 강 롯 데) 승리는 누구 (최 강 롯 데) 롯데는 승리한다 (최 강 롯 데) 이 자리 이 곳에서 다 같이 일어나 롯데의 승리 위하여 힘차게 외치자 언제나 변함없이 다함께 힘모아 롯데의 승리 항하여 힘차게 나가자 고난과 역경이 오더라도 언제나 변함없이 우리는 이 곳 이 자리에서 승리를 외친다 우리는 누구 (최 강 롯 데) 승리는 누구 (최 강 롯 데) 롯데는 승리한다 (최 강 롯 데)", - "songInfo": "" - }, - { - "songTitle": "승리를 외치자", - "lyric": "승리를 외치자 최강 롯데자이언츠 오오 오오 오오오 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오 승리의 그이름 최강 롯데 자이언츠 우리는 언제나 외친다 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오 최강롯데(최강롯데!) 승리한다(승리한다!) 최강롯데(최강롯데!) 승리한다(승리한다!) 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오 승리를 외치자 최강 롯데 자이언츠 오오 오오 오오오", - "songInfo": "율동이 존재한다. \n'승리를' 과 '외치자' 구절 중간에 오우!를 외치며, 구절 끝에는 점프를 한다.\n'최강 롯데 자이언츠 오오 오오 오오오'에서는 양 팔을 달리듯 젓는다.\n'승리의 그이름 최강 롯데 자이언츠'에서는 양 팔을 위로 쭉 뻗어 옆으로 와이퍼처럼 흔든다.\n'최강 롯데 승리한다'에서는 주먹을 위로 뻗어 구호 외치듯 한다." - }, - { - "songTitle": "승전가", - "lyric": "롯데 롯데롯데 롯~데~ 롯데 롯데롯데 롯~데~ 롯데 롯데롯데 롯~데~ 승리의 롯데! (파이팅!) 승리의~ 노래를~ 랄라랄라랄라랄라랄라 롯데 자이언츠 승리의~ 노래를~ 랄라랄라랄라랄라랄라 롯데 자이언츠 롯데 롯데롯데 롯~데~ 롯데 롯데롯데 롯~데~ 롯데 롯데롯데 롯~데~ 승리의 롯데! (파이팅!)", - "songInfo": "" - }, - { - "songTitle": "열정과 낭만", - "lyric": "최강 롯데 승리를 위해 하나 되어 외쳐보자 전진하라 승리 위하여 최강 롯데 오 오오오~ 거인의 열정과 낭만이 살아 숨 쉬는 이곳에 다 함께 승리를 위하여 외쳐보자 영광의 순간 위하여 모두 나가자 싸우자 이기자 오오오오~ 오오오 오~ 오오오오~ 오오오 오~ (최!강!롯!데!) 오오오오~ 오오오 오~ 오오오~ 오오오 오~ (최!강!롯!데!) 거인의 열정과 낭만이 살아 숨 쉬는 이곳에 다 함께 승리를 위하여 외쳐보자 영광의 순간 위하여 모두 나가자 싸우자 이기자", - "songInfo": "" - }, - { - "songTitle": "영광의 순간", - "lyric": "오오오 오 오오오오 (최!강!롯!데!) 오오오 오 오오오오 (승!리!한!다!) 오오오 오 오오오오 (최!강!롯!데!) 오오오 오 오오오오 (승!리!한!다!) 영광의 순간이 오는 그날까지 우리는 언제나 외친다 오오오 오 오오오오 (최!강!롯!데!) 오오오 오 오오오오 (승!리!한!다!) 오오오 오 오오오오 (최!강!롯!데!) 오오오 오 오오오오 (승!리!한!다!) 그 어떤 고난과 시련이 온다 해도 우리는 언제나 외친다 오오오 오 오오오오 (최!강!롯!데!) 오오오 오 오오오오 (승!리!한!다!)", - "songInfo": "" - }, - { - "songTitle": "오 최강롯데", - "lyric": "승리를 위하여 다 함께 외쳐라 우리 모두 최강 롯데 자이언츠 승리를 위하여 다 함께 외쳐라 우리 모두 최강 롯데 자이언츠 오~ 최강롯데~ 오~ 최강롯데~ 오~ 최강롯데~ 오~ 최강롯데~ 라라라 라라라 라라라 라라라 우리 모두 최강 롯데 자이언츠 짝짝짝짝짝짝 롯데 짝짝짝짝 자이언츠 짝짝짝짝짝짝 롯데 짝짝짝짝 자이언츠 오~ 최강롯데~ 오~ 최강롯데~ 오~ 최강롯데~ 오~ 최강롯데~ 라라라 라라라 라라라 라라라 우리 모두 최강 롯데 자이언츠 오~ 최강롯데~ 오~ 최강롯데~ 오~ 최강롯데~ 오~ 최강롯데~ 라라라 라라라 라라라 라라라 우리 모두 최강 롯데 자이언츠", - "songInfo": "" - }, - { - "songTitle": "오늘도 승리한다", - "lyric": "롯데 롯데 롯데 오오 오오오오 오 오오 롯데 롯데 롯데 오오 오오오오 오 오오오 롯데 롯데 롯데 오오 오오오오 오 오오 오늘도 승리한다 내일도 승리한다 언제나 우리는 최강롯데 롯데 롯데 롯데 오오 오오오오 오 오오 롯데 롯데 롯데 오오 오오오오 오 오오오 롯데 롯데 롯데 오오 오오오오 오 오오 오늘도 승리한다 내일도 승리한다 언제나 우리는 최강롯데 롯데 롯데 롯데 오오 오오오오 오 오오 롯데 롯데 롯데 오오 오오오오 오 오오오 롯데 롯데 롯데 오오 오오오오 오 오오 오늘도 승리한다 내일도 승리한다 언제나 우리는 최강롯데 오늘도 승리한다 내일도 승리한다 언제나 우리는 최강롯데 ", - "songInfo": "" - }, - { - "songTitle": "힘차게 외쳐보자", - "lyric": "롯데 자이언츠 (롯데!) 롯데 자이언츠 (롯데!) 롯데 자이언츠 오오오 다 같이 소리 높여 힘차게 외쳐보자 롯데의 승리 위하여 다 같이 소리 높여 힘차게 외쳐보자 롯데의 승리 위하여 오~ 오~ 오~ 오~ 오~ 오~ 롯데~yeah 롯데~yeah 롯데~yeah 롯데 자이언츠 (롯데!) 롯데 자이언츠 오오오 다 같이 소리 높여 힘차게 외쳐보자 롯데의 승리 위하여 다 같이 소리 높여 힘차게 외쳐보자 롯데의 승리 위하여", - "songInfo": "" - } - ], - "player": [ - { - "playerName": "정훈", - "songInfo": "", - "lyric": "오 정훈 자이언츠 정훈 오오오오오~ 오오오오오~ (날려버려~) 오 정훈 자이언츠 정훈 오오오오오~ 오오오오오~" - }, - { - "playerName": "전준우", - "songInfo": "", - "lyric": "안타 안타 썌리라 쌔리라 롯데! 전준우~~ 안타 안타 썌리라 쌔리라 롯데! 전준우~~ 안타 안타 썌리라 쌔리라 롯데! 전준우~~ 안타 안타 썌리라 쌔리라 롯데! 전준우~~" - }, - { - "playerName": "한동희", - "songInfo": "", - "lyric": "롯데 한~동희 워어~ 롯데 한~동희 워어~ 롯데 한동희 안타 롯데 한동희 안타 오오~오오~오~ 롯데 한~동희 워어~ 롯데 한~동희 워어~ 롯데 한동희 안타 롯데 한동희 안타 오오~오오~오~" - }, - { - "playerName": "한동희2", - "songInfo": "", - "lyric": "롯데의 (짝짝) 한동희 (짝짝) 안타 안타 한동희~ 롯데의 (짝짝) 한동희 (짝짝) 오오오오오오오~ 롯데의 (짝짝) 한동희 (짝짝) 안타 안타 한동희~ 롯데의 (짝짝) 한동희 (짝짝) 오오오오오오오~" - }, - { - "playerName": "김재유", - "songInfo": "", - "lyric": "오오오오~ 롯데 김재유 안타 안타 쌔리라 오오오오~ 롯데 김재유 안타 안타 안타 쌔리라 오오오오~ 롯데 김재유 안타 안타 쌔리라 오오오오~ 롯데 김재유 안타 안타 안타 쌔리라" - }, - { - "playerName": "안치홍", - "songInfo": "", - "lyric": "오~~ 롯데의 안치홍 안타 안타 안타 안타 오~~ 롯데의 안치홍 안!치!홍! 오~~ 롯데의 안치홍 안타 안타 안타 안타 오~~ 롯데의 안치홍 안!치!홍!" - }, - { - "playerName": "전준우", - "songInfo": "", - "lyric": "안타 안타 썌리라 쌔리라 롯데! 전준우~~ 안타 안타 썌리라 쌔리라 롯데! 전준우~~ 안타 안타 썌리라 쌔리라 롯데! 전준우~~ 안타 안타 썌리라 쌔리라 롯데! 전준우~~" - }, - { - "playerName": "정보근", - "songInfo": "", - "lyric": "롯데 자이언츠 정보근(Hey!) 롯데 자이언츠 정보근(Hey!) 롯데 자이언츠 정보근(Hey!) 워~워~워워워~ 롯데 자이언츠 정보근(Hey!) 롯데 자이언츠 정보근(Hey!) 롯데 자이언츠 정보근(Hey!) 워~워~워워워~" - }, - { - "playerName": "나원탁", - "songInfo": "", - "lyric": "롯데 나원탁 롯데 나원탁 워워~워워워~워워워~ 안타! 롯데 나원탁 롯데 나원탁 워워~워워워~워워워~ 안타!" - }, - { - "playerName": "추재현", - "songInfo": "", - "lyric": "롯데! 롯데 추재현 안타! 안타 쌔리라 롯데! 롯데 추재현 워어어어 추!재!현! 롯데! 롯데 추재현 안타! 안타 쌔리라 롯데! 롯데 추재현 워어어어 추!재!현!" - }, - { - "playerName": "김민수", - "songInfo": "", - "lyric": "롯데의 김민수 안타~ 오오오~ 오오오오오~ 롯데의 김민수 안타~ 오오오~ 오오오~ 롯데의 김민수 안타~ 오오오~ 오오오오오~ 롯데의 김민수 안타~ 오오오~ 오오오~" - }, - { - "playerName": "지시완", - "songInfo": "", - "lyric": "안타 안타 안타 롯데의 지시완 오~오~오~ 지시완! 안타 안타 안타 롯데의 지시완 오~오~오~ 지시완! 오~오오~오오오오~ 안타! 지시완! 오~오오~오오오오~ 안타! 지시완!" - }, - { - "playerName": "지시완2", - "songInfo": "", - "lyric": "롯데 지시완 롯데 지시완 오~오~오~ 롯데 지시완 롯데 지시완 오~오~오~~" - }, - { - "playerName": "안중열", - "songInfo": "", - "lyric": "자이언츠 롯데 안중열 안타 안타 오오오 오오오오오~ 자이언츠 롯데 안중열 안타 안타 오오오 오오오오오~ 자이언츠 롯데 안중열 안타 안타 오오오 오오오오오~ 자이언츠 롯데 안중열 안타 안타 오오오 오오오오오~" - }, - { - "playerName": "배성근", - "songInfo": "", - "lyric": "롯데 배성근 안타 안타 롯데 배성근 안타 안타 롯데 배성근 안타 안타 롯데 배성근 오 오오오오~ 롯데 배성근 안타 안타 롯데 배성근 안타 안타 롯데 배성근 안타 안타 롯데 배성근 오 오오오오~" - }, - { - "playerName": "신용수", - "songInfo": "", - "lyric": "롯데 신용수 롯데 신용수 안타 안타 오오오오~ 롯데 신용수 롯데 신용수 안타 안타 오오오오~" - }, - { - "playerName": "박승욱", - "songInfo": "", - "lyric": "롯데의 박승욱 안타 안타 롯데의 박승욱 안타 안타 오오오 오오오 오오오 오오오 롯!데! 박!승!욱! 롯데의 박승욱 안타 안타 롯데의 박승욱 안타 안타 오오오 오오오 오오오 오오오 롯!데! 박!승!욱!" - }, - { - "playerName": "고승민", - "songInfo": "", - "lyric": "롯데의 고승민 안타 안타 롯데의 고승민 안타 안타 오오오오오오오오오오~ 오오오오오오오오오오~ 롯데의 고승민 안타 안타" - }, - { - "playerName": "장두성", - "songInfo": "", - "lyric": "롯데 롯데 장두성 롯데의 장두성 롯데 롯데 장두성 오오오~ 오오오~ 안타! 롯데 롯데 장두성 롯데의 장두성 롯데 롯데 장두성 오오오~ 오오오~ 안타!" - }, - { - "playerName": "조세진", - "songInfo": "", - "lyric": "오오오~ 오오~ 오오오~ 오 롯데 롯데 조세진 오오오~ 오오~ 오오오~ 오 롯데 롯데 조세진 오오오~ 오오~ 오오오~ 오 롯데 롯데 조세진 오오오~ 오오~ 오오오~ 오 롯데 롯데 조세진" - }, - { - "playerName": "이학주", - "songInfo": "", - "lyric": "오오 롯데 이학주 오오 롯데 이학주 오오오~ 오오오~ 오오오~ 오오 롯데 이학주 오오 롯데 이학주 오오오~ 오오오~ 오오오~ 오오 롯데 이학주 오오 롯데 이학주 오오오~ 오오오~ 오오오~ 오오 롯데 이학주 오오 롯데 이학주 오오오~ 오오오~ 오오오~" - }, - { - "playerName": "한태양", - "songInfo": "", - "lyric": "롯데 자이언츠 한태양 안타 오오오오오 오오오 롯데 자이언츠 한태양 안타 오오오오오 오오오 롯데 자이언츠 한태양 안타 오오오오오 오오오 롯데 자이언츠 한태양 안타 오오오오오 오오오" - }, - { - "playerName": "황성빈", - "songInfo": "", - "lyric": "롯!데! 황!성!빈! 롯데의 황성빈 오오오~ 롯데의 황성빈~ 오오오~오오오오오오~ 롯!데! 황!성!빈! 롯데의 황성빈 오오오~ 롯데의 황성빈~ 오오오~오오오오오오~ 롯!데! 황!성!빈!" - }, - { - "playerName": "이호연", - "songInfo": "", - "lyric": "롯데의 이호연 롯데의 이호연 워어어~ 워어어어~ (롯!데! 이!호!연!) 롯데의 이호연 롯데의 이호연 워어어~ 워어어어~ (롯!데! 이!호!연!)" - }, - { - "playerName": "잭 렉스", - "songInfo": "", - "lyric": "안타 안타 잭 렉스 오오 오오 잭 렉스 안타 안타 잭 렉스 오오 오오 오 오오~ 안타 안타 잭 렉스 오오 오오 잭 렉스 안!타! 잭 렉스! 안타 안타 잭 렉스 오오 오오 잭 렉스 안타 안타 잭 렉스 오오 오오 오 오오~ 안타 안타 잭 렉스 오오 오오 잭 렉스 안!타! 잭 렉스!" - }, - { - "playerName": "잭 렉스2", - "songInfo": "", - "lyric": "롯데 잭 렉스 안타(짝짝짝) 잭 렉스 안타 오~오오~ 롯데 잭 렉스 안타(짝짝짝) 잭 렉스 안타 오~오오~ 롯데 잭 렉스 안타(짝짝짝) 잭 렉스 안타 오~오오~ 롯데 잭 렉스 안타(짝짝짝) 잭 렉스 안타 오~오오~" - } - ] - }, - { - "id": 3, - "teamName": "한화이글스", - "teamSongs": [ - { - "songTitle": "나는 행복합니다", - "lyric": "나는 행복합니다 나는 행복합니다 나는 행복합니다 이글스라 행복합니다~~~ \n나는 행복합니다 나는 행복합니다 나는 행복합니다 한화라서 행복합니다", - "songInfo": "" - }, - { - "songTitle": "내고향 충청도", - "lyric": "일사후퇴때 피난 내려와 살다 정든 곳 두메나 산골 \n태어난 곳은 아니었지만 나를 키워준 고향 충청도 \n내 아내와 내 아들과 셋이서 함께 가고싶은 곳 \n논과 밭사이 작은 초가집 내 고향은 충청도라오 \n\n어머니는 밭에 나가고 아버지는 장에 가시고 \n나와 내 동생 논길을 따라 메뚜기잡이 하루가 갔죠 \n내 아내와 내 아들과 셋이서 함께 가고싶은 곳 \n논과 밭사이 작은 초가집 내 고향은 충청도라오 \n내 고향은 충청도라오", - "songInfo": "" - }, - { - "songTitle": "내사랑 한화 내사랑 이글스", - "lyric": "다같이 일어나! 최강한화의 승리를 위하여! 함성 발사~~~!! \n최!강!한!화! 이!글!스! 최!강!한!화! 이!글!스! \n내 사랑 한화~ 내 사랑 이글스! 영원토록 사랑한다 최!강!한!화!\n내 사랑 한화~ 내 사랑 이글스! 영원토록 사랑한다 최!강!한!화! \n반복 \n워어어 어어어 어어어어어어어~~~ 최!강!한!화! \n워어어 어어어 어어어어어어어~~~ 최!강!한!화!", - "songInfo": "" - } - ], - "player": [ - { - "playerName": "노수광", - "songInfo": "", - "lyric": "노~ 수~ 광~ 예! \n이글스 승리 위해 달려간다 노수광 \n이글스 승리 위해 안타 노수광 \n\n달려! \n\n이글스의 승리 위해 달려간다 노수광 \n이글스 승리 위해 안타 노수광" - }, - { - "playerName": "노시환", - "songInfo": "", - "lyric": "노시환상적으로 날려줘요 \n환상적으로 날려줘요 \n이글스 노시환상적으로 \n날려버려 노시환 \n\n노시환상적으로 날려줘요 \n환상적으로 날려줘요 \n이글스 노시환상적으로 \n날려버려 노시환 \n\nHey! Hey! Hey! Hey! \nHey! Hey! Hey! Hey! \nHey! Hey! Hey! Hey! \n날려버려 노시환" - }, - { - "playerName": "이성곤", - "songInfo": "", - "lyric": "한화의 이성곤 날려버려라 \n한화의 이성곤 워어워어 \n최강한화의 승리를 위해 \n날려라 고니~ \n\n한화의 이성곤 날려버려라 \n한화의 이성곤 워어워어 \n최강한화의 승리를 위해 \n날려라 고니~ (이성곤!)" - }, - { - "playerName": "하주석", - "songInfo": "", - "lyric": "하주석 유후! \n하주석 승리를 위해~ 함께 외쳐라! \n하주석 유후! \n하주석 승리를 위해~ 함께 외쳐라! \n워어~(하!주!석!)" - }, - { - "playerName": "김태균", - "songInfo": "", - "lyric": "한화의 김태균! (짝짝) \n한화의 김태균! (짝짝) \n그대는 진정 한화의 김태균~ \n워어어어어 \n워어어 워 워! 예! \n그대는 진정 한화의 김태균~ \n\n한화의 김태균! (짝짝) \n한화의 김태균! (짝짝) \n그대는 진정 한화의 김태균~ \n워어어어어 \n워어어 워 워! 예! \n그대는 진정 한화의 김태균~" - }, - { - "playerName": "김성근 감독", - "songInfo": "", - "lyric": "한화의 김성근 감독님 사랑해~ \nㅖ ㅖ ㅖ ㅖㅖㅖ ㅖ ㅖ ㅖ ㅖㅖㅖ \nㅖ ㅖ ㅖ ㅖㅖㅖ ㅖ ㅖ ㅖ ㅖㅖㅖ \nㅖ ㅖ ㅖ ㅖㅖㅖ ㅖ ㅖ ㅖ ㅖㅖㅖ \nㅖ ㅖ ㅖ ㅖㅖㅖ ㅖ~ 한화의 김성근 감독님 사랑해~" - } - ] - } - ] -} diff --git a/Halmap/Features/SongList/MainSongListViewModel.swift b/Halmap/Features/SongList/MainSongListViewModel.swift index 3af8f8b..0a8da32 100644 --- a/Halmap/Features/SongList/MainSongListViewModel.swift +++ b/Halmap/Features/SongList/MainSongListViewModel.swift @@ -11,7 +11,7 @@ class MainSongListTabViewModel: ObservableObject { @Published var showingTeamChangingView: Bool = false @Published var index: Int = 0 - let dataManager: DataManager = DataManager() + let dataManager: DataManager = DataManager.instance func getSongImage(for songInfo: SongInfo) -> String { if dataManager.checkSeasonSong(data: songInfo) { From 01b87fd03a694a89467ab86c43d234819be83c11 Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Wed, 20 Dec 2023 16:56:32 +0900 Subject: [PATCH 51/56] =?UTF-8?q?[Feature]=20=EA=B2=80=EC=83=89=ED=83=AD?= =?UTF-8?q?=20=EC=9D=91=EC=9B=90=EA=B0=80=20=EC=9A=94=EC=B2=AD=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EA=B0=84=EA=B2=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/Features/SongDetail/MiniPlayerView.swift | 1 - Halmap/Features/SongSearch/SongSearchView.swift | 14 +++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index 05e8f66..388576e 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -99,7 +99,6 @@ struct MiniPlayerView: View { .padding(.top, miniPlayerViewModel.isMiniPlayerActivate ? 0 : safeAreaInsets.top) .contentShape(Rectangle()) .gesture(DragGesture().updating($gestureOffset, body: { (value, state, _) in - state = value.translation.height }) .onEnded(onEnd(value:))) diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index 412a439..6791281 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -107,8 +107,20 @@ struct SongSearchView: View { .scaledToFit() .frame(width: UIScreen.getHeight(200)) .padding(.top, UIScreen.getHeight(60)) + Link(destination: URL(string: "https://forms.gle/KmJaL1UTYahUVGkk7")!) { + HStack(spacing: 8) { + Image(systemName: "paperplane.fill") + Text("응원가 신청하기") + .font(Font.Halmap.CustomBodyBold) + } + .padding(EdgeInsets(top: 8, leading: 38, bottom: 8, trailing: 38)) + .foregroundColor(Color.mainGreen) + .background { + RoundedRectangle(cornerRadius: 18) + .strokeBorder(Color.mainGreen, lineWidth: 1) + } + } Spacer() - RequestSongView(buttonColor: Color.mainGreen) } case .result: List { From f4415837d7ce54e77a178d73c0523db2b0b80f75 Mon Sep 17 00:00:00 2001 From: Anti9uA Date: Wed, 20 Dec 2023 18:13:24 +0900 Subject: [PATCH 52/56] =?UTF-8?q?[BugFix]=2013=EB=B2=88=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 14 ++++---------- .../TeamSelection/TeamSelectionViewModel.swift | 3 ++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index ad3dc82..6c564c2 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -683,13 +683,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -707,7 +705,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -723,12 +720,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -746,7 +741,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift b/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift index 3aa9793..081be95 100644 --- a/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift +++ b/Halmap/Features/TeamSelection/TeamSelectionViewModel.swift @@ -10,6 +10,7 @@ import SwiftUI final class TeamSelectionViewModel: ObservableObject { @AppStorage("_isFirstLaunching") var isFirstLaunching: Bool = true + @AppStorage("selectedTeam") var selectedTeamName: String = "" @Published var buttonPressed = [Bool](repeating: false, count: 10) @Published var selectedTeam: TeamName? = nil @@ -30,7 +31,7 @@ final class TeamSelectionViewModel: ObservableObject { func isSelectedTeam(with team: TeamName) -> Bool { guard let selectedTeam else { - return initialSelectedTeam == team + return team.rawValue == dataManager.selectedTeam } return selectedTeam == team } From 2b3f6267a50d1c61f87ffe1fa0cd3aa142f76695 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Wed, 20 Dec 2023 21:18:43 +0900 Subject: [PATCH 53/56] =?UTF-8?q?[Feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=ED=95=9C=EA=B3=A1=EC=9D=B8=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20-=20=ED=95=9C?= =?UTF-8?q?=EA=B3=A1=20=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EB=8B=A4=EC=9D=8C?= =?UTF-8?q?=EA=B3=A1=20=EC=9D=B4=EC=A0=84=EA=B3=A1=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20(=EB=AF=B8=EB=8B=88?= =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=EC=96=B4,=20=EC=9E=AC=EC=83=9D?= =?UTF-8?q?=ED=99=94=EB=A9=B4,=20=EC=9E=A0=EA=B8=88=ED=99=94=EB=A9=B4)=20-?= =?UTF-8?q?=20=EC=9E=AC=EC=83=9D=EB=B0=94=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=9C=20=EB=AF=B8=EB=94=94=EC=96=B4=20=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=EC=99=80=20=EC=97=B0=EB=8F=99=20-?= =?UTF-8?q?=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=ED=94=84=EB=A6=B0?= =?UTF-8?q?=ED=8A=B8=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 14 +++++--- Halmap/Data/AudioManager.swift | 11 ++++-- .../Features/SongDetail/MiniPlayerView.swift | 36 +++++++++++++++---- .../SongDetail/MiniPlayerViewModel.swift | 6 +++- Halmap/Features/SongDetail/PlaylistView.swift | 1 - .../SongDetail/PlaylistViewModel.swift | 2 -- Halmap/Persistence.swift | 3 -- Halmap/View/Component/Progressbar.swift | 7 ++-- 8 files changed, 58 insertions(+), 22 deletions(-) diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index 6c564c2..ad3dc82 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -683,11 +683,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -705,6 +707,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -720,10 +723,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Halmap/Preview Content\""; - DEVELOPMENT_TEAM = Y6P5ZFWBM8; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y6P5ZFWBM8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Halmap/Info.plist; @@ -741,6 +746,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Gwamegis.Halmap; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = moya_dev; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/Halmap/Data/AudioManager.swift b/Halmap/Data/AudioManager.swift index c04d90d..c3bff54 100644 --- a/Halmap/Data/AudioManager.swift +++ b/Halmap/Data/AudioManager.swift @@ -155,7 +155,7 @@ extension AudioManager { MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } - private func updateNowPlayingPlaybackRate() { + func updateNowPlayingPlaybackRate() { if var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo { nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentItem?.currentTime().seconds nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate @@ -168,7 +168,14 @@ extension AudioManager { commandCenter.playCommand.addTarget { [unowned self] event in if self.player.rate == 0.0 { - player.play() + if self.player.currentItem == nil { + if let song { + AMset(song: song) + } + + } else { + player.play() + } isPlaying = true updateNowPlayingPlaybackRate() diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index 388576e..d55d80f 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -84,7 +84,9 @@ struct MiniPlayerView: View { }, label: { Image(systemName: "forward.end.fill") .font(.system(size: 20, weight: .medium)) + .foregroundStyle(defaultPlaylistSongs.count > 1 ? Color.systemBackground : Color.systemBackground.opacity(0.2)) }) + .disabled(defaultPlaylistSongs.count <= 1) } .disabled(defaultPlaylistSongs.count == 0) .foregroundStyle(defaultPlaylistSongs.count > 0 ? Color.systemBackground : Color.systemBackground.opacity(0.2)) @@ -143,9 +145,6 @@ struct MiniPlayerView: View { } .ignoresSafeArea() } - .onAppear() { - setMediaPlayerNextTrack() - } .onChange(of: miniPlayerViewModel.song.id) { _ in self.currentSongId = miniPlayerViewModel.song.id } @@ -155,6 +154,17 @@ struct MiniPlayerView: View { self.miniPlayerViewModel.setPlayer() } } + .onAppear() { + setMediaPlayerNextTrack() + } + .onChange(of: defaultPlaylistSongs.count) { [oldValue = defaultPlaylistSongs.count] newValue in + if oldValue > newValue && newValue == 1 { + removeMediaPlayerNextTrack() + } else if oldValue < newValue && newValue == 2 { + setMediaPlayerNextTrack() + } + + } } .background(Color("\(miniPlayerViewModel.song.team)Sub")) .ignoresSafeArea() @@ -252,6 +262,12 @@ struct MiniPlayerView: View { return .success } } + private func removeMediaPlayerNextTrack() { + let commandCenter = MPRemoteCommandCenter.shared() + + commandCenter.nextTrackCommand.removeTarget(nil) + commandCenter.previousTrackCommand.removeTarget(nil) + } } private struct Lyric: View { @@ -325,8 +341,10 @@ private struct PlayBar: View { } label: { Image(systemName: "backward.end.fill") .font(.system(size: 28, weight: .regular)) - .foregroundColor(.customGray) + .foregroundColor(defaultPlaylistSongs.count > 1 ? .customGray : .customGray.opacity(0.4)) } + .disabled(defaultPlaylistSongs.count <= 1) + Button { viewModel.handlePlayButtonTap() } label: { @@ -334,12 +352,15 @@ private struct PlayBar: View { .font(.system(size: 60, weight: .medium)) .foregroundStyle(Color.customGray) } + Button { // - MARK: 다음곡 재생 기능 if let index = defaultPlaylistSongs.firstIndex(where: {$0.id == viewModel.song.id}) { if index + 1 > defaultPlaylistSongs.count - 1 { - toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") - viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) + if defaultPlaylistSongs.count > 1 { + toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") + viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs.first!) + } } else { viewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) } @@ -347,8 +368,9 @@ private struct PlayBar: View { } label: { Image(systemName: "forward.end.fill") .font(.system(size: 28, weight: .regular)) - .foregroundColor(.customGray) + .foregroundColor(defaultPlaylistSongs.count > 1 ? .customGray : .customGray.opacity(0.4)) } + .disabled( defaultPlaylistSongs.count <= 1) } .padding(.bottom, 54) } diff --git a/Halmap/Features/SongDetail/MiniPlayerViewModel.swift b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift index 257f48e..ee7d09a 100644 --- a/Halmap/Features/SongDetail/MiniPlayerViewModel.swift +++ b/Halmap/Features/SongDetail/MiniPlayerViewModel.swift @@ -59,7 +59,11 @@ class MiniPlayerViewModel: ObservableObject { func handlePlayButtonTap() { if !audioManager.isPlaying { - audioManager.AMplay() + if audioManager.player.currentItem == nil { + setPlayer() + } else { + audioManager.AMplay() + } } else { audioManager.AMstop() } diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 9de6cc0..60806f1 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -27,7 +27,6 @@ struct PlaylistView: View { getPlaylistRowView(song: playListSong) .background(Color.white.opacity(0.001)) .onTapGesture { - print("self.song", playListSong.safeTitle, playListSong.order) self.song = viewModel.didTappedSongCell(song: playListSong) } .background( diff --git a/Halmap/Features/SongDetail/PlaylistViewModel.swift b/Halmap/Features/SongDetail/PlaylistViewModel.swift index f54c645..2198ac2 100644 --- a/Halmap/Features/SongDetail/PlaylistViewModel.swift +++ b/Halmap/Features/SongDetail/PlaylistViewModel.swift @@ -45,10 +45,8 @@ final class PlaylistViewModel: ObservableObject { } func didTappedSongCell(song: CollectedSong) -> SongInfo { - print("song: ", song.safeTitle, "order: ", song.order) if song.id != self.song.id { let song = convertSongToSongInfo(song: song) - print(#function, song.title) self.song = song audioManager.removePlayer() return song diff --git a/Halmap/Persistence.swift b/Halmap/Persistence.swift index 6566811..1a3fb95 100644 --- a/Halmap/Persistence.swift +++ b/Halmap/Persistence.swift @@ -129,7 +129,6 @@ struct PersistenceController { if index < currentIndex { var startOrder = collectedSongs[index].order for i in index+1...currentIndex { - print(collectedSongs[i].safeTitle, collectedSongs[i].order, startOrder) collectedSongs[i].order = startOrder startOrder += 1 } @@ -138,7 +137,6 @@ struct PersistenceController { let newOrder = collectedSongs[currentIndex+1].order var startOrder = collectedSongs[currentIndex+1].order+1 for i in currentIndex+1.. 1 { + toast = Toast(team: defaultPlaylistSongs[0].safeTeam, message: "재생목록이 처음으로 돌아갑니다.") + self.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[0]) + } } } } @@ -131,6 +133,7 @@ struct AudioPlayerControlsView: View { player.seek(to: targetTime) { _ in self.timeObserver.pause(false) self.state = .playing + AudioManager.instance.updateNowPlayingPlaybackRate() } } } From 4a8002fdf08cac024e8d01c33a60bc7c5d08f895 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Tue, 2 Jan 2024 17:26:14 +0900 Subject: [PATCH 54/56] =?UTF-8?q?[Fix]=20QA15=EB=B2=88=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B2=B0=EA=B3=BC=EC=B0=BD=20=ED=84=B0=EC=B9=98=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20-=20button=20->=20onT?= =?UTF-8?q?apGesture=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/SongSearch/SongSearchView.swift | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index 6791281..cd0ebe9 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -133,7 +133,28 @@ struct SongSearchView: View { info: song.info, url: song.url) - Button(action: { + HStack(spacing: 16) { + Image(viewModel.getAlbumImage(with: song)) + .resizable() + .frame(width: 40, height: 40) + .cornerRadius(8) + VStack(alignment: .leading, spacing: 8) { + Text(song.title) + .font(Font.Halmap.CustomBodyMedium) + .foregroundColor(.black) + Text(viewModel.getTeamName(with: song)) + .font(Font.Halmap.CustomCaptionMedium) + .foregroundColor(.customDarkGray) + } + .frame(height: 45) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color.white.opacity(0.0001)) + .lineLimit(1) + } + .listRowBackground(Color(UIColor.clear)) + .listRowInsets((EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))) + .listRowSeparatorTint(Color.customGray) + .onTapGesture { miniPlayerViewModel.removePlayer() self.miniPlayerViewModel.song = song miniPlayerViewModel.setPlayer() @@ -143,27 +164,7 @@ struct SongSearchView: View { miniPlayerViewModel.isMiniPlayerActivate = false } miniPlayerViewModel.addDefaultPlaylist(defaultPlaylistSongs: defaultPlaylistSongs) - }, label: { - HStack(spacing: 16) { - Image(viewModel.getAlbumImage(with: song)) - .resizable() - .frame(width: 40, height: 40) - .cornerRadius(8) - VStack(alignment: .leading, spacing: 8) { - Text(song.title) - .font(Font.Halmap.CustomBodyMedium) - .foregroundColor(.black) - Text(viewModel.getTeamName(with: song)) - .font(Font.Halmap.CustomCaptionMedium) - .foregroundColor(.customDarkGray) - } - .frame(height: 45) - .lineLimit(1) - } - }) - .listRowBackground(Color(UIColor.clear)) - .listRowInsets((EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))) - .listRowSeparatorTint(Color.customGray) + } } } .padding(.horizontal, 20) From 0feb9761ed4d6be6ef57e01d7ee746da6da6e798 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Tue, 2 Jan 2024 17:39:29 +0900 Subject: [PATCH 55/56] =?UTF-8?q?[Fix]=20QA14=EB=B2=88=20=ED=95=98?= =?UTF-8?q?=ED=94=84=EB=AA=A8=EB=8B=AC=EC=97=90=EC=84=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=B7=A8=EC=86=8C=20=EC=8B=9C=20=EB=AF=B8?= =?UTF-8?q?=EB=8B=88=ED=94=8C=EB=A0=88=EC=9D=B4=EC=96=B4=EC=9D=98=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap/View/HalfSheetView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Halmap/View/HalfSheetView.swift b/Halmap/View/HalfSheetView.swift index 1bfd6a0..9569dfe 100644 --- a/Halmap/View/HalfSheetView.swift +++ b/Halmap/View/HalfSheetView.swift @@ -116,6 +116,10 @@ enum MenuType { case .liked: return persistence.saveSongs(song: Utility.convertSongToSongInfo(song: collectedSong), playListTitle: "favorite") case .cancelLiked: + let instance = MiniPlayerViewModel.instance + if instance.song.id == collectedSong.id { + instance.isFavorite = false + } return persistence.deleteSongs(song: collectedSong) case .playNext: //TODO: 바로 다음에 재생 기능 추가 From 0314f3ea8a14ac8c5eb2869a65c925873093a458 Mon Sep 17 00:00:00 2001 From: jeonjimin Date: Sat, 27 Jan 2024 20:12:19 +0900 Subject: [PATCH 56/56] =?UTF-8?q?[Feat]=20GA=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=82=BD=EC=9E=85=20=EB=B0=8F=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=A7=A4=EB=8B=88=EC=A0=80=20=EA=B8=B0=EB=B3=B8=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Halmap.xcodeproj/project.pbxproj | 31 +++++++ .../xcshareddata/xcschemes/Halmap.xcscheme | 87 +++++++++++++++++++ .../Features/SongDetail/MiniPlayerView.swift | 7 ++ Halmap/Features/SongDetail/PlaylistView.swift | 3 + .../SongList/MainSongListTabView.swift | 3 + .../Features/SongSearch/SongSearchView.swift | 1 + .../SongStorage/StorageContentView.swift | 3 + .../TeamSelection/TeamSelectionView.swift | 7 ++ Halmap/Info.plist | 11 +++ Halmap/View/Component/Utility.swift | 63 +++++++++++++- Halmap/View/HalfSheetView.swift | 4 +- container/GTM-T8PJDH46.json | 18 ++++ 12 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 Halmap.xcodeproj/xcshareddata/xcschemes/Halmap.xcscheme create mode 100644 container/GTM-T8PJDH46.json diff --git a/Halmap.xcodeproj/project.pbxproj b/Halmap.xcodeproj/project.pbxproj index ad3dc82..0ae4eba 100644 --- a/Halmap.xcodeproj/project.pbxproj +++ b/Halmap.xcodeproj/project.pbxproj @@ -66,6 +66,8 @@ F98F622B29B4CB450025F50E /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98F622A29B4CB450025F50E /* ThemeManager.swift */; }; F98F623129B59CE60025F50E /* TeamName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98F623029B59CE60025F50E /* TeamName.swift */; }; F98F8D5D29D85B0C00F9C956 /* RequestSongView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98F8D5C29D85B0C00F9C956 /* RequestSongView.swift */; }; + F995D0402B5197B80013E5F5 /* GoogleTagManager in Frameworks */ = {isa = PBXBuildFile; productRef = F995D03F2B5197B80013E5F5 /* GoogleTagManager */; }; + F995D0452B5199590013E5F5 /* GTM-T8PJDH46.json in Resources */ = {isa = PBXBuildFile; fileRef = F995D0442B5199590013E5F5 /* GTM-T8PJDH46.json */; }; F9A178222A1BF5CB003E8E4C /* HalfSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A178212A1BF5CB003E8E4C /* HalfSheetView.swift */; }; F9A178262A1C8B01003E8E4C /* PlaylistFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A178252A1C8B01003E8E4C /* PlaylistFilter.swift */; }; F9A2DAA62A07DE9A008B84A9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F9A2DAA52A07DE9A008B84A9 /* Preview Assets.xcassets */; }; @@ -132,6 +134,7 @@ F98F622A29B4CB450025F50E /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; F98F623029B59CE60025F50E /* TeamName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamName.swift; sourceTree = ""; }; F98F8D5C29D85B0C00F9C956 /* RequestSongView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSongView.swift; sourceTree = ""; }; + F995D0442B5199590013E5F5 /* GTM-T8PJDH46.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "GTM-T8PJDH46.json"; sourceTree = ""; }; F9A178212A1BF5CB003E8E4C /* HalfSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HalfSheetView.swift; sourceTree = ""; }; F9A178252A1C8B01003E8E4C /* PlaylistFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistFilter.swift; sourceTree = ""; }; F9A2DAA52A07DE9A008B84A9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; @@ -150,6 +153,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F995D0402B5197B80013E5F5 /* GoogleTagManager in Frameworks */, 20A687E02A711C48005F18FA /* FirebaseDatabaseSwift in Frameworks */, 20A687DC2A711C48005F18FA /* FirebaseCrashlytics in Frameworks */, 20A687E82A711C48005F18FA /* FirebaseStorage in Frameworks */, @@ -231,6 +235,7 @@ A81FFC6D28F15D9900B0FC7C = { isa = PBXGroup; children = ( + F995D0432B5199510013E5F5 /* container */, 8764EEEB2ABFEF6A0006F438 /* GoogleService-Info.plist */, B2699E0E28F1CB8800267A4F /* Font */, A81FFC7828F15D9900B0FC7C /* Halmap */, @@ -370,6 +375,14 @@ path = Theme; sourceTree = ""; }; + F995D0432B5199510013E5F5 /* container */ = { + isa = PBXGroup; + children = ( + F995D0442B5199590013E5F5 /* GTM-T8PJDH46.json */, + ); + path = container; + sourceTree = ""; + }; F9B95BBC28F2733200728048 /* Data */ = { isa = PBXGroup; children = ( @@ -416,6 +429,7 @@ 20A687E72A711C48005F18FA /* FirebaseStorage */, F949421F2B17373C00075DE1 /* Lottie */, F9CB6A592B18A104005DF514 /* SwiftUIIntrospect */, + F995D03F2B5197B80013E5F5 /* GoogleTagManager */, ); productName = Halmap; productReference = A81FFC7628F15D9900B0FC7C /* Halmap.app */; @@ -450,6 +464,7 @@ 20A687D62A711C48005F18FA /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, F949421E2B17373C00075DE1 /* XCRemoteSwiftPackageReference "lottie-ios" */, F9CB6A582B18A104005DF514 /* XCRemoteSwiftPackageReference "swiftui-introspect" */, + F995D03E2B5197B70013E5F5 /* XCRemoteSwiftPackageReference "google-tag-manager-ios-sdk" */, ); productRefGroup = A81FFC7728F15D9900B0FC7C /* Products */; projectDirPath = ""; @@ -469,6 +484,7 @@ F9A2DAA62A07DE9A008B84A9 /* Preview Assets.xcassets in Resources */, A81FFC8128F15D9C00B0FC7C /* Preview Assets.xcassets in Resources */, B2699E0D28F1CB7E00267A4F /* Pretendard-Bold.otf in Resources */, + F995D0452B5199590013E5F5 /* GTM-T8PJDH46.json in Resources */, F94942252B17398100075DE1 /* waveform.json in Resources */, A81FFC7E28F15D9C00B0FC7C /* Assets.xcassets in Resources */, B2699E1028F1CB9700267A4F /* Pretendard-Medium.otf in Resources */, @@ -617,6 +633,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + "OTHER_LDFLAGS[arch=*]" = "-ObjC"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -670,6 +687,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + "OTHER_LDFLAGS[arch=*]" = "-ObjC"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -796,6 +814,14 @@ minimumVersion = 4.3.3; }; }; + F995D03E2B5197B70013E5F5 /* XCRemoteSwiftPackageReference "google-tag-manager-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/googleanalytics/google-tag-manager-ios-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.4.3; + }; + }; F9CB6A582B18A104005DF514 /* XCRemoteSwiftPackageReference "swiftui-introspect" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/siteline/swiftui-introspect"; @@ -857,6 +883,11 @@ package = F949421E2B17373C00075DE1 /* XCRemoteSwiftPackageReference "lottie-ios" */; productName = Lottie; }; + F995D03F2B5197B80013E5F5 /* GoogleTagManager */ = { + isa = XCSwiftPackageProductDependency; + package = F995D03E2B5197B70013E5F5 /* XCRemoteSwiftPackageReference "google-tag-manager-ios-sdk" */; + productName = GoogleTagManager; + }; F9CB6A592B18A104005DF514 /* SwiftUIIntrospect */ = { isa = XCSwiftPackageProductDependency; package = F9CB6A582B18A104005DF514 /* XCRemoteSwiftPackageReference "swiftui-introspect" */; diff --git a/Halmap.xcodeproj/xcshareddata/xcschemes/Halmap.xcscheme b/Halmap.xcodeproj/xcshareddata/xcschemes/Halmap.xcscheme new file mode 100644 index 0000000..5c0a860 --- /dev/null +++ b/Halmap.xcodeproj/xcshareddata/xcschemes/Halmap.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Halmap/Features/SongDetail/MiniPlayerView.swift b/Halmap/Features/SongDetail/MiniPlayerView.swift index d55d80f..47a2c6c 100644 --- a/Halmap/Features/SongDetail/MiniPlayerView.swift +++ b/Halmap/Features/SongDetail/MiniPlayerView.swift @@ -69,6 +69,7 @@ struct MiniPlayerView: View { HStack(spacing: 21) { Button(action: { miniPlayerViewModel.handlePlayButtonTap() + Utility.analyticsPlayPauseSongMini() }, label: { Image(systemName: miniPlayerViewModel.isPlaying ? "pause.fill" : "play.fill") .font(.system(size: 20, weight: .medium)) @@ -81,6 +82,7 @@ struct MiniPlayerView: View { miniPlayerViewModel.song = Utility.convertSongToSongInfo(song: defaultPlaylistSongs[index + 1]) } } + Utility.analyticsPlayNextSongMini() }, label: { Image(systemName: "forward.end.fill") .font(.system(size: 20, weight: .medium)) @@ -175,6 +177,11 @@ struct MiniPlayerView: View { Color("\(miniPlayerViewModel.song.team)Sub") .cornerRadius(8) ) + .onChange(of: miniPlayerViewModel.isMiniPlayerActivate) { newValue in + if !newValue { + Utility.analyticsScreenEvent(screenName: "응원가 재생하기", screenClass: "MiniPlayerView") + } + } } func onChanged(){ diff --git a/Halmap/Features/SongDetail/PlaylistView.swift b/Halmap/Features/SongDetail/PlaylistView.swift index 60806f1..d8d9e8c 100644 --- a/Halmap/Features/SongDetail/PlaylistView.swift +++ b/Halmap/Features/SongDetail/PlaylistView.swift @@ -90,6 +90,9 @@ struct PlaylistView: View { } } .background(Color("\(song.team)Sub")) + .onAppear() { + Utility.analyticsScreenEvent(screenName: "재생목록", screenClass: "PlaylistView") + } } @ViewBuilder diff --git a/Halmap/Features/SongList/MainSongListTabView.swift b/Halmap/Features/SongList/MainSongListTabView.swift index 7891499..6935be3 100644 --- a/Halmap/Features/SongList/MainSongListTabView.swift +++ b/Halmap/Features/SongList/MainSongListTabView.swift @@ -247,6 +247,9 @@ struct MainSongListTabView: View { TeamSelectionView(viewModel: TeamSelectionViewModel(dataManager: dataManager), isShowing: $viewModel.showingTeamChangingView) } .navigationBarHidden(true) + .onAppear() { + Utility.analyticsScreenEvent(screenName: "팀 응원가 / 선수 응원가", screenClass: "MainSongListTabView") + } } } diff --git a/Halmap/Features/SongSearch/SongSearchView.swift b/Halmap/Features/SongSearch/SongSearchView.swift index cd0ebe9..775b083 100644 --- a/Halmap/Features/SongSearch/SongSearchView.swift +++ b/Halmap/Features/SongSearch/SongSearchView.swift @@ -40,6 +40,7 @@ struct SongSearchView: View { .navigationBarHidden(true) .onAppear { isFocused = true + Utility.analyticsScreenEvent(screenName: "검색", screenClass: "SongSearchView") } .onTapGesture { hideKeyboard() diff --git a/Halmap/Features/SongStorage/StorageContentView.swift b/Halmap/Features/SongStorage/StorageContentView.swift index 6325ef7..2afa5ef 100644 --- a/Halmap/Features/SongStorage/StorageContentView.swift +++ b/Halmap/Features/SongStorage/StorageContentView.swift @@ -19,5 +19,8 @@ struct StorageContentView: View { ScalingHeaderView(viewModel: SongStorageViewModel(dataManager: dataManager, persistence: persistence, topEdge: topEdge)) .ignoresSafeArea(.all, edges: .top) } + .onAppear() { + Utility.analyticsScreenEvent(screenName: "보관함", screenClass: "SongStorageView") + } } } diff --git a/Halmap/Features/TeamSelection/TeamSelectionView.swift b/Halmap/Features/TeamSelection/TeamSelectionView.swift index 31f007f..55e67b3 100644 --- a/Halmap/Features/TeamSelection/TeamSelectionView.swift +++ b/Halmap/Features/TeamSelection/TeamSelectionView.swift @@ -10,6 +10,7 @@ import SwiftUI struct TeamSelectionView: View { @EnvironmentObject var dataManager: DataManager @AppStorage("selectedTeam") var selectedTeamName: String = "" + @AppStorage("_isFirstLaunching") var isFirstLaunching: Bool = true @StateObject var viewModel: TeamSelectionViewModel @Binding var isShowing: Bool @@ -47,6 +48,9 @@ struct TeamSelectionView: View { isShowing = viewModel.didTappedStartButton() selectedTeamName = viewModel.getSelectedTeamName() } + if !isFirstLaunching { + Utility.analyticsChangeTeam() + } } label: { RoundedRectangle(cornerRadius: 8) .foregroundColor(Color.mainGreen) @@ -64,5 +68,8 @@ struct TeamSelectionView: View { .onDisappear{ Color.setColor(selectedTeamName) } + .onAppear() { + Utility.analyticsScreenEvent(screenName: "팀 선택", screenClass: "TeamSelectionView") + } } } diff --git a/Halmap/Info.plist b/Halmap/Info.plist index 4e8e353..c17ead3 100644 --- a/Halmap/Info.plist +++ b/Halmap/Info.plist @@ -6,6 +6,17 @@ $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLTypes + + + CFBundleURLName + com.Gwamegis.Halmap + CFBundleURLSchemes + + tagmanager.c.com.Gwamegis.Halmap + + + UIAppFonts Pretendard-Bold.otf diff --git a/Halmap/View/Component/Utility.swift b/Halmap/View/Component/Utility.swift index 3c3561f..ca51e8c 100644 --- a/Halmap/View/Component/Utility.swift +++ b/Halmap/View/Component/Utility.swift @@ -6,6 +6,7 @@ // import Foundation +import FirebaseAnalytics class Utility: NSObject { @@ -53,5 +54,65 @@ class Utility: NSObject { return "\(song.team)\(song.type ? "Player" : "Album")" } } - + static func analyticsPlaySong(song: SongInfo, event: String) { + let event = "PlaySong" + let parameters = [ + "id": song.id, + "team": song.team, + "title": song.title, + "event": event + ] + Analytics.logEvent(event, parameters: parameters) + } + static func analyticsPlayAllSong(index: Int) { + let event = "PlayAllSong" + let parameters = [ + "위치": index == 0 ? "TeamTab" : "PlayerTab", + "event": "PlayAll" + ] + Analytics.logEvent(event, parameters: parameters) + } + static func analyticsPlayNextSongMini() { + let event = "PlayNextSongMini" + let parameters = [ + "event": "NextSong_Mini" + ] + Analytics.logEvent(event, parameters: parameters) + } + static func analyticsPlayPauseSongMini() { + let event = "PlayPauseSongMini" + let parameters = [ + "event": "PauseSong_Mini" + ] + Analytics.logEvent(event, parameters: parameters) + } + static func analyticsLikeSong() { + } + static func analyticsChangeTeam() { + let event = "ChangeTeam" + let parameters = [ + "event": "event occur" + ] + Analytics.logEvent(event, parameters: parameters) + } + static func analyticsAddSongFirst() { + let event = "AddSongFirst" + let parameters = [ + "event": "AddSongFirst" + ] + Analytics.logEvent(event, parameters: parameters) + } + static func analyticsAddSongLast() { + let event = "AddSongLast" + let parameters = [ + "event": "AddSongLast" + ] + Analytics.logEvent(event, parameters: parameters) + } +//Screen View Event + static func analyticsScreenEvent(screenName: String, screenClass: String) { + Analytics.logEvent(AnalyticsEventScreenView, + parameters: [AnalyticsParameterScreenName: screenName, + AnalyticsParameterScreenClass: screenClass]) + } } diff --git a/Halmap/View/HalfSheetView.swift b/Halmap/View/HalfSheetView.swift index 9569dfe..4562641 100644 --- a/Halmap/View/HalfSheetView.swift +++ b/Halmap/View/HalfSheetView.swift @@ -122,10 +122,10 @@ enum MenuType { } return persistence.deleteSongs(song: collectedSong) case .playNext: - //TODO: 바로 다음에 재생 기능 추가 + Utility.analyticsAddSongFirst() return persistence.saveSongs(collectedSong: collectedSong, playListTitle: "defaultPlaylist", menuType: .playNext, collectedSongs: playlists) case .playLast: - //TODO: 맨 마지막에 재생 기능 추가 + Utility.analyticsAddSongLast() return persistence.saveSongs(collectedSong: collectedSong, playListTitle: "defaultPlaylist", menuType: .playLast, collectedSongs: playlists) } } diff --git a/container/GTM-T8PJDH46.json b/container/GTM-T8PJDH46.json new file mode 100644 index 0000000..4e5d612 --- /dev/null +++ b/container/GTM-T8PJDH46.json @@ -0,0 +1,18 @@ +{ + +"fingerprint":"MQ$0", + +"resource": { + "version":"1", + + "macros":[{"function":"__ai","instance_name":"App ID"},{"function":"__an","instance_name":"App Name"},{"function":"__av","instance_name":"App Version Code"},{"function":"__e","instance_name":"Event Name"}], + "tags":[], + "predicates":[], + "rules":[] +}, +"runtime": +[ +[50,"__ai_main",[46],[36,[2,[17,[15,"gtmUtils"],"mobile"],"applicationId",[7]]]],[50,"__ai",[46,"data"],[36,["__ai_main",[15,"data"]]]],[50,"__an_main",[46],[36,[2,[17,[15,"gtmUtils"],"mobile"],"applicationName",[7]]]],[50,"__an",[46,"data"],[36,["__an_main",[15,"data"]]]],[50,"__av_main",[46],[36,[2,[17,[15,"gtmUtils"],"mobile"],"applicationVersion",[7]]]],[50,"__av",[46,"data"],[36,["__av_main",[15,"data"]]]],[50,"__e_main",[46],[41,"a","b"],[3,"a",[2,[17,[15,"gtmUtils"],"mobile"],"event",[7]]],[22,[1,[1,[15,"a"],[18,[17,[15,"a"],"length"],0]],[12,[16,[15,"a"],0],"_"]],[46,[3,"b",[8,"_f","first_open","_v","first_visit","_iap","in_app_purchase","_e","user_engagement","_s","session_start","_ssr","session_start_with_rollout","_au","app_update","_ui","app_remove","_ab","app_background","_ou","os_update","_cd","app_clear_data","_ae","app_exception","_nf","notification_foreground","_nr","notification_receive","_no","notification_open","_nd","notification_dismiss","_cmp","firebase_campaign","_cmpx","invalid_campaign","_vs","screen_view","_ar","ad_reward","_asr","app_store_refund","_assc","app_store_subscription_convert","_assr","app_store_subscription_renew","_asse","app_store_subscription_cancel"]],[3,"a",[30,[16,[15,"b"],[15,"a"]],[15,"a"]]]]],[36,[15,"a"]]],[50,"__e",[46,"data"],[36,["__e_main",[15,"data"]]]], +[50,"main",[46,"a"],[43,[17,[15,"a"],"common"],"tableToMap",[15,"tableToMap"]],[43,[17,[15,"a"],"common"],"stringify",[15,"stringify"]],[43,[17,[15,"a"],"common"],"copy",[15,"copy"]],[43,[17,[15,"a"],"common"],"split",[15,"split"]]],[50,"tableToMap",[46,"a","b","c"],[41,"d","e","f"],[3,"d",[8]],[3,"e",false],[3,"f",0],[42,[1,[15,"a"],[23,[15,"f"],[17,[15,"a"],"length"]]],[33,[15,"f"],[3,"f",[0,[15,"f"],1]]],false,[46,[22,[1,[1,[16,[15,"a"],[15,"f"]],[2,[16,[15,"a"],[15,"f"]],"hasOwnProperty",[7,[15,"b"]]]],[2,[16,[15,"a"],[15,"f"]],"hasOwnProperty",[7,[15,"c"]]]],[46,[43,[15,"d"],[16,[16,[15,"a"],[15,"f"]],[15,"b"]],[16,[16,[15,"a"],[15,"f"]],[15,"c"]]],[3,"e",true]]]]],[36,[39,[15,"e"],[15,"d"],[45]]]],[50,"stringify",[46,"a"],[41,"b","c","d","e"],[22,[20,[15,"a"],[45]],[46,[36,"null"]]],[22,[20,[15,"a"],[44]],[46,[36,[44]]]],[22,[30,[12,[40,[15,"a"]],"number"],[12,[40,[15,"a"]],"boolean"]],[46,[36,[2,[15,"a"],"toString",[7]]]]],[22,[12,[40,[15,"a"]],"string"],[46,[36,[0,[0,"\"",[2,["split",[15,"a"],"\""],"join",[7,"\\\""]]],"\""]]]],[22,[2,[17,[15,"gtmUtils"],"common"],"isArray",[7,[15,"a"]]],[46,[3,"b",[7]],[3,"c",0],[42,[23,[15,"c"],[17,[15,"a"],"length"]],[33,[15,"c"],[3,"c",[0,[15,"c"],1]]],false,[46,[3,"d",["stringify",[16,[15,"a"],[15,"c"]]]],[22,[12,[15,"d"],[44]],[46,[2,[15,"b"],"push",[7,"null"]]],[46,[2,[15,"b"],"push",[7,[15,"d"]]]]]]],[36,[0,[0,"[",[2,[15,"b"],"join",[7,","]]],"]"]]]],[22,[12,[40,[15,"a"]],"object"],[46,[3,"b",[7]],[47,"e",[15,"a"],[46,[3,"d",["stringify",[16,[15,"a"],[15,"e"]]]],[22,[29,[15,"d"],[44]],[46,[2,[15,"b"],"push",[7,[0,[0,[0,"\"",[15,"e"]],"\":"],[15,"d"]]]]]]]],[36,[0,[0,"{",[2,[15,"b"],"join",[7,","]]],"}"]]]],[2,[17,[15,"gtmUtils"],"common"],"log",[7,"e","Attempting to stringify unknown type!"]],[36,[44]]],[50,"split",[46,"a","b"],[41,"c","d","e","f"],[3,"c",[7]],[22,[20,[15,"b"],""],[46,[3,"d",[17,[15,"a"],"length"]],[3,"e",0],[42,[23,[15,"e"],[15,"d"]],[33,[15,"e"],[3,"e",[0,[15,"e"],1]]],false,[46,[2,[15,"c"],"push",[7,[16,[15,"a"],[15,"e"]]]]]],[36,[15,"c"]]]],[42,[1,[15,"a"],[19,[2,[15,"a"],"indexOf",[7,[15,"b"]]],0]],[46],false,[46,[3,"f",[2,[15,"a"],"indexOf",[7,[15,"b"]]]],[22,[12,[15,"f"],0],[46,[2,[15,"c"],"push",[7,""]]],[46,[2,[15,"c"],"push",[7,[2,[15,"a"],"substring",[7,0,[15,"f"]]]]]]],[3,"a",[2,[15,"a"],"substring",[7,[0,[15,"f"],[17,[15,"b"],"length"]]]]]]],[2,[15,"c"],"push",[7,[15,"a"]]],[36,[15,"c"]]],[50,"copy",[46,"a","b"],[41,"c","d"],[3,"b",[30,[15,"b"],[39,[2,[17,[15,"gtmUtils"],"common"],"isArray",[7,[15,"a"]]],[7],[8]]]],[47,"c",[15,"a"],[46,[3,"d",[16,[15,"a"],[15,"c"]]],[22,[2,[17,[15,"gtmUtils"],"common"],"isArray",[7,[15,"d"]]],[46,[22,[28,[2,[17,[15,"gtmUtils"],"common"],"isArray",[7,[16,[15,"b"],[15,"c"]]]]],[46,[43,[15,"b"],[15,"c"],[7]]]],[43,[15,"b"],[15,"c"],["copy",[15,"d"],[16,[15,"b"],[15,"c"]]]]],[46,[22,[1,[29,[15,"d"],[45]],[12,[40,[15,"d"]],"object"]],[46,[22,[29,[40,[16,[15,"b"],[15,"c"]]],"object"],[46,[43,[15,"b"],[15,"c"],[8]]]],[43,[15,"b"],[15,"c"],["copy",[15,"d"],[16,[15,"b"],[15,"c"]]]]],[46,[43,[15,"b"],[15,"c"],[15,"d"]]]]]]]],[36,[15,"b"]]] +] +}