Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrating Decide into FoodTruck app #5

Draft
wants to merge 8 commits into
base: feature/using-keyed-state
Choose a base branch
from
Draft
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
7 changes: 6 additions & 1 deletion App/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The single entry point for the Food Truck app on iOS and macOS.

import SwiftUI
import FoodTruckKit
import Decide

/// The app's entry point.
///
Expand All @@ -18,7 +19,11 @@ struct FoodTruckApp: App {
@StateObject private var model = FoodTruckModel()
/// The in-app purchase store's state.
@StateObject private var accountStore = AccountStore()


init() {
ApplicationEnvironment.bootstrap(donuts: model.donuts)
}

/// The app's body function.
///
/// This app uses a [`WindowGroup`](https://developer.apple.com/documentation/swiftui/windowgroup) scene, which contains the root view of the app, ``ContentView``.
Expand Down
20 changes: 13 additions & 7 deletions App/Donut/DonutEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ The donut editor view.

import SwiftUI
import FoodTruckKit
import Decide

struct DonutEditor: View {
@Binding var donut: Donut

@Observe(\FoodTruckState.$selectedDonut) var id
@BindKeyed(\FoodTruckState.Data.$donut) var donuts

var donut: Donut {
donuts[id]
}

var body: some View {
ZStack {
#if os(macOS)
Expand Down Expand Up @@ -76,7 +82,7 @@ struct DonutEditor: View {
@ViewBuilder
var editorContent: some View {
Section("Donut") {
TextField("Name", text: $donut.name, prompt: Text("Donut Name"))
TextField("Name", text: donuts[id].name, prompt: Text("Donut Name"))
}

Section("Flavor Profile") {
Expand Down Expand Up @@ -108,14 +114,14 @@ struct DonutEditor: View {
}

Section("Ingredients") {
Picker("Dough", selection: $donut.dough) {
Picker("Dough", selection: donuts[id].dough) {
ForEach(Donut.Dough.all) { dough in
Text(dough.name)
.tag(dough)
}
}

Picker("Glaze", selection: $donut.glaze) {
Picker("Glaze", selection: donuts[id].glaze) {
Section {
Text("None")
.tag(nil as Donut.Glaze?)
Expand All @@ -126,7 +132,7 @@ struct DonutEditor: View {
}
}

Picker("Topping", selection: $donut.topping) {
Picker("Topping", selection: donuts[id].topping) {
Section {
Text("None")
.tag(nil as Donut.Topping?)
Expand Down Expand Up @@ -165,7 +171,7 @@ struct DonutEditor_Previews: PreviewProvider {
@State private var donut = Donut.preview

var body: some View {
DonutEditor(donut: $donut)
DonutEditor()
}
}

Expand Down
30 changes: 22 additions & 8 deletions App/Donut/DonutGallery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ The donut gallery view.

import SwiftUI
import FoodTruckKit
import Decide

struct DonutGallery: View {
@ObservedObject var model: FoodTruckModel

@Bind(\FoodTruckState.$selectedDonut) var selectedDonut
@Bind(\FoodTruckState.Index.$donut) var donutsIndex
@ObserveKeyed(\FoodTruckState.Data.$donut) var donutsData

@State private var layout = BrowserLayout.grid
@State private var sort = DonutSortOrder.popularity(.week)
Expand All @@ -19,8 +24,8 @@ struct DonutGallery: View {
@State private var selection = Set<Donut.ID>()
@State private var searchText = ""

var filteredDonuts: [Donut] {
model.donuts(sortedBy: sort).filter { $0.matches(searchText: searchText) }
var filteredDonuts: [Donut.ID] {
donutsIndex.filter { donutsData[$0].matches(searchText: searchText) }
}

var tableImageSize: Double {
Expand Down Expand Up @@ -61,10 +66,18 @@ struct DonutGallery: View {
.searchable(text: $searchText)
.navigationTitle("Donuts")
.navigationDestination(for: Donut.ID.self) { donutID in
DonutEditor(donut: model.donutBinding(id: donutID))
DonutEditor()
.onAppear() {
selectedDonut = donutID
}
}
.navigationDestination(for: String.self) { _ in
DonutEditor(donut: $model.newDonut)
DonutEditor()
.onAppear() {
let newID = donutsIndex.count
selectedDonut = newID
donutsIndex.append(newID)
}
}
}

Expand All @@ -78,13 +91,13 @@ struct DonutGallery: View {

var table: some View {
Table(filteredDonuts, selection: $selection) {
TableColumn("Name") { donut in
NavigationLink(value: donut.id) {
TableColumn("Name") { donutId in
NavigationLink(value: donutId) {
HStack {
DonutView(donut: donut)
DonutView(donut: donutsData[donutId])
.frame(width: tableImageSize, height: tableImageSize)

Text(donut.name)
Text(donutsData[donutId].name)
}
}
}
Expand Down Expand Up @@ -180,5 +193,6 @@ struct DonutBakery_Previews: PreviewProvider {
NavigationStack {
Preview()
}
.appEnvironment(.preview)
}
}
25 changes: 16 additions & 9 deletions App/Donut/DonutGalleryGrid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ The grid view used in the DonutGallery.

import SwiftUI
import FoodTruckKit
import Decide

struct DonutGalleryGrid: View {
var donuts: [Donut]
var donuts: [Donut.ID]
var width: Double

@ObserveKeyed(\FoodTruckState.Data.$donut) var donutsData

#if os(iOS)
@Environment(\.horizontalSizeClass) private var sizeClass
Expand Down Expand Up @@ -58,15 +61,15 @@ struct DonutGalleryGrid: View {

var body: some View {
LazyVGrid(columns: gridItems, spacing: 20) {
ForEach(donuts) { donut in
NavigationLink(value: donut.id) {
ForEach(donuts, id: \.self) { donutId in
NavigationLink(value: donutId) {
VStack {
DonutView(donut: donut)
DonutView(donut: donutsData[donutId])
.frame(width: thumbnailSize, height: thumbnailSize)

VStack {
let flavor = donut.flavors.mostPotentFlavor
Text(donut.name)
let flavor = donutsData[donutId].flavors.mostPotentFlavor
Text(donutsData[donutId].name)
HStack(spacing: 4) {
flavor.image
Text(flavor.name)
Expand All @@ -86,18 +89,22 @@ struct DonutGalleryGrid: View {

struct DonutGalleryGrid_Previews: PreviewProvider {
struct Preview: View {
@State private var donuts = Donut.all
@State private var donuts = Donut.all.map { $0.id }

var body: some View {
GeometryReader { geometryProxy in
ScrollView {
DonutGalleryGrid(donuts: donuts, width: geometryProxy.size.width)
DonutGalleryGrid(
donuts: donuts,
width: geometryProxy.size.width
)
}
}
}
}

static var previews: some View {
Preview()
.appEnvironment(.preview)
}
}
49 changes: 49 additions & 0 deletions App/General/ApplicationEnvironment+Preview.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// ApplicationEnvironment+Preview.swift
//
//
// Created by Anton Kolchunov on 21.08.23.
//

import Foundation
import FoodTruckKit
@testable import Decide

extension ApplicationEnvironment {
static var preview: ApplicationEnvironment {
bootstrap()
return `default`
}

static func bootstrap(donuts: [Donut] = Donut.all) {
bootstrap(donuts.map { $0.id }, for: \FoodTruckState.Index.$donut)
for donut in donuts {
bootstrap(donut, for: \FoodTruckState.Data.$donut, at: donut.id)
}
bootstrap(0, for: \FoodTruckState.$selectedDonut)
}

// TODO: Move to Decide framework
static func bootstrap<S: AtomicState, Value>(
_ newValue: Value,
for keyPath: KeyPath<S, Mutable<Value>>
) {
`default`.setValue(
newValue,
keyPath.appending(path: \.wrappedValue)
)
}

// TODO: Move to Decide framework
static func bootstrap<I:Hashable, S: KeyedState<I>, Value>(
_ newValue: Value,
for keyPath: KeyPath<S, Mutable<Value>>,
at identifier: I
) {
`default`.setValue(
newValue,
keyPath.appending(path: \.wrappedValue),
at: identifier)
}
}

13 changes: 13 additions & 0 deletions App/General/Int+Identifiable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Int+Identifiable.swift
// Food Truck
//
// Created by Anton Kolchunov on 21.08.23.
// Copyright © 2023 Apple. All rights reserved.
//

import Foundation

extension Int: Identifiable {
public var id: Int { self }
}
2 changes: 1 addition & 1 deletion App/Navigation/DetailColumn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct DetailColumn: View {
case .donuts:
DonutGallery(model: model)
case .donutEditor:
DonutEditor(donut: $model.newDonut)
DonutEditor()
case .topFive:
TopFiveDonutsView(model: model)
case .city(let id):
Expand Down
32 changes: 32 additions & 0 deletions App/Truck/FoodTruckState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// FoodTruckState.swift
// Food Truck
//
// Created by Anton Kolchunov on 21.08.23.
// Copyright © 2023 Apple. All rights reserved.
//

import Decide
import FoodTruckKit

final class FoodTruckState: AtomicState {
@Mutable @Property public var selectedDonut: Donut.ID = -1

final class Index: AtomicState {
@Mutable @Property public var donut = [Donut.ID]()
}

final class Data: KeyedState<Donut.ID> {
@Mutable @Property public var donut: Donut = Donut.newDonut
}
}

extension Donut {
static var newDonut = Donut(
id: Donut.all.count,
name: String(localized: "New Donut", comment: "New donut-placeholder name."),
dough: .plain,
glaze: .none,
topping: .none
)
}
Loading