Repository navigation
Fix the Medium and Low findings of the architecture review (#52 to #65) - #67
Merged
Merged
Conversation
The host mapped TopStart to Alignment.TopEnd in RTL, but Compose Start and End alignments already flip in RTL, so the cards were mirrored twice and sat on the wrong side. - Positions map straight to the Start and End alignments. - The layout of each position (alignment, arrangement, slide direction, full width) lives in ToastPosition.kt as exhaustive when blocks with no else, so a new position must define them. - A test checks the card side for the four corners in LTR and RTL. Fixes #52
The urgent path evicted the oldest toast with removeSilently, which promoted a queued toast into the freed slot before the urgent toast was added, so the stack held maxVisible + 1 cards. - The eviction no longer promotes from the queue; the urgent toast takes the slot. - The evicted toast's onDismiss runs after the urgent toast is in, so a show() from that callback waits in the queue. Fixes #53
styleForType always set titleColor and iconTint to the type's content color, so after merging they were never null and a custom contentColor changed only the message text. - The type defaults leave titleColor and iconTint unset, so they fall back to the content color, including a global or per-toast one. Fixes #54
Minor releases changed public JVM signatures with no check and no changelog, and the public surface was wider than documented. - PublicApiTest dumps the public API with the binary compatibility validator library and compares it with toaststack/api/toaststack.api. The validator's Gradle plugin and KGP's ABI validation do not work with AGP 9's built-in Kotlin, so the library runs in a unit test. -PupdateApiDump writes a new dump. - CHANGELOG.md covers 1.1.0 to 1.3.0 and the unreleased #43 work. - ToastStackInitializer is internal, and ToastData's constructor and copy() are internal. - consumer-rules.pro is empty. The demo release build runs R8 in CI. It skips the Compose mapping file, which AGP 9.4 cannot fetch for its built-in Kotlin 2.2.10. - ToastStackConfig, rememberToastStackState(config) and WithToastStack are deprecated. - Compose runtime, ui, foundation-layout, animation-core and lifecycle-viewmodel are api dependencies, since their types appear in the public API. - README.md and the API dump are test inputs, so a change to either reruns the tests. Fixes #55
The library manifest added VIBRATE to every app, even when no toast vibrates, and an app that removed it crashed with SecurityException on the first haptic toast. - Haptics use View.performHapticFeedback, which needs no permission and follows the system haptic setting: reject for errors and warnings, confirm for success (Android 11+, with older fallbacks), a key tap for the rest. - The VIBRATE permission and the Vibrator code are removed. - README and CHANGELOG describe the change. Fixes #56
The screen reader prefixes, the close label, the Dismiss action and "(xN)" were hard coded in English, and the decorative type icon had its own label, so TalkBack could read an extra stop. - The labels and a toaststack_repeat_count plural are string resources. An app overrides them by name in its own values-xx. - The type icon has no label; the type is in the card's label. - A test uses a fake "zz" locale in the debug source set to show an override. It is not part of the release AAR. - README lists the resource names. Fixes #57
The icon lost its label in the previous commit, so check it by a test tag instead. Refs #57
The host wrote its render list during composition, read the animator scale on every recomposition, and prepared the toast sound on the main thread with errors swallowed. - The render list follows state.toasts in a snapshotFlow effect, so the host never writes state it is drawing. - The animator scale is read once per host. - The sound plays on Dispatchers.IO, and a failure is logged once. - A test removes and adds the host with the same state and checks onShow fired once. The state keeps the shown ids since #47. Fixes #58
The 8 toast defaults were listed in 9 places, and the state read the singleton's fields through a followsGlobalDefaults flag. - ToastDefaults is one internal immutable data class. Its default values are the only place each default is written; the public parameters read them from ToastDefaults.Standard. - ToastStack keeps one @volatile defaults value that configure() replaces in one write. - ToastStackState reads its defaults through defaults(): its own values, or ToastStack.defaults for the auto overlay. The 8 own* fields, the branching getters and the flag are gone. - The default content padding is written once. No behaviour change. All tests pass unchanged. Fixes #59
- ToastStackDefaults.Shape is a RoundedCornerShape, so compose foundation is an api dependency too. - CHANGELOG lists every signature change in 1.1.0, 1.2.0 and the unreleased work, and says that a positional onDismiss no longer compiles. - The API dump and the README check read files with LF line ends, and .gitattributes keeps *.api at LF. - The demo R8 comment says it only builds. Refs #55
The bugs in the #43 review passed CI because of gaps in the tests. - ToastStack.resetForTest() and a ToastStackReset rule, used by every test class that touches the singleton, so a failed test does not leak a host or a configure() value. - ToastStackState reads time through an internal now(), a monotonic clock (SystemClock.uptimeMillis) instead of the wall clock. Tests set it to the test scheduler's time. - New tests: the paused timer keeps the time left, the dedup window edge, the ViewModel helpers from a real ViewModel, swipe dismiss, action tap, and the string resource overloads. Touch pass-through, RTL, threading and the multi-Activity cases came with #44 to #52. - Phase7Test is now ToastBuilderTest and GapFixTest is now ToastQueueAndDedupTest. The assertTrue(true) test, the fake ViewModel test and a test that checked its own list are gone. Fixes #60
- KDoc no longer says toasts are green, red, amber or blue (colors follow the color source since 1.1.0), that the oldest toast is evicted at the limit (normal toasts queue), that show() returns an id, or that timers pause when the app goes to the background. - ToastData documents every property. - The toast sound is held back in Do Not Disturb, as README and KDoc said, and uses notification audio attributes. CLAUDE.md and CONTRIBUTING were corrected with #51, #55 and #60. Fixes #61
- jitpack.yml uses JDK 21 Temurin and runs the same publish command as CI, so CI checks the path JitPack takes. CodeQL uses JDK 21 too. - CI and the release workflow validate the Gradle wrapper first, and gradle-wrapper.properties pins the distribution checksum. - Every action is pinned by commit SHA, with the tag in a comment, so Dependabot can keep them current. - The release workflow fails when the tag does not match the version in the build, and attaches the AAR to the GitHub release. - The library drops its unused instrumentation runner. (The demo's missing proguard-rules.pro was removed with #55.) Fixes #62
Since the render list moved into an effect, a toast could be added and then dismissed before the next frame. Its own effect still made it visible and fired onShow, haptic and sound, and the card stayed on screen with no way to close it. - The per-toast effect returns at once when the toast is no longer in the state, so the exit runs and nothing is shown. - ToastDefaults moves above the ToastStackState KDoc, which it had pushed off the class. Refs #58 #59
- maxVisible below 1 fails fast in configure() and the ToastStackState constructor (rememberToastStackState and ToastStackConfig go through it), instead of sending every toast to the queue forever. - A NaN progress counts as 0. - The per-message time map is gone. Each card keeps its own last-shown time, which deduplication already looks up, so nothing is left once the card is dismissed. Fixes #63
No behaviour change; all tests pass. - ToastItem is split: the card, DefaultToastContent, one CloseButton (it was written twice), ActionButton, messageText and an internal accessibilityLabel. No composable is over about 110 lines. - ToastStackHost is split: RegisterHost (registration and TalkBack), ToastSlot (one toast's effects, exit and card), and an internal syncRenderList with its own unit test. - The timer engine (jobs, pause and remaining time, the clock and the scope) is a small internal ToastTimers class. - StringResolver.resolve handles empty format arguments, so the five copies of that branch in ToastStackState are gone. Fixes #64
The overlay went into every ComponentActivity, including third-party
login, payment and ad screens, with no way to leave one out short of
turning the feature off everywhere.
- ToastStack.configure(autoOverlay = { activity -> ... }) chooses
which Activities get the overlay. Every ComponentActivity by default.
- A hand-placed host with the default tag in an Activity that also has
the overlay logs one warning, since both would take toasts sent
without a hostTag.
- README documents the filter; the API dump, CHANGELOG and samples
are updated.
Fixes #65
The old images were crops of the demo app with its tabs showing behind the toasts, and showed only the 1.0 colors. - A Robolectric test (ReadmeScreenshots, skipped unless run with -PrecordScreenshots) renders clean scenes in light and dark with real shadows: the toast types in app theme colors, the library colors, actions and progress, positions, custom content, and an animated demo GIF. - README shows them side by side, and its feature list now covers thread safety, the TalkBack pause, translations, rotation, leaving Activities out and the lower floor. It links the CHANGELOG. - CONTRIBUTING says how to remake the images.
- Deduplication looks for a card with the message that is still in its window, so an older card out of its window no longer makes every repeat add a new card. - configure() checks maxVisible first, so a bad value changes nothing. - The dedup window test checks the window counts from the last repeat, and a test checks an Activity with only the overlay logs no shared tag warning. Refs #60 #63 #65
22 tasks done
zeevy
added a commit
that referenced
this pull request
Oct 13, 2026
… (#67) * fix: RTL Start and End positions sit on the right side The host mapped TopStart to Alignment.TopEnd in RTL, but Compose Start and End alignments already flip in RTL, so the cards were mirrored twice and sat on the wrong side. - Positions map straight to the Start and End alignments. - The layout of each position (alignment, arrangement, slide direction, full width) lives in ToastPosition.kt as exhaustive when blocks with no else, so a new position must define them. - A test checks the card side for the four corners in LTR and RTL. Fixes #52 * fix: an urgent toast keeps the stack at maxVisible The urgent path evicted the oldest toast with removeSilently, which promoted a queued toast into the freed slot before the urgent toast was added, so the stack held maxVisible + 1 cards. - The eviction no longer promotes from the queue; the urgent toast takes the slot. - The evicted toast's onDismiss runs after the urgent toast is in, so a show() from that callback waits in the queue. Fixes #53 * fix: a custom contentColor reaches the title and the icon styleForType always set titleColor and iconTint to the type's content color, so after merging they were never null and a custom contentColor changed only the message text. - The type defaults leave titleColor and iconTint unset, so they fall back to the content color, including a global or per-toast one. Fixes #54 * feat: API dump check, CHANGELOG and a smaller public surface Minor releases changed public JVM signatures with no check and no changelog, and the public surface was wider than documented. - PublicApiTest dumps the public API with the binary compatibility validator library and compares it with toaststack/api/toaststack.api. The validator's Gradle plugin and KGP's ABI validation do not work with AGP 9's built-in Kotlin, so the library runs in a unit test. -PupdateApiDump writes a new dump. - CHANGELOG.md covers 1.1.0 to 1.3.0 and the unreleased #43 work. - ToastStackInitializer is internal, and ToastData's constructor and copy() are internal. - consumer-rules.pro is empty. The demo release build runs R8 in CI. It skips the Compose mapping file, which AGP 9.4 cannot fetch for its built-in Kotlin 2.2.10. - ToastStackConfig, rememberToastStackState(config) and WithToastStack are deprecated. - Compose runtime, ui, foundation-layout, animation-core and lifecycle-viewmodel are api dependencies, since their types appear in the public API. - README.md and the API dump are test inputs, so a change to either reruns the tests. Fixes #55 * fix: haptics without the VIBRATE permission The library manifest added VIBRATE to every app, even when no toast vibrates, and an app that removed it crashed with SecurityException on the first haptic toast. - Haptics use View.performHapticFeedback, which needs no permission and follows the system haptic setting: reject for errors and warnings, confirm for success (Android 11+, with older fallbacks), a key tap for the rest. - The VIBRATE permission and the Vibrator code are removed. - README and CHANGELOG describe the change. Fixes #56 * fix: translatable accessibility labels and repeat count The screen reader prefixes, the close label, the Dismiss action and "(xN)" were hard coded in English, and the decorative type icon had its own label, so TalkBack could read an extra stop. - The labels and a toaststack_repeat_count plural are string resources. An app overrides them by name in its own values-xx. - The type icon has no label; the type is in the card's label. - A test uses a fake "zz" locale in the debug source set to show an override. It is not part of the release AAR. - README lists the resource names. Fixes #57 * test: the type icon is still drawn for each type The icon lost its label in the previous commit, so check it by a test tag instead. Refs #57 * fix: host bookkeeping runs outside composition, sound off main The host wrote its render list during composition, read the animator scale on every recomposition, and prepared the toast sound on the main thread with errors swallowed. - The render list follows state.toasts in a snapshotFlow effect, so the host never writes state it is drawing. - The animator scale is read once per host. - The sound plays on Dispatchers.IO, and a failure is logged once. - A test removes and adds the host with the same state and checks onShow fired once. The state keeps the shown ids since #47. Fixes #58 * refactor: one defaults object for the global and the local paths The 8 toast defaults were listed in 9 places, and the state read the singleton's fields through a followsGlobalDefaults flag. - ToastDefaults is one internal immutable data class. Its default values are the only place each default is written; the public parameters read them from ToastDefaults.Standard. - ToastStack keeps one @volatile defaults value that configure() replaces in one write. - ToastStackState reads its defaults through defaults(): its own values, or ToastStack.defaults for the auto overlay. The 8 own* fields, the branching getters and the flag are gone. - The default content padding is written once. No behaviour change. All tests pass unchanged. Fixes #59 * fix: review follow-ups for the API check - ToastStackDefaults.Shape is a RoundedCornerShape, so compose foundation is an api dependency too. - CHANGELOG lists every signature change in 1.1.0, 1.2.0 and the unreleased work, and says that a positional onDismiss no longer compiles. - The API dump and the README check read files with LF line ends, and .gitattributes keeps *.api at LF. - The demo R8 comment says it only builds. Refs #55 * test: shared singleton reset, injectable clock, missing tests The bugs in the #43 review passed CI because of gaps in the tests. - ToastStack.resetForTest() and a ToastStackReset rule, used by every test class that touches the singleton, so a failed test does not leak a host or a configure() value. - ToastStackState reads time through an internal now(), a monotonic clock (SystemClock.uptimeMillis) instead of the wall clock. Tests set it to the test scheduler's time. - New tests: the paused timer keeps the time left, the dedup window edge, the ViewModel helpers from a real ViewModel, swipe dismiss, action tap, and the string resource overloads. Touch pass-through, RTL, threading and the multi-Activity cases came with #44 to #52. - Phase7Test is now ToastBuilderTest and GapFixTest is now ToastQueueAndDedupTest. The assertTrue(true) test, the fake ViewModel test and a test that checked its own list are gone. Fixes #60 * docs: bring KDoc in line with the code, and honour Do Not Disturb - KDoc no longer says toasts are green, red, amber or blue (colors follow the color source since 1.1.0), that the oldest toast is evicted at the limit (normal toasts queue), that show() returns an id, or that timers pause when the app goes to the background. - ToastData documents every property. - The toast sound is held back in Do Not Disturb, as README and KDoc said, and uses notification audio attributes. CLAUDE.md and CONTRIBUTING were corrected with #51, #55 and #60. Fixes #61 * ci: harden the release pipeline - jitpack.yml uses JDK 21 Temurin and runs the same publish command as CI, so CI checks the path JitPack takes. CodeQL uses JDK 21 too. - CI and the release workflow validate the Gradle wrapper first, and gradle-wrapper.properties pins the distribution checksum. - Every action is pinned by commit SHA, with the tag in a comment, so Dependabot can keep them current. - The release workflow fails when the tag does not match the version in the build, and attaches the AAR to the GitHub release. - The library drops its unused instrumentation runner. (The demo's missing proguard-rules.pro was removed with #55.) Fixes #62 * fix: a toast dismissed before its first frame leaves no card Since the render list moved into an effect, a toast could be added and then dismissed before the next frame. Its own effect still made it visible and fired onShow, haptic and sound, and the card stayed on screen with no way to close it. - The per-toast effect returns at once when the toast is no longer in the state, so the exit runs and nothing is shown. - ToastDefaults moves above the ToastStackState KDoc, which it had pushed off the class. Refs #58 #59 * fix: input checks and a dedup state that does not grow - maxVisible below 1 fails fast in configure() and the ToastStackState constructor (rememberToastStackState and ToastStackConfig go through it), instead of sending every toast to the queue forever. - A NaN progress counts as 0. - The per-message time map is gone. Each card keeps its own last-shown time, which deduplication already looks up, so nothing is left once the card is dismissed. Fixes #63 * refactor: split the long composables and move the timers out No behaviour change; all tests pass. - ToastItem is split: the card, DefaultToastContent, one CloseButton (it was written twice), ActionButton, messageText and an internal accessibilityLabel. No composable is over about 110 lines. - ToastStackHost is split: RegisterHost (registration and TalkBack), ToastSlot (one toast's effects, exit and card), and an internal syncRenderList with its own unit test. - The timer engine (jobs, pause and remaining time, the clock and the scope) is a small internal ToastTimers class. - StringResolver.resolve handles empty format arguments, so the five copies of that branch in ToastStackState are gone. Fixes #64 * feat: let apps leave Activities out of the auto overlay The overlay went into every ComponentActivity, including third-party login, payment and ad screens, with no way to leave one out short of turning the feature off everywhere. - ToastStack.configure(autoOverlay = { activity -> ... }) chooses which Activities get the overlay. Every ComponentActivity by default. - A hand-placed host with the default tag in an Activity that also has the overlay logs one warning, since both would take toasts sent without a hostTag. - README documents the filter; the API dump, CHANGELOG and samples are updated. Fixes #65 * docs: new README images and an up to date README The old images were crops of the demo app with its tabs showing behind the toasts, and showed only the 1.0 colors. - A Robolectric test (ReadmeScreenshots, skipped unless run with -PrecordScreenshots) renders clean scenes in light and dark with real shadows: the toast types in app theme colors, the library colors, actions and progress, positions, custom content, and an animated demo GIF. - README shows them side by side, and its feature list now covers thread safety, the TalkBack pause, translations, rotation, leaving Activities out and the lower floor. It links the CHANGELOG. - CONTRIBUTING says how to remake the images. * fix: review follow-ups for dedup and configure - Deduplication looks for a card with the message that is still in its window, so an older card out of its window no longer makes every repeat add a new card. - configure() checks maxVisible first, so a bad value changes nothing. - The dedup window test checks the window counts from the last repeat, and a test checks an Activity with only the overlay logs no shared tag warning. Refs #60 #63 #65 ---------
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the Medium and Low findings of the architecture review in #43 (#52 to #65), and refreshes the README. One commit per issue, plus review follow-ups.
whenblocks.maxVisible.contentColoralso colors the title and icon.toaststack/api/toaststack.api), CHANGELOG.md, a smaller public surface (internal initializer andToastDataconstructor), empty consumer rules, demo release built with R8 in CI,apidependencies for types in the public API.maxVisiblebelow 1 fails fast, NaN progress counts as 0, the dedup state no longer grows.ToastTimersclass, oneStringResolver.resolve.ToastStack.configure(autoOverlay = ...)leaves chosen Activities out.-PrecordScreenshots), an animated demo, and an updated feature list.Tests: 318 unit tests pass (254 at the start of #43). consumer-check (AGP 8.6, Kotlin 2.0, compileSdk 35) builds against the published library.
Behavior and API changes are listed in CHANGELOG.md under Unreleased.