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
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
## [Unreleased]

- Fixed [#650](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/650) - Fix null-check crash in ShowcaseService.getController during didUpdateWidget.
- Fixed [#633](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/633) - Improve showcase flow by consolidating finish logic and handling missing targets
- Fixed [#633](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/633) - Added `ShowcaseView.isTargetRendered` to detect whether a showcase target is currently rendered, replacing the 4.x.x `key.currentContext` / `key.currentWidget` check.
- Fixed [#650](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/650) - Fix null-check crash in ShowcaseService.getController during didUpdateWidget. Fixed by @[vatsaltanna-simformsolutions](https://github.com/vatsaltanna-simformsolutions)
- Fixed [#633](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/633) - Improve showcase flow by consolidating finish logic and handling missing targets. Fixed by @[vasu-nageshri](https://github.com/vasu-nageshri)
- Fixed [#633](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/633) - Added `ShowcaseView.isTargetRendered` to detect whether a showcase target is currently rendered, replacing the 4.x.x `key.currentContext` / `key.currentWidget` check. Fixed by @[vatsaltanna-simformsolutions](https://github.com/vatsaltanna-simformsolutions)
- Fixed [#645](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/645) - Prevent re-entrant calls to _onComplete during rapid barrier taps. Fixed by @[apizon](https://github.com/apizon)

## [5.0.2]

Expand Down
57 changes: 36 additions & 21 deletions lib/src/showcase/showcase_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ class ShowcaseView {
/// Whether the manager is mounted and active.
bool _mounted = true;

/// Whether a step completion animation is currently in progress.
/// Used to prevent re-entrant calls to [_onComplete] from rapid barrier taps.
bool _isCompleting = false;

/// Map to store keys for which floating action widget should be hidden.
late final Map<GlobalKey, bool> _hideFloatingWidgetKeys;

Expand Down Expand Up @@ -439,6 +443,8 @@ class ShowcaseView {
/// - Updates the overlay to reflect current state
void _changeSequence(ShowcaseProgressType type) {
assert(_activeWidgetId != null, 'Please ensure to call startShowcase.');
// Ignore re-entrant taps while a completion is in progress.
if (_isCompleting) return;
final id = switch (type) {
ShowcaseProgressType.forward => _activeWidgetId! + 1,
ShowcaseProgressType.backward => _activeWidgetId! - 1,
Expand Down Expand Up @@ -566,30 +572,39 @@ class ShowcaseView {
///
/// Runs reverse animations and triggers completion callbacks.
Future<void> _onComplete() async {
final currentControllers = _getCurrentActiveControllers;
final controllerLength = currentControllers.length;
if (skipIfTargetNotPresent && controllerLength == 0) {
return;
}

await Future.wait([
for (var i = 0; i < controllerLength; i++)
if (!(currentControllers[i].config.disableScaleAnimation ??
disableScaleAnimation) &&
currentControllers[i].reverseAnimationCallback != null)
currentControllers[i].reverseAnimationCallback!.call(),
]);
// Guard against re-entrant calls (e.g. rapid barrier taps during the
// reverse animation await below), which would call reverse() on already-
// disposed AnimationControllers and trigger a null-check crash.
if (_isCompleting) return;
_isCompleting = true;
try {
final currentControllers = _getCurrentActiveControllers;
final controllerLength = currentControllers.length;
if (skipIfTargetNotPresent && controllerLength == 0) {
return;
}

final activeId = _activeWidgetId ?? -1;
if (activeId < (_ids?.length ?? activeId)) {
onComplete?.call(activeId, _ids![activeId]);
// Call all registered onComplete callbacks
for (final callback in _onCompleteCallbacks) {
callback.call(activeId, _ids![activeId]);
await Future.wait([
for (var i = 0; i < controllerLength; i++)
if (!(currentControllers[i].config.disableScaleAnimation ??
disableScaleAnimation) &&
currentControllers[i].reverseAnimationCallback != null)
currentControllers[i].reverseAnimationCallback!.call(),
]);

final activeId = _activeWidgetId ?? -1;
if (activeId < (_ids?.length ?? activeId)) {
onComplete?.call(activeId, _ids![activeId]);
// Call all registered onComplete callbacks
for (final callback in _onCompleteCallbacks) {
callback.call(activeId, _ids![activeId]);
}
}
}

if (autoPlay) _cancelTimer();
if (autoPlay) _cancelTimer();
} finally {
_isCompleting = false;
}
}

/// Finishes the showcase and triggers all finish callbacks.
Expand Down
Loading