Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 00721ff03a3007b0c070a96d5c58c16f92ab8027

COCOAPODS: 1.5.2
COCOAPODS: 1.7.0
12 changes: 6 additions & 6 deletions Stitcher/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
<true/>
<key>Spotify Client Id</key>
<string>${SPOTIFY_CLIENT_ID}</string>
<key>Spotify Consumer Secret</key>
<string>${SPOTIFY_CONSUMER_SECRET}</string>
<key>Spotify Client Secret</key>
<string>${SPOTIFY_CLIENT_SECRET}</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
Expand All @@ -47,17 +47,17 @@
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~iphone</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
Expand Down
2 changes: 1 addition & 1 deletion Stitcher/Resources/BuildConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ struct BuildConfig {

private enum Key: String {
case clientId = "Spotify Client Id"
case clientSecret = "Spotify Consumer Secret"
case clientSecret = "Spotify Client Secret"
}
}
8 changes: 8 additions & 0 deletions Stitcher/UI/Common/BaseTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ class BaseTableViewController<T>: UITableViewController, DZNEmptyDataSetSource,
DZNEmptyDataSetDelegate, BasePresenterDelegate where T: BasePresenter {

// MARK: - Properties
public var childView: KeyboardTableView! { return tableView as! KeyboardTableView }

override func loadView() {
tableView = KeyboardTableView(frame: UIScreen.main.bounds, style: .plain)
tableView.delegate = self
tableView.dataSource = self
view = tableView
}

let presenter: T

Expand Down
45 changes: 45 additions & 0 deletions Stitcher/UI/Common/KeyboardSearchBar.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// KeyboardSearchBar.swift
// Stitcher
//
// Created by Giulio Montagner on 22/06/2019.
// Copyright © 2019 Meaningless. All rights reserved.
//

import UIKit

class KeyboardSearchBar: UISearchBar {

var selectBelowDiscoverabilityTitle = "Select First Result"

override var keyCommands: [UIKeyCommand]? {
get {
var commands = super.keyCommands ?? []

commands.append(UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(downArrowPressed), discoverabilityTitle: selectBelowDiscoverabilityTitle))
return commands
}
}

@objc private func downArrowPressed() {
resignFirstResponder()
if(arrowKeyDelegate != nil) {
arrowKeyDelegate?.downArrowButtonPressed()
}
}

weak var arrowKeyDelegate: KeyboardSearchBarDelegate?
override weak var delegate: UISearchBarDelegate? {
didSet {
arrowKeyDelegate = delegate as? KeyboardSearchBarDelegate
}
}

}


protocol KeyboardSearchBarDelegate: UISearchBarDelegate {

func downArrowButtonPressed()

}
40 changes: 40 additions & 0 deletions Stitcher/UI/Common/KeyboardSearchController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// KeyboardSearchController.swift
// Stitcher
//
// Created by Giulio Montagner on 22/06/2019.
// Copyright © 2019 Meaningless. All rights reserved.
//
import UIKit

class KeyboardSearchController: UISearchController, KeyboardSearchBarDelegate {

var mySearchBar = KeyboardSearchBar()

override var searchBar: KeyboardSearchBar {
get{
mySearchBar.delegate = self
return mySearchBar;
}
}

weak var keyboardSearchControllerDelegate: KeyboardSearchControllerDelegate?
override weak var delegate: UISearchControllerDelegate? {
didSet {
keyboardSearchControllerDelegate = delegate as? KeyboardSearchControllerDelegate
}
}

func downArrowButtonPressed() {
if(keyboardSearchControllerDelegate != nil) {
keyboardSearchControllerDelegate?.didStartResultSelectionFromKeyboard(searchController: self)
}
}

}

protocol KeyboardSearchControllerDelegate: UISearchControllerDelegate {

func didStartResultSelectionFromKeyboard(searchController: UISearchController)

}
114 changes: 114 additions & 0 deletions Stitcher/UI/Common/KeyboardTableView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Douglas Hill, December 2018
// Made for https://douglashill.co/reading-app/
import UIKit

