Skip to content

Commit

Permalink
Merge pull request #707 from insidegui/rambo/fix-loading-with-hero
Browse files Browse the repository at this point in the history
Improved handling of schedule event hero
  • Loading branch information
insidegui authored May 30, 2024
2 parents 070ff33 + cc5aed6 commit 687cf68
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 56 deletions.
14 changes: 11 additions & 3 deletions WWDC/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,21 @@ final class AppCoordinator: Logging, Signposting {
// Wait for the data to be loaded to hide the loading spinner
// this avoids some jittery UI. Technically this could be changed to only watch
// the tab that will be visible during startup.
Publishers.CombineLatest(
Publishers.CombineLatest3(
self.videosController.listViewController.$hasPerformedInitialRowDisplay,
self.scheduleController.splitViewController.listViewController.$hasPerformedInitialRowDisplay
self.scheduleController.splitViewController.listViewController.$hasPerformedInitialRowDisplay,
self.scheduleController.$isShowingHeroView
)
.replaceErrorWithEmpty()
.drop {
$0.0 == false || $0.1 == false
/// The videos tab has content.
let videosAvailable = $0.0
/// The schedule tab has content.
let scheduleAvailable = $0.1
/// The schedule tab has an event hero landing screen.
let scheduleHeroAvailable = $0.2
/// We want to reveal the UI once the videos tab has content and the schedule tab has content, be it a schedule or a landing screen.
return videosAvailable == false || (scheduleAvailable == false && scheduleHeroAvailable == false)
}
.prefix(1) // Happens once then automatically completes
.receive(on: DispatchQueue.main)
Expand Down
85 changes: 42 additions & 43 deletions WWDC/EventHeroViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,49 +142,48 @@ public final class EventHeroViewController: NSViewController {
private lazy var cancellables: Set<AnyCancellable> = []

private func bindViews() {
let image = $hero.compactMap({ $0?.backgroundImage }).compactMap(URL.init)

image.driveUI { [weak self] imageUrl in
guard let self = self else { return }

self.imageDownloadOperation?.cancel()

self.imageDownloadOperation = ImageDownloadCenter.shared.downloadImage(from: imageUrl, thumbnailHeight: Constants.thumbnailHeight) { url, result in
guard url == imageUrl, result.original != nil else { return }

self.backgroundImageView.image = result.original
}
}.store(in: &cancellables)

let heroUnavailable = $hero.map({ $0 == nil })
heroUnavailable.replaceError(with: true).driveUI(\.isHidden, on: backgroundImageView).store(in: &cancellables)
heroUnavailable.toggled().replaceError(with: false).driveUI(\.isHidden, on: placeholderImageView).store(in: &cancellables)

$hero.map(\.?.title).replaceNil(with: "Schedule not available").replaceError(with: "Schedule not available").driveUI(\.stringValue, on: titleLabel).store(in: &cancellables)
$hero.map({ hero in
let unavailable = "The schedule is not currently available. Check back later."
guard let hero = hero else { return unavailable }
if hero.textComponents.isEmpty {
return hero.body
} else {
return hero.textComponents.joined(separator: "\n\n")
}
}).replaceError(with: "").driveUI(\.stringValue, on: bodyLabel).store(in: &cancellables)

$hero.compactMap({ $0?.titleColor }).driveUI { [weak self] colorHex in
guard let self = self else { return }
self.titleLabel.textColor = NSColor.fromHexString(hexString: colorHex)
}.store(in: &cancellables)

// Dim background when there's a lot of text to show
$hero.compactMap({ $0 }).map({ $0.textComponents.count > 2 }).driveUI { [weak self] largeText in
self?.backgroundImageView.alphaValue = 0.5
}.store(in: &cancellables)

$hero.compactMap({ $0?.bodyColor }).driveUI { [weak self] colorHex in
guard let self = self else { return }
self.bodyLabel.textColor = NSColor.fromHexString(hexString: colorHex)
}.store(in: &cancellables)
$hero
.drop(while: { $0 == nil })
.sink
{ [weak self] hero in
guard let self else { return }

/// A lack of event hero is handled by the app coordinator / schedule controller by hiding this view controller,
/// so there's no special handling required when the hero is not available.
guard let hero else { return }

setupBackground(with: hero)
setupText(with: hero)
}
.store(in: &cancellables)
}

private func setupBackground(with hero: EventHero) {
guard let imageUrl = URL(string: hero.backgroundImage) else { return }

placeholderImageView.isHidden = true

backgroundImageView.alphaValue = hero.textComponents.count > 1 ? 0.5 : 1
backgroundImageView.isHidden = false

imageDownloadOperation?.cancel()

imageDownloadOperation = ImageDownloadCenter.shared.downloadImage(from: imageUrl, thumbnailHeight: Constants.thumbnailHeight) { url, result in
guard url == imageUrl, result.original != nil else { return }

self.backgroundImageView.image = result.original
}
}

private func setupText(with hero: EventHero) {
titleLabel.stringValue = hero.title
if hero.textComponents.isEmpty {
bodyLabel.stringValue = hero.body
} else {
bodyLabel.stringValue = hero.textComponents.joined(separator: "\n\n")
}
titleLabel.textColor = hero.titleColor.flatMap { NSColor.fromHexString(hexString: $0) }
bodyLabel.textColor = hero.bodyColor.flatMap { NSColor.fromHexString(hexString: $0) }
}

}
2 changes: 1 addition & 1 deletion WWDC/Main.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "TeamID.xcconfig"

MARKETING_VERSION = 7.4.2
CURRENT_PROJECT_VERSION = 1040
CURRENT_PROJECT_VERSION = 1041

MACOSX_DEPLOYMENT_TARGET = 12.0

Expand Down
21 changes: 12 additions & 9 deletions WWDC/ScheduleContainerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,18 @@ final class ScheduleContainerViewController: WWDCWindowContentViewController {
}

private func bindViews() {
$isShowingHeroView.replaceError(with: false).driveUI(\.view.isHidden, on: splitViewController)
.store(in: &cancellables)

$isShowingHeroView.toggled().replaceError(with: true)
.driveUI(\.view.isHidden, on: heroController)
.store(in: &cancellables)

$isShowingHeroView.driveUI { [weak self] _ in
self?.view.needsUpdateConstraints = true
/// The debounce in here prevents a little UI flicker when the event hero is available.
$isShowingHeroView
.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
.removeDuplicates()
.sink
{ [weak self] isShowingHeroView in
guard let self else { return }

splitViewController.view.isHidden = isShowingHeroView
heroController.view.isHidden = !isShowingHeroView

view.needsUpdateConstraints = true
}
.store(in: &cancellables)
}
Expand Down

0 comments on commit 687cf68

Please sign in to comment.