From 9294f6fcb45a64b6c90eaeb57aa478d73a404ae6 Mon Sep 17 00:00:00 2001 From: "Glenn R. Fisher" Date: Wed, 8 Feb 2017 15:07:19 -0600 Subject: [PATCH] Update design of video queue (#282) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change “UP NEXT” to “QUEUE” * Update VideoQueueTableViewCell * Add play icon to assets * Update cellFor(queueTableView:at:) * Set background color of content view * Change separator color for queue table view * Improve comments * Hide separator for current video * Change number property from Int? to String? * Make current video editable * Set selection style to none * Update highlighted color * Highlight background of current video when editing * Start counting videos from 1 instead of 0 * Swap highlighting when a new video is selected * Swap highlight when video ends * Update comments and formatting * Reset background color and separator when no longer the current video * Hide separator of previous video * Code cleanup * Update row numbering after moving a video * Reload rows to update video numbers * Remove row numbering for now * Scroll to current video when showing the queue * Add helper function to highlight/unhighlight * Set previous video when loading cells * Add support for deleting the current video * Code cleanup — use switch instead of if/else * Remove ability to delete the current video * Remove video cell numbers and play icon * Update guard statements --- .../playing.imageset/Contents.json | 12 ++ .../Assets.xcassets/playing.imageset/path.pdf | Bin 0 -> 3893 bytes .../Controllers/StreamViewController.swift | 179 +++++++++++------- .../Storyboards/Stream.storyboard | 3 +- .../Views/VideoQueueTableViewCell.swift | 69 +++++-- .../Views/VideoQueueTableViewCell.xib | 51 ++--- 6 files changed, 205 insertions(+), 109 deletions(-) create mode 100644 iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/Contents.json create mode 100644 iOS/Stormtrooper/Stormtrooper/Assets.xcassets/playing.imageset/path.pdf 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 0000000000000000000000000000000000000000..c2b242ab36baa81f830529b32df9460e88a9437e GIT binary patch literal 3893 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 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 @@ + -