Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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