diff --git a/iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/Contents.json b/iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/Contents.json new file mode 100644 index 0000000..97087d6 --- /dev/null +++ b/iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "path.pdf" + } + ], + "info" : { + "author" : "zeplin", + "version" : "1" + } +} \ No newline at end of file diff --git a/iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/path.pdf b/iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/path.pdf new file mode 100644 index 0000000..c2b242a Binary files /dev/null and b/iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/path.pdf differ diff --git a/iOS/Stormtrooper/Stormtrooper/Controllers/StreamViewController.swift b/iOS/Stormtrooper/Stormtrooper/Controllers/StreamViewController.swift index cb750f9..e666d84 100644 --- a/iOS/Stormtrooper/Stormtrooper/Controllers/StreamViewController.swift +++ b/iOS/Stormtrooper/Stormtrooper/Controllers/StreamViewController.swift @@ -398,6 +398,10 @@ class StreamViewController: UIViewController { self.headerArrowImageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi) }, completion: { complete in self.queueView.isHidden = false + if let currentVideoIndex = self.viewModel.currentVideoIndex { + let currentVideoIndexPath = IndexPath(row: currentVideoIndex, section: 0) + self.queueTableView.scrollToRow(at: currentVideoIndexPath, at: .top, animated: true) + } }) } else { @@ -545,7 +549,50 @@ class StreamViewController: UIViewController { } } } - + + fileprivate func setHighlightForVideo(at row: Int, highlighted: Bool) { + // update the previous video (i.e. whether to hide its separator) + let previousIndexPath = IndexPath(row: row-1, section: 0) + let previousVideoCell = queueTableView.cellForRow(at: previousIndexPath) as? VideoQueueTableViewCell + previousVideoCell?.isPreviousVideo = highlighted + + // update the current video (i.e. whether to highlight it) + let indexPath = IndexPath(row: row, section: 0) + let videoCell = queueTableView.cellForRow(at: indexPath) as? VideoQueueTableViewCell + videoCell?.isCurrentVideo = highlighted + } + + fileprivate func deleteVideo(at indexPath: IndexPath) { + guard let currentVideoIndex = viewModel.currentVideoIndex else { return } + let previousIndexPath = IndexPath(row: indexPath.row - 1, section: 0) + let nextIndexPath = IndexPath(row: indexPath.row + 1, section: 0) + + // deleted the previous video + if indexPath.row == currentVideoIndex - 1 { + let previousCell = queueTableView.cellForRow(at: previousIndexPath) + let previousVideoCell = previousCell as? VideoQueueTableViewCell + previousVideoCell?.isPreviousVideo = true + } + + // deleted the current video + if indexPath.row == currentVideoIndex { + guard let videoQueue = viewModel.videoQueue else { return } + setHighlightForVideo(at: nextIndexPath.row, highlighted: true) + viewModel.currentVideoIndex = nextIndexPath.row + let nextVideoId = videoQueue[nextIndexPath.row].id + playerView.cueVideo(byId: nextVideoId, startSeconds: 0, suggestedQuality: .default) + playerView.playVideo() + } + + // remove deleted video from queue and view model + queueTableView.beginUpdates() + queueTableView.deleteRows(at: [indexPath], with: .automatic) + viewModel.videoQueue?.remove(at: indexPath.row) + if currentVideoIndex > indexPath.row { + viewModel.currentVideoIndex = currentVideoIndex - 1 + } + queueTableView.endUpdates() + } } extension StreamViewController: StreamViewModelDelegate { @@ -643,13 +690,11 @@ extension StreamViewController: StreamViewModelDelegate { extension StreamViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if tableView.tag == chatTableTag { - return cellFor(chatTableView: tableView, at: indexPath) + switch tableView.tag { + case chatTableTag: return cellFor(chatTableView: tableView, at: indexPath) + case queueTableTag: return cellFor(queueTableView: tableView, at: indexPath) + default: return UITableViewCell() } - else if tableView.tag == queueTableTag { - return cellFor(queueTableView: tableView, at: indexPath) - } - return UITableViewCell() } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { @@ -664,70 +709,62 @@ extension StreamViewController: UITableViewDelegate, UITableViewDataSource { if isKeyboardShowing { visualEffectView.isHidden = false dismissView.isHidden = false - } - else { + } else { visualEffectView.isHidden = true dismissView.isHidden = true } } func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - if tableView.tag == chatTableTag { - return false - } else if tableView.tag == queueTableTag { - return indexPath.row != viewModel.currentVideoIndex - } else { - return false + switch tableView.tag { + case chatTableTag: return false + case queueTableTag: return indexPath.row != viewModel.currentVideoIndex + default: return false } } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete, indexPath.row != viewModel.currentVideoIndex { - tableView.beginUpdates() - viewModel.videoQueue?.remove(at: indexPath.row) - tableView.deleteRows(at: [indexPath], with: .automatic) - if viewModel.currentVideoIndex ?? 0 > indexPath.row { - viewModel.currentVideoIndex = (viewModel.currentVideoIndex ?? 0) - 1 - } - tableView.endUpdates() + switch editingStyle { + case .delete: deleteVideo(at: indexPath) + default: break } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if tableView.tag == chatTableTag { - return viewModel.messages.count //TODO: limit this to 50 or whatever performance allows - } - else if tableView.tag == queueTableTag { - return viewModel.videoQueue?.count ?? 0 + switch tableView.tag { + case chatTableTag: return viewModel.messages.count + case queueTableTag: return viewModel.videoQueue?.count ?? 0 + default: return 0 } - return 0 } func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - if tableView.tag == queueTableTag { - return true + switch tableView.tag { + case chatTableTag: return false + case queueTableTag: return true + default: return false } - return false } func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { - guard let currentVideoIndex = viewModel.currentVideoIndex, let video = viewModel.videoQueue?.remove(at: sourceIndexPath.row) else { - return + guard let currentVideoIndex = viewModel.currentVideoIndex, + let video = viewModel.videoQueue?.remove(at: sourceIndexPath.row) else { + return } viewModel.videoQueue?.insert(video, at: destinationIndexPath.row) + + // update the view model's current video index if sourceIndexPath.row == currentVideoIndex { + // moved the current video viewModel.currentVideoIndex = destinationIndexPath.row - } - else if destinationIndexPath.row == currentVideoIndex { + } else if destinationIndexPath.row == currentVideoIndex { + // moved a video to the current video's index viewModel.currentVideoIndex = destinationIndexPath.row + (sourceIndexPath.row > destinationIndexPath.row ? 1 : -1) - } - // left-to-right - else if sourceIndexPath.row < currentVideoIndex, currentVideoIndex < destinationIndexPath.row { + } else if sourceIndexPath.row < currentVideoIndex, currentVideoIndex < destinationIndexPath.row { + // moved a video from the left-side of the current video to the right-side viewModel.currentVideoIndex = currentVideoIndex - 1 - } - - // right-to-left - else if sourceIndexPath.row > currentVideoIndex, currentVideoIndex > destinationIndexPath.row { + } else if sourceIndexPath.row > currentVideoIndex, currentVideoIndex > destinationIndexPath.row { + // moved a video from the right-side of the current video to the left-side viewModel.currentVideoIndex = currentVideoIndex + 1 } } @@ -737,11 +774,15 @@ extension StreamViewController: UITableViewDelegate, UITableViewDataSource { } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if viewModel.currentVideoIndex != indexPath.row { - viewModel.currentVideoIndex = indexPath.row - playerView.cueVideo(byId: viewModel.videoQueue?[indexPath.row].id ?? "", startSeconds: 0, suggestedQuality: .default) - playerView.playVideo() + guard viewModel.currentVideoIndex != indexPath.row, + let currentVideoIndex = viewModel.currentVideoIndex else { + return } + setHighlightForVideo(at: currentVideoIndex, highlighted: false) + setHighlightForVideo(at: indexPath.row, highlighted: true) + viewModel.currentVideoIndex = indexPath.row + playerView.cueVideo(byId: viewModel.videoQueue?[indexPath.row].id ?? "", startSeconds: 0, suggestedQuality: .default) + playerView.playVideo() } private func cellFor(chatTableView tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { @@ -784,22 +825,22 @@ extension StreamViewController: UITableViewDelegate, UITableViewDataSource { } private func cellFor(queueTableView tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { - guard - let cell = tableView.dequeueReusableCell(withIdentifier: "queueCell") as? VideoQueueTableViewCell, - let video = viewModel.videoQueue?[indexPath.row] else { - return UITableViewCell() + guard let cell = tableView.dequeueReusableCell(withIdentifier: "queueCell") as? VideoQueueTableViewCell, + let video = viewModel.videoQueue?[indexPath.row], + let currentVideoIndex = viewModel.currentVideoIndex else { + return UITableViewCell() } - cell.titleLabel.text = video.title - cell.channelTitleLabel.text = video.channelTitle + + cell.title = video.title + cell.channel = video.channelTitle + cell.isPreviousVideo = (currentVideoIndex-1 == indexPath.row) + cell.isCurrentVideo = (currentVideoIndex == indexPath.row) + YouTubeDataManager.sharedInstance.getThumbnailForVideo(with: video.mediumThumbnailURL) {error, image in - if let image = image { - cell.thumbnailImageView.image = image - } - } - if indexPath.row == viewModel.currentVideoIndex { - cell.isSelected = true + guard let image = image else { return } + cell.thumbnail = image } - cell.selectionStyle = .none + return cell } } @@ -868,11 +909,8 @@ extension StreamViewController: YTPlayerViewDelegate { } break case .ended: - if viewModel.isHost, let queue = viewModel.videoQueue, var queueIndex = viewModel.currentVideoIndex { - queueIndex = queueIndex < queue.count - 1 ? queueIndex + 1 : 0 - playerView.cueVideo(byId: queue[queueIndex].id, startSeconds: 0, suggestedQuality: .default) - viewModel.currentVideoIndex = queueIndex - playerView.playVideo() + if viewModel.isHost { + playNextVideo() } break case .queued: @@ -884,6 +922,17 @@ extension StreamViewController: YTPlayerViewDelegate { } } + private func playNextVideo() { + guard let videoQueue = viewModel.videoQueue else { return } + guard let currentVideoIndex = viewModel.currentVideoIndex else { return } + let nextVideoIndex = currentVideoIndex < videoQueue.count-1 ? currentVideoIndex+1 : 0 + setHighlightForVideo(at: currentVideoIndex, highlighted: false) + setHighlightForVideo(at: nextVideoIndex, highlighted: true) + viewModel.currentVideoIndex = nextVideoIndex + let nextVideoId = videoQueue[nextVideoIndex].id + playerView.cueVideo(byId: nextVideoId, startSeconds: 0, suggestedQuality: .default) + playerView.playVideo() + } } extension StreamViewController: UITextFieldDelegate { diff --git a/iOS/Stormtrooper/Stormtrooper/Storyboards/Stream.storyboard b/iOS/Stormtrooper/Stormtrooper/Storyboards/Stream.storyboard index 65b06aa..48ffbe5 100644 --- a/iOS/Stormtrooper/Stormtrooper/Storyboards/Stream.storyboard +++ b/iOS/Stormtrooper/Stormtrooper/Storyboards/Stream.storyboard @@ -214,12 +214,13 @@ + -