Skip to content

Commit

Permalink
added playing indicator to small player
Browse files Browse the repository at this point in the history
  • Loading branch information
0PandaDEV committed Aug 14, 2024
1 parent 30016c4 commit 2716adc
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 51 deletions.
8 changes: 4 additions & 4 deletions Vleer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
1545A64F2C62431F004B2DD7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1545A64E2C62431F004B2DD7 /* Preview Assets.xcassets */; };
15682C6F2C62C5AA00627168 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 15682C6E2C62C5AA00627168 /* Assets.xcassets */; };
15682C712C62C89800627168 /* Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15682C702C62C89800627168 /* Download.swift */; };
157280A12C6CC2880005124E /* PlayerControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157280A02C6CC2880005124E /* PlayerControls.swift */; };
1585D65B2C638DE400A88BD3 /* DMMono-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1585D6542C638DE400A88BD3 /* DMMono-Light.ttf */; };
1585D65D2C638DE400A88BD3 /* DMMono-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1585D6562C638DE400A88BD3 /* DMMono-Medium.ttf */; };
1585D65F2C638DE400A88BD3 /* DMMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1585D6582C638DE400A88BD3 /* DMMono-Regular.ttf */; };
1585D67C2C639F1500A88BD3 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1585D67B2C639F1500A88BD3 /* Models.swift */; };
1585D6802C639F3A00A88BD3 /* PlayerControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1585D67F2C639F3A00A88BD3 /* PlayerControls.swift */; };
1585D6822C639F4800A88BD3 /* NavBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1585D6812C639F4800A88BD3 /* NavBar.swift */; };
1585D6842C639F5B00A88BD3 /* AudioPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1585D6832C639F5B00A88BD3 /* AudioPlayerManager.swift */; };
1585D6872C63A04700A88BD3 /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1585D6862C63A04700A88BD3 /* Home.swift */; };
Expand All @@ -37,11 +37,11 @@
1545A64E2C62431F004B2DD7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
15682C6E2C62C5AA00627168 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
15682C702C62C89800627168 /* Download.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Download.swift; sourceTree = "<group>"; };
157280A02C6CC2880005124E /* PlayerControls.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerControls.swift; sourceTree = "<group>"; };
1585D6542C638DE400A88BD3 /* DMMono-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DMMono-Light.ttf"; sourceTree = "<group>"; };
1585D6562C638DE400A88BD3 /* DMMono-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DMMono-Medium.ttf"; sourceTree = "<group>"; };
1585D6582C638DE400A88BD3 /* DMMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DMMono-Regular.ttf"; sourceTree = "<group>"; };
1585D67B2C639F1500A88BD3 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
1585D67F2C639F3A00A88BD3 /* PlayerControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControls.swift; sourceTree = "<group>"; };
1585D6812C639F4800A88BD3 /* NavBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBar.swift; sourceTree = "<group>"; };
1585D6832C639F5B00A88BD3 /* AudioPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerManager.swift; sourceTree = "<group>"; };
1585D6862C63A04700A88BD3 /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -109,7 +109,7 @@
isa = PBXGroup;
children = (
1585D67B2C639F1500A88BD3 /* Models.swift */,
1585D67F2C639F3A00A88BD3 /* PlayerControls.swift */,
157280A02C6CC2880005124E /* PlayerControls.swift */,
1585D6812C639F4800A88BD3 /* NavBar.swift */,
);
path = Components;
Expand Down Expand Up @@ -237,12 +237,12 @@
15682C712C62C89800627168 /* Download.swift in Sources */,
1585D6912C63A50100A88BD3 /* KeyboardAdaptive.swift in Sources */,
1585D6952C63EAD100A88BD3 /* APIService.swift in Sources */,
157280A12C6CC2880005124E /* PlayerControls.swift in Sources */,
1545A64A2C62431E004B2DD7 /* ContentView.swift in Sources */,
1585D68B2C63A05400A88BD3 /* Settings.swift in Sources */,
1545A6482C62431E004B2DD7 /* VleerApp.swift in Sources */,
1585D67C2C639F1500A88BD3 /* Models.swift in Sources */,
1585D6842C639F5B00A88BD3 /* AudioPlayerManager.swift in Sources */,
1585D6802C639F3A00A88BD3 /* PlayerControls.swift in Sources */,
1585D6892C63A04E00A88BD3 /* Songs.swift in Sources */,
1585D68F2C63A06100A88BD3 /* Search.swift in Sources */,
);
Expand Down
2 changes: 1 addition & 1 deletion Vleer/Components/NavBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct NavBarItem: View {
Text(text)
.font(.custom("DMMono-Medium", size: 10))
}
.foregroundColor(isSelected ? .white : .gray)
.foregroundColor(isSelected ? .white : Color(red: 83/255, green: 83/255, blue: 83/255))
.frame(maxWidth: .infinity)
}
}
90 changes: 51 additions & 39 deletions Vleer/Components/PlayerControls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,66 @@ struct PlayerControls: View {
@ObservedObject var audioPlayer: AudioPlayerManager

var body: some View {
HStack {
if let currentSong = audioPlayer.currentSong {
AsyncImage(url: URL(string: currentSong.thumbnailUrl)) { image in
image.resizable()
} placeholder: {
VStack(spacing: 0) {
HStack {
if let currentSong = audioPlayer.currentSong {
AsyncImage(url: URL(string: currentSong.thumbnailUrl)) { image in
image.resizable()
} placeholder: {
Color(red: 0.16, green: 0.16, blue: 0.16)
}
.frame(width: 36, height: 36)

VStack(alignment: .leading) {
Text(currentSong.title)
.font(.custom("DMMono-Medium", size: 14))
.foregroundColor(.white)
.padding(.leading, 4)
}
} else {
Color(red: 0.16, green: 0.16, blue: 0.16)
}
.frame(width: 36, height: 36)

VStack(alignment: .leading) {
Text(currentSong.title)
.frame(width: 36, height: 36)

Text("Not Playing")
.font(.custom("DMMono-Medium", size: 14))
.foregroundColor(.white)
.padding(.leading, 4)
}
} else {
Color(red: 0.16, green: 0.16, blue: 0.16)
.frame(width: 36, height: 36)

Text("Not Playing")
.font(.custom("DMMono-Medium", size: 14))
.foregroundColor(.white)
.padding(.leading, 4)
}

Spacer()

Button(action: {
audioPlayer.isPlaying ? audioPlayer.pause() : audioPlayer.play()
}) {
Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill")
.font(.system(size: 20))
.foregroundColor(.white)
}

Button(action: audioPlayer.nextTrack) {
Image(systemName: "forward.fill")
.font(.system(size: 20))
.foregroundColor(.white)
Spacer()

Button(action: {
audioPlayer.isPlaying ? audioPlayer.pause() : audioPlayer.play()
}) {
Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill")
.font(.system(size: 20))
.foregroundColor(.white)
}

Button(action: audioPlayer.nextTrack) {
Image(systemName: "forward.fill")
.font(.system(size: 20))
.foregroundColor(.white)
}
}
.padding(.leading, 8)
.padding(.top, 8)
.padding(.bottom, 8)
.padding(.trailing, 20)
.frame(height: 50)
.frame(maxWidth: .infinity)
.background(Color(red: 0.07, green: 0.07, blue: 0.07))
}
.padding(.leading, 8)
.padding(.top, 8)
.padding(.bottom, 8)
.padding(.trailing, 20)
.frame(height: 52)
.frame(maxWidth: .infinity)
.background(Color(red: 0.07, green: 0.07, blue: 0.07))
.border(Color(red: 0.325, green: 0.325, blue: 0.325), width: 1)
.overlay(
GeometryReader { geometry in
Rectangle()
.fill(Color(red: 160/255, green: 88/255, blue: 255/255))
.frame(width: geometry.size.width * CGFloat(audioPlayer.progress), height: 2)
.position(x: geometry.size.width * CGFloat(audioPlayer.progress) / 2, y: geometry.size.height - 1)
}
)
}
}
}
57 changes: 54 additions & 3 deletions Vleer/Pages/Search.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ struct SearchView: View {

ScrollView {
LazyVStack(spacing: 0) {
Color.clear.frame(height: 14) // Top padding

ForEach(searchViewModel.searchResults) { song in
SongRow(song: song, audioPlayer: audioPlayer, download: download, query: searchText)
.padding(.horizontal, 20)
SongRowWithSpacing(song: song, audioPlayer: audioPlayer, download: download, query: searchText)
}

Color.clear.frame(height: 6) // Bottom padding (14 - 8 = 6)
}
.padding(.bottom, 120)
.background(Color(red: 0.07, green: 0.07, blue: 0.07))
}
}
Expand All @@ -45,6 +47,55 @@ struct SearchView: View {
}
}

// MARK: - SongRowWithSpacing

struct SongRowWithSpacing: View {
let song: Song
@ObservedObject var audioPlayer: AudioPlayerManager
@ObservedObject var download: Download
let query: String

var body: some View {
VStack(spacing: 0) {
SongRow(song: song, audioPlayer: audioPlayer, download: download, query: query)
.padding(.horizontal, 20)

Color.clear.frame(height: 8) // 8px spacing
}
}
}

// MARK: - Custom Spacing Modifier

struct CustomSpacingModifier: ViewModifier {
let spacing: CGFloat

func body(content: Content) -> some View {
content
.padding(.bottom, spacing)
.frame(maxWidth: .infinity)
.overlay(
GeometryReader { geometry in
Color.clear.preference(key: ViewOffsetKey.self,
value: geometry.frame(in: .named("scroll")).maxY)
}
)
}
}

struct ViewOffsetKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}

extension View {
func customSpacing(_ spacing: CGFloat) -> some View {
self.modifier(CustomSpacingModifier(spacing: spacing))
}
}

// MARK: - SearchViewModel

class SearchViewModel: ObservableObject {
Expand Down
12 changes: 8 additions & 4 deletions Vleer/lib/AudioPlayerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class AudioPlayerManager: ObservableObject {
@Published var currentTime: TimeInterval = 0
@Published var duration: TimeInterval = 0
@Published var currentSong: Song?
@Published var progress: Double = 0

init() {
player = AVPlayer()
Expand All @@ -26,9 +27,12 @@ class AudioPlayerManager: ObservableObject {
}

private func setupObservers() {
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1), queue: .main) { [weak self] time in
self?.currentTime = time.seconds
self?.updateNowPlayingInfo()
let interval = CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
guard let self = self else { return }
self.currentTime = time.seconds
self.progress = self.duration > 0 ? self.currentTime / self.duration : 0
self.updateNowPlayingInfo()
}

NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
Expand Down Expand Up @@ -161,4 +165,4 @@ class AudioPlayerManager: ObservableObject {
// Implement next track logic
print("Next track")
}
}
}

0 comments on commit 2716adc

Please sign in to comment.