diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b8cb101..54582b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [2.0.4] (Un-Released) - Feature [#387](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/387) - Provided barrier click disable functionality for a particular showcase. +- Fixed [#383](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/383) - Targeted widget focusing issue when we applying size constraint on root widget(MaterialApp). ## [2.0.3] - Feature [#148](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/148) - Add feasibility to add `textDirection` of `title` and `description`. diff --git a/example/android/build.gradle b/example/android/build.gradle index 7d84ac3c..01b8d324 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.4.32' + ext.kotlin_version = '1.5.20' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index b8f72730..9b1832fb 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/lib/src/get_position.dart b/lib/src/get_position.dart index 76781d2a..a3722fab 100644 --- a/lib/src/get_position.dart +++ b/lib/src/get_position.dart @@ -27,18 +27,23 @@ class GetPosition { final EdgeInsets padding; final double? screenWidth; final double? screenHeight; + final RenderObject? rootRenderObject; GetPosition({ this.key, this.padding = EdgeInsets.zero, this.screenWidth, this.screenHeight, + this.rootRenderObject, }); Rect getRect() { final box = key!.currentContext!.findRenderObject() as RenderBox; - var boxOffset = box.localToGlobal(const Offset(0.0, 0.0)); + var boxOffset = box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ); if (boxOffset.dx.isNaN || boxOffset.dy.isNaN) { return const Rect.fromLTRB(0, 0, 0, 0); } @@ -61,7 +66,10 @@ class GetPosition { ///Get the bottom position of the widget double getBottom() { final box = key!.currentContext!.findRenderObject() as RenderBox; - final boxOffset = box.localToGlobal(const Offset(0.0, 0.0)); + final boxOffset = box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ); if (boxOffset.dy.isNaN) return padding.bottom; final bottomRight = box.size.bottomRight(boxOffset); return bottomRight.dy + padding.bottom; @@ -70,7 +78,10 @@ class GetPosition { ///Get the top position of the widget double getTop() { final box = key!.currentContext!.findRenderObject() as RenderBox; - final boxOffset = box.localToGlobal(const Offset(0.0, 0.0)); + final boxOffset = box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ); if (boxOffset.dy.isNaN) return 0 - padding.top; final topLeft = box.size.topLeft(boxOffset); return topLeft.dy - padding.top; @@ -79,7 +90,10 @@ class GetPosition { ///Get the left position of the widget double getLeft() { final box = key!.currentContext!.findRenderObject() as RenderBox; - final boxOffset = box.localToGlobal(const Offset(0.0, 0.0)); + final boxOffset = box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ); if (boxOffset.dx.isNaN) return 0 - padding.left; final topLeft = box.size.topLeft(boxOffset); return topLeft.dx - padding.left; @@ -88,10 +102,17 @@ class GetPosition { ///Get the right position of the widget double getRight() { final box = key!.currentContext!.findRenderObject() as RenderBox; - final boxOffset = box.localToGlobal(const Offset(0.0, 0.0)); + final boxOffset = box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ); if (boxOffset.dx.isNaN) return padding.right; - final bottomRight = - box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0))); + final bottomRight = box.size.bottomRight( + box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ), + ); return bottomRight.dx + padding.right; } diff --git a/lib/src/layout_overlays.dart b/lib/src/layout_overlays.dart index 82e11eea..c3549c3f 100644 --- a/lib/src/layout_overlays.dart +++ b/lib/src/layout_overlays.dart @@ -47,12 +47,14 @@ class AnchoredOverlay extends StatelessWidget { final bool showOverlay; final OverlayBuilderCallback? overlayBuilder; final Widget? child; + final RenderObject? rootRenderObject; const AnchoredOverlay({ Key? key, this.showOverlay = false, this.overlayBuilder, this.child, + this.rootRenderObject, }) : super(key: key); @override @@ -65,10 +67,19 @@ class AnchoredOverlay extends StatelessWidget { // 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; - final topLeft = - box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0))); - final bottomRight = - box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0))); + + final topLeft = box.size.topLeft( + box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ), + ); + final bottomRight = box.size.bottomRight( + box.localToGlobal( + const Offset(0.0, 0.0), + ancestor: rootRenderObject, + ), + ); Rect anchorBounds; anchorBounds = (topLeft.dx.isNaN || topLeft.dy.isNaN || diff --git a/lib/src/showcase.dart b/lib/src/showcase.dart index 930e8edd..50c6a7f1 100644 --- a/lib/src/showcase.dart +++ b/lib/src/showcase.dart @@ -368,20 +368,32 @@ class _ShowcaseState extends State { bool _enableShowcase = true; Timer? timer; GetPosition? position; + Size? rootWidgetSize; + RenderBox? rootRenderObject; ShowCaseWidgetState get showCaseWidgetState => ShowCaseWidget.of(context); + @override + void initState() { + super.initState(); + initRootWidget(); + } + @override void didChangeDependencies() { super.didChangeDependencies(); _enableShowcase = showCaseWidgetState.enableShowcase; + recalculateRootWidgetSize(); + if (_enableShowcase) { + final size = MediaQuery.of(context).size; position ??= GetPosition( + rootRenderObject: rootRenderObject, key: widget.key, padding: widget.targetPadding, - screenWidth: MediaQuery.of(context).size.width, - screenHeight: MediaQuery.of(context).size.height, + screenWidth: rootWidgetSize?.width ?? size.width, + screenHeight: rootWidgetSize?.height ?? size.height, ); showOverlay(); } @@ -423,9 +435,12 @@ class _ShowcaseState extends State { Widget build(BuildContext context) { if (_enableShowcase) { return AnchoredOverlay( + key: showCaseWidgetState.anchoredOverlayKey, + rootRenderObject: rootRenderObject, overlayBuilder: (context, rectBound, offset) { - final size = MediaQuery.of(context).size; + final size = rootWidgetSize ?? MediaQuery.of(context).size; position = GetPosition( + rootRenderObject: rootRenderObject, key: widget.key, padding: widget.targetPadding, screenWidth: size.width, @@ -440,6 +455,24 @@ class _ShowcaseState extends State { return widget.child; } + void initRootWidget() { + ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) { + rootWidgetSize = showCaseWidgetState.rootWidgetSize; + rootRenderObject = showCaseWidgetState.rootRenderObject; + }); + } + + void recalculateRootWidgetSize() { + ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) { + final rootWidget = + context.findRootAncestorStateOfType>(); + rootRenderObject = rootWidget?.context.findRenderObject() as RenderBox?; + rootWidgetSize = rootWidget == null + ? MediaQuery.of(context).size + : rootRenderObject?.size; + }); + } + Future _nextIfAny() async { if (timer != null && timer!.isActive) { if (showCaseWidgetState.enableAutoPlayLock) { @@ -485,6 +518,7 @@ class _ShowcaseState extends State { Rect rectBound, Size screenSize, ) { + final mediaQuerySize = MediaQuery.of(context).size; var blur = 0.0; if (_showShowCase) { blur = widget.blurValue ?? showCaseWidgetState.blurValue; @@ -520,8 +554,8 @@ class _ShowcaseState extends State { ? BackdropFilter( filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), child: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, + width: mediaQuerySize.width, + height: mediaQuerySize.height, decoration: BoxDecoration( color: widget.overlayColor .withOpacity(widget.overlayOpacity), @@ -529,8 +563,8 @@ class _ShowcaseState extends State { ), ) : Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, + width: mediaQuerySize.width, + height: mediaQuerySize.height, decoration: BoxDecoration( color: widget.overlayColor .withOpacity(widget.overlayOpacity), diff --git a/lib/src/showcase_widget.dart b/lib/src/showcase_widget.dart index 0e662db8..5c56c93f 100644 --- a/lib/src/showcase_widget.dart +++ b/lib/src/showcase_widget.dart @@ -23,6 +23,7 @@ import 'package:flutter/material.dart'; import '../showcaseview.dart'; +import 'extension.dart'; class ShowCaseWidget extends StatefulWidget { final Builder builder; @@ -122,6 +123,9 @@ class ShowCaseWidget extends StatefulWidget { class ShowCaseWidgetState extends State { List? ids; int? activeWidgetId; + RenderBox? rootRenderObject; + Size? rootWidgetSize; + Key? anchoredOverlayKey; /// These properties are only here so that it can be accessed by /// [Showcase] @@ -144,6 +148,23 @@ class ShowCaseWidgetState extends State { /// Returns value of [ShowCaseWidget.blurValue] double get blurValue => widget.blurValue; + @override + void initState() { + super.initState(); + initRootWidget(); + } + + void initRootWidget() { + ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) { + final rootWidget = context.findAncestorStateOfType>(); + rootRenderObject = rootWidget?.context.findRenderObject() as RenderBox?; + rootWidgetSize = rootWidget == null + ? MediaQuery.of(context).size + : rootRenderObject?.size; + anchoredOverlayKey = UniqueKey(); + }); + } + /// Starts Showcase view from the beginning of specified list of widget ids. /// If this function is used when showcase has been disabled then it will /// throw an exception. diff --git a/lib/src/tooltip_widget.dart b/lib/src/tooltip_widget.dart index 3caff7e4..841053d8 100644 --- a/lib/src/tooltip_widget.dart +++ b/lib/src/tooltip_widget.dart @@ -21,10 +21,12 @@ */ import 'dart:math'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'enum.dart'; +import 'extension.dart'; import 'get_position.dart'; import 'measure_size.dart'; @@ -33,7 +35,7 @@ const _kDefaultPaddingFromParent = 14.0; class ToolTipWidget extends StatefulWidget { final GetPosition? position; final Offset? offset; - final Size? screenSize; + final Size screenSize; final String? title; final TextAlign? titleAlignment; final String? description; @@ -122,12 +124,16 @@ class _ToolTipWidgetState extends State position.dy + ((widget.position?.getHeight() ?? 0) / 2); final topPosition = position.dy - ((widget.position?.getHeight() ?? 0) / 2); final hasSpaceInTop = topPosition >= height; + // TODO: need to update for flutter version > 3.8.X + // ignore: deprecated_member_use final EdgeInsets viewInsets = EdgeInsets.fromWindowPadding( - WidgetsBinding.instance.window.viewInsets, - WidgetsBinding.instance.window.devicePixelRatio); + // ignore: deprecated_member_use + ambiguate(WidgetsBinding.instance)?.window.viewInsets ?? ViewPadding.zero, + // ignore: deprecated_member_use + ambiguate(WidgetsBinding.instance)?.window.devicePixelRatio ?? 1, + ); final double actualVisibleScreenHeight = - (widget.screenSize?.height ?? MediaQuery.of(context).size.height) - - viewInsets.bottom; + widget.screenSize.height - viewInsets.bottom; final hasSpaceInBottom = (actualVisibleScreenHeight - bottomPosition) >= height; return widget.tooltipPosition ?? @@ -162,8 +168,8 @@ class _ToolTipWidgetState extends State (widget.descriptionPadding?.right ?? 0) + (widget.descriptionPadding?.left ?? 0)); var maxTextWidth = max(titleLength, descriptionLength); - if (maxTextWidth > widget.screenSize!.width - tooltipScreenEdgePadding) { - tooltipWidth = widget.screenSize!.width - tooltipScreenEdgePadding; + if (maxTextWidth > widget.screenSize.width - tooltipScreenEdgePadding) { + tooltipWidth = widget.screenSize.width - tooltipScreenEdgePadding; } else { tooltipWidth = maxTextWidth + tooltipTextPadding; } @@ -174,7 +180,7 @@ class _ToolTipWidgetState extends State final width = widget.container != null ? _customContainerWidth.value : tooltipWidth; double leftPositionValue = widget.position!.getCenter() - (width * 0.5); - if ((leftPositionValue + width) > MediaQuery.of(context).size.width) { + if ((leftPositionValue + width) > widget.screenSize.width) { return null; } else if ((leftPositionValue) < _kDefaultPaddingFromParent) { return _kDefaultPaddingFromParent; @@ -191,10 +197,10 @@ class _ToolTipWidgetState extends State widget.container != null ? _customContainerWidth.value : tooltipWidth; final left = _getLeft(); - if (left == null || (left + width) > MediaQuery.of(context).size.width) { + if (left == null || (left + width) > widget.screenSize.width) { final rightPosition = widget.position!.getCenter() + (width * 0.5); - return (rightPosition + width) > MediaQuery.of(context).size.width + return (rightPosition + width) > widget.screenSize.width ? _kDefaultPaddingFromParent : null; } else { @@ -206,8 +212,8 @@ class _ToolTipWidgetState extends State double _getSpace() { var space = widget.position!.getCenter() - (widget.contentWidth! / 2); - if (space + widget.contentWidth! > widget.screenSize!.width) { - space = widget.screenSize!.width - widget.contentWidth! - 8; + if (space + widget.contentWidth! > widget.screenSize.width) { + space = widget.screenSize.width - widget.contentWidth! - 8; } else if (space < (widget.contentWidth! / 2)) { space = 16; } @@ -220,7 +226,7 @@ class _ToolTipWidgetState extends State ? 0 : (widget.position!.getCenter() - calculatedLeft); var right = _getLeft() == null - ? (MediaQuery.of(context).size.width - widget.position!.getCenter()) - + ? (widget.screenSize.width - widget.position!.getCenter()) - (_getRight() ?? 0) : 0; final containerWidth = @@ -308,8 +314,14 @@ class _ToolTipWidgetState extends State @override void didChangeDependencies() { - _getTooltipWidth(); super.didChangeDependencies(); + _getTooltipWidth(); + } + + @override + void didUpdateWidget(covariant ToolTipWidget oldWidget) { + super.didUpdateWidget(oldWidget); + _getTooltipWidth(); } @override @@ -539,7 +551,7 @@ class _ToolTipWidgetState extends State double? _getArrowRight(double arrowWidth) { if (_getLeft() != null) return null; - return (MediaQuery.of(context).size.width - widget.position!.getCenter()) - + return (widget.screenSize.width - widget.position!.getCenter()) - (_getRight() ?? 0) - (arrowWidth / 2); } diff --git a/pubspec.yaml b/pubspec.yaml index 8522dd93..de61abee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: showcaseview description: A Flutter package to Showcase/Highlight widgets step by step. -version: 2.0.3 +version: 2.0.4 homepage: https://github.com/simformsolutions/flutter_showcaseview issue_tracker: https://github.com/simformsolutions/flutter_showcaseview/issues