Skip to content
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ on:

jobs:
build-and-test:
runs-on: macos-12
runs-on: macos-14

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4.2.2
- name: Build
run: swift build -v
- name: Run tests
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ on:
jobs:
update_documentation:
name: Update documentation
runs-on: macos-12
runs-on: macos-14
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Swift version
uses: swift-actions/setup-swift@v1
uses: swift-actions/setup-swift@v2.0.0
with:
swift-version: "5.7"
swift-version: "5.10"
- name: Generate documentation
uses: fwcd/swift-docc-action@v1
uses: fwcd/swift-docc-action@v1.0.2
with:
target: Boutique
output: ./docs
Expand Down
79 changes: 0 additions & 79 deletions .swiftpm/xcode/xcshareddata/xcschemes/Boutique.xcscheme

This file was deleted.

2 changes: 0 additions & 2 deletions Demo/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,6 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -410,7 +409,6 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Demo/Demo/App/App.BoutiqueDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import SwiftUI

@main
struct BoutiqueDemoApp: App {
@State private var appState = AppState()
@StateObject private var appState = AppState()

var body: some Scene {
WindowGroup {
ContentView()
.environment(appState)
.environmentObject(appState)
.onAppear(perform: {
// Saving the last time the app was opened to demonstrate how @StoredValue
// persists values. The next time you open the app it should print
Expand Down
9 changes: 1 addition & 8 deletions Demo/Demo/App/App.State.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import Boutique
import Foundation

@Observable
final class AppState {
@ObservationIgnored
final class AppState: ObservableObject {
@StoredValue(key: "funkyRedPandaModeEnabled")
var funkyRedPandaModeEnabled = false

@ObservationIgnored
@StoredValue(key: "fetchedRedPandas")
var fetchedRedPandas: [URL] = []

@ObservationIgnored
@StoredValue<Date?>(key: "lastAppLaunchTimestamp")
var lastAppLaunchTimestamp = nil
}
33 changes: 2 additions & 31 deletions Demo/Demo/App/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import SwiftUI

struct ContentView: View {
@Environment(AppState.self) private var appState

@StateObject private var carouselFocusController = ScrollFocusController<String>()
@State private var imagesController = ImagesController()
@StateObject private var imagesController = ImagesController()

var body: some View {
VStack(spacing: 0.0) {
FavoritesCarouselView()
.padding(.bottom, 8.0)
.environmentObject(carouselFocusController)
.environment(imagesController)
.environmentObject(imagesController)

Divider()

Expand All @@ -22,32 +20,5 @@ struct ContentView: View {
}
.padding(.horizontal, 16.0)
.background(Color.palette.background)
.onChange(of: self.appState.funkyRedPandaModeEnabled) { oldValue, newValue in
print("Funky red panda mode was \(oldValue) and now is \(newValue)")
}
.task({
await self.monitorImageStoreEvents()
})
}
}

private extension ContentView {
func monitorImageStoreEvents() async {
for await event in self.imagesController.$images.events {
switch event.operation {

case .initialized:
print("[Store Event: initial] Our Images Store has initialized")

case .loaded:
print("[Store Event: loaded] Our Images Store has loaded with images", event.items.map(\.url))

case .insert:
print("[Store Event: insert] Our Images Store inserted images", event.items.map(\.url))

case .remove:
print("[Store Event: remove] Our Images Store removed images", event.items.map(\.url))
}
}
}
}
43 changes: 17 additions & 26 deletions Demo/Demo/Components/FavoritesCarouselView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ import SwiftUI

/// A horizontally scrolling carousel that displays the red panda images a user has favorited.
struct FavoritesCarouselView: View {
@Environment(AppState.self) private var appState
@Environment(ImagesController.self) private var imagesController
@EnvironmentObject private var imagesController: ImagesController

@State private var animation: Animation? = nil
@State private var images: [RemoteImage] = []
@State private var itemsHaveLoaded = false

@EnvironmentObject private var appState: AppState

var body: some View {
VStack {
// Demonstrating how an EmptyStateView can work with Boutique by leveraging itemsHaveLoaded
Expand All @@ -74,19 +75,18 @@ struct FavoritesCarouselView: View {
EmptyView()
}
}
.onChange(of: self.imagesController.images, initial: true, {
self.images = self.imagesController.images.sorted(by: { $0.createdAt > $1.createdAt})
.onReceive(self.imagesController.$images.$items, perform: {
self.images = $0.sorted(by: { $0.createdAt > $1.createdAt})
})
.frame(height: 200.0)
.background(Color.palette.background)
.task({
do {
try await self.imagesController.$images.itemsHaveLoaded()
self.itemsHaveLoaded = true
} catch {
.onStoreDidLoad(
self.imagesController.$images,
update: $itemsHaveLoaded,
onError: { error in
print("Failed to load images", error)
}
})
)
}
}

Expand Down Expand Up @@ -116,18 +116,8 @@ private extension FavoritesCarouselView {

Button(action: {
Task {
try await imagesController.clearAllImages()
self.appState.$fetchedRedPandas.reset()
self.appState.$funkyRedPandaModeEnabled.toggle()
}
}, label: {
Image(systemName: "xmark.circle.fill")
.opacity(self.images.isEmpty ? 0.0 : 1.0)
.font(.title)
.foregroundColor(.red)
})

Button(action: {
self.appState.funkyRedPandaModeEnabled.toggle()
}, label: {
Image(systemName: "sun.max.circle.fill")
.opacity(self.images.isEmpty ? 0.0 : 1.0)
Expand All @@ -142,14 +132,15 @@ private extension FavoritesCarouselView {
})

Button(action: {
print("We've seen \(self.appState.fetchedRedPandas.count) red pandas")
print(self.appState.fetchedRedPandas)
Task {
try await imagesController.clearAllImages()
}
}, label: {
Text("\(self.appState.fetchedRedPandas.count)")
Image(systemName: "xmark.circle.fill")
.opacity(self.images.isEmpty ? 0.0 : 1.0)
.font(.title)
.monospacedDigit()
.foregroundColor(.red)
})

}
.padding(.top)
}
Expand Down
16 changes: 5 additions & 11 deletions Demo/Demo/Components/RedPandaCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import SwiftUI

/// A view that fetches a red panda image from the server and allows a user to favorite the red panda.
struct RedPandaCardView: View {
@Environment(AppState.self) private var appState
@EnvironmentObject private var focusController: ScrollFocusController<String>

@State private var imagesController = ImagesController()
@StateObject private var imagesController = ImagesController()

@State private var currentImage: RemoteImage?

@State private var requestInFlight = false

@EnvironmentObject private var appState: AppState

@State private var progress: CGFloat = 0.0

var body: some View {
Expand Down Expand Up @@ -119,15 +122,6 @@ private extension RedPandaCardView {

self.currentImage = nil // Assigning nil shows the progress spinner
self.currentImage = try await self.imagesController.fetchImage()

guard let url = self.currentImage?.url else { return }

if self.appState.fetchedRedPandas.contains(url) {
print("Fetched an already seen red panda from URL", url)
} else {
self.appState.$fetchedRedPandas.append(url)
print("Fetched a new red panda from URL", url)
}
}

var currentImageIsSaved: Bool {
Expand Down
4 changes: 1 addition & 3 deletions Demo/Demo/Images/ImagesController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import SwiftUI
// @StateObject private var imagesController = ImagesController(store: Store.imagesStore)

/// A controller that allows you to fetch images remotely, and save or delete them from a `Store`.
@Observable
final class ImagesController {
final class ImagesController: ObservableObject {
/// The `Store` that we'll be using to save images.
@ObservationIgnored
@Stored(in: .imagesStore) var images

/// Fetches `RemoteImage` from the API, providing the user with a red panda if the request succeeds.
Expand Down
3 changes: 1 addition & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// swift-tools-version: 5.10
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Boutique",
platforms: [
.iOS(.v17),
.macOS(.v14),
.iOS(.v13),
.macOS(.v11),
],
products: [
.library(
Expand Down
Loading