Skip to content

Commit

Permalink
Merge pull request #71 from mergesort/3.0
Browse files Browse the repository at this point in the history
Boutique 3.0 beta 1
  • Loading branch information
mergesort authored Oct 31, 2024
2 parents 28e295c + cbd86f0 commit 110becf
Show file tree
Hide file tree
Showing 52 changed files with 1,454 additions and 1,430 deletions.
79 changes: 79 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/Boutique.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Boutique"
BuildableName = "Boutique"
BlueprintName = "Boutique"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BoutiqueTests"
BuildableName = "BoutiqueTests"
BlueprintName = "BoutiqueTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Boutique"
BuildableName = "Boutique"
BlueprintName = "Boutique"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
2 changes: 2 additions & 0 deletions Demo/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
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 @@ -409,6 +410,7 @@
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"originHash" : "70ca90eb3fbfec4a3d93a1da9661356805637a48d9b94fd45ff8771cc4dd7582",
"pins" : [
{
"identity" : "bodega",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mergesort/Bodega.git",
"state" : {
"revision" : "3e7c1c58ad9a46aa8551cebfe87770003cdaaaca",
"version" : "2.0.2"
"revision" : "bfd8871e9c2590d31b200e54c75428a71483afdf",
"version" : "2.1.3"
}
},
{
Expand Down Expand Up @@ -37,5 +38,5 @@
}
}
],
"version" : 2
"version" : 3
}
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 {
@StateObject private var appState = AppState()
@State private var appState = AppState()

var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appState)
.environment(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: 8 additions & 1 deletion Demo/Demo/App/App.State.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import Boutique
import Foundation

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

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

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

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

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

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

Divider()

Expand All @@ -20,5 +22,32 @@ 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))
}
}
}
}
32 changes: 20 additions & 12 deletions Demo/Demo/Components/FavoritesCarouselView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,13 @@ import SwiftUI

/// A horizontally scrolling carousel that displays the red panda images a user has favorited.
struct FavoritesCarouselView: View {
@EnvironmentObject private var imagesController: ImagesController
@Environment(AppState.self) private var appState
@Environment(ImagesController.self) private var 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 @@ -75,8 +74,8 @@ struct FavoritesCarouselView: View {
EmptyView()
}
}
.onReceive(self.imagesController.$images.$items, perform: {
self.images = $0.sorted(by: { $0.createdAt > $1.createdAt})
.onChange(of: self.imagesController.images, initial: true, {
self.images = self.imagesController.images.sorted(by: { $0.createdAt > $1.createdAt})
})
.frame(height: 200.0)
.background(Color.palette.background)
Expand Down Expand Up @@ -117,8 +116,18 @@ private extension FavoritesCarouselView {

Button(action: {
Task {
self.appState.$funkyRedPandaModeEnabled.toggle()
try await imagesController.clearAllImages()
self.appState.$fetchedRedPandas.reset()
}
}, 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 @@ -133,15 +142,14 @@ private extension FavoritesCarouselView {
})

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

}
.padding(.top)
}
Expand Down
16 changes: 11 additions & 5 deletions Demo/Demo/Components/RedPandaCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ 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>

@StateObject private var imagesController = ImagesController()

@State 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 @@ -122,6 +119,15 @@ 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: 3 additions & 1 deletion Demo/Demo/Images/ImagesController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ 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`.
final class ImagesController: ObservableObject {
@Observable
final class ImagesController {
/// 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
7 changes: 4 additions & 3 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"originHash" : "cc0a5555f8ada9d39eb7aa3ebf517983770c3fb59b40991749c3f025fce9ea00",
"pins" : [
{
"identity" : "bodega",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mergesort/Bodega.git",
"state" : {
"revision" : "f0554077c178088ba11557bbdbb71775cc6a1b84",
"version" : "2.1.0"
"revision" : "bfd8871e9c2590d31b200e54c75428a71483afdf",
"version" : "2.1.3"
}
},
{
Expand Down Expand Up @@ -37,5 +38,5 @@
}
}
],
"version" : 2
"version" : 3
}
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.6
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

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

0 comments on commit 110becf

Please sign in to comment.