Skip to content

Commit

Permalink
FEAT: Add print stream to printer detail
Browse files Browse the repository at this point in the history
  • Loading branch information
josefdolezal committed May 9, 2017
1 parent 86dac0e commit ef4a35f
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 20 deletions.
16 changes: 15 additions & 1 deletion OctoPhone/Coordinators/DetailCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,22 @@ import UIKit

/// Printer detail flow controller
final class DetailCoordinator: TabCoordinator {

/// Identifier of printer to be opened
private let printerID: String

init(tabbarController: UITabBarController?, navigationController: UINavigationController?,
contextManager: ContextManagerType, provider: OctoPrintProvider, printerID: String) {

self.printerID = printerID

super.init(tabbarController: tabbarController, navigationController: navigationController,
contextManager: contextManager, provider: provider)
}

override func start() {
let viewModel = DetailViewModel(delegate: self, provider: provider)
let viewModel = DetailViewModel(delegate: self, provider: provider,
contextManager: contextManager, printerID: printerID)
let controller = DetailViewController(viewModel: viewModel)

controller.title = tr(.printerDetail)
Expand Down
9 changes: 7 additions & 2 deletions OctoPhone/Coordinators/OverviewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ final class OverviewCoordinator: ContextCoordinator {
/// Network connections provider
private let provider: OctoPrintProvider

/// The identifier of selected printer
private let printerID: String

init(navigationController: UINavigationController?, contextManager: ContextManagerType,
provider: OctoPrintProvider) {
provider: OctoPrintProvider, printerID: String) {

self.provider = provider
self.printerID = printerID

super.init(navigationController: navigationController, contextManager: contextManager)
}

Expand All @@ -33,7 +38,7 @@ final class OverviewCoordinator: ContextCoordinator {
let detailCoordinator = DetailCoordinator(tabbarController: tabbarController,
navigationController: detailNavigationController,
contextManager: contextManager,
provider: provider)
provider: provider, printerID: printerID)
let filesCoodinator = FilesCoordinator(tabbarController: tabbarController,
navigationController: filesNavigationController,
contextManager: contextManager,
Expand Down
10 changes: 4 additions & 6 deletions OctoPhone/Coordinators/PrinterListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ final class PrinterListCoordinator: ContextCoordinator {

// MARK: - Delegate for printer list controller flow
extension PrinterListCoordinator: PrinterListViewControllerDelegate {
func selectedPrinterProvider(provider: OctoPrintProvider) {
let coordinator = OverviewCoordinator(
navigationController: navigationController,
contextManager: contextManager,
provider: provider
)
func selectedPrinterProvider(provider: OctoPrintProvider, printerID: String) {
let coordinator = OverviewCoordinator(navigationController: navigationController,
contextManager: contextManager, provider: provider,
printerID: printerID)

coordinator.completed = { [weak self] in
self?.childCoordinators.removeLast()
Expand Down
8 changes: 8 additions & 0 deletions OctoPhone/Generated/Localizable.Generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ enum L10n {
case couldNotDeletePrintProfileFromPrinter
/// Could not load list of slicers.
case couldNotLoadListOfSlicers
/// Could not load printer settings
case couldNotLoadPrinter
/// Could not load printer stream.
case couldNotLoadPrinterStream
/// Could not save downloaded list of logs.
case couldNotSaveDownloadedListOfLogs
/// Could not save printer profiles.
Expand Down Expand Up @@ -393,6 +397,10 @@ extension L10n: CustomStringConvertible {
return L10n.tr(key: "Could not delete print profile from printer")
case .couldNotLoadListOfSlicers:
return L10n.tr(key: "Could not load list of slicers")
case .couldNotLoadPrinter:
return L10n.tr(key: "Could not load printer")
case .couldNotLoadPrinterStream:
return L10n.tr(key: "Could not load printer stream")
case .couldNotSaveDownloadedListOfLogs:
return L10n.tr(key: "Could not save downloaded list of logs")
case .couldNotSavePrinterProfiles:
Expand Down
1 change: 1 addition & 0 deletions OctoPhone/View Related/Detail/DetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class DetailViewController: BaseCollectionViewController {
collectionView.reactive.reloadData <~ viewModel.outputs.dataChanged
}

reactive.displayableError <~ viewModel.outputs.displayError
printJobButton.reactive.isEnabled <~ viewModel.outputs.jobCancellable
emptyView.reactive.isHidden <~ viewModel.outputs.contentIsAvailable

Expand Down
92 changes: 84 additions & 8 deletions OctoPhone/View Related/Detail/DetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ protocol DetailViewModelOutputs {

/// Streams which indicates when data should be reloaded
var dataChanged: SignalProducer<(), NoError> { get }

/// Stream of errors displayable to user
var displayError: SignalProducer<DisplayableError, NoError> { get }
}

// MARK: - Common public interface
Expand Down Expand Up @@ -125,36 +128,66 @@ final class DetailViewModel: DetailViewModelType, DetailViewModelInputs, DetailV

let dataChanged: SignalProducer<(), NoError>

let displayError: SignalProducer<DisplayableError, NoError>

// MARK: Private properties

let contentIsAvailableProperty = MutableProperty(false)
/// Current printer value
private let printerProperty = MutableProperty<Printer?>(nil)

/// Indicates whether the content is available, broadcasts its value
private let contentIsAvailableProperty = MutableProperty(false)

/// Current bed value
private let bedProperty = MutableProperty<Bed?>(nil)

/// Current job value
private let jobProperty = MutableProperty<Job?>(nil)

let bedProperty = MutableProperty<Bed?>(nil)
/// Current state value
private let stateProperty = MutableProperty<PrinterState?>(nil)

let jobProperty = MutableProperty<Job?>(nil)
/// Last error occured
private let displayErrorProperty = MutableProperty<DisplayableError?>(nil)

let stateProperty = MutableProperty<PrinterState?>(nil)
/// Stream image
private let imageProperty = MutableProperty<UIImage?>(nil)

/// Printer requests provider
private let provider: OctoPrintProvider

/// User content requests provider
private let staticProvider = StaticContentProvider()

/// Database connection manager
private let contextManager: ContextManagerType

/// Timer to download stream image
private var streamTimerDisposable: Disposable?

/// Detail flow delegate
private weak var delegate: DetailViewControllerDelegate?

// MARK: Initializers
// swiftlint:disable function_body_length
init(delegate: DetailViewControllerDelegate, provider: OctoPrintProvider,
contextManager: ContextManagerType, printerID: String) {

init(delegate: DetailViewControllerDelegate, provider: OctoPrintProvider) {
self.delegate = delegate
self.provider = provider
self.contextManager = contextManager

let streamTimer = timer(interval: DispatchTimeInterval.seconds(10), on: QueueScheduler.main)

let stateProducer = stateProperty.producer.skipNil()
let jobProducer = jobProperty.producer.skipNil()
let bedProducer = bedProperty.producer.skipNil()
let printerProducer = printerProperty.producer.skipNil()
let imageProducer = imageProperty.producer.skipNil()

self.displayError = displayErrorProperty.producer.skipNil()
self.contentIsAvailable = Property(capturing: contentIsAvailableProperty)
self.printerState = Property(initial: tr(.unknown), then: stateProducer.map({ $0.state }))
self.jobPreview = Property(value: FontAwesomeIcon.lightBulbIcon.image(ofSize: CGSize(width: 60, height: 60),
color: Colors.Pallete.greyHue3))
self.jobTitle = Property(initial: tr(.unknown),
then: jobProducer.map({ $0.fileName }).skipNil())
self.fileName = Property(initial: tr(.unknown),
Expand All @@ -169,8 +202,12 @@ final class DetailViewModel: DetailViewModelType, DetailViewModelInputs, DetailV
then: bedProducer.map({ $0.targetTemperature }).formatTemperature())
self.bedTemperatureOffset = Property(initial: tr(.unknown),
then: bedProducer.map({ $0.offsetTemperature }).formatTemperature())
self.jobPreview = Property(initial: FontAwesomeIcon.lightBulbIcon.image(ofSize: CGSize(width: 60, height: 60),
color: Colors.Pallete.greyHue3),
then: imageProducer)

let dataChanged = SignalProducer.combineLatest(stateProducer, jobProducer, bedProducer).ignoreValues()
let dataChanged = SignalProducer.combineLatest(stateProducer, jobProducer, bedProducer,
printerProperty.producer).ignoreValues()

let jobCancellable = SignalProducer.combineLatest(
jobProducer.map(DetailViewModel.validJob),
Expand All @@ -180,6 +217,22 @@ final class DetailViewModel: DetailViewModelType, DetailViewModelInputs, DetailV
self.jobCancellable = Property(initial: false, then: jobCancellable)
self.dataChanged = SignalProducer.merge([dataChanged, contentIsAvailable.producer.ignoreValues()])

printerProducer.map({ $0.streamUrl }).skipNil().flatMap(.latest) { url in
return self.staticProvider.request(.get(url))
}
.startWithResult { [weak self] result in
switch result {
case let .success(response): self?.imageProperty.value = UIImage(data: response.data)
case .failure: self?.displayErrorProperty.value = (tr(.anErrorOccured),
tr(.couldNotLoadPrinterStream))
}
}

self.streamTimerDisposable = streamTimer.startWithValues { [weak self] _ in
self?.printerProperty.value = self?.printerProperty.value
}

loadPrinter(with: printerID)
requestData()
}

Expand Down Expand Up @@ -209,6 +262,21 @@ final class DetailViewModel: DetailViewModelType, DetailViewModelInputs, DetailV

// MARK: Internal logic

/// Loads printer object from local storage by it's identifier
///
/// - Parameter printerID: Identifier of printer which will be loaded
private func loadPrinter(with printerID: String) {
self.contextManager.createObservableContext()
.fetch(Printer.self, forPrimaryKey: printerID)
.startWithResult { [weak self] result in
switch result {
case let .success(printer): self?.printerProperty.value = printer
case .failure: self?.displayErrorProperty.value = (tr(.anErrorOccured),
tr(.couldNotLoadPrinter))
}
}
}

/// Requests all needed data for printer detail screen,
/// chains PrinterState, CurrentJob and CurrentBedState requests
private func requestData() {
Expand Down Expand Up @@ -246,7 +314,15 @@ final class DetailViewModel: DetailViewModelType, DetailViewModelInputs, DetailV
}
}

/// Determines whether the job is valid
///
/// - Parameter job: Job to be validated
/// - Returns: True if job is valid, false otherwise
private static func validJob(_ job: Job) -> Bool {
return job.fileName != nil && job.fileSize.value != nil && job.printTimeLeft.value != nil
}

deinit {
streamTimerDisposable?.dispose()
}
}
3 changes: 2 additions & 1 deletion OctoPhone/View Related/Detail/View/JobPreviewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class JobPreviewCell: UICollectionViewCell, TypedCell {
let jobPreviewImage: UIImageView = {
let view = UIImageView()

view.contentMode = .center
view.contentMode = .scaleAspectFit
view.clipsToBounds = true

return view
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import ReactiveCocoa
/// Printer list flow delegate interface
protocol PrinterListViewControllerDelegate: class {
/// Call when requests provider is selected
func selectedPrinterProvider(provider: OctoPrintProvider)
func selectedPrinterProvider(provider: OctoPrintProvider, printerID: String)

/// Call when add printer button is tapped by user
func addPrinterButtonTapped()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ PrinterListViewModelOutputs {
let provider = OctoPrintProvider(baseURL: printer.url,
plugins: [tokenPlugin])

delegate?.selectedPrinterProvider(provider: provider)
delegate?.selectedPrinterProvider(provider: provider, printerID: printer.ID)
}

func addPrinterButtonTapped() {
Expand Down
2 changes: 2 additions & 0 deletions en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"Bed temperature offset" = "Bed temperature offset";
"Printer is currently not in operational state" = "Printer is currently not in operational state. If you want to connect printer, click the button below.";
"Connect printer" = "Connect.";
"Could not load printer stream" = "Could not load printer stream.";
"Could not load printer" = "Could not load printer settings";


// *
Expand Down

0 comments on commit ef4a35f

Please sign in to comment.