Skip to content

Commit

Permalink
Update 05-HigherOrderReducers CaseStudies (#3342)
Browse files Browse the repository at this point in the history
* Update 05-HigherOrderReducers CaseStudies

* Replace alert(store:) to alert(_:)

* View clean up.

* Fixed alert.

* Update DownloadComponent.swift

---------

Co-authored-by: Brandon Williams <mbrandonw@hey.com>
Co-authored-by: Stephen Celis <stephen.celis@gmail.com>
  • Loading branch information
3 people committed Sep 4, 2024
1 parent c448177 commit 5267e5b
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import SwiftUI

@Reducer
struct DownloadComponent {
@ObservableState
struct State: Equatable {
@PresentationState var alert: AlertState<Action.Alert>?
@Presents var alert: AlertState<Action.Alert>?
let id: AnyHashable
var mode: Mode
var mode: Mode = .notDownloaded
let url: URL
}

Expand Down Expand Up @@ -134,36 +135,49 @@ enum Mode: Equatable {
}

struct DownloadComponentView: View {
let store: StoreOf<DownloadComponent>
@State var isVisible = false
@Bindable var store: StoreOf<DownloadComponent>

var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
Button {
viewStore.send(.buttonTapped)
} label: {
if viewStore.mode == .downloaded {
Image(systemName: "checkmark.circle")
.tint(.accentColor)
} else if viewStore.mode.progress > 0 {
ZStack {
CircularProgressView(value: viewStore.mode.progress)
.frame(width: 16, height: 16)
Rectangle()
.frame(width: 6, height: 6)
}
} else if viewStore.mode == .notDownloaded {
Image(systemName: "icloud.and.arrow.down")
} else if viewStore.mode == .startingToDownload {
ZStack {
ProgressView()
Rectangle()
.frame(width: 6, height: 6)
}
VStack {
if isVisible {
button
.alert($store.scope(state: \.alert, action: \.alert))
} else {
button
}
}
.onAppear { isVisible = true }
.onDisappear { isVisible = false }
}

@MainActor
private var button: some View {
Button {
store.send(.buttonTapped)
} label: {
if store.mode == .downloaded {
Image(systemName: "checkmark.circle")
.tint(.accentColor)
} else if store.mode.progress > 0 {
ZStack {
CircularProgressView(value: store.mode.progress)
.frame(width: 16, height: 16)
Rectangle()
.frame(width: 6, height: 6)
}
} else if store.mode == .notDownloaded {
Image(systemName: "icloud.and.arrow.down")
} else if store.mode == .startingToDownload {
ZStack {
ProgressView()
Rectangle()
.frame(width: 6, height: 6)
}
}
.foregroundStyle(.primary)
.alert(store: self.store.scope(state: \.$alert, action: \.alert))
}
.buttonStyle(.borderless)
.foregroundStyle(.primary)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,21 @@ private let readMe = """

@Reducer
struct CityMap {
@ObservableState
struct State: Equatable, Identifiable {
var download: Download
var downloadAlert: AlertState<DownloadComponent.Action.Alert>?
var downloadMode: Mode
var downloadComponent: DownloadComponent.State

init(download: Download) {
self.download = download
self.downloadComponent = DownloadComponent.State(
id: download.id,
url: download.downloadVideoUrl
)
}

var id: UUID { download.id }

var downloadComponent: DownloadComponent.State {
get {
DownloadComponent.State(
alert: downloadAlert,
id: download.id,
mode: downloadMode,
url: download.downloadVideoUrl
)
}
set {
downloadAlert = newValue.alert
downloadMode = newValue.mode
}
}

struct Download: Equatable, Identifiable {
var blurb: String
var downloadVideoUrl: URL
Expand Down Expand Up @@ -80,24 +73,16 @@ struct CityMapRowView: View {
let store: StoreOf<CityMap>

var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
NavigationLink(
destination: CityMapDetailView(store: store)
) {
HStack {
NavigationLink(
destination: CityMapDetailView(store: store)
) {
HStack {
Image(systemName: "map")
Text(viewStore.download.title)
}
.layoutPriority(1)

Spacer()

DownloadComponentView(
store: store.scope(state: \.downloadComponent, action: \.downloadComponent)
)
.padding(.trailing, 8)
}
Image(systemName: "map")
Text(store.download.title)
Spacer()
DownloadComponentView(
store: store.scope(state: \.downloadComponent, action: \.downloadComponent)
)
}
}
}
Expand All @@ -107,36 +92,35 @@ struct CityMapDetailView: View {
let store: StoreOf<CityMap>

var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack(spacing: 32) {
Text(viewStore.download.blurb)

HStack {
if viewStore.downloadMode == .notDownloaded {
Text("Download for offline viewing")
} else if viewStore.downloadMode == .downloaded {
Text("Downloaded")
} else {
Text("Downloading \(Int(100 * viewStore.downloadComponent.mode.progress))%")
}

Spacer()
Form {
Text(store.download.blurb)

DownloadComponentView(
store: store.scope(state: \.downloadComponent, action: \.downloadComponent)
)
HStack {
switch store.downloadComponent.mode {
case .notDownloaded:
Text("Download for offline viewing")
case .downloaded:
Text("Downloaded")
case .downloading(progress: let progress):
Text("Downloading \(Int(100 * progress))%")
case .startingToDownload:
Text("Downloading…")
}

Spacer()

DownloadComponentView(
store: store.scope(state: \.downloadComponent, action: \.downloadComponent)
)
}
.navigationTitle(viewStore.download.title)
.padding()
}
.navigationTitle(store.download.title)
}
}

@Reducer
struct MapApp {
@ObservableState
struct State: Equatable {
var cityMaps: IdentifiedArrayOf<CityMap.State> = .mocks
}
Expand All @@ -160,9 +144,8 @@ struct CitiesView: View {
Section {
AboutView(readMe: readMe)
}
ForEachStore(store.scope(state: \.cityMaps, action: \.cityMaps)) { cityMapStore in
ForEach(store.scope(state: \.cityMaps, action: \.cityMaps)) { cityMapStore in
CityMapRowView(store: cityMapStore)
.buttonStyle(.borderless)
}
}
.navigationTitle("Offline Downloads")
Expand All @@ -182,8 +165,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State
downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!,
id: UUID(),
title: "New York, NY"
),
downloadMode: .notDownloaded
)
),
CityMap.State(
download: CityMap.State.Download(
Expand All @@ -198,8 +180,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State
downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!,
id: UUID(),
title: "Los Angeles, LA"
),
downloadMode: .notDownloaded
)
),
CityMap.State(
download: CityMap.State.Download(
Expand All @@ -212,8 +193,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State
downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!,
id: UUID(),
title: "Paris, France"
),
downloadMode: .notDownloaded
)
),
CityMap.State(
download: CityMap.State.Download(
Expand All @@ -227,8 +207,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State
downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!,
id: UUID(),
title: "Tokyo, Japan"
),
downloadMode: .notDownloaded
)
),
CityMap.State(
download: CityMap.State.Download(
Expand All @@ -243,8 +222,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State
downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!,
id: UUID(),
title: "Buenos Aires, Argentina"
),
downloadMode: .notDownloaded
)
),
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ private let readMe = """
favorite state and rendering an alert.
"""

@ObservableState
struct FavoritingState<ID: Hashable & Sendable>: Equatable {
@PresentationState var alert: AlertState<FavoritingAction.Alert>?
@Presents var alert: AlertState<FavoritingAction.Alert>?
let id: ID
var isFavorite: Bool
}
Expand Down Expand Up @@ -69,23 +70,22 @@ struct Favoriting<ID: Hashable & Sendable> {
}

struct FavoriteButton<ID: Hashable & Sendable>: View {
let store: Store<FavoritingState<ID>, FavoritingAction>
@Bindable var store: Store<FavoritingState<ID>, FavoritingAction>

var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
Button {
viewStore.send(.buttonTapped)
} label: {
Image(systemName: "heart")
.symbolVariant(viewStore.isFavorite ? .fill : .none)
}
.alert(store: self.store.scope(state: \.$alert, action: \.alert))
Button {
store.send(.buttonTapped)
} label: {
Image(systemName: "heart")
.symbolVariant(store.isFavorite ? .fill : .none)
}
.alert($store.scope(state: \.alert, action: \.alert))
}
}

@Reducer
struct Episode {
@ObservableState
struct State: Equatable, Identifiable {
var alert: AlertState<FavoritingAction.Alert>?
let id: UUID
Expand Down Expand Up @@ -115,20 +115,19 @@ struct EpisodeView: View {
let store: StoreOf<Episode>

var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
HStack(alignment: .firstTextBaseline) {
Text(viewStore.title)
HStack(alignment: .firstTextBaseline) {
Text(store.title)

Spacer()
Spacer()

FavoriteButton(store: self.store.scope(state: \.favorite, action: \.favorite))
}
FavoriteButton(store: store.scope(state: \.favorite, action: \.favorite))
}
}
}

@Reducer
struct Episodes {
@ObservableState
struct State: Equatable {
var episodes: IdentifiedArrayOf<Episode.State> = []
}
Expand Down Expand Up @@ -157,7 +156,8 @@ struct EpisodesView: View {
Section {
AboutView(readMe: readMe)
}
ForEachStore(self.store.scope(state: \.episodes, action: \.episodes)) { rowStore in

ForEach(store.scope(state: \.episodes, action: \.episodes)) { rowStore in
EpisodeView(store: rowStore)
}
.buttonStyle(.borderless)
Expand Down
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"location" : "https://github.com/swiftlang/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
Expand Down

0 comments on commit 5267e5b

Please sign in to comment.