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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## AI
docs/superpowers
Comment thread
opficdev marked this conversation as resolved.

# MACOS
.DS_Store

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct LoginView: View {
.multilineTextAlignment(.center)
.padding(.vertical)
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.background {
WindowSceneIdentifierReader {
store.send(.setPresentationContext(
Expand Down
2 changes: 1 addition & 1 deletion Application/Presentation/Entry/Sources/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ struct MainView: View {
profileViewCoordinator.fetchData()
}
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.toastHost()
}

Expand Down
2 changes: 1 addition & 1 deletion Application/Presentation/Entry/Sources/Root/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public struct RootView: View {
guard let mainTab = widgetURLTab(url) else { return }
store.send(.openWidgetRoute(mainTab))
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { sheetStore in
sheetContent(todoId: sheetStore.todoId) {
sheetStore.send(.tapCloseButton)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct CategoryManageView: View {
.sheet(item: $store.scope(state: \.categorySheet, action: \.categorySheet)) { sheetStore in
sheetContent(sheetStore)
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct HomeView: View {
.listStyle(.insetGrouped)
.navigationTitle(String(localized: "nav_home"))
.toolbar { toolbar }
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.sheet(item: $store.scope(state: \.sheet, action: \.sheet), content: sheetContent)
.fullScreenCover(item: $store.scope(state: \.fullScreenCover, action: \.fullScreenCover), content: coverContent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct SearchView: View {
dismiss()
}
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public struct PushNotificationListView: View {
.navigationTitle(String(localized: "nav_push_notifications"))
.listStyle(.plain)
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.sheet(item: sheetStore) { store in
sheetContent(store)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// View+Alert.swift
// PresentationShared
//
// Created by opfic on 7/30/26.
//

import ComposableArchitecture
import SwiftUI

public extension View {
@preconcurrency @MainActor
@ViewBuilder
func prominentAlert<State, Action, AlertAction>(
_ store: Store<State, Action>,
state: KeyPath<State, AlertState<AlertAction>?>,
action: CaseKeyPath<Action, PresentationAction<AlertAction>>
) -> some View where State: ObservableState {
@Bindable var store = store
let item = $store.scope(state: state, action: action)
let alertStore = item.wrappedValue
let alertState = store.state[keyPath: state]

alert(
alertState.map(\.title).map(Text.init) ?? Text(verbatim: ""),
isPresented: Binding(item),
presenting: alertState,
actions: { alertState in
ForEach(alertState.buttons) { button in
let usesDefaultAction = alertState.usesDefaultAction(for: button)

Button(
role: alertState.buttonRole(for: button),
action: {
button.withAction { action in
if let action {
alertStore?.send(action)
}
}
}
) {
Text(button.label)
}
.keyboardShortcut(
usesDefaultAction ? .defaultAction : nil
)
}
},
message: {
$0.message.map(Text.init)
}
)
}
}

extension AlertState {
func buttonRole(for button: ButtonState<Action>) -> ButtonRole? {
if #available(iOS 26, *), usesDefaultAction(for: button) { return .confirm }
Comment thread
opficdev marked this conversation as resolved.
if buttons.count == 1 { return nil }
return button.role.map(ButtonRole.init)
}

func usesDefaultAction(for button: ButtonState<Action>) -> Bool {
guard 1 < buttons.count else { return true }
return buttons.first { $0.role == .destructive }?.id == button.id
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public struct TodoDetailView: View {
}
.onAppear { store.send(.onAppear) }
.navigationBarTitleDisplayMode(.inline)
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { store in
sheetContent(store)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public struct TodoEditorView: View {
.disabled(!store.isReadyToSubmit)
}
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public struct TodoListView: View {
)
}
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.navigationTitle(TodoCategoryItem(from: store.category).localizedName)
.fullScreenCover(
item: $store.scope(state: \.fullScreenCover, action: \.fullScreenCover)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public struct ProfileView: View {
.onChange(of: focused) { _, newValue in
store.send(.updateStatusTextFieldFocus(newValue), animation: .default)
}
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.sheet(isPresented: $store.showQuarterPicker) { quarterPickerSheet }
.overlay {
if store.isLoading {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ struct AccountView: View {
.listStyle(.insetGrouped)
.navigationTitle(String(localized: "nav_account"))
.onAppear { store.send(.onAppear) }
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.background {
WindowSceneIdentifierReader {
store.send(.setPresentationContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct PushNotificationSettingsView: View {
.listStyle(.insetGrouped)
.navigationTitle(String(localized: "nav_push_settings"))
.onAppear { store.send(.fetchSettings) }
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.sheet(item: $store.scope(state: \.timePicker, action: \.timePicker)) { timePickerStore in
TimePickerView(
store: timePickerStore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ struct SettingsView: View {
}
.navigationTitle(String(localized: "nav_settings"))
.navigationBarTitleDisplayMode(.inline)
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.onAppear {
store.send(.updateDirSize)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct TodayView: View {
.toolbar { toolbarContent }
.background(NavigationBarConfigurator())
.refreshable { await store.send(.refresh).finish() }
.alert($store.scope(state: \.alert, action: \.alert))
.prominentAlert(store, state: \.alert, action: \.alert)
.overlay {
if store.isLoading {
LoadingView()
Expand Down
Loading
Loading