Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ struct TodoDetailView: View {
.onAppear { store.send(.onAppear) }
.navigationBarTitleDisplayMode(.inline)
.alert($store.scope(state: \.alert, action: \.alert))
.sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { sheetStore in
sheetContent(sheetStore)
.sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { store in
sheetContent(store)
}
.fullScreenCover(
item: $store.scope(state: \.fullScreenCover, action: \.fullScreenCover)
) { coverStore in
fullScreenCoverContent(coverStore)
) { store in
fullScreenCoverContent(store)
}
.toolbar { toolbarContent }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ struct TodoEditorView: View {
.navigationTitle(store.navigationTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.background, for: .navigationBar)
.sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { sheetStore in
sheetContent(sheetStore)
.sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { store in
sheetContent(store)
}
.toolbar {
if !isiOSAppOnMac {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ extension TodoListFeature {
let query = TodoQuery(categoryId: category.storageValue, keyword: keyword)
let page = try await fetchTodosUseCase.execute(query, cursor: nil)
try Task.checkCancellation()
await send(.fetchSearchResults(page.items.compactMap(TodoListItem.init(from:))))
await send(.store(.fetchSearchResults(page.items.compactMap(TodoListItem.init(from:)))))
await send(.loading(.end(target: .default, mode: .immediate)))
} catch is CancellationError {
return
} catch {
await send(.setAlert(true))
await send(.store(.setAlert(true)))
await send(.loading(.end(target: .default, mode: .immediate)))
}
}
Expand All @@ -47,14 +47,14 @@ extension TodoListFeature {
trackAnalyticsEventUseCase?.execute(.todoComplete)
}
guard let todoListItem = TodoListItem(from: todo) else {
await send(.setAlert(true))
await send(.store(.setAlert(true)))
await send(.loading(.end(target: .default, mode: .delayed)))
return
}
await send(.didToggleCompleted(todoListItem))
await send(.store(.didToggleCompleted(todoListItem)))
await send(.loading(.end(target: .default, mode: .delayed)))
} catch {
await send(.setAlert(true))
await send(.store(.setAlert(true)))
await send(.loading(.end(target: .default, mode: .delayed)))
}
}
Expand All @@ -71,14 +71,14 @@ extension TodoListFeature {
todo.updatedAt = Date()
try await upsertTodoUseCase.execute(todo)
guard let todoListItem = TodoListItem(from: todo) else {
await send(.setAlert(true))
await send(.store(.setAlert(true)))
await send(.loading(.end(target: .default, mode: .delayed)))
return
}
await send(.didTogglePinned(todoListItem))
await send(.store(.didTogglePinned(todoListItem)))
await send(.loading(.end(target: .default, mode: .delayed)))
} catch {
await send(.setAlert(true))
await send(.store(.setAlert(true)))
await send(.loading(.end(target: .default, mode: .delayed)))
}
}
Expand All @@ -88,7 +88,6 @@ extension TodoListFeature {
func swipeTodoEffect(_ todo: TodoListItem, state: inout State) -> Effect<Action> {
guard state.todos.contains(where: { $0.id == todo.id }) else { return .none }
state.undoTodoId = todo.id
state.deleteToastTodoId = todo.id
Self.setTodoHidden(&state, todoId: todo.id, isHidden: true)
return deleteEffect(todo)
}
Expand All @@ -98,8 +97,8 @@ extension TodoListFeature {
do {
try await deleteTodoUseCase.execute(item.id)
} catch {
await send(.setTodoHidden(item.id, false))
await send(.setAlert(true))
await send(.store(.setTodoHidden(item.id, false)))
await send(.store(.setAlert(true)))
}
}
}
Expand All @@ -109,8 +108,8 @@ extension TodoListFeature {
do {
try await undoDeleteTodoUseCase.execute(todoId)
} catch {
await send(.setTodoHidden(todoId, true))
await send(.setAlert(true))
await send(.store(.setTodoHidden(todoId, true)))
await send(.store(.setAlert(true)))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ struct TodoListFeature {
var loading = LoadingFeature.State()
var undoTodoId: String?
var nextCursor: TodoCursor?
var deleteToastTodoId: String?
let searchResultsLimit = 5

init(category: TodoCategory) {
Expand Down Expand Up @@ -65,26 +64,29 @@ struct TodoListFeature {
case fullScreenCover(PresentationAction<Never>)
case binding(BindingAction<State>)
case refresh
case setAlert(Bool)
case setFullScreenCover(FullScreenCoverState?)
case swipeTodo(TodoListItem)
case resetFilters
case finishDeleteToast(String)
case presentedDeleteToast
case tapToggleCompleted(TodoListItem)
case tapTogglePinned(TodoListItem)
case undoDelete
case onAppear
case loadNextPage
case applySearchQuery(String)
case fetchSearchResults([TodoListItem])
case didToggleCompleted(TodoListItem)
case didTogglePinned(TodoListItem)
case setTodoHidden(String, Bool)
case appendTodos([TodoListItem], nextCursor: TodoCursor?)
case resetPagination
case setHasMore(Bool)
case store(StoreAction)
case loading(LoadingFeature.Action)

enum StoreAction: Equatable {
case setAlert(Bool)
case applySearchQuery(String)
case fetchSearchResults([TodoListItem])
case didToggleCompleted(TodoListItem)
case didTogglePinned(TodoListItem)
case setTodoHidden(String, Bool)
case appendTodos([TodoListItem], nextCursor: TodoCursor?)
case resetPagination
case setHasMore(Bool)
}
}

enum CancelID: Hashable {
Expand Down Expand Up @@ -187,7 +189,7 @@ private extension TodoListFeature {
break
case .refresh, .onAppear:
return fetchEffect(query: state.query, cursor: nil)
case .setAlert(let value):
case .store(.setAlert(let value)):
Self.setAlert(&state, isPresented: value)
case .setFullScreenCover(let cover):
state.fullScreenCover = cover
Expand All @@ -203,8 +205,6 @@ private extension TodoListFeature {
if state.undoTodoId == todoId {
state.undoTodoId = nil
}
case .presentedDeleteToast:
state.deleteToastTodoId = nil
case .tapToggleCompleted(let todo):
return toggleCompletedEffect(todo)
case .tapTogglePinned(let todo):
Expand All @@ -217,24 +217,24 @@ private extension TodoListFeature {
case .loadNextPage:
guard state.hasMore, !state.isLoading else { return .none }
return fetchEffect(query: state.query, cursor: state.nextCursor, resetsPagination: false)
case .applySearchQuery(let query):
case .store(.applySearchQuery(let query)):
return applySearchQueryEffect(query, state: &state)
case .fetchSearchResults(let items):
case .store(.fetchSearchResults(let items)):
state.searchResults = items
case .didToggleCompleted(let todo), .didTogglePinned(let todo):
case .store(.didToggleCompleted(let todo)), .store(.didTogglePinned(let todo)):
if let index = state.todos.firstIndex(where: { $0.id == todo.id }) {
state.todos[index] = todo
}
case .setTodoHidden(let todoId, let isHidden):
case .store(.setTodoHidden(let todoId, let isHidden)):
Self.setTodoHidden(&state, todoId: todoId, isHidden: isHidden)
case .appendTodos(let todos, let nextCursor):
case .store(.appendTodos(let todos, let nextCursor)):
state.todos.append(contentsOf: todos)
state.nextCursor = nextCursor
case .resetPagination:
case .store(.resetPagination):
state.todos = []
state.nextCursor = nil
state.hasMore = false
case .setHasMore(let value):
case .store(.setHasMore(let value)):
state.hasMore = value
case .loading:
break
Expand All @@ -254,18 +254,18 @@ private extension TodoListFeature {
do {
let page = try await fetchTodosUseCase.execute(query, cursor: cursor)
if resetsPagination {
await send(.resetPagination)
await send(.store(.resetPagination))
}
await send(.appendTodos(
await send(.store(.appendTodos(
page.items.compactMap(TodoListItem.init(from:)),
nextCursor: page.nextCursor
))
await send(.setHasMore(page.items.count == query.pageSize && page.nextCursor != nil))
)))
await send(.store(.setHasMore(page.items.count == query.pageSize && page.nextCursor != nil)))
await send(.loading(.end(target: .default, mode: .delayed)))
} catch is CancellationError {
return
} catch {
await send(.setAlert(true))
await send(.store(.setAlert(true)))
await send(.loading(.end(target: .default, mode: .delayed)))
}
}
Expand Down Expand Up @@ -311,7 +311,7 @@ private extension TodoListFeature {
.send(.loading(.begin(target: .default, mode: .immediate))),
.run { [clock, searchDebounceDelay] send in
try await clock.sleep(for: searchDebounceDelay)
await send(.applySearchQuery(keyword))
await send(.store(.applySearchQuery(keyword)))
}
.cancellable(id: CancelID.debounce, cancelInFlight: true)
)
Expand Down
117 changes: 53 additions & 64 deletions Application/DevLogPresentation/Sources/Home/List/TodoListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ struct TodoListView: View {
.background(NavigationBarConfigurator())
.background(Color(.systemGroupedBackground))
.task { store.send(.onAppear) }
.onChange(of: store.deleteToastTodoId) { _, todoId in
guard let todoId else { return }
presentDeleteTodoToast(todoId)
store.send(.presentedDeleteToast)
}
}

@ViewBuilder
Expand Down Expand Up @@ -143,6 +138,7 @@ struct TodoListView: View {
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive, action: {
store.send(.swipeTodo(todo))
presentDeleteTodoToast(todo.id)
}) {
Image(systemName: "trash")
}
Expand Down Expand Up @@ -327,8 +323,58 @@ struct TodoListView: View {
}
}

sortMenu
filterMenu
Menu {
Picker(selection: $store.query.sortTarget) {
ForEach([TodoQuery.SortTarget.createdAt, .updatedAt], id: \.self) { option in
Text(option.title).tag(option)
}
} label: {
Text(String(localized: "todo_list_sort_by"))
}
Picker(selection: $store.query.sortOrder) {
ForEach([TodoQuery.SortOrder.latest, .oldest], id: \.self) { option in
Text(option.title).tag(option)
}
} label: {
Text(String(localized: "todo_list_sort_order"))
}
} label: {
let condition = store.state.query.sortTarget == .createdAt && store.state.query.sortOrder == .latest
HStack {
Text(
String.localizedStringWithFormat(
String(localized: "todo_list_sort_format"),
store.state.query.sortTarget.title,
store.state.query.sortOrder.title
)
)
Image(systemName: "chevron.down")
}
.foregroundStyle(condition ? Color(.label) : .white)
.adaptiveButtonStyle(color: condition ? .clear : .blue)
}

Menu {
Toggle(isOn: $store.query.isPinned) {
Text(String(localized: "todo_pinned"))
}

Picker(selection: $store.query.completionFilter) {
ForEach([TodoQuery.CompletionFilter.all, .incomplete, .completed], id: \.self) { option in
Text(option.title).tag(option)
}
} label: {
Text(String(localized: "todo_list_completion_status"))
}
} label: {
let condition = store.state.query.isPinned || store.state.query.completionFilter != .all
HStack {
Text(String(localized: "todo_list_filter_options"))
Image(systemName: "chevron.down")
}
.foregroundStyle(condition ? .white : Color(.label))
.adaptiveButtonStyle(color: condition ? .blue : .clear)
}
}
}
.scrollIndicators(.never)
Expand All @@ -344,63 +390,6 @@ struct TodoListView: View {
}
}

private var sortMenu: some View {
Menu {
Picker(selection: $store.query.sortTarget) {
ForEach([TodoQuery.SortTarget.createdAt, .updatedAt], id: \.self) { option in
Text(option.title).tag(option)
}
} label: {
Text(String(localized: "todo_list_sort_by"))
}
Picker(selection: $store.query.sortOrder) {
ForEach([TodoQuery.SortOrder.latest, .oldest], id: \.self) { option in
Text(option.title).tag(option)
}
} label: {
Text(String(localized: "todo_list_sort_order"))
}
} label: {
let condition = store.state.query.sortTarget == .createdAt && store.state.query.sortOrder == .latest
HStack {
Text(
String.localizedStringWithFormat(
String(localized: "todo_list_sort_format"),
store.state.query.sortTarget.title,
store.state.query.sortOrder.title
)
)
Image(systemName: "chevron.down")
}
.foregroundStyle(condition ? Color(.label) : .white)
.adaptiveButtonStyle(color: condition ? .clear : .blue)
}
}

private var filterMenu: some View {
Menu {
Toggle(isOn: $store.query.isPinned) {
Text(String(localized: "todo_pinned"))
}

Picker(selection: $store.query.completionFilter) {
ForEach([TodoQuery.CompletionFilter.all, .incomplete, .completed], id: \.self) { option in
Text(option.title).tag(option)
}
} label: {
Text(String(localized: "todo_list_completion_status"))
}
} label: {
let condition = store.state.query.isPinned || store.state.query.completionFilter != .all
HStack {
Text(String(localized: "todo_list_filter_options"))
Image(systemName: "chevron.down")
}
.foregroundStyle(condition ? .white : Color(.label))
.adaptiveButtonStyle(color: condition ? .blue : .clear)
}
}

private var filterBadge: some View {
let isDark = colorScheme == .dark
let blue = Color(uiColor: .systemBlue)
Expand Down
2 changes: 1 addition & 1 deletion Application/DevLogPresentation/Sources/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ struct MainView: View {

@ViewBuilder
private var notificationRegularDetailView: some View {
if let todoId = pushNotificationListViewCoordinator.todoIdToPresent?.id {
if let todoId = pushNotificationListViewCoordinator.store.selectedTodoId?.id {
TodoDetailView(
store: pushNotificationListViewCoordinator.makeTodoDetailStore(
todoId: todoId
Expand Down
Loading
Loading