diff --git a/WOKA.xcodeproj/project.pbxproj b/WOKA.xcodeproj/project.pbxproj index c3fec10..41d9ffb 100644 --- a/WOKA.xcodeproj/project.pbxproj +++ b/WOKA.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ 527AC6FA2C17387300434FB7 /* SongBlogDM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527AC6F92C17387300434FB7 /* SongBlogDM.swift */; }; 527AC6FD2C173A5100434FB7 /* SongListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527AC6FB2C173A5100434FB7 /* SongListCell.swift */; }; 527AC6FE2C173A5100434FB7 /* SongListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 527AC6FC2C173A5100434FB7 /* SongListCell.xib */; }; + 527AC7012C182DCE00434FB7 /* TimeStringToSeconds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527AC7002C182DCE00434FB7 /* TimeStringToSeconds.swift */; }; 529B0DD42C06156B00CFC54B /* LoginNavVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529B0DD32C06156B00CFC54B /* LoginNavVC.swift */; }; 529B0DD62C070C0F00CFC54B /* GuestDataDM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529B0DD52C070C0F00CFC54B /* GuestDataDM.swift */; }; 52A3F6A52BECBA8D0000BB0B /* LinkedChildDM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A3F6A42BECBA8D0000BB0B /* LinkedChildDM.swift */; }; @@ -274,6 +275,7 @@ 527AC6F92C17387300434FB7 /* SongBlogDM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongBlogDM.swift; sourceTree = ""; }; 527AC6FB2C173A5100434FB7 /* SongListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongListCell.swift; sourceTree = ""; }; 527AC6FC2C173A5100434FB7 /* SongListCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SongListCell.xib; sourceTree = ""; }; + 527AC7002C182DCE00434FB7 /* TimeStringToSeconds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeStringToSeconds.swift; sourceTree = ""; }; 529B0DD32C06156B00CFC54B /* LoginNavVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginNavVC.swift; sourceTree = ""; }; 529B0DD52C070C0F00CFC54B /* GuestDataDM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestDataDM.swift; sourceTree = ""; }; 52A3F6A42BECBA8D0000BB0B /* LinkedChildDM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkedChildDM.swift; sourceTree = ""; }; @@ -770,6 +772,14 @@ path = Alerts; sourceTree = ""; }; + 527AC6FF2C182D1700434FB7 /* Timer */ = { + isa = PBXGroup; + children = ( + 527AC7002C182DCE00434FB7 /* TimeStringToSeconds.swift */, + ); + path = Timer; + sourceTree = ""; + }; 52C1A4DF2C05B670007BAA50 /* Delegate */ = { isa = PBXGroup; children = ( @@ -792,6 +802,7 @@ 52C8B0512BDA4B51003B51D0 /* Helpers */ = { isa = PBXGroup; children = ( + 527AC6FF2C182D1700434FB7 /* Timer */, 9CBE1B3E2C0F37B200CA6E61 /* DropDown */, 9C535DB62C0089A700DA6DCD /* Animation */, 525953D22BE8B2CD00191286 /* UIApplication */, @@ -1422,6 +1433,7 @@ 9C535DB52C005A6D00DA6DCD /* KeyWindowFix.swift in Sources */, 9C9BEEC72BEE1BBF004ECC2F /* CollectionViewCenteredFlowLayout.swift in Sources */, 5222426A2BFC7AFC0085C632 /* SideMenuVC.swift in Sources */, + 527AC7012C182DCE00434FB7 /* TimeStringToSeconds.swift in Sources */, 9CBCB29D2BE4D6BB007D7934 /* LoginVM.swift in Sources */, 524C42312C0499560016A11C /* NotificationCenterReloads.swift in Sources */, 9C8C4FAE2C1315410017DD3B /* WebViewVC.swift in Sources */, diff --git a/WOKA/Helpers/Timer/TimeStringToSeconds.swift b/WOKA/Helpers/Timer/TimeStringToSeconds.swift new file mode 100644 index 0000000..dc4ae23 --- /dev/null +++ b/WOKA/Helpers/Timer/TimeStringToSeconds.swift @@ -0,0 +1,48 @@ +// +// TimeStringToSeconds.swift +// WOKA +// +// Created by MacBook Pro on 11/06/24. +// + +import UIKit + +extension String { + + func timeStringToSeconds() -> Int? { + let components = self.split(separator: ":") + + switch components.count { + case 2: + // MM:SS format + let minutes = Int(components[0]) ?? 0 + let seconds = Int(components[1]) ?? 0 + return (minutes * 60) + seconds + case 3: + // HH:MM:SS format + let hours = Int(components[0]) ?? 0 + let minutes = Int(components[1]) ?? 0 + let seconds = Int(components[2]) ?? 0 + return (hours * 3600) + (minutes * 60) + seconds + default: + // Invalid format + return nil + } + } + + func checkHourZero() -> String{ + // Function to format the time + // Split the time string by ":" + let components = self.split(separator: ":") + + // Check if the time string starts with "00" + if components.count == 3 && components[0] == "00" { + // Remove the initial "00:" + return "\(components[1]):\(components[2])" + } else { + // Return the original time string + return self + } + } +} + diff --git a/WOKA/Theme/Base.lproj/Theme.storyboard b/WOKA/Theme/Base.lproj/Theme.storyboard index 20228a3..d9f4711 100644 --- a/WOKA/Theme/Base.lproj/Theme.storyboard +++ b/WOKA/Theme/Base.lproj/Theme.storyboard @@ -757,7 +757,7 @@ - + diff --git a/WOKA/Theme/Controller/MoreVC.swift b/WOKA/Theme/Controller/MoreVC.swift index 8b81fba..1f4c121 100644 --- a/WOKA/Theme/Controller/MoreVC.swift +++ b/WOKA/Theme/Controller/MoreVC.swift @@ -16,14 +16,16 @@ class MoreVC: UIViewController { @IBOutlet weak var songTableView: UITableView! var vm = MoreVM() - + override func viewDidLoad() { super.viewDidLoad() vm.vc = self vm.initView() - } - + + override func viewWillDisappear(_ animated: Bool) { + vm.player.pause() + } } // MARK: - TableView DataSource , Delegates @@ -36,17 +38,104 @@ extension MoreVC : TableViewSRC{ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: K.CellIdentifier.Home.songListCell) as! SongListCell let data = vm.songData[indexPath.row] - cell.setData(data: data) + cell.setData(data: data,playerStatus: vm.playerStatus) +// if let playingIndex = vm.currentIndexPlayingSong { +// if indexPath.row == playingIndex{ +// if vm.onGoingTime != nil{ // if index is nil that means its just paused +// cell.setData(data: data,playerStatus: vm.playerStatus,onGoingTime: vm.onGoingTime) +// }else{ // if index is there it means song is playing and needs to be paused +// cell.setData(data: data,playerStatus: .resume,onGoingTime: vm.onGoingTime) +// } +// }else{ +// cell.setData(data: data,playerStatus: vm.playerStatus,onGoingTime: nil) +// } +// }else{ +// cell.setData(data: data,playerStatus: .stopped ,onGoingTime: nil) +// } return cell } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let data = vm.songData[indexPath.row] - if let url = data.contentMoreDetails?.first?.url{ - vm.player = AVPlayer() - vm.playSong(urlString: url) + func getFormattedTime(from player: AVPlayer) -> String { + let currentTime = player.currentTime() + let seconds = CMTimeGetSeconds(currentTime) + + if !seconds.isFinite { + return "00:00:00" } + let hours = Int(seconds) / 3600 + let minutes = (Int(seconds) % 3600) / 60 + let secs = Int(seconds) % 60 + + return String(format: "%02d:%02d:%02d", hours, minutes, secs) + } + + func handleTap(indexPath : IndexPath){ + + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cell = tableView.cellForRow(at: indexPath) else { return } + addAnimation(cell: cell) { [weak self] in + guard let self else{return} + /* + this will declare if the song is playing or not + */ + if let playingIndex = vm.currentIndexPlayingSong { + if indexPath.row == playingIndex{ // if same row is selected pause the row + print(vm.playerStatus) + switch vm.playerStatus{ + case .play: + print("Player is playing") + vm.player.pause() + case .pause: + print("Player is pause") + case .loading: + print("Player is loading") + case .resume: + print("Player is resume") + default: + break + } + }else{ + /* + this means other cell was playing now stop it and play this new cell + first reload the playing cell and pause the audio + */ + + print("Other cell playing") + } + }else{ + // if there is no playing audio before + vm.playerStatus = .play + vm.currentIndexPlayingSong = indexPath.row + tableView.reloadRows(at: [indexPath], with: .none) + let data = vm.songData[indexPath.row] + playSong(url: data.contentMoreDetails?.first?.url) + print("First Play") + } + } + + } + + func addAnimation(cell : UITableViewCell, completion : @escaping () -> Void){ + UIView.animate(withDuration: 0.1,animations: { + cell.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) + }, + completion: { _ in + UIView.animate(withDuration: 0.1) { + cell.transform = CGAffineTransform.identity + completion() + } + }) + } + + func playSong(url : String?){ + if let url { + vm.player = AVPlayer(url: URL(string: url)!) + vm.observePlayer() + vm.player.play() + } } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { @@ -95,8 +184,48 @@ extension MoreVC : UICollectionViewDelegateFlowLayout{ let widthPerItem = availableWidth / itemsPerRow return CGSize(width: widthPerItem, height: widthPerItem - 20) } - -// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { -// return UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5) -// } +} + + +class VideoAVPlayer { + + var player: AVPlayer? + var playerObserver: NSKeyValueObservation? + var status = PlayerStatus.loading + + init(url: URL) { + self.player = AVPlayer(url: url) + observePlayer() + } + + private func observePlayer() { + playerObserver = player?.observe(\.timeControlStatus, options: [.new, .old], changeHandler: { [weak self] player, change in + guard let self = self else { return } + switch player.timeControlStatus { + case .playing: + print("Player is playing") + self.status = .play + case .paused: + print("Player is paused") + self.status = .pause + case .waitingToPlayAtSpecifiedRate: + print("Player is waiting to play at specified rate") + self.status = .loading + @unknown default: + print("Unknown player status") + } + }) + } + + deinit { + playerObserver?.invalidate() + } + + func play() { + player?.play() + } + + func pause() { + player?.pause() + } } diff --git a/WOKA/Theme/View/SongListCell.swift b/WOKA/Theme/View/SongListCell.swift index d7b0a8f..74f3b79 100644 --- a/WOKA/Theme/View/SongListCell.swift +++ b/WOKA/Theme/View/SongListCell.swift @@ -2,7 +2,7 @@ // SongListCell.swift // WOKA // -// Created by MacBook Pro on 10/06/24. +// Created by Bilal Khan on 10/06/24. // import UIKit @@ -12,22 +12,129 @@ class SongListCell: UITableViewCell { @IBOutlet weak var ongoingTimeLabel: UILabel! @IBOutlet weak var totalTimeLabel: UILabel! @IBOutlet weak var songTitle: UILabel! + @IBOutlet weak var outerStack: UIStackView! + @IBOutlet weak var playPauseImage: UIImageView! + @IBOutlet weak var indicatorView: UIView! + +// typealias btnTappedBlock = (String?) -> Void +// var btnTapped : btnTappedBlock! + + var timer: Timer? + private var currentTime: Int = 0 + var totalTime: Int = 0 override func awakeFromNib() { super.awakeFromNib() - // Initialization code + +// self.addTapGesture { [weak self] in +// guard let self else{return} +// UIView.animate(withDuration: 0.1, animations: { +// self.outerStack.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) +// }) { _ in +// UIView.animate(withDuration: 0.1) { +// self.outerStack.transform = .identity +// self.btnTapped(self.ongoingTimeLabel.text) +// } +// } +// } } - + override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) - // Configure the view for the selected state } - func setData(data : SongBlogDM.PaintDatum){ - self.totalTimeLabel.text = "/" + (data.songDuration ?? "0:00") - self.ongoingTimeLabel.text = "0:00" - self.songTitle.text = data.title! + override func prepareForReuse() { + super.prepareForReuse() + // Invalidate the timer when the cell is reused + timer?.invalidate() + timer = nil + ongoingTimeLabel.text = "0:00" } + func setData(data: SongBlogDM.PaintDatum, playerStatus: PlayerStatus) { + ongoingTimeLabel.text = "00:00" + totalTime = 0 + currentTime = 0 + + + switch playerStatus{ + case .stopped: + playPauseImage.image = UIImage(systemName: "play.fill") + case .pause: + playPauseImage.image = UIImage(systemName: "play.fill") + case .play: + playPauseImage.image = UIImage(systemName: "pause.fill") + totalTime = data.songDuration?.timeStringToSeconds() ?? 0 + +// if let onGoingTime{ +// currentTime = onGoingTime.timeStringToSeconds() ?? 0 +// ongoingTimeLabel.text = onGoingTime +// }else{ +// currentTime = 0 +// } + startCountdown() + + case .resume: + playPauseImage.image = UIImage(systemName: "pause.fill") + totalTime = data.songDuration?.timeStringToSeconds() ?? 0 +// if let onGoingTime{ +// currentTime = onGoingTime.timeStringToSeconds() ?? 0 +// ongoingTimeLabel.text = onGoingTime +// }else{ +// currentTime = 0 +// } + startCountdown() + case .loading: + break + } + + // Set total time label and song title + if let totalDuration = data.songDuration{ + //Check of the hour is zero removing it + let totalTimeFiltered = totalDuration.checkHourZero() + totalTimeLabel.text = "/" + totalTimeFiltered + }else{ + totalTimeLabel.text = "/" + (data.songDuration ?? "00:00") + } + + songTitle.text = data.title ?? "Unknown Title" + } + + private func startCountdown() { + // Invalidate any existing timer + timer?.invalidate() + + // Schedule a new timer + timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true) + } + + @objc private func updateCountdown() { + // Update the timer label + if currentTime < totalTime { + currentTime += 1 + ongoingTimeLabel.text = formatTime(currentTime) + } else { + timer?.invalidate() + timer = nil + } + } +// @objc private func updateCountdown() { +// // Update countdown label +// if totalTime > 0 { +// totalTime -= 1 +// ongoingTimeLabel.text = formatTime(totalTime) +// } else { +// timer?.invalidate() +// timer = nil +// ongoingTimeLabel.text = "0:00" +// } +// } + + private func formatTime(_ seconds: Int) -> String { + // Format time as MM:SS + let minutes = seconds / 60 + let seconds = seconds % 60 + return String(format: "%02d:%02d", minutes, seconds) + } } diff --git a/WOKA/Theme/View/SongListCell.xib b/WOKA/Theme/View/SongListCell.xib index ccde6b9..ead649d 100644 --- a/WOKA/Theme/View/SongListCell.xib +++ b/WOKA/Theme/View/SongListCell.xib @@ -28,13 +28,25 @@ - - - - - - - + + + + + + + + + + + + + - - + + -