Skip to content

Commit

Permalink
0.31.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dankinsoid committed Mar 18, 2024
1 parent 2bb5f38 commit 33cccec
Show file tree
Hide file tree
Showing 85 changed files with 611 additions and 2,574 deletions.
2 changes: 1 addition & 1 deletion .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
--disable sortDeclarations
--disable blankLinesAtStartOfScope
--disable opaqueGenericParameters
--enable redundanttype
--header ""
--enable redundanttype
--enable organizeDeclarations
--organizetypes markcategories
--extensionacl on-extension
Expand Down
16 changes: 4 additions & 12 deletions Examples/Search/Search.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
832055D92B94A0F000AEABBB /* SearchActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832055D82B94A0F000AEABBB /* SearchActions.swift */; };
832055DB2B94A11300AEABBB /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832055DA2B94A11300AEABBB /* Search.swift */; };
83E5F5EF2A90165500C772A0 /* VDStore in Frameworks */ = {isa = PBXBuildFile; productRef = 83E5F5EE2A90165500C772A0 /* VDStore */; };
CA66690B242547B000A639B3 /* WeatherClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA66690A242547B000A639B3 /* WeatherClient.swift */; };
CA86E49D24253C2500357AD9 /* SearchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA86E49C24253C2500357AD9 /* SearchApp.swift */; };
CA86E49F24253C2500357AD9 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA86E49E24253C2500357AD9 /* SearchView.swift */; };
CA86E49F24253C2500357AD9 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA86E49E24253C2500357AD9 /* Search.swift */; };
CA86E4A124253C2700357AD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CA86E4A024253C2700357AD9 /* Assets.xcassets */; };
CA86E4B224253C2700357AD9 /* SearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA86E4B124253C2700357AD9 /* SearchTests.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -51,12 +49,10 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
832055D82B94A0F000AEABBB /* SearchActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchActions.swift; sourceTree = "<group>"; };
832055DA2B94A11300AEABBB /* Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Search.swift; sourceTree = "<group>"; };
CA66690A242547B000A639B3 /* WeatherClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherClient.swift; sourceTree = "<group>"; };
CA86E49724253C2500357AD9 /* Search.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Search.app; sourceTree = BUILT_PRODUCTS_DIR; };
CA86E49C24253C2500357AD9 /* SearchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchApp.swift; sourceTree = "<group>"; };
CA86E49E24253C2500357AD9 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
CA86E49E24253C2500357AD9 /* Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Search.swift; sourceTree = "<group>"; };
CA86E4A024253C2700357AD9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
CA86E4AD24253C2700357AD9 /* SearchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SearchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
CA86E4B124253C2700357AD9 /* SearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -115,9 +111,7 @@
isa = PBXGroup;
children = (
CA86E49C24253C2500357AD9 /* SearchApp.swift */,
CA86E49E24253C2500357AD9 /* SearchView.swift */,
832055D82B94A0F000AEABBB /* SearchActions.swift */,
832055DA2B94A11300AEABBB /* Search.swift */,
CA86E49E24253C2500357AD9 /* Search.swift */,
CA66690A242547B000A639B3 /* WeatherClient.swift */,
CA86E4A024253C2700357AD9 /* Assets.xcassets */,
);
Expand Down Expand Up @@ -241,10 +235,8 @@
buildActionMask = 2147483647;
files = (
CA66690B242547B000A639B3 /* WeatherClient.swift in Sources */,
832055D92B94A0F000AEABBB /* SearchActions.swift in Sources */,
832055DB2B94A11300AEABBB /* Search.swift in Sources */,
CA86E49D24253C2500357AD9 /* SearchApp.swift in Sources */,
CA86E49F24253C2500357AD9 /* SearchView.swift in Sources */,
CA86E49F24253C2500357AD9 /* Search.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Binary file not shown.
177 changes: 175 additions & 2 deletions Examples/Search/Search/Search.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import Foundation
import SwiftUI
import VDStore

// MARK: - Search feature domain
private let readMe = """
This application demonstrates live-searching with the VDStore. As you type the \
events are debounced for 300ms, and when you stop typing an API request is made to load \
locations. Then tapping on a location will load weather.
"""

// MARK: - Search state

struct Search: Equatable {

Expand All @@ -23,3 +30,169 @@ struct Search: Equatable {
}
}
}

// MARK: - Search actions

@Actions
extension Store<Search> {

func searchQueryChanged(query: String) {
state.searchQuery = query
cancel(Self.searchQueryChangeDebounced)
guard query.isEmpty else { return }
state.results = []
state.weather = nil
}

@CancelInFlight
func searchQueryChangeDebounced() async {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC / 3)
guard !state.searchQuery.isEmpty, !Task.isCancelled else {
return
}
do {
let response = try await di.weatherClient.search(state.searchQuery)
try Task.checkCancellation()
state.results = response.results
} catch {
guard !Task.isCancelled, !(error is CancellationError) else { return }
state.results = []
}
}
}

@Actions
extension Store<Search> {

@CancelInFlight
func searchResultTapped(location: GeocodingSearch.Result) async {
state.resultForecastRequestInFlight = location
defer { state.resultForecastRequestInFlight = nil }
do {
let forecast = try await di.weatherClient.forecast(location)
state.weather = State.Weather(
id: location.id,
days: forecast.daily.time.indices.map {
State.Weather.Day(
date: forecast.daily.time[$0],
temperatureMax: forecast.daily.temperatureMax[$0],
temperatureMaxUnit: forecast.dailyUnits.temperatureMax,
temperatureMin: forecast.daily.temperatureMin[$0],
temperatureMinUnit: forecast.dailyUnits.temperatureMin
)
}
)
} catch {
state.weather = nil
}
}
}

// MARK: - Search feature view

struct SearchView: View {

@ViewStore var state = Search()

var body: some View {
NavigationStack {
VStack(alignment: .leading) {
Text(readMe)
.padding()

HStack {
Image(systemName: "magnifyingglass")
TextField(
"New York, San Francisco, ...",
text: Binding {
state.searchQuery
} set: { text in
$state.searchQueryChanged(query: text)
}
)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
.disableAutocorrection(true)
}
.padding(.horizontal, 16)

List {
ForEach(state.results) { location in
VStack(alignment: .leading) {
Button {
Task {
await $state.searchResultTapped(location: location)
}
} label: {
HStack {
Text(location.name)

if state.resultForecastRequestInFlight?.id == location.id {
ProgressView()
}
}
}

if location.id == state.weather?.id {
weatherView(locationWeather: state.weather)
}
}
}
}

Button("Weather API provided by Open-Meteo") {
UIApplication.shared.open(URL(string: "https://open-meteo.com/en")!)
}
.foregroundColor(.gray)
.padding(.all, 16)
}
.navigationTitle("Search")
}
.task(id: state.searchQuery) {
await $state.searchQueryChangeDebounced()
}
}

@ViewBuilder
func weatherView(locationWeather: Search.Weather?) -> some View {
if let locationWeather {
let days = locationWeather.days
.enumerated()
.map { idx, weather in formattedWeather(day: weather, isToday: idx == 0) }

VStack(alignment: .leading) {
ForEach(days, id: \.self) { day in
Text(day)
}
}
.padding(.leading, 16)
}
}
}

// MARK: - Private helpers

private func formattedWeather(day: Search.Weather.Day, isToday: Bool) -> String {
let date =
isToday
? "Today"
: dateFormatter.string(from: day.date).capitalized
let min = "\(day.temperatureMin)\(day.temperatureMinUnit)"
let max = "\(day.temperatureMax)\(day.temperatureMaxUnit)"

return "\(date), \(min)\(max)"
}

private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "EEEE"
return formatter
}()

// MARK: - SwiftUI previews

struct SearchView_Previews: PreviewProvider {
static var previews: some View {
SearchView()
}
}
55 changes: 0 additions & 55 deletions Examples/Search/Search/SearchActions.swift

This file was deleted.

Loading

0 comments on commit 33cccec

Please sign in to comment.