Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
Improved Tooltip widget
- Feature [#54](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/54) - Added
Feasibility to position tooltip left and right to the target widget.
- Feature [#113](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/113) - Added
multiple showcase feature
- Improvement [#514](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/pull/514) -
Improved showcase widget and showcase with widget, Removed inherited widget, keys and setStates,
Added controller to manage showcase

## [4.0.1]
- Fixed [#493](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/493) - ShowCase.withWidget not showing issue
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,36 @@ WidgetsBinding.instance.addPostFrameCallback((_) =>
);
```

## MultiShowcaseView
To show multiple showcase at the same time provide same key to showcase.
Note: auto scroll to showcase will not work in case of the multi-showcase and we will use property
of first initialized showcase for common things like barrier tap and colors.

```dart
GlobalKey _one = GlobalKey();
...

Showcase(
key: _one,
title: 'Showcase one',
description: 'Click here to see menu options',
child: Icon(
Icons.menu,
color: Colors.black45,
),
),

Showcase(
key: _one,
title: 'Showcase two',
description: 'Click here to see menu options',
child: Icon(
Icons.menu,
color: Colors.black45,
),
),
```

## Functions of `ShowCaseWidget.of(context)`:

| Function Name | Description |
Expand Down
2 changes: 1 addition & 1 deletion lib/showcaseview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export 'src/enum.dart';
export 'src/models/action_button_icon.dart';
export 'src/models/tooltip_action_button.dart';
export 'src/models/tooltip_action_config.dart';
export 'src/showcase.dart';
export 'src/showcase/showcase.dart';
export 'src/showcase_widget.dart';
export 'src/tooltip_action_button_widget.dart';
export 'src/widget/floating_action_widget.dart';
15 changes: 15 additions & 0 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:flutter/material.dart';

class Constants {
Constants._();

Expand Down Expand Up @@ -25,4 +27,17 @@ class Constants {
/// i.e if it is bottom position then centerBottom + [extraAlignmentOffset]
/// in bottom
static const double extraAlignmentOffset = 5;

static const defaultTargetRadius = Radius.circular(3.0);

static const ShapeBorder defaultTargetShapeBorder = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
);

static const Widget defaultProgressIndicator =
CircularProgressIndicator.adaptive(
backgroundColor: Colors.white,
);

static const Duration defaultAnimationDuration = Duration(milliseconds: 2000);
}
2 changes: 1 addition & 1 deletion lib/src/enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ enum TooltipDefaultActionType {
void onTap(ShowCaseWidgetState showCaseState) {
switch (this) {
case TooltipDefaultActionType.next:
showCaseState.next();
showCaseState.next(forceNext: true);
break;
case TooltipDefaultActionType.previous:
showCaseState.previous();
Expand Down
31 changes: 22 additions & 9 deletions lib/src/get_position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,27 @@ import 'package:flutter/material.dart';

class GetPosition {
GetPosition({
required this.key,
required this.renderBox,
required this.screenWidth,
required this.screenHeight,
this.padding = EdgeInsets.zero,
this.rootRenderObject,
}) {
getRenderBox();
_getRenderBox();
}

final GlobalKey key;
final RenderBox? renderBox;
final EdgeInsets padding;
final double screenWidth;
final double screenHeight;
final RenderObject? rootRenderObject;

late final RenderBox? _box;
late final Offset? _boxOffset;
RenderBox? _box;
Offset? _boxOffset;

RenderBox? get box => _box;

void getRenderBox() {
var renderBox = key.currentContext?.findRenderObject() as RenderBox?;

void _getRenderBox() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename this to match the work its doing.

if (renderBox == null) return;

_box = renderBox;
Expand All @@ -72,7 +70,8 @@ class GetPosition {
final topLeft = _box!.size.topLeft(_boxOffset!);
final bottomRight = _box!.size.bottomRight(_boxOffset!);
final leftDx = topLeft.dx - padding.left;
final leftDy = topLeft.dy - padding.top;
var leftDy = topLeft.dy - padding.top;
if (leftDy < 0) leftDy = 0;
final rect = Rect.fromLTRB(
leftDx.clamp(0, leftDx),
leftDy.clamp(0, leftDy),
Expand Down Expand Up @@ -123,4 +122,18 @@ class GetPosition {
double getWidth() => getRight() - getLeft();

double getCenter() => (getLeft() + getRight()) * 0.5;

Offset topLeft() {
final box = _box;
if (box == null) return Offset.zero;

return box.size.topLeft(
box.localToGlobal(
Offset.zero,
ancestor: rootRenderObject,
),
);
}

Offset getOffset() => _box?.size.center(topLeft()) ?? Offset.zero;
}
121 changes: 20 additions & 101 deletions lib/src/layout_overlays.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,94 +24,6 @@ import 'package:flutter/material.dart';

import 'showcase_widget.dart';

typedef OverlayBuilderCallback = Widget Function(
BuildContext context,
Rect anchorBounds,
Offset anchor,
);

/// Displays an overlay Widget anchored directly above the center of this
/// [AnchoredOverlay].
///
/// The overlay Widget is created by invoking the provided [overlayBuilder].
///
/// The [anchor] position is provided to the [overlayBuilder], but the builder
/// does not have to respect it. In other words, the [overlayBuilder] can
/// interpret the meaning of "anchor" however it wants - the overlay will not
/// be forced to be centered about the [anchor].
///
/// The overlay built by this [AnchoredOverlay] can be conditionally shown
/// and hidden by settings the [showOverlay] property to true or false.
///
/// The [overlayBuilder] is invoked every time this Widget is rebuilt.
///
class AnchoredOverlay extends StatelessWidget {
final bool showOverlay;
final OverlayBuilderCallback? overlayBuilder;
final Widget? child;
final RenderObject? rootRenderObject;

const AnchoredOverlay({
super.key,
this.showOverlay = false,
this.overlayBuilder,
this.child,
this.rootRenderObject,
});

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return OverlayBuilder(
showOverlay: showOverlay,
overlayBuilder: (overlayContext) {
// To calculate the "anchor" point we grab the render box of
// our parent Container and then we find the center of that box.
final box = context.findRenderObject() as RenderBox?;

/// Handle null RenderBox safely.
final topLeft = box?.size.topLeft(
box.localToGlobal(
Offset.zero,
ancestor: rootRenderObject,
),
) ??
Offset.zero;
final bottomRight = box?.size.bottomRight(
box.localToGlobal(
Offset.zero,
ancestor: rootRenderObject,
),
) ??
Offset.zero;

/// Provide a default anchorBounds if box is null.
final anchorBounds = (topLeft.dx.isNaN ||
topLeft.dy.isNaN ||
bottomRight.dx.isNaN ||
bottomRight.dy.isNaN)
? const Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)
: Rect.fromLTRB(
topLeft.dx,
topLeft.dy,
bottomRight.dx,
bottomRight.dy,
);

/// Calculate the anchor center or default to Offset.zero.
final anchorCenter = box?.size.center(topLeft) ?? Offset.zero;

/// Pass the anchor details to the overlay builder.
return overlayBuilder!(overlayContext, anchorBounds, anchorCenter);
},
child: child,
);
},
);
}
}

/// Displays an overlay Widget as constructed by the given [overlayBuilder].
///
/// The overlay built by the [overlayBuilder] can be conditionally shown and
Expand All @@ -125,37 +37,46 @@ class AnchoredOverlay extends StatelessWidget {
/// exist in [OverlayEntry]s which are inaccessible to outside Widgets. But if
/// a better approach is found then feel free to use it.
class OverlayBuilder extends StatefulWidget {
final bool showOverlay;
final WidgetBuilder? overlayBuilder;
final Widget? child;

const OverlayBuilder({
super.key,
this.showOverlay = false,
required this.child,
required this.updateOverlay,
this.overlayBuilder,
this.child,
});

final WidgetBuilder? overlayBuilder;
final Widget child;
final ValueSetter<ValueSetter<bool>> updateOverlay;

@override
State<OverlayBuilder> createState() => _OverlayBuilderState();
}

class _OverlayBuilderState extends State<OverlayBuilder> {
OverlayEntry? _overlayEntry;

bool _showOverlay = false;

@override
void initState() {
super.initState();

if (widget.showOverlay) {
if (_showOverlay) {
WidgetsBinding.instance.addPostFrameCallback((_) => showOverlay());
}
widget.updateOverlay.call(_updateOverlay);
}

void _updateOverlay(bool showOverlay) {
_showOverlay = showOverlay;
buildOverlay();
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
}

@override
void didUpdateWidget(OverlayBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
WidgetsBinding.instance.addPostFrameCallback((_) => showOverlay());
}

@override
Expand Down Expand Up @@ -207,9 +128,9 @@ class _OverlayBuilderState extends State<OverlayBuilder> {
}

void syncWidgetAndOverlay() {
if (isShowingOverlay() && !widget.showOverlay) {
if (isShowingOverlay() && !_showOverlay) {
hideOverlay();
} else if (!isShowingOverlay() && widget.showOverlay) {
} else if (!isShowingOverlay() && _showOverlay) {
showOverlay();
}
}
Expand All @@ -221,8 +142,6 @@ class _OverlayBuilderState extends State<OverlayBuilder> {

@override
Widget build(BuildContext context) {
buildOverlay();

return widget.child!;
return widget.child;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this expression body.

}
17 changes: 17 additions & 0 deletions lib/src/models/linked_showcase_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/widgets.dart';

/// This model is used to move linked showcase overlay data to parent
/// showcase to crop linked showcase rect
class LinkedShowcaseDataModel {
const LinkedShowcaseDataModel({
required this.rect,
required this.radius,
required this.overlayPadding,
required this.isCircle,
});

final Rect rect;
final EdgeInsets overlayPadding;
final BorderRadius? radius;
final bool isCircle;
}
Loading