Skip to content

Commit

Permalink
Store the dive site detail information (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
DevYeom authored Oct 20, 2024
1 parent 5f1d723 commit 89950ad
Show file tree
Hide file tree
Showing 28 changed files with 452 additions and 118 deletions.
8 changes: 7 additions & 1 deletion Sources/BadaApp/AppReducer+Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ extension AppReducer {
}
}
register {
GetLocalSearchResultsUseCase { searchText in
GetLocalSearchCompletionsUseCase { searchText in
let repository = LocalSearchRepository()
return await repository.search(text: searchText)
}
}
register {
GetLocalSearchResultUseCase { searchCompletion throws(LocalSearchRepositoryError) in
let repository = LocalSearchRepository()
return try await repository.search(for: searchCompletion)
}
}
}

private func register<T: ExecutableUseCase>(
Expand Down
24 changes: 24 additions & 0 deletions Sources/BadaApp/Logbook/LogbookAddReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ struct LogbookAddReducer: Reducer {
enum Action: Sendable {
case setLogNumber(Int?)
case setLogDate(Date)
case setDiveSite(LocalSearchResult)
case setDiveCenter(String)
case setDiveStyle(DiveStyle)
case setEntryTime(Date)
case setExitTime(Date)
Expand All @@ -33,6 +35,8 @@ struct LogbookAddReducer: Reducer {
struct State: Sendable, Equatable {
var logNumber: Int?
var logDate: Date = Date(timeIntervalSince1970: 0)
var diveSite: DiveSite?
var diveCenter: String = ""
var diveStyle: DiveStyle = .boat
var entryTime: Date = Date(timeIntervalSince1970: 0)
var exitTime: Date = Date(timeIntervalSince1970: 0)
Expand All @@ -59,6 +63,26 @@ struct LogbookAddReducer: Reducer {
case let .setLogDate(logDate):
state.logDate = logDate
return .none
case let .setDiveSite(searchResult):
if let coordinate = searchResult.coordinate {
state.diveSite = DiveSite(
title: searchResult.title,
subtitle: searchResult.subtitle,
coordinate: DiveSite.Coordinate(
latitude: coordinate.latitude,
longitude: coordinate.longitude
)
)
} else {
state.diveSite = DiveSite(
title: searchResult.title,
subtitle: searchResult.subtitle
)
}
return .none
case let .setDiveCenter(diveCenter):
state.diveCenter = diveCenter
return .none
case let .setDiveStyle(diveStyle):
state.diveStyle = diveStyle
return .none
Expand Down
37 changes: 25 additions & 12 deletions Sources/BadaApp/Logbook/LogbookAddSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct LogbookAddSheet: View {
NavigationStack {
Form {
Section {
LabeledTextField(
LabeledFormattedTextField(
value: store.binding(\.logNumber, send: { .setLogNumber($0) }),
format: .number,
prompt: "123",
Expand All @@ -36,12 +36,20 @@ struct LogbookAddSheet: View {
displayedComponents: .date
)
LabeledContent("Dive site") {
Text("Bohol")
Text(store.state.diveSite?.title ?? "search")
.foregroundStyle(store.state.diveSite == nil ? .tertiary : .secondary)
}
.contentShape(Rectangle())
.onTapGesture {
store.send(.setIsDiveSiteSearchSheetPresenting(true))
}
LabeledTextField(
value: store.binding(\.diveCenter, send: { .setDiveCenter($0) }),
prompt: "name",
label: "Dive center",
keyboardType: .default
)
.focused($focusedField, equals: .diveCenter)
Picker(
"Dive style",
selection: store.binding(\.diveStyle, send: { .setDiveStyle($0) })
Expand All @@ -62,7 +70,7 @@ struct LogbookAddSheet: View {
selection: store.binding(\.exitTime, send: { .setExitTime($0) }),
displayedComponents: .hourAndMinute
)
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.bottomTime?.rawValue },
set: { store.send(.setBottomTime($0)) }),
Expand All @@ -72,7 +80,7 @@ struct LogbookAddSheet: View {
keyboardType: .decimalPad
)
.focused($focusedField, equals: .bottomTime)
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.surfaceInterval?.rawValue },
set: { store.send(.setSurfaceInterval($0)) }),
Expand All @@ -84,7 +92,7 @@ struct LogbookAddSheet: View {
.focused($focusedField, equals: .surfaceInterval)
}
Section(header: Text("Air pressure")) {
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.entryAir?.rawValue },
set: { store.send(.setEntryAir($0)) }),
Expand All @@ -94,7 +102,7 @@ struct LogbookAddSheet: View {
keyboardType: .numberPad
)
.focused($focusedField, equals: .entryAir)
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.exitAir?.rawValue },
set: { store.send(.setExitAir($0)) }),
Expand All @@ -106,7 +114,7 @@ struct LogbookAddSheet: View {
.focused($focusedField, equals: .exitAir)
}
Section(header: Text("Depth")) {
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.maximumDepth?.rawValue },
set: { store.send(.setMaximumDepth($0)) }),
Expand All @@ -116,7 +124,7 @@ struct LogbookAddSheet: View {
keyboardType: .numberPad
)
.focused($focusedField, equals: .maximumDepth)
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.averageDepth?.rawValue },
set: { store.send(.setAverageDepth($0)) }),
Expand All @@ -128,7 +136,7 @@ struct LogbookAddSheet: View {
.focused($focusedField, equals: .averageDepth)
}
Section(header: Text("Temperature")) {
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.airTemperature?.rawValue },
set: { store.send(.setAirTemperature($0)) }),
Expand All @@ -138,7 +146,7 @@ struct LogbookAddSheet: View {
keyboardType: .numberPad
)
.focused($focusedField, equals: .maximumWaterTemperature)
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.surfaceTemperature?.rawValue },
set: { store.send(.setSurfaceTemperature($0)) }),
Expand All @@ -148,7 +156,7 @@ struct LogbookAddSheet: View {
keyboardType: .numberPad
)
.focused($focusedField, equals: .minimumWaterTemperature)
LabeledTextField(
LabeledFormattedTextField(
value: Binding(
get: { store.state.bottomTemperature?.rawValue },
set: { store.send(.setBottomTemperature($0)) }),
Expand Down Expand Up @@ -226,7 +234,7 @@ struct LogbookAddSheet: View {
get: { store.state.isDiveSiteSearchSheetPresenting },
set: { store.send(.setIsDiveSiteSearchSheetPresenting($0)) }
),
content: { LogbookDiveSiteSearchSheet() }
content: { LogbookDiveSiteSearchSheet(action: selectDiveSite) }
)
#if os(macOS)
.padding()
Expand Down Expand Up @@ -275,6 +283,10 @@ struct LogbookAddSheet: View {
dismiss()
}

private func selectDiveSite(_ searchResult: LocalSearchResult) {
store.send(.setDiveSite(searchResult))
}

private func tapDoneButton() {
focusedField = nil
}
Expand All @@ -291,6 +303,7 @@ struct LogbookAddSheet: View {
extension LogbookAddSheet {
private enum Field: Int, CaseIterable {
case logNumber = 0
case diveCenter
case bottomTime
case surfaceInterval
case entryAir
Expand Down
73 changes: 60 additions & 13 deletions Sources/BadaApp/Logbook/LogbookDiveSiteSearchReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,82 @@ import BadaDomain

struct LogbookDiveSiteSearchReducer: Reducer {
enum Action: Sendable {
case search(for: LocalSearchCompletion)
case setSearchResult(LocalSearchResult?)
case setSearchText(String)
case setSearchResults([LocalSearchResultItem])
case setSearchCompletions([LocalSearchCompletion])
case setIsSearching(Bool)
}

struct State: Sendable, Equatable {
var searchText: String = ""
var searchResults: [LocalSearchResultItem] = []
var searchCompletions: [LocalSearchCompletion] = []
var searchResult: LocalSearchResult?
var isSearching: Bool = false
}

@UseCase private var getLocalSearchResultsUseCase: GetLocalSearchResultsUseCase
@UseCase private var getLocalSearchCompletionsUseCase: GetLocalSearchCompletionsUseCase
@UseCase private var getLocalSearchResultUseCase: GetLocalSearchResultUseCase

enum DebounceID {
case searchText
}

func reduce(state: inout State, action: Action) -> AnyEffect<Action> {
switch action {
case let .search(searchCompletion):
return .concat(
.just(.setIsSearching(true)),
.single { await executeGetLocalSearchResultUseCase(for: searchCompletion) },
.just(.setIsSearching(false))
)
case let .setSearchResult(searchResult):
state.searchResult = searchResult
return .none
case let .setSearchText(searchText):
state.searchText = searchText
return .single {
let searchResults = await getLocalSearchResultsUseCase.execute(searchText: searchText)
.map { result in
LocalSearchResultItem(
title: result.title,
subtitle: result.subtitle
)
}
let searchCompletions = await getLocalSearchCompletionsUseCase.execute(for: searchText)
.sorted { $0.title < $1.title }
return .setSearchResults(searchResults)
return .setSearchCompletions(searchCompletions)
}
case let .setSearchResults(searchResults):
state.searchResults = searchResults
.debounce(id: DebounceID.searchText, for: .milliseconds(500))
case let .setSearchCompletions(searchCompletions):
if searchCompletions.isEmpty {
let manualCompletion = LocalSearchCompletion(
title: state.searchText,
subtitle: "No matching results",
rawValue: nil
)
state.searchCompletions = [manualCompletion]
} else {
state.searchCompletions = searchCompletions
}
return .none
case let .setIsSearching(isSearching):
state.isSearching = isSearching
return .none
}
}

private func executeGetLocalSearchResultUseCase(for searchCompletion: LocalSearchCompletion) async -> Action {
do {
let searchResult = try await getLocalSearchResultUseCase.execute(for: searchCompletion)
return .setSearchResult(searchResult)
} catch {
switch error {
case .invalidSearchCompletion:
let searchResult = LocalSearchResult(
title: searchCompletion.title,
subtitle: searchCompletion.subtitle,
coordinate: nil
)
return .setSearchResult(searchResult)
case .searchFailed,
.searchCompletionNotFound,
.mapItemNotFound:
return .setSearchResult(nil)
}
}
}
}
61 changes: 52 additions & 9 deletions Sources/BadaApp/Logbook/LogbookDiveSiteSearchSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,76 @@
//

import BadaCore
import BadaDomain
import BadaUI

struct LogbookDiveSiteSearchSheet: View {
let action: (LocalSearchResult) -> Void

@Environment(\.dismiss) private var dismiss
@StateObject private var store = ViewStore(
reducer: LogbookDiveSiteSearchReducer(),
state: LogbookDiveSiteSearchReducer.State()
)

var body: some View {
NavigationStack {
List(store.state.searchResults, id: \.self) { result in
VStack(alignment: .leading) {
Text(result.title)
.font(.headline)
.foregroundStyle(.primary)
if !result.subtitle.isEmpty {
Text(result.subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
List(Array(store.state.searchCompletions.enumerated()), id: \.offset) { index, searchCompletion in
Button(action: { tapItem(searchCompletion) }) {
VStack(alignment: .leading) {
Text(searchCompletion.title)
.font(.headline)
.foregroundStyle(Color.primary)
if !searchCompletion.subtitle.isEmpty {
Text(searchCompletion.subtitle)
.font(.subheadline)
.foregroundStyle(Color.secondary)
}
}
}
}
.navigationTitle("Dive site")
#if os(macOS)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
cancelButton
}
}
.frame(idealWidth: 400, idealHeight: 600)
#endif
.searchable(
text: store.binding(\.searchText, send: { .setSearchText($0) }),
prompt: Text("Search")
)
.disabled(store.state.isSearching)
.overlay {
if store.state.isSearching {
ProgressView()
.progressViewStyle(.circular)
.controlSize(.large)
}
}
.onChange(of: store.state.searchResult, searchResultChanged)
}
}

private var cancelButton: some View {
Button(action: tapCancelButton) {
Text("Cancel")
}
}

private func searchResultChanged() {
guard let searchResult = store.state.searchResult else { return }
action(searchResult)
dismiss()
}

private func tapItem(_ item: LocalSearchCompletion) {
store.send(.search(for: item))
}

private func tapCancelButton() {
dismiss()
}
}
Loading

0 comments on commit 89950ad

Please sign in to comment.