/// A table view that allows navigation and selection using a hardware keyboard.
/// Only supports a single section.
class KeyboardTableView: UITableView {
// These properties may be set or overridden to provide discoverability titles for key commands.
var selectAboveDiscoverabilityTitle: String?
var selectBelowDiscoverabilityTitle: String?
var selectTopDiscoverabilityTitle: String?
var selectBottomDiscoverabilityTitle: String?
var clearSelectionDiscoverabilityTitle: String?
var activateSelectionDiscoverabilityTitle: String?

override var canBecomeFirstResponder: Bool {
return true
}

override var keyCommands: [UIKeyCommand]? {
var commands = super.keyCommands ?? []

commands.append(UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectAbove), maybeDiscoverabilityTitle: selectAboveDiscoverabilityTitle))
commands.append(UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectBelow), maybeDiscoverabilityTitle: selectBelowDiscoverabilityTitle))
commands.append(UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: .command, action: #selector(selectTop), maybeDiscoverabilityTitle: selectTopDiscoverabilityTitle))
commands.append(UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: .command, action: #selector(selectBottom), maybeDiscoverabilityTitle: selectBottomDiscoverabilityTitle))

commands.append(UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(clearSelection), maybeDiscoverabilityTitle: clearSelectionDiscoverabilityTitle))

commands.append(UIKeyCommand(input: " ", modifierFlags: [], action: #selector(activateSelection)))
commands.append(UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(activateSelection), maybeDiscoverabilityTitle: activateSelectionDiscoverabilityTitle))

return commands
}

@objc func selectAbove() {
if let oldSelectedIndexPath = indexPathForSelectedRow {
selectRowAtIndex(oldSelectedIndexPath.row - 1)
} else {
selectBottom()
}
}

@objc func selectBelow() {
if let oldSelectedIndexPath = indexPathForSelectedRow {
selectRowAtIndex(oldSelectedIndexPath.row + 1)
} else {
selectTop()
}
}

@objc func selectTop() {
selectRowAtIndex(0)
}

@objc func selectBottom() {
selectRowAtIndex(numberOfRows(inSection: 0) - 1)
}

/// Tries to select and scroll to the row at the given index in section 0.
/// Does not require the index to be in bounds. Does nothing if out of bounds.
private func selectRowAtIndex(_ rowIndex: Int) {
guard rowIndex >= 0 && rowIndex < numberOfRows(inSection: 0) else {
return
}

let indexPath = IndexPath(row: rowIndex, section: 0)

switch cellVisibility(atIndexPath: indexPath) {
case .fullyVisible:
selectRow(at: indexPath, animated: false, scrollPosition: .none)
case .notFullyVisible(let scrollPosition):
// Looks better and feel more responsive if the selection updates without animation.
selectRow(at: indexPath, animated: false, scrollPosition: .none)
scrollToRow(at: indexPath, at: scrollPosition, animated: true)
flashScrollIndicators()
}
}

/// Whether a row is fully visible, or if not if it’s above or below the viewport.
enum CellVisibility { case fullyVisible; case notFullyVisible(ScrollPosition); }

/// Whether the given row is fully visible, or if not if it’s above or below the viewport.
private func cellVisibility(atIndexPath indexPath: IndexPath) -> CellVisibility {
let rowRect = rectForRow(at: indexPath)
if bounds.inset(by: adjustedContentInset).contains(rowRect) {
return .fullyVisible
}

let position: ScrollPosition = rowRect.midY < bounds.midY ? .top : .bottom
return .notFullyVisible(position)
}

@objc func clearSelection() {
selectRow(at: nil, animated: false, scrollPosition: .none)
}

@objc func activateSelection() {
guard let indexPathForSelectedRow = indexPathForSelectedRow else {
return
}
delegate?.tableView?(self, didSelectRowAt: indexPathForSelectedRow)
}
}

