* Re-introduce legacy alert and action sheet APIs
With 1.0 we removed `View.alert(_ store:dismiss:)`, which was a
problematic API. It used different SwiftUI alert APIs depending on OS
version, which led to different runtime behavior for apps using it
depending on the OS and an unfixable bug:
https://github.com/pointfreeco/swift-composable-architecture/issues/1981
This was reason enough to remove the API, but it was also an API that
predated the Composable Architecture's navigation tools. This meant it
awkwardly took an explicit `dismiss:` action, and it was up to the
developer to remember to use this action to manually clear out state.
The Composable Architecture's navigation tools handle dismissal
automatically once integrated, but also only support iOS 15 alerts at
the moment.
This PR addresses the above: it introduces view modifiers for the old
iOS 13 style of alert (and `actionSheet`) providing some presentation
domain, and it will automatically dismiss these modals accordingly:
- It adds `View.legacyAlert(store:)`. While we don't love
`legacyAlert`, we haven't come up with a better option. We need a
statically unique view modifier, and `alert(store:)` is already in
use for iOS 15. We're open to suggestions here, though!
- It adds `View.actionSheet(store:)`.
* wip
Currently, `BindingViewStore`s can only directly derive
`BindingViewState` for a view state struct, and then the view store can
derive a binding and use dynamic member lookup to pluck out a field for
a view. This means potentially exposing view state to far more state
than necessary.
To prevent this we can add dynamic member lookup to the binding view
state itself, which allows a view state struct to chip away any state it
doesn't care about.
* Abbreviate nested binding action values
Now that binding action key paths cannot use dynamic member lookup,
dumps are a little more unwieldy to read when nested state is involved.
Generally one can look at the state diff to figure out what changed, so
let's abbreviate the action's state when we can.
* wip
* fixed dismiss action sending when navigating back
* Revert "fixed dismiss action sending when navigating back"
This reverts commit 52f7340406.
* added integration ui test
* fixed id for presentation with `isPresented` parameter
* fixed integration tests
Fixes#2182.
The `id` used for comparison appears to sometimes be `nil`, for example
on macOS when drilling out of a `navigationDestination`. This PR adds a
little nuance to the dismissal logic of `presentationModifier`:
* It explicitly checks that something is presented
* It always sends `dismiss` if the cached `id` was `nil`
We should maybe have some integration tests for macOS for this...
* Fixed BindingAction.set(keyPath:value:) causes equality test on value to fail
* clean up
---------
Co-authored-by: Bharath Booshan <bbooshan@atlassian.com>
Co-authored-by: Brandon Williams <mbrandonw@hey.com>
* Make Send wrap a sendable closure.
* Move Send under the effects docs.
* Fixed a bunch of doc references.
* wip
* wip
* fix
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* Deprecate `Store.scope(state:)` for view store `observe`
Explicit scoping is most appropriate for transforming domains, which
almost always requires an action transform. In the rare case it doesn't,
we should prefer an explicit `{ $0 }`.
Scoping for the view has been deprecated for awhile for the `observe`
parameter when creating view stores, so let's lead folks that direction.
* wip
* wip
For some reason, using `memcmp` with an empty ordered set and a
non-empty ordered set returns true:
```swift
areOrderedSetsDuplicates([1, 2, 3], []) // true
```
Because of this, we should always check the count before delegating to
`memcmp`. We should also be on the lookout for other exceptions in case
`memcmp` is not appropriate to use here.
Runtime warnings are not test helpers that should propagate XCTest
failures to the original file/line, so we should always omit that
information. This leads to better test failures that show up alongside
the test, not buried in application code.
* Pass state to `SwitchStore.init` view builder
Rather than rely on many, many overloads of `SwitchStore.init` to
strictly accept only `CaseLet`s and an optional, final `Default` view,
let's introduce a single initializer without constraints that is passed
the current state whenever the case changes. This state can be switched
on and route to the appropriate view via `CaseLet`.
The pros:
* We get exhaustive switching at compile time.
* We get more flexibility in the view layer. Previously, any
customization needed to be pushed into the `CaseLet` builder, but
now `CaseLet` can freely apply view modifiers.
* We can deprecate and eventually remove the many, many initializer
overloads, and hopefully slim down binary size in the process, and
improve view builder compile times when dealing with `SwitchStore`.
We can also deprecate and eventually remove `Default`.
The cons:
* It's slightly more verbose and repetitive. You have a `SwitchStore`
and then a `switch` inside it with many `case`s with `CaseLet`s
inside them, and the `case .screen:` is followed by
`CaseLet(/State.screen, ...)`.
* One is free to extract the state's associated value(s) and use it to
render a view, but this view will be inert and will not re-render if
the value it depends on changes in the store. This can lead to some
surprises, but we can document against the pattern, and maybe in the
future we can use something like macros to prevent this from being
possible.
* You can still get things wrong if you're not careful: there's
nothing enforcing that the `case` you switch on is the same case you
send to `CaseLet`, so `case .screenA: CaseLet(/State.screenB, ...)`
is possible. While unlikely, it's still a copy-paste error in
waiting.
* wip
* wip
* wip
* Bump
* fix test
* clean up
* clean up
* clean up
* wip
---------
Co-authored-by: Brandon Williams <mbrandonw@hey.com>
* `IfLetStore`: ignore view store binding writes to `nil` state
* swift-format
* wip
* add test for filter
---------
Co-authored-by: Brandon Williams <mbrandonw@hey.com>