private extension UIKeyCommand {
convenience init(input: String, modifierFlags: UIKeyModifierFlags, action: Selector, maybeDiscoverabilityTitle: String?) {
if let discoverabilityTitle = maybeDiscoverabilityTitle {
self.init(input: input, modifierFlags: modifierFlags, action: action, discoverabilityTitle: discoverabilityTitle)
} else {
self.init(input: input, modifierFlags: modifierFlags, action: action)
}
}
}
30 changes: 28 additions & 2 deletions Stitcher/UI/Playlist/PlaylistViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import DZNEmptyDataSet
/**
The user interface for adding, re-ordering, and remove tacks in a playlist.
*/
final class PlaylistViewController: BaseTableViewController<PlaylistPresenter>, UISearchControllerDelegate, UISearchResultsUpdating {
final class PlaylistViewController: BaseTableViewController<PlaylistPresenter>, KeyboardSearchControllerDelegate, UISearchResultsUpdating {

// MARK: - Properties

Expand All @@ -26,12 +26,28 @@ final class PlaylistViewController: BaseTableViewController<PlaylistPresenter>,
return isSearchActive && !isSearchTextEmpty
}

var startSearchDiscoverabilityTitle = "Search Track"

override var keyCommands: [UIKeyCommand]? {
get {
var commands = super.keyCommands ?? []

commands.append(UIKeyCommand(input: "F", modifierFlags: [UIKeyModifierFlags.command], action: #selector(startSearch), discoverabilityTitle: startSearchDiscoverabilityTitle))
return commands
}
}


// MARK: - Lifecycle

override init(presenter: PlaylistPresenter) {
super.init(presenter: presenter)
presenter.playlistDelegate = self

childView.selectAboveDiscoverabilityTitle = "Select Search Result Above"
childView.selectBelowDiscoverabilityTitle = "Select Search Result Below"
childView.selectTopDiscoverabilityTitle = "Select First Search Result"
childView.selectBottomDiscoverabilityTitle = "Select Last Search Result"
}

required init?(coder aDecoder: NSCoder) {
Expand Down Expand Up @@ -218,7 +234,7 @@ final class PlaylistViewController: BaseTableViewController<PlaylistPresenter>,
navigationItem.hidesSearchBarWhenScrolling = false

// Setup search
let searchController = UISearchController(searchResultsController: nil)
let searchController = KeyboardSearchController(searchResultsController: nil)
searchController.delegate = self
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
Expand Down Expand Up @@ -293,6 +309,16 @@ final class PlaylistViewController: BaseTableViewController<PlaylistPresenter>,
isReorderControlHidden = presenter.tracksDataSource.items.count == 0
}

// MARK: - KEYBOARD TRIGGERS

@objc func startSearch() {
navigationItem.searchController?.searchBar.becomeFirstResponder()
}

func didStartResultSelectionFromKeyboard(searchController: UISearchController) {
childView.becomeFirstResponder()
childView.selectBelow()
}

// MARK: - Base Presenter Delegate

Expand Down
19 changes: 19 additions & 0 deletions Stitcher/UI/Playlists/PlaylistsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,27 @@ final class PlaylistsViewController: BaseTableViewController<PlaylistsPresenter>

private let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil)

var newPlaylistDiscoverabilityTitle = "New Playlist"

override var keyCommands: [UIKeyCommand]? {
get {
var commands = super.keyCommands ?? []

commands.append(UIKeyCommand(input: "N", modifierFlags: [UIKeyModifierFlags.command], action: #selector(newPlaylistTriggered), discoverabilityTitle: newPlaylistDiscoverabilityTitle))
return commands
}
}

// MARK: - Lifecycle

override init(presenter: PlaylistsPresenter) {
super.init(presenter: presenter)
presenter.playlistsDelegate = self

childView.selectAboveDiscoverabilityTitle = "Select Playlist Above"
childView.selectBelowDiscoverabilityTitle = "Select Playlist Below"
childView.selectTopDiscoverabilityTitle = "Select First Playlist"
childView.selectBottomDiscoverabilityTitle = "Select Last Playlist"
}

required init?(coder aDecoder: NSCoder) {
Expand Down Expand Up @@ -64,6 +79,10 @@ final class PlaylistsViewController: BaseTableViewController<PlaylistsPresenter>
openPlaylist(playlist: nil)
}

@objc func newPlaylistTriggered() {
openPlaylist(playlist: nil)
}

@objc
private func didRefreshTable(_ refreshControl: UIRefreshControl) {
presenter.playlistsDataSource.refresh()
Expand Down