diff --git a/lib/common/skeleton/fav_pgc_item.dart b/lib/common/skeleton/fav_pgc_item.dart index 0273d1550..86fa755ee 100644 --- a/lib/common/skeleton/fav_pgc_item.dart +++ b/lib/common/skeleton/fav_pgc_item.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/skeleton.dart'; -import 'package:flutter/material.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class FavPgcItemSkeleton extends StatelessWidget { const FavPgcItemSkeleton({super.key}); diff --git a/lib/common/skeleton/space_opus.dart b/lib/common/skeleton/space_opus.dart index e62e15c42..2d34fea8e 100644 --- a/lib/common/skeleton/space_opus.dart +++ b/lib/common/skeleton/space_opus.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/skeleton/skeleton.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class SpaceOpusSkeleton extends StatelessWidget { const SpaceOpusSkeleton({super.key}); diff --git a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_dyn.dart b/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_dyn.dart index 25f3c6a60..e121715fc 100644 --- a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_dyn.dart +++ b/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_dyn.dart @@ -17,10 +17,12 @@ library; import 'dart:math' as math; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide DraggableScrollableSheet; +import 'package:flutter/material.dart' + hide DraggableScrollableSheet, LayoutBuilder; /// Controls a [DraggableScrollableSheet]. /// diff --git a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_topic.dart b/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_topic.dart index 03e6c14c4..2c25288fc 100644 --- a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_topic.dart +++ b/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_topic.dart @@ -17,10 +17,12 @@ library; import 'dart:math' as math; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide DraggableScrollableSheet; +import 'package:flutter/material.dart' + hide DraggableScrollableSheet, LayoutBuilder; /// Controls a [DraggableScrollableSheet]. /// diff --git a/lib/common/widgets/flutter/dyn/button.dart b/lib/common/widgets/flutter/dyn/button.dart deleted file mode 100644 index 0651f6813..000000000 --- a/lib/common/widgets/flutter/dyn/button.dart +++ /dev/null @@ -1,789 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// ignore_for_file: uri_does_not_exist_in_doc_import - -/// @docImport 'elevated_button_theme.dart'; -/// @docImport 'menu_anchor.dart'; -/// @docImport 'text_button_theme.dart'; -/// @docImport 'text_theme.dart'; -/// @docImport 'theme.dart'; -library; - -import 'dart:math' as math; - -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart' hide ButtonStyleButton, InkWell; -import 'package:flutter/rendering.dart'; - -/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object. -/// -/// Concrete subclasses must override [defaultStyleOf] and [themeStyleOf]. -/// -/// See also: -/// * [ElevatedButton], a filled button whose material elevates when pressed. -/// * [FilledButton], a filled button that doesn't elevate when pressed. -/// * [FilledButton.tonal], a filled button variant that uses a secondary fill color. -/// * [OutlinedButton], a button with an outlined border and no fill color. -/// * [TextButton], a button with no outline or fill color. -/// * , an overview of each of -/// the Material Design button types and how they should be used in designs. -abstract class ButtonStyleButton extends StatefulWidget { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const ButtonStyleButton({ - super.key, - required this.onPressed, - required this.onLongPress, - required this.onHover, - required this.onFocusChange, - required this.style, - required this.focusNode, - required this.autofocus, - required this.clipBehavior, - this.statesController, - this.isSemanticButton = true, - @Deprecated( - 'Remove this parameter as it is now ignored. ' - 'Use ButtonStyle.iconAlignment instead. ' - 'This feature was deprecated after v3.28.0-1.0.pre.', - ) - this.iconAlignment, - this.tooltip, - required this.child, - }); - - /// Called when the button is tapped or otherwise activated. - /// - /// If this callback and [onLongPress] are null, then the button will be disabled. - /// - /// See also: - /// - /// * [enabled], which is true if the button is enabled. - final VoidCallback? onPressed; - - /// Called when the button is long-pressed. - /// - /// If this callback and [onPressed] are null, then the button will be disabled. - /// - /// See also: - /// - /// * [enabled], which is true if the button is enabled. - final VoidCallback? onLongPress; - - /// Called when a pointer enters or exits the button response area. - /// - /// The value passed to the callback is true if a pointer has entered this - /// part of the material and false if a pointer has exited this part of the - /// material. - final ValueChanged? onHover; - - /// Handler called when the focus changes. - /// - /// Called with true if this widget's node gains focus, and false if it loses - /// focus. - final ValueChanged? onFocusChange; - - /// Customizes this button's appearance. - /// - /// Non-null properties of this style override the corresponding - /// properties in [themeStyleOf] and [defaultStyleOf]. [WidgetStateProperty]s - /// that resolve to non-null values will similarly override the corresponding - /// [WidgetStateProperty]s in [themeStyleOf] and [defaultStyleOf]. - /// - /// Null by default. - final ButtonStyle? style; - - /// {@macro flutter.material.Material.clipBehavior} - /// - /// Defaults to [Clip.none] unless [ButtonStyle.backgroundBuilder] or - /// [ButtonStyle.foregroundBuilder] is specified. In those - /// cases the default is [Clip.antiAlias]. - final Clip? clipBehavior; - - /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode? focusNode; - - /// {@macro flutter.widgets.Focus.autofocus} - final bool autofocus; - - /// {@macro flutter.material.inkwell.statesController} - final WidgetStatesController? statesController; - - /// Determine whether this subtree represents a button. - /// - /// If this is null, the screen reader will not announce "button" when this - /// is focused. This is useful for [MenuItemButton] and [SubmenuButton] when we - /// traverse the menu system. - /// - /// Defaults to true. - final bool? isSemanticButton; - - /// {@macro flutter.material.ButtonStyle.iconAlignment} - @Deprecated( - 'Remove this parameter as it is now ignored. ' - 'Use ButtonStyle.iconAlignment instead. ' - 'This feature was deprecated after v3.28.0-1.0.pre.', - ) - final IconAlignment? iconAlignment; - - /// Text that describes the action that will occur when the button is pressed or - /// hovered over. - /// - /// This text is displayed when the user long-presses or hovers over the button - /// in a tooltip. This string is also used for accessibility. - /// - /// If null, the button will not display a tooltip. - final String? tooltip; - - /// Typically the button's label. - /// - /// {@macro flutter.widgets.ProxyWidget.child} - final Widget? child; - - /// Returns a [ButtonStyle] that's based primarily on the [Theme]'s - /// [ThemeData.textTheme] and [ThemeData.colorScheme], but has most values - /// filled out (non-null). - /// - /// The returned style can be overridden by the [style] parameter and by the - /// style returned by [themeStyleOf] that some button-specific themes like - /// [TextButtonTheme] or [ElevatedButtonTheme] override. For example the - /// default style of the [TextButton] subclass can be overridden with its - /// [TextButton.style] constructor parameter, or with a [TextButtonTheme]. - /// - /// Concrete button subclasses should return a [ButtonStyle] with as many - /// non-null properties as possible, where all of the non-null - /// [WidgetStateProperty] properties resolve to non-null values. - /// - /// ## Properties that can be null - /// - /// Some properties, like [ButtonStyle.fixedSize] would override other values - /// in the same [ButtonStyle] if set, so they are allowed to be null. Here is - /// a summary of properties that are allowed to be null when returned in the - /// [ButtonStyle] returned by this function, an why: - /// - /// - [ButtonStyle.fixedSize] because it would override other values in the - /// same [ButtonStyle], like [ButtonStyle.maximumSize]. - /// - [ButtonStyle.side] because null is a valid value for a button that has - /// no side. [OutlinedButton] returns a non-null default for this, however. - /// - [ButtonStyle.backgroundBuilder] and [ButtonStyle.foregroundBuilder] - /// because they would override the [ButtonStyle.foregroundColor] and - /// [ButtonStyle.backgroundColor] of the same [ButtonStyle]. - /// - /// See also: - /// - /// * [themeStyleOf], returns the ButtonStyle of this button's component - /// theme. - @protected - ButtonStyle defaultStyleOf(BuildContext context); - - /// Returns the ButtonStyle that belongs to the button's component theme. - /// - /// The returned style can be overridden by the [style] parameter. - /// - /// Concrete button subclasses should return the ButtonStyle for the - /// nearest subclass-specific inherited theme, and if no such theme - /// exists, then the same value from the overall [Theme]. - /// - /// See also: - /// - /// * [defaultStyleOf], Returns the default [ButtonStyle] for this button. - @protected - ButtonStyle? themeStyleOf(BuildContext context); - - /// Whether the button is enabled or disabled. - /// - /// Buttons are disabled by default. To enable a button, set its [onPressed] - /// or [onLongPress] properties to a non-null value. - bool get enabled => onPressed != null || onLongPress != null; - - @override - State createState() => _ButtonStyleState(); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add( - FlagProperty('enabled', value: enabled, ifFalse: 'disabled'), - ) - ..add( - DiagnosticsProperty('style', style, defaultValue: null), - ) - ..add( - DiagnosticsProperty( - 'focusNode', - focusNode, - defaultValue: null, - ), - ); - } - - /// Returns null if [value] is null, otherwise `WidgetStatePropertyAll(value)`. - /// - /// A convenience method for subclasses. - static WidgetStateProperty? allOrNull(T? value) => - value == null ? null : WidgetStatePropertyAll(value); - - /// Returns null if [enabled] and [disabled] are null. - /// Otherwise, returns a [WidgetStateProperty] that resolves to [disabled] - /// when [WidgetState.disabled] is active, and [enabled] otherwise. - /// - /// A convenience method for subclasses. - static WidgetStateProperty? defaultColor( - Color? enabled, - Color? disabled, - ) { - if ((enabled ?? disabled) == null) { - return null; - } - return WidgetStateProperty.fromMap({ - WidgetState.disabled: disabled, - WidgetState.any: enabled, - }); - } - - /// A convenience method used by subclasses in the framework, that returns an - /// interpolated value based on the [fontSizeMultiplier] parameter: - /// - /// * 0 - 1 [geometry1x] - /// * 1 - 2 lerp([geometry1x], [geometry2x], [fontSizeMultiplier] - 1) - /// * 2 - 3 lerp([geometry2x], [geometry3x], [fontSizeMultiplier] - 2) - /// * otherwise [geometry3x] - /// - /// This method is used by the framework for estimating the default paddings to - /// use on a button with a text label, when the system text scaling setting - /// changes. It's usually supplied with empirical [geometry1x], [geometry2x], - /// [geometry3x] values adjusted for different system text scaling values, when - /// the unscaled font size is set to 14.0 (the default [TextTheme.labelLarge] - /// value). - /// - /// The `fontSizeMultiplier` argument, for historical reasons, is the default - /// font size specified in the [ButtonStyle], scaled by the ambient font - /// scaler, then divided by 14.0 (the default font size used in buttons). - static EdgeInsetsGeometry scaledPadding( - EdgeInsetsGeometry geometry1x, - EdgeInsetsGeometry geometry2x, - EdgeInsetsGeometry geometry3x, - double fontSizeMultiplier, - ) { - return switch (fontSizeMultiplier) { - <= 1 => geometry1x, - < 2 => EdgeInsetsGeometry.lerp( - geometry1x, - geometry2x, - fontSizeMultiplier - 1, - )!, - < 3 => EdgeInsetsGeometry.lerp( - geometry2x, - geometry3x, - fontSizeMultiplier - 2, - )!, - _ => geometry3x, - }; - } -} - -/// The base [State] class for buttons whose style is defined by a [ButtonStyle] object. -/// -/// See also: -/// -/// * [ButtonStyleButton], the [StatefulWidget] subclass for which this class is the [State]. -/// * [ElevatedButton], a filled button whose material elevates when pressed. -/// * [FilledButton], a filled ButtonStyleButton that doesn't elevate when pressed. -/// * [OutlinedButton], similar to [TextButton], but with an outline. -/// * [TextButton], a simple button without a shadow. -class _ButtonStyleState extends State - with TickerProviderStateMixin { - AnimationController? controller; - double? elevation; - Color? backgroundColor; - WidgetStatesController? internalStatesController; - - void handleStatesControllerChange() { - // Force a rebuild to resolve WidgetStateProperty properties - setState(() {}); - } - - WidgetStatesController get statesController => - widget.statesController ?? internalStatesController!; - - void initStatesController() { - if (widget.statesController == null) { - internalStatesController = WidgetStatesController(); - } - statesController - ..update(WidgetState.disabled, !widget.enabled) - ..addListener(handleStatesControllerChange); - } - - @override - void initState() { - super.initState(); - initStatesController(); - } - - @override - void didUpdateWidget(ButtonStyleButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.statesController != oldWidget.statesController) { - oldWidget.statesController?.removeListener(handleStatesControllerChange); - if (widget.statesController != null) { - internalStatesController?.dispose(); - internalStatesController = null; - } - initStatesController(); - } - if (widget.enabled != oldWidget.enabled) { - statesController.update(WidgetState.disabled, !widget.enabled); - if (!widget.enabled) { - // The button may have been disabled while a press gesture is currently underway. - statesController.update(WidgetState.pressed, false); - } - } - } - - @override - void dispose() { - statesController.removeListener(handleStatesControllerChange); - internalStatesController?.dispose(); - controller?.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final IconThemeData iconTheme = IconTheme.of(context); - final ButtonStyle? widgetStyle = widget.style; - final ButtonStyle? themeStyle = widget.themeStyleOf(context); - final ButtonStyle defaultStyle = widget.defaultStyleOf(context); - - T? effectiveValue(T? Function(ButtonStyle? style) getProperty) { - final T? widgetValue = getProperty(widgetStyle); - final T? themeValue = getProperty(themeStyle); - final T? defaultValue = getProperty(defaultStyle); - return widgetValue ?? themeValue ?? defaultValue; - } - - T? resolve( - WidgetStateProperty? Function(ButtonStyle? style) getProperty, - ) { - return effectiveValue((ButtonStyle? style) { - return getProperty(style)?.resolve(statesController.value); - }); - } - - Color? effectiveIconColor() { - return widgetStyle?.iconColor?.resolve(statesController.value) ?? - themeStyle?.iconColor?.resolve(statesController.value) ?? - widgetStyle?.foregroundColor?.resolve(statesController.value) ?? - themeStyle?.foregroundColor?.resolve(statesController.value) ?? - defaultStyle.iconColor?.resolve(statesController.value) ?? - // Fallback to foregroundColor if iconColor is null. - defaultStyle.foregroundColor?.resolve(statesController.value); - } - - final double? resolvedElevation = resolve( - (ButtonStyle? style) => style?.elevation, - ); - final TextStyle? resolvedTextStyle = resolve( - (ButtonStyle? style) => style?.textStyle, - ); - Color? resolvedBackgroundColor = resolve( - (ButtonStyle? style) => style?.backgroundColor, - ); - final Color? resolvedForegroundColor = resolve( - (ButtonStyle? style) => style?.foregroundColor, - ); - final Color? resolvedShadowColor = resolve( - (ButtonStyle? style) => style?.shadowColor, - ); - final Color? resolvedSurfaceTintColor = resolve( - (ButtonStyle? style) => style?.surfaceTintColor, - ); - final EdgeInsetsGeometry? resolvedPadding = resolve( - (ButtonStyle? style) => style?.padding, - ); - final Size? resolvedMinimumSize = resolve( - (ButtonStyle? style) => style?.minimumSize, - ); - final Size? resolvedFixedSize = resolve( - (ButtonStyle? style) => style?.fixedSize, - ); - final Size? resolvedMaximumSize = resolve( - (ButtonStyle? style) => style?.maximumSize, - ); - final Color? resolvedIconColor = effectiveIconColor(); - final double? resolvedIconSize = resolve( - (ButtonStyle? style) => style?.iconSize, - ); - final BorderSide? resolvedSide = resolve( - (ButtonStyle? style) => style?.side, - ); - final OutlinedBorder? resolvedShape = resolve( - (ButtonStyle? style) => style?.shape, - ); - - final WidgetStateMouseCursor mouseCursor = _MouseCursor( - (Set states) => effectiveValue( - (ButtonStyle? style) => style?.mouseCursor?.resolve(states), - ), - ); - - final WidgetStateProperty overlayColor = - WidgetStateProperty.resolveWith( - (Set states) => effectiveValue( - (ButtonStyle? style) => style?.overlayColor?.resolve(states), - ), - ); - - final VisualDensity? resolvedVisualDensity = effectiveValue( - (ButtonStyle? style) => style?.visualDensity, - ); - final MaterialTapTargetSize? resolvedTapTargetSize = effectiveValue( - (ButtonStyle? style) => style?.tapTargetSize, - ); - final Duration? resolvedAnimationDuration = effectiveValue( - (ButtonStyle? style) => style?.animationDuration, - ); - final bool resolvedEnableFeedback = - effectiveValue((ButtonStyle? style) => style?.enableFeedback) ?? true; - final AlignmentGeometry? resolvedAlignment = effectiveValue( - (ButtonStyle? style) => style?.alignment, - ); - final Offset densityAdjustment = resolvedVisualDensity!.baseSizeAdjustment; - final InteractiveInkFeatureFactory? resolvedSplashFactory = effectiveValue( - (ButtonStyle? style) => style?.splashFactory, - ); - final ButtonLayerBuilder? resolvedBackgroundBuilder = effectiveValue( - (ButtonStyle? style) => style?.backgroundBuilder, - ); - final ButtonLayerBuilder? resolvedForegroundBuilder = effectiveValue( - (ButtonStyle? style) => style?.foregroundBuilder, - ); - - final Clip effectiveClipBehavior = - widget.clipBehavior ?? - ((resolvedBackgroundBuilder ?? resolvedForegroundBuilder) != null - ? Clip.antiAlias - : Clip.none); - - BoxConstraints effectiveConstraints = resolvedVisualDensity - .effectiveConstraints( - BoxConstraints( - minWidth: resolvedMinimumSize!.width, - minHeight: resolvedMinimumSize.height, - maxWidth: resolvedMaximumSize!.width, - maxHeight: resolvedMaximumSize.height, - ), - ); - if (resolvedFixedSize != null) { - final Size size = effectiveConstraints.constrain(resolvedFixedSize); - if (size.width.isFinite) { - effectiveConstraints = effectiveConstraints.copyWith( - minWidth: size.width, - maxWidth: size.width, - ); - } - if (size.height.isFinite) { - effectiveConstraints = effectiveConstraints.copyWith( - minHeight: size.height, - maxHeight: size.height, - ); - } - } - - // Per the Material Design team: don't allow the VisualDensity - // adjustment to reduce the width of the left/right padding. If we - // did, VisualDensity.compact, the default for desktop/web, would - // reduce the horizontal padding to zero. - final double dy = densityAdjustment.dy; - final double dx = math.max(0, densityAdjustment.dx); - final EdgeInsetsGeometry padding = resolvedPadding! - .add(EdgeInsets.fromLTRB(dx, dy, dx, dy)) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); - - // If an opaque button's background is becoming translucent while its - // elevation is changing, change the elevation first. Material implicitly - // animates its elevation but not its color. SKIA renders non-zero - // elevations as a shadow colored fill behind the Material's background. - if (resolvedAnimationDuration! > Duration.zero && - elevation != null && - backgroundColor != null && - elevation != resolvedElevation && - backgroundColor!.value != resolvedBackgroundColor!.value && - backgroundColor!.opacity == 1 && - resolvedBackgroundColor.opacity < 1 && - resolvedElevation == 0) { - if (controller?.duration != resolvedAnimationDuration) { - controller?.dispose(); - controller = - AnimationController( - duration: resolvedAnimationDuration, - vsync: this, - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - setState(() {}); // Rebuild with the final background color. - } - }); - } - resolvedBackgroundColor = - backgroundColor; // Defer changing the background color. - controller!.value = 0; - controller!.forward(); - } - elevation = resolvedElevation; - backgroundColor = resolvedBackgroundColor; - - Widget result = Padding( - padding: padding, - child: Align( - alignment: resolvedAlignment!, - widthFactor: 1.0, - heightFactor: 1.0, - child: resolvedForegroundBuilder != null - ? resolvedForegroundBuilder( - context, - statesController.value, - widget.child, - ) - : widget.child, - ), - ); - if (resolvedBackgroundBuilder != null) { - result = resolvedBackgroundBuilder( - context, - statesController.value, - result, - ); - } - - result = AnimatedTheme( - duration: resolvedAnimationDuration, - data: theme.copyWith( - iconTheme: iconTheme.merge( - IconThemeData(color: resolvedIconColor, size: resolvedIconSize), - ), - ), - child: InkWell( - onTap: widget.onPressed, - onLongPress: widget.onLongPress, - onHover: widget.onHover, - mouseCursor: mouseCursor, - enableFeedback: resolvedEnableFeedback, - focusNode: widget.focusNode, - canRequestFocus: widget.enabled, - onFocusChange: widget.onFocusChange, - autofocus: widget.autofocus, - splashFactory: resolvedSplashFactory, - overlayColor: overlayColor, - highlightColor: Colors.transparent, - customBorder: resolvedShape!.copyWith(side: resolvedSide), - statesController: statesController, - child: result, - ), - ); - - if (widget.tooltip != null) { - result = Tooltip(message: widget.tooltip, child: result); - } - - final Size minSize; - switch (resolvedTapTargetSize!) { - case MaterialTapTargetSize.padded: - minSize = Size( - kMinInteractiveDimension + densityAdjustment.dx, - kMinInteractiveDimension + densityAdjustment.dy, - ); - assert(minSize.width >= 0.0); - assert(minSize.height >= 0.0); - case MaterialTapTargetSize.shrinkWrap: - minSize = Size.zero; - } - - return Semantics( - container: true, - button: widget.isSemanticButton, - enabled: widget.enabled, - child: _InputPadding( - minSize: minSize, - child: ConstrainedBox( - constraints: effectiveConstraints, - child: Material( - elevation: resolvedElevation!, - textStyle: resolvedTextStyle?.copyWith( - color: resolvedForegroundColor, - ), - shape: resolvedShape.copyWith(side: resolvedSide), - color: resolvedBackgroundColor, - shadowColor: resolvedShadowColor, - surfaceTintColor: resolvedSurfaceTintColor, - type: resolvedBackgroundColor == null - ? MaterialType.transparency - : MaterialType.button, - animationDuration: resolvedAnimationDuration, - clipBehavior: effectiveClipBehavior, - borderOnForeground: false, - child: result, - ), - ), - ), - ); - } -} - -class _MouseCursor extends WidgetStateMouseCursor { - const _MouseCursor(this.resolveCallback); - - final WidgetPropertyResolver resolveCallback; - - @override - MouseCursor resolve(Set states) => resolveCallback(states)!; - - @override - String get debugDescription => 'ButtonStyleButton_MouseCursor'; -} - -/// A widget to pad the area around a [ButtonStyleButton]'s inner [Material]. -/// -/// Redirect taps that occur in the padded area around the child to the center -/// of the child. This increases the size of the button and the button's -/// "tap target", but not its material or its ink splashes. -class _InputPadding extends SingleChildRenderObjectWidget { - const _InputPadding({super.child, required this.minSize}); - - final Size minSize; - - @override - RenderObject createRenderObject(BuildContext context) { - return _RenderInputPadding(minSize); - } - - @override - void updateRenderObject( - BuildContext context, - covariant _RenderInputPadding renderObject, - ) { - renderObject.minSize = minSize; - } -} - -class _RenderInputPadding extends RenderShiftedBox { - _RenderInputPadding(this._minSize, [RenderBox? child]) : super(child); - - Size get minSize => _minSize; - Size _minSize; - set minSize(Size value) { - if (_minSize == value) { - return; - } - _minSize = value; - markNeedsLayout(); - } - - @override - double computeMinIntrinsicWidth(double height) { - if (child != null) { - return math.max(child!.getMinIntrinsicWidth(height), minSize.width); - } - return 0.0; - } - - @override - double computeMinIntrinsicHeight(double width) { - if (child != null) { - return math.max(child!.getMinIntrinsicHeight(width), minSize.height); - } - return 0.0; - } - - @override - double computeMaxIntrinsicWidth(double height) { - if (child != null) { - return math.max(child!.getMaxIntrinsicWidth(height), minSize.width); - } - return 0.0; - } - - @override - double computeMaxIntrinsicHeight(double width) { - if (child != null) { - return math.max(child!.getMaxIntrinsicHeight(width), minSize.height); - } - return 0.0; - } - - Size _computeSize({ - required BoxConstraints constraints, - required ChildLayouter layoutChild, - }) { - if (child != null) { - final Size childSize = layoutChild(child!, constraints); - final double width = math.max(childSize.width, minSize.width); - final double height = math.max(childSize.height, minSize.height); - return constraints.constrain(Size(width, height)); - } - return Size.zero; - } - - @override - Size computeDryLayout(BoxConstraints constraints) { - return _computeSize( - constraints: constraints, - layoutChild: ChildLayoutHelper.dryLayoutChild, - ); - } - - @override - double? computeDryBaseline( - covariant BoxConstraints constraints, - TextBaseline baseline, - ) { - final RenderBox? child = this.child; - if (child == null) { - return null; - } - final double? result = child.getDryBaseline(constraints, baseline); - if (result == null) { - return null; - } - final Size childSize = child.getDryLayout(constraints); - return result + - Alignment.center - .alongOffset(getDryLayout(constraints) - childSize as Offset) - .dy; - } - - @override - void performLayout() { - size = _computeSize( - constraints: constraints, - layoutChild: ChildLayoutHelper.layoutChild, - ); - if (child != null) { - final childParentData = child!.parentData! as BoxParentData; - childParentData.offset = Alignment.center.alongOffset( - size - child!.size as Offset, - ); - } - } - - @override - bool hitTest(BoxHitTestResult result, {required Offset position}) { - if (super.hitTest(result, position: position)) { - return true; - } - final Offset center = child!.size.center(Offset.zero); - return result.addWithRawTransform( - transform: MatrixUtils.forceToPoint(center), - position: center, - hitTest: (BoxHitTestResult result, Offset position) { - assert(position == center); - return child!.hitTest(result, position: center); - }, - ); - } -} diff --git a/lib/common/widgets/flutter/dyn/ink_well.dart b/lib/common/widgets/flutter/dyn/ink_well.dart deleted file mode 100644 index 5c456fc04..000000000 --- a/lib/common/widgets/flutter/dyn/ink_well.dart +++ /dev/null @@ -1,1446 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// ignore_for_file: uri_does_not_exist_in_doc_import - -/// @docImport 'data_table.dart'; -/// @docImport 'elevated_button.dart'; -/// @docImport 'icon_button.dart'; -/// @docImport 'ink_decoration.dart'; -/// @docImport 'ink_ripple.dart'; -/// @docImport 'ink_splash.dart'; -/// @docImport 'text_button.dart'; -library; - -import 'dart:async'; -import 'dart:collection'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide InkWell; -import 'package:flutter/rendering.dart'; - -abstract class _ParentInkResponseState { - void markChildInkResponsePressed( - _ParentInkResponseState childState, - bool value, - ); -} - -class _ParentInkResponseProvider extends InheritedWidget { - const _ParentInkResponseProvider({required this.state, required super.child}); - - final _ParentInkResponseState state; - - @override - bool updateShouldNotify(_ParentInkResponseProvider oldWidget) => - state != oldWidget.state; - - static _ParentInkResponseState? maybeOf(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType<_ParentInkResponseProvider>() - ?.state; - } -} - -typedef _GetRectCallback = RectCallback? Function(RenderBox referenceBox); -typedef _CheckContext = bool Function(BuildContext context); - -/// An area of a [Material] that responds to touch. Has a configurable shape and -/// can be configured to clip splashes that extend outside its bounds or not. -/// -/// For a variant of this widget that is specialized for rectangular areas that -/// always clip splashes, see [InkWell]. -/// -/// An [InkResponse] widget does two things when responding to a tap: -/// -/// * It starts to animate a _highlight_. The shape of the highlight is -/// determined by [highlightShape]. If it is a [BoxShape.circle], the -/// default, then the highlight is a circle of fixed size centered in the -/// [InkResponse]. If it is [BoxShape.rectangle], then the highlight is a box -/// the size of the [InkResponse] itself, unless [getRectCallback] is -/// provided, in which case that callback defines the rectangle. The color of -/// the highlight is set by [highlightColor]. -/// -/// * Simultaneously, it starts to animate a _splash_. This is a growing circle -/// initially centered on the tap location. If this is a [containedInkWell], -/// the splash grows to the [radius] while remaining centered at the tap -/// location. Otherwise, the splash migrates to the center of the box as it -/// grows. -/// -/// The following two diagrams show how [InkResponse] looks when tapped if the -/// [highlightShape] is [BoxShape.circle] (the default) and [containedInkWell] -/// is false (also the default). -/// -/// The first diagram shows how it looks if the [InkResponse] is relatively -/// large: -/// -/// ![The highlight is a disc centered in the box, smaller than the child widget.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_large.png) -/// -/// The second diagram shows how it looks if the [InkResponse] is small: -/// -/// ![The highlight is a disc overflowing the box, centered on the child.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_small.png) -/// -/// The main thing to notice from these diagrams is that the splashes happily -/// exceed the bounds of the widget (because [containedInkWell] is false). -/// -/// The following diagram shows the effect when the [InkResponse] has a -/// [highlightShape] of [BoxShape.rectangle] with [containedInkWell] set to -/// true. These are the values used by [InkWell]. -/// -/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png) -/// -/// The [InkResponse] widget must have a [Material] widget as an ancestor. The -/// [Material] widget is where the ink reactions are actually painted. This -/// matches the Material Design premise wherein the [Material] is what is -/// actually reacting to touches by spreading ink. -/// -/// If a Widget uses this class directly, it should include the following line -/// at the top of its build function to call [debugCheckHasMaterial]: -/// -/// ```dart -/// assert(debugCheckHasMaterial(context)); -/// ``` -/// -/// ## Troubleshooting -/// -/// ### The ink splashes aren't visible! -/// -/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or -/// [DecoratedBox], between the [Material] widget and the [InkResponse] widget, -/// then the splash won't be visible because it will be under the opaque graphic. -/// This is because ink splashes draw on the underlying [Material] itself, as -/// if the ink was spreading inside the material. -/// -/// The [Ink] widget can be used as a replacement for [Image], [Container], or -/// [DecoratedBox] to ensure that the image or decoration also paints in the -/// [Material] itself, below the ink. -/// -/// If this is not possible for some reason, e.g. because you are using an -/// opaque [CustomPaint] widget, alternatively consider using a second -/// [Material] above the opaque widget but below the [InkResponse] (as an -/// ancestor to the ink response). The [MaterialType.transparency] material -/// kind can be used for this purpose. -/// -/// See also: -/// -/// * [GestureDetector], for listening for gestures without ink splashes. -/// * [ElevatedButton] and [TextButton], two kinds of buttons in Material Design. -/// * [IconButton], which combines [InkResponse] with an [Icon]. -class InkResponse extends StatelessWidget { - /// Creates an area of a [Material] that responds to touch. - /// - /// Must have an ancestor [Material] widget in which to cause ink reactions. - const InkResponse({ - super.key, - this.child, - this.onTap, - this.onTapDown, - this.onTapUp, - this.onTapCancel, - this.onDoubleTap, - this.onLongPress, - this.onLongPressUp, - this.onSecondaryTap, - this.onSecondaryTapUp, - this.onSecondaryTapDown, - this.onSecondaryTapCancel, - this.onHighlightChanged, - this.onHover, - this.mouseCursor, - this.containedInkWell = false, - this.highlightShape = BoxShape.circle, - this.radius, - this.borderRadius, - this.customBorder, - this.focusColor, - this.hoverColor, - this.highlightColor, - this.overlayColor, - this.splashColor, - this.splashFactory, - this.enableFeedback = true, - this.excludeFromSemantics = false, - this.focusNode, - this.canRequestFocus = true, - this.onFocusChange, - this.autofocus = false, - this.statesController, - this.hoverDuration, - }); - - /// The widget below this widget in the tree. - /// - /// {@macro flutter.widgets.ProxyWidget.child} - final Widget? child; - - /// Called when the user taps this part of the material. - final GestureTapCallback? onTap; - - /// Called when the user taps down this part of the material. - final GestureTapDownCallback? onTapDown; - - /// Called when the user releases a tap that was started on this part of the - /// material. [onTap] is called immediately after. - final GestureTapUpCallback? onTapUp; - - /// Called when the user cancels a tap that was started on this part of the - /// material. - final GestureTapCallback? onTapCancel; - - /// Called when the user double taps this part of the material. - final GestureTapCallback? onDoubleTap; - - /// Called when the user long-presses on this part of the material. - final GestureLongPressCallback? onLongPress; - - /// Called when the user lifts their finger after a long press on the button. - /// - /// This callback is triggered at the end of a long press gesture, specifically - /// after the user holds a long press and then releases it. It does not include - /// position details. - /// - /// Common use cases include performing an action only after the long press completes, - /// such as displaying a context menu or confirming a held gesture. - /// - /// See also: - /// * [onLongPress], which is triggered when the long press gesture is first recognized. - final GestureLongPressUpCallback? onLongPressUp; - - /// Called when the user taps this part of the material with a secondary button. - /// - /// See also: - /// - /// * [kSecondaryButton], the button this callback responds to. - final GestureTapCallback? onSecondaryTap; - - /// Called when the user taps down on this part of the material with a - /// secondary button. - /// - /// See also: - /// - /// * [kSecondaryButton], the button this callback responds to. - final GestureTapDownCallback? onSecondaryTapDown; - - /// Called when the user releases a secondary button tap that was started on - /// this part of the material. [onSecondaryTap] is called immediately after. - /// - /// See also: - /// - /// * [onSecondaryTap], a handler triggered right after this one that doesn't - /// pass any details about the tap. - /// * [kSecondaryButton], the button this callback responds to. - final GestureTapUpCallback? onSecondaryTapUp; - - /// Called when the user cancels a secondary button tap that was started on - /// this part of the material. - /// - /// See also: - /// - /// * [kSecondaryButton], the button this callback responds to. - final GestureTapCallback? onSecondaryTapCancel; - - /// Called when this part of the material either becomes highlighted or stops - /// being highlighted. - /// - /// The value passed to the callback is true if this part of the material has - /// become highlighted and false if this part of the material has stopped - /// being highlighted. - /// - /// If all of [onTap], [onDoubleTap], [onLongPress], and [onLongPressUp] become null while a - /// gesture is ongoing, then [onTapCancel] will be fired and - /// [onHighlightChanged] will be fired with the value false _during the - /// build_. This means, for instance, that in that scenario [State.setState] - /// cannot be called. - final ValueChanged? onHighlightChanged; - - /// Called when a pointer enters or exits the ink response area. - /// - /// The value passed to the callback is true if a pointer has entered this - /// part of the material and false if a pointer has exited this part of the - /// material. - final ValueChanged? onHover; - - /// The cursor for a mouse pointer when it enters or is hovering over the - /// widget. - /// - /// {@template flutter.material.InkWell.mouseCursor} - /// If [mouseCursor] is a [WidgetStateMouseCursor], - /// [WidgetStateProperty.resolve] is used for the following [WidgetState]s: - /// - /// * [WidgetState.hovered]. - /// * [WidgetState.focused]. - /// * [WidgetState.disabled]. - /// {@endtemplate} - /// - /// If this property is null, [WidgetStateMouseCursor.adaptiveClickable] will be used. - final MouseCursor? mouseCursor; - - /// Whether this ink response should be clipped its bounds. - /// - /// This flag also controls whether the splash migrates to the center of the - /// [InkResponse] or not. If [containedInkWell] is true, the splash remains - /// centered around the tap location. If it is false, the splash migrates to - /// the center of the [InkResponse] as it grows. - /// - /// See also: - /// - /// * [highlightShape], the shape of the focus, hover, and pressed - /// highlights. - /// * [borderRadius], which controls the corners when the box is a rectangle. - /// * [getRectCallback], which controls the size and position of the box when - /// it is a rectangle. - final bool containedInkWell; - - /// The shape (e.g., circle, rectangle) to use for the highlight drawn around - /// this part of the material when pressed, hovered over, or focused. - /// - /// The same shape is used for the pressed highlight (see [highlightColor]), - /// the focus highlight (see [focusColor]), and the hover highlight (see - /// [hoverColor]). - /// - /// If the shape is [BoxShape.circle], then the highlight is centered on the - /// [InkResponse]. If the shape is [BoxShape.rectangle], then the highlight - /// fills the [InkResponse], or the rectangle provided by [getRectCallback] if - /// the callback is specified. - /// - /// See also: - /// - /// * [containedInkWell], which controls clipping behavior. - /// * [borderRadius], which controls the corners when the box is a rectangle. - /// * [highlightColor], the color of the highlight. - /// * [getRectCallback], which controls the size and position of the box when - /// it is a rectangle. - final BoxShape highlightShape; - - /// The radius of the ink splash. - /// - /// Splashes grow up to this size. By default, this size is determined from - /// the size of the rectangle provided by [getRectCallback], or the size of - /// the [InkResponse] itself. - /// - /// See also: - /// - /// * [splashColor], the color of the splash. - /// * [splashFactory], which defines the appearance of the splash. - final double? radius; - - /// The border radius of the containing rectangle. This is effective only if - /// [highlightShape] is [BoxShape.rectangle]. - /// - /// If this is null, it is interpreted as [BorderRadius.zero]. - final BorderRadius? borderRadius; - - /// The custom clip border. - /// - /// If this is null, the ink response will not clip its content. - final ShapeBorder? customBorder; - - /// The color of the ink response when the parent widget is focused. If this - /// property is null then the focus color of the theme, - /// [ThemeData.focusColor], will be used. - /// - /// See also: - /// - /// * [highlightShape], the shape of the focus, hover, and pressed - /// highlights. - /// * [hoverColor], the color of the hover highlight. - /// * [splashColor], the color of the splash. - /// * [splashFactory], which defines the appearance of the splash. - final Color? focusColor; - - /// The color of the ink response when a pointer is hovering over it. If this - /// property is null then the hover color of the theme, - /// [ThemeData.hoverColor], will be used. - /// - /// See also: - /// - /// * [highlightShape], the shape of the focus, hover, and pressed - /// highlights. - /// * [highlightColor], the color of the pressed highlight. - /// * [focusColor], the color of the focus highlight. - /// * [splashColor], the color of the splash. - /// * [splashFactory], which defines the appearance of the splash. - final Color? hoverColor; - - /// The highlight color of the ink response when pressed. If this property is - /// null then the highlight color of the theme, [ThemeData.highlightColor], - /// will be used. - /// - /// See also: - /// - /// * [hoverColor], the color of the hover highlight. - /// * [focusColor], the color of the focus highlight. - /// * [highlightShape], the shape of the focus, hover, and pressed - /// highlights. - /// * [splashColor], the color of the splash. - /// * [splashFactory], which defines the appearance of the splash. - final Color? highlightColor; - - /// Defines the ink response focus, hover, and splash colors. - /// - /// This default null property can be used as an alternative to - /// [focusColor], [hoverColor], [highlightColor], and - /// [splashColor]. If non-null, it is resolved against one of - /// [WidgetState.focused], [WidgetState.hovered], and - /// [WidgetState.pressed]. It's convenient to use when the parent - /// widget can pass along its own WidgetStateProperty value for - /// the overlay color. - /// - /// [WidgetState.pressed] triggers a ripple (an ink splash), per - /// the current Material Design spec. The [overlayColor] doesn't map - /// a state to [highlightColor] because a separate highlight is not - /// used by the current design guidelines. See - /// https://material.io/design/interaction/states.html#pressed - /// - /// If the overlay color is null or resolves to null, then [focusColor], - /// [hoverColor], [splashColor] and their defaults are used instead. - /// - /// See also: - /// - /// * The Material Design specification for overlay colors and how they - /// match a component's state: - /// . - final WidgetStateProperty? overlayColor; - - /// The splash color of the ink response. If this property is null then the - /// splash color of the theme, [ThemeData.splashColor], will be used. - /// - /// See also: - /// - /// * [splashFactory], which defines the appearance of the splash. - /// * [radius], the (maximum) size of the ink splash. - /// * [highlightColor], the color of the highlight. - final Color? splashColor; - - /// Defines the appearance of the splash. - /// - /// Defaults to the value of the theme's splash factory: [ThemeData.splashFactory]. - /// - /// See also: - /// - /// * [radius], the (maximum) size of the ink splash. - /// * [splashColor], the color of the splash. - /// * [highlightColor], the color of the highlight. - /// * [InkSplash.splashFactory], which defines the default splash. - /// * [InkRipple.splashFactory], which defines a splash that spreads out - /// more aggressively than the default. - final InteractiveInkFeatureFactory? splashFactory; - - /// Whether detected gestures should provide acoustic and/or haptic feedback. - /// - /// For example, on Android a tap will produce a clicking sound and a - /// long-press will produce a short vibration, when feedback is enabled. - /// - /// See also: - /// - /// * [Feedback] for providing platform-specific feedback to certain actions. - final bool enableFeedback; - - /// Whether to exclude the gestures introduced by this widget from the - /// semantics tree. - /// - /// For example, a long-press gesture for showing a tooltip is usually - /// excluded because the tooltip itself is included in the semantics - /// tree directly and so having a gesture to show it would result in - /// duplication of information. - final bool excludeFromSemantics; - - /// {@template flutter.material.inkwell.onFocusChange} - /// Handler called when the focus changes. - /// - /// Called with true if this widget's node gains focus, and false if it loses - /// focus. - /// {@endtemplate} - final ValueChanged? onFocusChange; - - /// {@macro flutter.widgets.Focus.autofocus} - final bool autofocus; - - /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode? focusNode; - - /// {@macro flutter.widgets.Focus.canRequestFocus} - final bool canRequestFocus; - - /// The rectangle to use for the highlight effect and for clipping - /// the splash effects if [containedInkWell] is true. - /// - /// This method is intended to be overridden by descendants that - /// specialize [InkResponse] for unusual cases. For example, - /// [TableRowInkWell] implements this method to return the rectangle - /// corresponding to the row that the widget is in. - /// - /// The default behavior returns null, which is equivalent to - /// returning the referenceBox argument's bounding box (though - /// slightly more efficient). - RectCallback? getRectCallback(RenderBox referenceBox) => null; - - /// {@template flutter.material.inkwell.statesController} - /// Represents the interactive "state" of this widget in terms of - /// a set of [WidgetState]s, like [WidgetState.pressed] and - /// [WidgetState.focused]. - /// - /// Classes based on this one can provide their own - /// [WidgetStatesController] to which they've added listeners. - /// They can also update the controller's [WidgetStatesController.value] - /// however, this may only be done when it's safe to call - /// [State.setState], like in an event handler. - /// {@endtemplate} - final WidgetStatesController? statesController; - - /// The duration of the animation that animates the hover effect. - /// - /// The default is 50ms. - final Duration? hoverDuration; - - @override - Widget build(BuildContext context) { - final _ParentInkResponseState? parentState = - _ParentInkResponseProvider.maybeOf(context); - return _InkResponseStateWidget( - onTap: onTap, - onTapDown: onTapDown, - onTapUp: onTapUp, - onTapCancel: onTapCancel, - onDoubleTap: onDoubleTap, - onLongPress: onLongPress, - onLongPressUp: onLongPressUp, - onSecondaryTap: onSecondaryTap, - onSecondaryTapUp: onSecondaryTapUp, - onSecondaryTapDown: onSecondaryTapDown, - onSecondaryTapCancel: onSecondaryTapCancel, - onHighlightChanged: onHighlightChanged, - onHover: onHover, - mouseCursor: mouseCursor, - containedInkWell: containedInkWell, - highlightShape: highlightShape, - radius: radius, - borderRadius: borderRadius, - customBorder: customBorder, - focusColor: focusColor, - hoverColor: hoverColor, - highlightColor: highlightColor, - overlayColor: overlayColor, - splashColor: splashColor, - splashFactory: splashFactory, - enableFeedback: enableFeedback, - excludeFromSemantics: excludeFromSemantics, - focusNode: focusNode, - canRequestFocus: canRequestFocus, - onFocusChange: onFocusChange, - autofocus: autofocus, - parentState: parentState, - getRectCallback: getRectCallback, - debugCheckContext: debugCheckContext, - statesController: statesController, - hoverDuration: hoverDuration, - child: child, - ); - } - - /// Asserts that the given context satisfies the prerequisites for - /// this class. - /// - /// This method is intended to be overridden by descendants that - /// specialize [InkResponse] for unusual cases. For example, - /// [TableRowInkWell] implements this method to verify that the widget is - /// in a table. - @mustCallSuper - bool debugCheckContext(BuildContext context) { - assert(debugCheckHasMaterial(context)); - assert(debugCheckHasDirectionality(context)); - return true; - } -} - -class _InkResponseStateWidget extends StatefulWidget { - const _InkResponseStateWidget({ - this.child, - this.onTap, - this.onTapDown, - this.onTapUp, - this.onTapCancel, - this.onDoubleTap, - this.onLongPress, - this.onLongPressUp, - this.onSecondaryTap, - this.onSecondaryTapUp, - this.onSecondaryTapDown, - this.onSecondaryTapCancel, - this.onHighlightChanged, - this.onHover, - this.mouseCursor, - this.containedInkWell = false, - this.highlightShape = BoxShape.circle, - this.radius, - this.borderRadius, - this.customBorder, - this.focusColor, - this.hoverColor, - this.highlightColor, - this.overlayColor, - this.splashColor, - this.splashFactory, - this.enableFeedback = true, - this.excludeFromSemantics = false, - this.focusNode, - this.canRequestFocus = true, - this.onFocusChange, - this.autofocus = false, - this.parentState, - this.getRectCallback, - required this.debugCheckContext, - this.statesController, - this.hoverDuration, - }); - - final Widget? child; - final GestureTapCallback? onTap; - final GestureTapDownCallback? onTapDown; - final GestureTapUpCallback? onTapUp; - final GestureTapCallback? onTapCancel; - final GestureTapCallback? onDoubleTap; - final GestureLongPressCallback? onLongPress; - final GestureLongPressUpCallback? onLongPressUp; - final GestureTapCallback? onSecondaryTap; - final GestureTapUpCallback? onSecondaryTapUp; - final GestureTapDownCallback? onSecondaryTapDown; - final GestureTapCallback? onSecondaryTapCancel; - final ValueChanged? onHighlightChanged; - final ValueChanged? onHover; - final MouseCursor? mouseCursor; - final bool containedInkWell; - final BoxShape highlightShape; - final double? radius; - final BorderRadius? borderRadius; - final ShapeBorder? customBorder; - final Color? focusColor; - final Color? hoverColor; - final Color? highlightColor; - final WidgetStateProperty? overlayColor; - final Color? splashColor; - final InteractiveInkFeatureFactory? splashFactory; - final bool enableFeedback; - final bool excludeFromSemantics; - final ValueChanged? onFocusChange; - final bool autofocus; - final FocusNode? focusNode; - final bool canRequestFocus; - final _ParentInkResponseState? parentState; - final _GetRectCallback? getRectCallback; - final _CheckContext debugCheckContext; - final WidgetStatesController? statesController; - final Duration? hoverDuration; - - @override - _InkResponseState createState() => _InkResponseState(); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - final gestures = [ - if (onTap != null) 'tap', - if (onDoubleTap != null) 'double tap', - if (onLongPress != null) 'long press', - if (onLongPressUp != null) 'long press up', - if (onTapDown != null) 'tap down', - if (onTapUp != null) 'tap up', - if (onTapCancel != null) 'tap cancel', - if (onSecondaryTap != null) 'secondary tap', - if (onSecondaryTapUp != null) 'secondary tap up', - if (onSecondaryTapDown != null) 'secondary tap down', - if (onSecondaryTapCancel != null) 'secondary tap cancel', - ]; - properties - ..add( - IterableProperty('gestures', gestures, ifEmpty: ''), - ) - ..add( - DiagnosticsProperty('mouseCursor', mouseCursor), - ) - ..add( - DiagnosticsProperty( - 'containedInkWell', - containedInkWell, - level: DiagnosticLevel.fine, - ), - ) - ..add( - DiagnosticsProperty( - 'highlightShape', - highlightShape, - description: - '${containedInkWell ? "clipped to " : ""}$highlightShape', - showName: false, - ), - ); - } -} - -/// Used to index the allocated highlights for the different types of highlights -/// in [_InkResponseState]. -enum _HighlightType { pressed, hover, focus } - -class _InkResponseState extends State<_InkResponseStateWidget> - implements _ParentInkResponseState { - Set? _splashes; - InteractiveInkFeature? _currentSplash; - bool _hovering = false; - final Map<_HighlightType, InkHighlight?> _highlights = - <_HighlightType, InkHighlight?>{}; - late final Map> _actionMap = >{ - ActivateIntent: CallbackAction(onInvoke: activateOnIntent), - ButtonActivateIntent: CallbackAction( - onInvoke: activateOnIntent, - ), - }; - WidgetStatesController? internalStatesController; - - bool get highlightsExist => _highlights.values - .where((InkHighlight? highlight) => highlight != null) - .isNotEmpty; - - final ObserverList<_ParentInkResponseState> _activeChildren = - ObserverList<_ParentInkResponseState>(); - - static const Duration _activationDuration = Duration(milliseconds: 100); - Timer? _activationTimer; - - @override - void markChildInkResponsePressed( - _ParentInkResponseState childState, - bool value, - ) { - final bool lastAnyPressed = _anyChildInkResponsePressed; - if (value) { - _activeChildren.add(childState); - } else { - _activeChildren.remove(childState); - } - final bool nowAnyPressed = _anyChildInkResponsePressed; - if (nowAnyPressed != lastAnyPressed) { - widget.parentState?.markChildInkResponsePressed(this, nowAnyPressed); - } - } - - bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty; - - void activateOnIntent(Intent? intent) { - _activationTimer?.cancel(); - _activationTimer = null; - _startNewSplash(context: context); - _currentSplash?.confirm(); - _currentSplash = null; - if (widget.onTap != null) { - if (widget.enableFeedback) { - Feedback.forTap(context); - } - widget.onTap?.call(); - } - // Delay the call to `updateHighlight` to simulate a pressed delay - // and give WidgetStatesController listeners a chance to react. - _activationTimer = Timer(_activationDuration, () { - updateHighlight(_HighlightType.pressed, value: false); - }); - } - - void simulateTap([Intent? intent]) { - _startNewSplash(context: context); - handleTap(); - } - - void simulateLongPress() { - _startNewSplash(context: context); - handleLongPress(); - } - - void handleStatesControllerChange() { - // Force a rebuild to resolve widget.overlayColor, widget.mouseCursor - setState(() {}); - } - - WidgetStatesController get statesController => - widget.statesController ?? internalStatesController!; - - void initStatesController() { - if (widget.statesController == null) { - internalStatesController = WidgetStatesController(); - } - statesController - ..update(WidgetState.disabled, !enabled) - ..addListener(handleStatesControllerChange); - } - - @override - void initState() { - super.initState(); - initStatesController(); - FocusManager.instance.addHighlightModeListener( - handleFocusHighlightModeChange, - ); - } - - @override - void didUpdateWidget(_InkResponseStateWidget oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.statesController != oldWidget.statesController) { - oldWidget.statesController?.removeListener(handleStatesControllerChange); - if (widget.statesController != null) { - internalStatesController?.dispose(); - internalStatesController = null; - } - initStatesController(); - } - if (widget.radius != oldWidget.radius || - widget.highlightShape != oldWidget.highlightShape || - widget.borderRadius != oldWidget.borderRadius) { - final InkHighlight? hoverHighlight = _highlights[_HighlightType.hover]; - if (hoverHighlight != null) { - hoverHighlight.dispose(); - updateHighlight( - _HighlightType.hover, - value: _hovering, - callOnHover: false, - ); - } - final InkHighlight? focusHighlight = _highlights[_HighlightType.focus]; - if (focusHighlight != null) { - focusHighlight.dispose(); - // Do not call updateFocusHighlights() here because it is called below - } - } - if (widget.customBorder != oldWidget.customBorder) { - _updateHighlightsAndSplashes(); - } - if (enabled != isWidgetEnabled(oldWidget)) { - statesController.update(WidgetState.disabled, !enabled); - if (!enabled) { - statesController.update(WidgetState.pressed, false); - // Remove the existing hover highlight immediately when enabled is false. - // Do not rely on updateHighlight or InkHighlight.deactivate to not break - // the expected lifecycle which is updating _hovering when the mouse exit. - // Manually updating _hovering here or calling InkHighlight.deactivate - // will lead to onHover not being called or call when it is not allowed. - final InkHighlight? hoverHighlight = _highlights[_HighlightType.hover]; - hoverHighlight?.dispose(); - } - // Don't call widget.onHover because many widgets, including the button - // widgets, apply setState to an ancestor context from onHover. - updateHighlight( - _HighlightType.hover, - value: _hovering, - callOnHover: false, - ); - } - updateFocusHighlights(); - } - - @override - void dispose() { - FocusManager.instance.removeHighlightModeListener( - handleFocusHighlightModeChange, - ); - statesController.removeListener(handleStatesControllerChange); - internalStatesController?.dispose(); - _activationTimer?.cancel(); - _activationTimer = null; - super.dispose(); - } - - Duration getFadeDurationForType(_HighlightType type) { - switch (type) { - case _HighlightType.pressed: - return const Duration(milliseconds: 200); - case _HighlightType.hover: - case _HighlightType.focus: - return widget.hoverDuration ?? const Duration(milliseconds: 50); - } - } - - void updateHighlight( - _HighlightType type, { - required bool value, - bool callOnHover = true, - }) { - final InkHighlight? highlight = _highlights[type]; - void handleInkRemoval() { - assert(_highlights[type] != null); - _highlights[type] = null; - } - - switch (type) { - case _HighlightType.pressed: - statesController.update(WidgetState.pressed, value); - case _HighlightType.hover: - if (callOnHover) { - statesController.update(WidgetState.hovered, value); - } - case _HighlightType.focus: - // see handleFocusUpdate() - break; - } - - if (type == _HighlightType.pressed) { - widget.parentState?.markChildInkResponsePressed(this, value); - } - if (value == (highlight != null && highlight.active)) { - return; - } - - if (value) { - if (highlight == null) { - final Color resolvedOverlayColor = - widget.overlayColor?.resolve(statesController.value) ?? - switch (type) { - // Use the backwards compatible defaults - _HighlightType.pressed => - widget.highlightColor ?? Theme.of(context).highlightColor, - _HighlightType.focus => - widget.focusColor ?? Theme.of(context).focusColor, - _HighlightType.hover => - widget.hoverColor ?? Theme.of(context).hoverColor, - }; - final referenceBox = context.findRenderObject()! as RenderBox; - _highlights[type] = InkHighlight( - controller: Material.of(context), - referenceBox: referenceBox, - color: enabled - ? resolvedOverlayColor - : resolvedOverlayColor.withAlpha(0), - shape: widget.highlightShape, - radius: widget.radius, - borderRadius: widget.borderRadius, - customBorder: widget.customBorder, - rectCallback: widget.getRectCallback!(referenceBox), - onRemoved: handleInkRemoval, - textDirection: Directionality.of(context), - fadeDuration: getFadeDurationForType(type), - ); - } else { - highlight.activate(); - } - } else { - highlight!.deactivate(); - } - assert(value == (_highlights[type] != null && _highlights[type]!.active)); - - switch (type) { - case _HighlightType.pressed: - widget.onHighlightChanged?.call(value); - case _HighlightType.hover: - if (callOnHover) { - widget.onHover?.call(value); - } - case _HighlightType.focus: - break; - } - } - - void _updateHighlightsAndSplashes() { - for (final InkHighlight? highlight in _highlights.values) { - highlight?.customBorder = widget.customBorder; - } - _currentSplash?.customBorder = widget.customBorder; - - if (_splashes != null && _splashes!.isNotEmpty) { - for (final InteractiveInkFeature inkFeature in _splashes!) { - inkFeature.customBorder = widget.customBorder; - } - } - } - - InteractiveInkFeature _createSplash(Offset globalPosition) { - final MaterialInkController inkController = Material.of(context); - final referenceBox = context.findRenderObject()! as RenderBox; - final Offset position = referenceBox.globalToLocal(globalPosition); - final Color color = - widget.overlayColor?.resolve(statesController.value) ?? - widget.splashColor ?? - Theme.of(context).splashColor; - final RectCallback? rectCallback = widget.containedInkWell - ? widget.getRectCallback!(referenceBox) - : null; - final BorderRadius? borderRadius = widget.borderRadius; - final ShapeBorder? customBorder = widget.customBorder; - - InteractiveInkFeature? splash; - void onRemoved() { - if (_splashes != null) { - assert(_splashes!.contains(splash)); - _splashes!.remove(splash); - if (_currentSplash == splash) { - _currentSplash = null; - } - } // else we're probably in deactivate() - } - - splash = (widget.splashFactory ?? Theme.of(context).splashFactory).create( - controller: inkController, - referenceBox: referenceBox, - position: position, - color: color, - containedInkWell: widget.containedInkWell, - rectCallback: rectCallback, - radius: widget.radius, - borderRadius: borderRadius, - customBorder: customBorder, - onRemoved: onRemoved, - textDirection: Directionality.of(context), - ); - - return splash; - } - - void handleFocusHighlightModeChange(FocusHighlightMode mode) { - if (!mounted) { - return; - } - setState(updateFocusHighlights); - } - - bool get _shouldShowFocus => - switch (MediaQuery.maybeNavigationModeOf(context)) { - NavigationMode.traditional || null => enabled && _hasFocus, - NavigationMode.directional => _hasFocus, - }; - - void updateFocusHighlights() { - final bool showFocus = switch (FocusManager.instance.highlightMode) { - FocusHighlightMode.touch => false, - FocusHighlightMode.traditional => _shouldShowFocus, - }; - updateHighlight(_HighlightType.focus, value: showFocus); - } - - bool _hasFocus = false; - void handleFocusUpdate(bool hasFocus) { - _hasFocus = hasFocus; - // Set here rather than updateHighlight because this widget's - // (WidgetState) states include WidgetState.focused if - // the InkWell _has_ the focus, rather than if it's showing - // the focus per FocusManager.instance.highlightMode. - statesController.update(WidgetState.focused, hasFocus); - updateFocusHighlights(); - widget.onFocusChange?.call(hasFocus); - } - - void handleAnyTapDown(TapDownDetails details) { - if (_anyChildInkResponsePressed) { - return; - } - _startNewSplash(details: details); - } - - void handleTapDown(TapDownDetails details) { - handleAnyTapDown(details); - widget.onTapDown?.call(details); - } - - void handleTapUp(TapUpDetails details) { - widget.onTapUp?.call(details); - } - - void handleSecondaryTapDown(TapDownDetails details) { - handleAnyTapDown(details); - widget.onSecondaryTapDown?.call(details); - } - - void handleSecondaryTapUp(TapUpDetails details) { - widget.onSecondaryTapUp?.call(details); - } - - void _startNewSplash({TapDownDetails? details, BuildContext? context}) { - assert(details != null || context != null); - - final Offset globalPosition; - if (context != null) { - final referenceBox = context.findRenderObject()! as RenderBox; - assert( - referenceBox.hasSize, - 'InkResponse must be done with layout before starting a splash.', - ); - globalPosition = referenceBox.localToGlobal( - referenceBox.paintBounds.center, - ); - } else { - globalPosition = details!.globalPosition; - } - statesController.update( - WidgetState.pressed, - true, - ); // ... before creating the splash - final InteractiveInkFeature splash = _createSplash(globalPosition); - _splashes ??= HashSet(); - _splashes!.add(splash); - _currentSplash?.cancel(); - _currentSplash = splash; - updateHighlight(_HighlightType.pressed, value: true); - } - - void handleTap() { - _currentSplash?.confirm(); - _currentSplash = null; - updateHighlight(_HighlightType.pressed, value: false); - if (widget.onTap != null) { - if (widget.enableFeedback) { - Feedback.forTap(context); - } - widget.onTap?.call(); - } - } - - void handleTapCancel() { - _currentSplash?.cancel(); - _currentSplash = null; - widget.onTapCancel?.call(); - updateHighlight(_HighlightType.pressed, value: false); - } - - void handleDoubleTap() { - _currentSplash?.confirm(); - _currentSplash = null; - updateHighlight(_HighlightType.pressed, value: false); - widget.onDoubleTap?.call(); - } - - void handleLongPress() { - _currentSplash?.confirm(); - _currentSplash = null; - if (widget.onLongPress != null) { - if (widget.enableFeedback) { - Feedback.forLongPress(context); - } - widget.onLongPress!(); - } - } - - void handleLongPressUp() { - _currentSplash?.confirm(); - _currentSplash = null; - widget.onLongPressUp?.call(); - } - - void handleSecondaryTap() { - _currentSplash?.confirm(); - _currentSplash = null; - updateHighlight(_HighlightType.pressed, value: false); - widget.onSecondaryTap?.call(); - } - - void handleSecondaryTapCancel() { - _currentSplash?.cancel(); - _currentSplash = null; - widget.onSecondaryTapCancel?.call(); - updateHighlight(_HighlightType.pressed, value: false); - } - - @override - void deactivate() { - if (_splashes != null) { - final Set splashes = _splashes!; - _splashes = null; - for (final splash in splashes) { - splash.dispose(); - } - _currentSplash = null; - } - assert(_currentSplash == null); - for (final _HighlightType highlight in _highlights.keys) { - _highlights[highlight]?.dispose(); - _highlights[highlight] = null; - } - widget.parentState?.markChildInkResponsePressed(this, false); - super.deactivate(); - } - - bool isWidgetEnabled(_InkResponseStateWidget widget) { - return _primaryButtonEnabled(widget) || _secondaryButtonEnabled(widget); - } - - bool _primaryButtonEnabled(_InkResponseStateWidget widget) { - return widget.onTap != null || - widget.onDoubleTap != null || - widget.onLongPress != null || - widget.onLongPressUp != null || - widget.onTapUp != null || - widget.onTapDown != null; - } - - bool _secondaryButtonEnabled(_InkResponseStateWidget widget) { - return widget.onSecondaryTap != null || - widget.onSecondaryTapUp != null || - widget.onSecondaryTapDown != null; - } - - bool get enabled => isWidgetEnabled(widget); - bool get _primaryEnabled => _primaryButtonEnabled(widget); - bool get _secondaryEnabled => _secondaryButtonEnabled(widget); - - void handleMouseEnter(PointerEnterEvent event) { - _hovering = true; - if (enabled) { - handleHoverChange(); - } - } - - void handleMouseExit(PointerExitEvent event) { - _hovering = false; - // If the exit occurs after we've been disabled, we still - // want to take down the highlights and run widget.onHover. - handleHoverChange(); - } - - void handleHoverChange() { - updateHighlight(_HighlightType.hover, value: _hovering); - } - - bool get _canRequestFocus => - switch (MediaQuery.maybeNavigationModeOf(context)) { - NavigationMode.traditional || null => enabled && widget.canRequestFocus, - NavigationMode.directional => true, - }; - - @override - Widget build(BuildContext context) { - assert(widget.debugCheckContext(context)); - - final ThemeData theme = Theme.of(context); - const highlightableStates = { - WidgetState.focused, - WidgetState.hovered, - WidgetState.pressed, - }; - final Set nonHighlightableStates = statesController.value - .difference( - highlightableStates, - ); - // Each highlightable state will be resolved separately to get the corresponding color. - // For this resolution to be correct, the non-highlightable states should be preserved. - final pressed = { - ...nonHighlightableStates, - WidgetState.pressed, - }; - final focused = { - ...nonHighlightableStates, - WidgetState.focused, - }; - final hovered = { - ...nonHighlightableStates, - WidgetState.hovered, - }; - - Color getHighlightColorForType(_HighlightType type) { - return switch (type) { - // The pressed state triggers a ripple (ink splash), per the current - // Material Design spec. A separate highlight is no longer used. - // See https://material.io/design/interaction/states.html#pressed - _HighlightType.pressed => - widget.overlayColor?.resolve(pressed) ?? - widget.highlightColor ?? - theme.highlightColor, - _HighlightType.focus => - widget.overlayColor?.resolve(focused) ?? - widget.focusColor ?? - theme.focusColor, - _HighlightType.hover => - widget.overlayColor?.resolve(hovered) ?? - widget.hoverColor ?? - theme.hoverColor, - }; - } - - for (final _HighlightType type in _highlights.keys) { - _highlights[type]?.color = getHighlightColorForType(type); - } - - _currentSplash?.color = - widget.overlayColor?.resolve(statesController.value) ?? - widget.splashColor ?? - Theme.of(context).splashColor; - - final MouseCursor effectiveMouseCursor = - WidgetStateProperty.resolveAs( - widget.mouseCursor ?? WidgetStateMouseCursor.adaptiveClickable, - statesController.value, - ); - - return _ParentInkResponseProvider( - state: this, - child: Actions( - actions: _actionMap, - child: Focus( - focusNode: widget.focusNode, - canRequestFocus: _canRequestFocus, - onFocusChange: handleFocusUpdate, - autofocus: widget.autofocus, - child: MouseRegion( - cursor: effectiveMouseCursor, - onEnter: handleMouseEnter, - onExit: handleMouseExit, - child: DefaultSelectionStyle.merge( - mouseCursor: effectiveMouseCursor, - child: Semantics( - onTap: widget.excludeFromSemantics || widget.onTap == null - ? null - : simulateTap, - onLongPress: - widget.excludeFromSemantics || widget.onLongPress == null - ? null - : simulateLongPress, - child: GestureDetector( - onTapDown: _primaryEnabled ? handleTapDown : null, - onTapUp: _primaryEnabled ? handleTapUp : null, - onTap: _primaryEnabled ? handleTap : null, - onTapCancel: _primaryEnabled ? handleTapCancel : null, - onDoubleTap: widget.onDoubleTap != null - ? handleDoubleTap - : null, - onLongPress: widget.onLongPress != null - ? handleLongPress - : null, - onLongPressUp: widget.onLongPressUp != null - ? handleLongPressUp - : null, - onSecondaryTapDown: _secondaryEnabled - ? handleSecondaryTapDown - : null, - onSecondaryTapUp: _secondaryEnabled - ? handleSecondaryTapUp - : null, - onSecondaryTap: _secondaryEnabled ? handleSecondaryTap : null, - onSecondaryTapCancel: _secondaryEnabled - ? handleSecondaryTapCancel - : null, - behavior: HitTestBehavior.opaque, - excludeFromSemantics: true, - child: widget.child, - ), - ), - ), - ), - ), - ), - ); - } -} - -/// A rectangular area of a [Material] that responds to touch. -/// -/// For a variant of this widget that does not clip splashes, see [InkResponse]. -/// -/// The following diagram shows how an [InkWell] looks when tapped, when using -/// default values. -/// -/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png) -/// -/// The [InkWell] widget must have a [Material] widget as an ancestor. The -/// [Material] widget is where the ink reactions are actually painted. This -/// matches the Material Design premise wherein the [Material] is what is -/// actually reacting to touches by spreading ink. -/// -/// If a Widget uses this class directly, it should include the following line -/// at the top of its build function to call [debugCheckHasMaterial]: -/// -/// ```dart -/// assert(debugCheckHasMaterial(context)); -/// ``` -/// -/// ## Troubleshooting -/// -/// ### The ink splashes aren't visible! -/// -/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or -/// [DecoratedBox], between the [Material] widget and the [InkWell] widget, then -/// the splash won't be visible because it will be under the opaque graphic. -/// This is because ink splashes draw on the underlying [Material] itself, as -/// if the ink was spreading inside the material. -/// -/// The [Ink] widget can be used as a replacement for [Image], [Container], or -/// [DecoratedBox] to ensure that the image or decoration also paints in the -/// [Material] itself, below the ink. -/// -/// If this is not possible for some reason, e.g. because you are using an -/// opaque [CustomPaint] widget, alternatively consider using a second -/// [Material] above the opaque widget but below the [InkWell] (as an -/// ancestor to the ink well). The [MaterialType.transparency] material -/// kind can be used for this purpose. -/// -/// ### InkWell isn't clipping properly -/// -/// If you want to clip an InkWell or any [Ink] widgets you need to keep in mind -/// that the [Material] that the Ink will be printed on is responsible for clipping. -/// This means you can't wrap the [Ink] widget in a clipping widget directly, -/// since this will leave the [Material] not clipped (and by extension the printed -/// [Ink] widgets as well). -/// -/// An easy solution is to deliberately wrap the [Ink] widgets you want to clip -/// in a [Material], and wrap that in a clipping widget instead. See [Ink] for -/// an example. -/// -/// ### The ink splashes don't track the size of an animated container -/// If the size of an InkWell's [Material] ancestor changes while the InkWell's -/// splashes are expanding, you may notice that the splashes aren't clipped -/// correctly. This can't be avoided. -/// -/// An example of this situation is as follows: -/// -/// {@tool dartpad} -/// Tap the container to cause it to grow. Then, tap it again and hold before -/// the widget reaches its maximum size to observe the clipped ink splash. -/// -/// ** See code in examples/api/lib/material/ink_well/ink_well.0.dart ** -/// {@end-tool} -/// -/// An InkWell's splashes will not properly update to conform to changes if the -/// size of its underlying [Material], where the splashes are rendered, changes -/// during animation. You should avoid using InkWells within [Material] widgets -/// that are changing size. -/// -/// See also: -/// -/// * [GestureDetector], for listening for gestures without ink splashes. -/// * [ElevatedButton] and [TextButton], two kinds of buttons in Material Design. -/// * [InkResponse], a variant of [InkWell] that doesn't force a rectangular -/// shape on the ink reaction. -class InkWell extends InkResponse { - /// Creates an ink well. - /// - /// Must have an ancestor [Material] widget in which to cause ink reactions. - const InkWell({ - super.key, - super.child, - super.onTap, - super.onDoubleTap, - super.onLongPress, - super.onLongPressUp, - super.onTapDown, - super.onTapUp, - super.onTapCancel, - super.onSecondaryTap, - super.onSecondaryTapUp, - super.onSecondaryTapDown, - super.onSecondaryTapCancel, - super.onHighlightChanged, - super.onHover, - super.mouseCursor, - super.focusColor, - super.hoverColor, - super.highlightColor, - super.overlayColor, - super.splashColor, - super.splashFactory, - super.radius, - super.borderRadius, - super.customBorder, - super.enableFeedback, - super.excludeFromSemantics, - super.focusNode, - super.canRequestFocus, - super.onFocusChange, - super.autofocus, - super.statesController, - super.hoverDuration, - }) : super(containedInkWell: true, highlightShape: BoxShape.rectangle); -} diff --git a/lib/common/widgets/flutter/dyn/text_button.dart b/lib/common/widgets/flutter/dyn/text_button.dart deleted file mode 100644 index 3dce452c4..000000000 --- a/lib/common/widgets/flutter/dyn/text_button.dart +++ /dev/null @@ -1,625 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// ignore_for_file: uri_does_not_exist_in_doc_import - -/// @docImport 'elevated_button.dart'; -/// @docImport 'filled_button.dart'; -/// @docImport 'material.dart'; -/// @docImport 'outlined_button.dart'; -library; - -import 'dart:ui' show lerpDouble; - -import 'package:PiliPlus/common/widgets/flutter/dyn/button.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart' - hide ButtonStyleButton, TextButton, InkWell; - -/// A Material Design "Text Button". -/// -/// Use text buttons on toolbars, in dialogs, or inline with other -/// content but offset from that content with padding so that the -/// button's presence is obvious. Text buttons do not have visible -/// borders and must therefore rely on their position relative to -/// other content for context. In dialogs and cards, they should be -/// grouped together in one of the bottom corners. Avoid using text -/// buttons where they would blend in with other content, for example -/// in the middle of lists. -/// -/// A text button is a label [child] displayed on a (zero elevation) -/// [Material] widget. The label's [Text] and [Icon] widgets are -/// displayed in the [style]'s [ButtonStyle.foregroundColor]. The -/// button reacts to touches by filling with the [style]'s -/// [ButtonStyle.backgroundColor]. -/// -/// The text button's default style is defined by [defaultStyleOf]. -/// The style of this text button can be overridden with its [style] -/// parameter. The style of all text buttons in a subtree can be -/// overridden with the [TextButtonTheme] and the style of all of the -/// text buttons in an app can be overridden with the [Theme]'s -/// [ThemeData.textButtonTheme] property. -/// -/// The static [styleFrom] method is a convenient way to create a -/// text button [ButtonStyle] from simple values. -/// -/// If the [onPressed] and [onLongPress] callbacks are null, then this -/// button will be disabled, it will not react to touch. -/// -/// {@tool dartpad} -/// This sample shows various ways to configure TextButtons, from the -/// simplest default appearance to versions that don't resemble -/// Material Design at all. -/// -/// ** See code in examples/api/lib/material/text_button/text_button.0.dart ** -/// {@end-tool} -/// -/// {@tool dartpad} -/// This sample demonstrates using the [statesController] parameter to create a button -/// that adds support for [WidgetState.selected]. -/// -/// ** See code in examples/api/lib/material/text_button/text_button.1.dart ** -/// {@end-tool} -/// -/// See also: -/// -/// * [ElevatedButton], a filled button whose material elevates when pressed. -/// * [FilledButton], a filled button that doesn't elevate when pressed. -/// * [FilledButton.tonal], a filled button variant that uses a secondary fill color. -/// * [OutlinedButton], a button with an outlined border and no fill color. -/// * -/// * -class TextButton extends ButtonStyleButton { - /// Create a [TextButton]. - const TextButton({ - super.key, - required super.onPressed, - super.onLongPress, - super.onHover, - super.onFocusChange, - super.style, - super.focusNode, - super.autofocus = false, - super.clipBehavior, - super.statesController, - super.isSemanticButton, - required Widget super.child, - }) : _addPadding = false; - - /// Create a text button from a pair of widgets that serve as the button's - /// [icon] and [label]. - /// - /// The icon and label are arranged in a row and padded by 8 logical pixels - /// at the ends, with an 8 pixel gap in between. - /// - /// If [icon] is null, this constructor will create a [TextButton] - /// that doesn't display an icon. - /// - /// {@macro flutter.material.ButtonStyle.iconAlignment} - /// - TextButton.icon({ - super.key, - required super.onPressed, - super.onLongPress, - super.onHover, - super.onFocusChange, - super.style, - super.focusNode, - super.autofocus = false, - super.clipBehavior = Clip.none, - super.statesController, - Widget? icon, - required Widget label, - IconAlignment? iconAlignment, - }) : _addPadding = icon != null, - super( - child: icon != null - ? _TextButtonWithIconChild( - label: label, - icon: icon, - buttonStyle: style, - iconAlignment: iconAlignment, - ) - : label, - ); - - final bool _addPadding; - - /// A static convenience method that constructs a text button - /// [ButtonStyle] given simple values. - /// - /// The [foregroundColor] and [disabledForegroundColor] colors are used - /// to create a [WidgetStateProperty] [ButtonStyle.foregroundColor], and - /// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified. - /// - /// The [backgroundColor] and [disabledBackgroundColor] colors are - /// used to create a [WidgetStateProperty] [ButtonStyle.backgroundColor]. - /// - /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor]. - /// - /// The [iconColor], [disabledIconColor] are used to construct - /// [ButtonStyle.iconColor] and [iconSize] is used to construct - /// [ButtonStyle.iconSize]. - /// - /// If [iconColor] is null, the button icon will use [foregroundColor]. If [foregroundColor] is also - /// null, the button icon will use the default icon color. - /// - /// If [overlayColor] is specified and its value is [Colors.transparent] - /// then the pressed/focused/hovered highlights are effectively defeated. - /// Otherwise a [WidgetStateProperty] with the same opacities as the - /// default is created. - /// - /// All of the other parameters are either used directly or used to - /// create a [WidgetStateProperty] with a single value for all - /// states. - /// - /// All parameters default to null. By default this method returns - /// a [ButtonStyle] that doesn't override anything. - /// - /// For example, to override the default text and icon colors for a - /// [TextButton], as well as its overlay color, with all of the - /// standard opacity adjustments for the pressed, focused, and - /// hovered states, one could write: - /// - /// ```dart - /// TextButton( - /// style: TextButton.styleFrom(foregroundColor: Colors.green), - /// child: const Text('Give Kate a mix tape'), - /// onPressed: () { - /// // ... - /// }, - /// ), - /// ``` - static ButtonStyle styleFrom({ - Color? foregroundColor, - Color? backgroundColor, - Color? disabledForegroundColor, - Color? disabledBackgroundColor, - Color? shadowColor, - Color? surfaceTintColor, - Color? iconColor, - double? iconSize, - IconAlignment? iconAlignment, - Color? disabledIconColor, - Color? overlayColor, - double? elevation, - TextStyle? textStyle, - EdgeInsetsGeometry? padding, - Size? minimumSize, - Size? fixedSize, - Size? maximumSize, - BorderSide? side, - OutlinedBorder? shape, - MouseCursor? enabledMouseCursor, - MouseCursor? disabledMouseCursor, - VisualDensity? visualDensity, - MaterialTapTargetSize? tapTargetSize, - Duration? animationDuration, - bool? enableFeedback, - AlignmentGeometry? alignment, - InteractiveInkFeatureFactory? splashFactory, - ButtonLayerBuilder? backgroundBuilder, - ButtonLayerBuilder? foregroundBuilder, - }) { - final WidgetStateProperty? backgroundColorProp = switch (( - backgroundColor, - disabledBackgroundColor, - )) { - (_?, null) => WidgetStatePropertyAll(backgroundColor), - (_, _) => ButtonStyleButton.defaultColor( - backgroundColor, - disabledBackgroundColor, - ), - }; - final WidgetStateProperty? iconColorProp = switch (( - iconColor, - disabledIconColor, - )) { - (_?, null) => WidgetStatePropertyAll(iconColor), - (_, _) => ButtonStyleButton.defaultColor(iconColor, disabledIconColor), - }; - final WidgetStateProperty? overlayColorProp = switch (( - foregroundColor, - overlayColor, - )) { - (null, null) => null, - (_, Color(a: 0.0)) => WidgetStatePropertyAll(overlayColor), - (_, final Color color) || (final Color color, _) => - WidgetStateProperty.fromMap({ - WidgetState.pressed: color.withValues(alpha: 0.1), - WidgetState.hovered: color.withValues(alpha: 0.08), - WidgetState.focused: color.withValues(alpha: 0.1), - }), - }; - - return ButtonStyle( - textStyle: ButtonStyleButton.allOrNull(textStyle), - foregroundColor: ButtonStyleButton.defaultColor( - foregroundColor, - disabledForegroundColor, - ), - backgroundColor: backgroundColorProp, - overlayColor: overlayColorProp, - shadowColor: ButtonStyleButton.allOrNull(shadowColor), - surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), - iconColor: iconColorProp, - iconSize: ButtonStyleButton.allOrNull(iconSize), - iconAlignment: iconAlignment, - elevation: ButtonStyleButton.allOrNull(elevation), - padding: ButtonStyleButton.allOrNull(padding), - minimumSize: ButtonStyleButton.allOrNull(minimumSize), - fixedSize: ButtonStyleButton.allOrNull(fixedSize), - maximumSize: ButtonStyleButton.allOrNull(maximumSize), - side: ButtonStyleButton.allOrNull(side), - shape: ButtonStyleButton.allOrNull(shape), - mouseCursor: WidgetStateProperty.fromMap( - { - WidgetState.disabled: disabledMouseCursor, - WidgetState.any: enabledMouseCursor, - }, - ), - visualDensity: visualDensity, - tapTargetSize: tapTargetSize, - animationDuration: animationDuration, - enableFeedback: enableFeedback, - alignment: alignment, - splashFactory: splashFactory, - backgroundBuilder: backgroundBuilder, - foregroundBuilder: foregroundBuilder, - ); - } - - /// Defines the button's default appearance. - /// - /// {@template flutter.material.text_button.default_style_of} - /// The button [child]'s [Text] and [Icon] widgets are rendered with - /// the [ButtonStyle]'s foreground color. The button's [InkWell] adds - /// the style's overlay color when the button is focused, hovered - /// or pressed. The button's background color becomes its [Material] - /// color and is transparent by default. - /// - /// All of the [ButtonStyle]'s defaults appear below. - /// - /// In this list "Theme.foo" is shorthand for - /// `Theme.of(context).foo`. Color scheme values like - /// "onSurface(0.38)" are shorthand for - /// `onSurface.withValues(alpha: 0.38)`. [WidgetStateProperty] valued - /// properties that are not followed by a sublist have the same - /// value for all states, otherwise the values are as specified for - /// each state and "others" means all other states. - /// - /// The "default font size" below refers to the font size specified in the - /// [defaultStyleOf] method (or 14.0 if unspecified), scaled by the - /// `MediaQuery.textScalerOf(context).scale` method. And the names of the - /// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been abbreviated - /// for readability. - /// - /// The color of the [ButtonStyle.textStyle] is not used, the - /// [ButtonStyle.foregroundColor] color is used instead. - /// {@endtemplate} - /// - /// ## Material 2 defaults - /// - /// * `textStyle` - Theme.textTheme.button - /// * `backgroundColor` - transparent - /// * `foregroundColor` - /// * disabled - Theme.colorScheme.onSurface(0.38) - /// * others - Theme.colorScheme.primary - /// * `overlayColor` - /// * hovered - Theme.colorScheme.primary(0.08) - /// * focused or pressed - Theme.colorScheme.primary(0.12) - /// * `shadowColor` - Theme.shadowColor - /// * `elevation` - 0 - /// * `padding` - /// * `default font size <= 14` - (horizontal(12), vertical(8)) - /// * `14 < default font size <= 28` - lerp(all(8), horizontal(8)) - /// * `28 < default font size <= 36` - lerp(horizontal(8), horizontal(4)) - /// * `36 < default font size` - horizontal(4) - /// * `minimumSize` - Size(64, 36) - /// * `fixedSize` - null - /// * `maximumSize` - Size.infinite - /// * `side` - null - /// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)) - /// * `mouseCursor` - WidgetStateMouseCursor.adaptiveClickable - /// * `visualDensity` - theme.visualDensity - /// * `tapTargetSize` - theme.materialTapTargetSize - /// * `animationDuration` - kThemeChangeDuration - /// * `enableFeedback` - true - /// * `alignment` - Alignment.center - /// * `splashFactory` - InkRipple.splashFactory - /// - /// The default padding values for the [TextButton.icon] factory are slightly different: - /// - /// * `padding` - /// * `default font size <= 14` - all(8) - /// * `14 < default font size <= 28 `- lerp(all(8), horizontal(4)) - /// * `28 < default font size` - horizontal(4) - /// - /// The default value for `side`, which defines the appearance of the button's - /// outline, is null. That means that the outline is defined by the button - /// shape's [OutlinedBorder.side]. Typically the default value of an - /// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn. - /// - /// ## Material 3 defaults - /// - /// If [ThemeData.useMaterial3] is set to true the following defaults will - /// be used: - /// - /// {@template flutter.material.text_button.material3_defaults} - /// * `textStyle` - Theme.textTheme.labelLarge - /// * `backgroundColor` - transparent - /// * `foregroundColor` - /// * disabled - Theme.colorScheme.onSurface(0.38) - /// * others - Theme.colorScheme.primary - /// * `overlayColor` - /// * hovered - Theme.colorScheme.primary(0.08) - /// * focused or pressed - Theme.colorScheme.primary(0.1) - /// * others - null - /// * `shadowColor` - Colors.transparent, - /// * `surfaceTintColor` - null - /// * `elevation` - 0 - /// * `padding` - /// * `default font size <= 14` - lerp(horizontal(12), horizontal(4)) - /// * `14 < default font size <= 28` - lerp(all(8), horizontal(8)) - /// * `28 < default font size <= 36` - lerp(horizontal(8), horizontal(4)) - /// * `36 < default font size` - horizontal(4) - /// * `minimumSize` - Size(64, 40) - /// * `fixedSize` - null - /// * `maximumSize` - Size.infinite - /// * `side` - null - /// * `shape` - StadiumBorder() - /// * `mouseCursor` - WidgetStateMouseCursor.adaptiveClickable - /// * `visualDensity` - theme.visualDensity - /// * `tapTargetSize` - theme.materialTapTargetSize - /// * `animationDuration` - kThemeChangeDuration - /// * `enableFeedback` - true - /// * `alignment` - Alignment.center - /// * `splashFactory` - Theme.splashFactory - /// - /// For the [TextButton.icon] factory, the end (generally the right) value of - /// `padding` is increased from 12 to 16. - /// {@endtemplate} - @override - ButtonStyle defaultStyleOf(BuildContext context) { - final ThemeData theme = Theme.of(context); - final ColorScheme colorScheme = theme.colorScheme; - final ButtonStyle buttonStyle = theme.useMaterial3 - ? _TextButtonDefaultsM3(context) - : styleFrom( - foregroundColor: colorScheme.primary, - disabledForegroundColor: colorScheme.onSurface.withValues( - alpha: 0.38, - ), - backgroundColor: Colors.transparent, - disabledBackgroundColor: Colors.transparent, - shadowColor: theme.shadowColor, - elevation: 0, - textStyle: theme.textTheme.labelLarge, - padding: _scaledPadding(context), - minimumSize: const Size(64, 36), - maximumSize: Size.infinite, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4)), - ), - enabledMouseCursor: kIsWeb - ? SystemMouseCursors.click - : SystemMouseCursors.basic, - disabledMouseCursor: SystemMouseCursors.basic, - visualDensity: theme.visualDensity, - tapTargetSize: theme.materialTapTargetSize, - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - splashFactory: InkRipple.splashFactory, - ); - - // Only apply padding when TextButton has an Icon. - if (_addPadding) { - final double defaultFontSize = - buttonStyle.textStyle?.resolve(const {})?.fontSize ?? - 14.0; - final double effectiveTextScale = - MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0; - final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding( - theme.useMaterial3 - ? const EdgeInsetsDirectional.fromSTEB(12, 8, 16, 8) - : const EdgeInsets.all(8), - const EdgeInsets.symmetric(horizontal: 4), - const EdgeInsets.symmetric(horizontal: 4), - effectiveTextScale, - ); - return buttonStyle.copyWith( - padding: WidgetStatePropertyAll(scaledPadding), - ); - } - - return buttonStyle; - } - - /// Returns the [TextButtonThemeData.style] of the closest - /// [TextButtonTheme] ancestor. - @override - ButtonStyle? themeStyleOf(BuildContext context) { - return TextButtonTheme.of(context).style; - } -} - -EdgeInsetsGeometry _scaledPadding(BuildContext context) { - final ThemeData theme = Theme.of(context); - final double defaultFontSize = theme.textTheme.labelLarge?.fontSize ?? 14.0; - final double effectiveTextScale = - MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0; - return ButtonStyleButton.scaledPadding( - theme.useMaterial3 - ? const EdgeInsets.symmetric(horizontal: 12, vertical: 8) - : const EdgeInsets.all(8), - const EdgeInsets.symmetric(horizontal: 8), - const EdgeInsets.symmetric(horizontal: 4), - effectiveTextScale, - ); -} - -class _TextButtonWithIconChild extends StatelessWidget { - const _TextButtonWithIconChild({ - required this.label, - required this.icon, - required this.buttonStyle, - required this.iconAlignment, - }); - - final Widget label; - final Widget icon; - final ButtonStyle? buttonStyle; - final IconAlignment? iconAlignment; - - @override - Widget build(BuildContext context) { - final double defaultFontSize = - buttonStyle?.textStyle?.resolve(const {})?.fontSize ?? - 14.0; - final double scale = - clampDouble( - MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, - 1.0, - 2.0, - ) - - 1.0; - final TextButtonThemeData textButtonTheme = TextButtonTheme.of(context); - final IconAlignment effectiveIconAlignment = - iconAlignment ?? - textButtonTheme.style?.iconAlignment ?? - buttonStyle?.iconAlignment ?? - IconAlignment.start; - return Row( - mainAxisSize: MainAxisSize.min, - spacing: lerpDouble(8, 4, scale)!, - children: effectiveIconAlignment == IconAlignment.start - ? [icon, Flexible(child: label)] - : [Flexible(child: label), icon], - ); - } -} - -// BEGIN GENERATED TOKEN PROPERTIES - TextButton - -// Do not edit by hand. The code between the "BEGIN GENERATED" and -// "END GENERATED" comments are generated from data in the Material -// Design token database by the script: -// dev/tools/gen_defaults/bin/gen_defaults.dart. - -// dart format off -class _TextButtonDefaultsM3 extends ButtonStyle { - _TextButtonDefaultsM3(this.context) - : super( - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - ); - - final BuildContext context; - late final ColorScheme _colors = Theme.of(context).colorScheme; - - @override - WidgetStateProperty get textStyle => - WidgetStatePropertyAll(Theme.of(context).textTheme.labelLarge); - - @override - WidgetStateProperty? get backgroundColor => - const WidgetStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get foregroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withValues(alpha: 0.38); - } - return _colors.primary; - }); - - @override - WidgetStateProperty? get overlayColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return _colors.primary.withValues(alpha: 0.1); - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary.withValues(alpha: 0.08); - } - if (states.contains(WidgetState.focused)) { - return _colors.primary.withValues(alpha: 0.1); - } - return null; - }); - - @override - WidgetStateProperty? get shadowColor => - const WidgetStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get surfaceTintColor => - const WidgetStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get elevation => - const WidgetStatePropertyAll(0.0); - - @override - WidgetStateProperty? get padding => - WidgetStatePropertyAll(_scaledPadding(context)); - - @override - WidgetStateProperty? get minimumSize => - const WidgetStatePropertyAll(Size(64.0, 40.0)); - - // No default fixedSize - - @override - WidgetStateProperty? get iconSize => - const WidgetStatePropertyAll(18.0); - - @override - WidgetStateProperty? get iconColor { - return WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withValues(alpha: 0.38); - } - if (states.contains(WidgetState.pressed)) { - return _colors.primary; - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary; - } - if (states.contains(WidgetState.focused)) { - return _colors.primary; - } - return _colors.primary; - }); - } - - @override - WidgetStateProperty? get maximumSize => - const WidgetStatePropertyAll(Size.infinite); - - // No default side - - @override - WidgetStateProperty? get shape => - const WidgetStatePropertyAll(StadiumBorder()); - - @override - WidgetStateProperty? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable; - - @override - VisualDensity? get visualDensity => Theme.of(context).visualDensity; - - @override - MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; - - @override - InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; -} -// dart format on - -// END GENERATED TOKEN PROPERTIES - TextButton diff --git a/lib/common/widgets/flutter/layout_builder.dart b/lib/common/widgets/flutter/layout_builder.dart new file mode 100644 index 000000000..af7a7a735 --- /dev/null +++ b/lib/common/widgets/flutter/layout_builder.dart @@ -0,0 +1,526 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +/// An abstract superclass for widgets that defer their building until layout. +/// +/// Similar to the [Builder] widget except that the implementation calls the [builder] +/// function at layout time and provides the [LayoutInfoType] that is required to +/// configure the child widget subtree. +/// +/// This is useful when the child widget tree relies on information that are only +/// available during layout, and doesn't depend on the child's intrinsic size. +/// +/// The [LayoutInfoType] should typically be immutable. The equality of the +/// [LayoutInfoType] type is used by the implementation to avoid unnecessary +/// rebuilds: if the new [LayoutInfoType] computed during layout is the same as +/// (defined by `LayoutInfoType.==`) the previous [LayoutInfoType], the +/// implementation will try to avoid calling the [builder] again unless +/// [updateShouldRebuild] returns true. The corresponding [RenderObject] produced +/// by this widget retains the most up-to-date [LayoutInfoType] for this purpose, +/// which may keep a [LayoutInfoType] object in memory until the widget is removed +/// from the tree. +/// +/// Subclasses must return a [RenderObject] that mixes in [RenderAbstractLayoutBuilderMixin]. +abstract class AbstractLayoutBuilder + extends RenderObjectWidget { + /// Creates a widget that defers its building until layout. + const AbstractLayoutBuilder({super.key}); + + /// Called at layout time to construct the widget tree. + /// + /// The builder must not return null. + Widget Function(BuildContext context, LayoutInfoType layoutInfo) get builder; + + @override + RenderObjectElement createElement() => + _LayoutBuilderElement(this); + + /// Whether [builder] needs to be called again even if the layout constraints + /// are the same. + /// + /// When this widget's configuration is updated, the [builder] callback most + /// likely needs to be called to build this widget's child. However, + /// subclasses may provide ways in which the widget can be updated without + /// needing to rebuild the child. Such subclasses can use this method to tell + /// the framework when the child widget should be rebuilt. + /// + /// When this method is called by the framework, the newly configured widget + /// is asked if it requires a rebuild, and it is passed the old widget as a + /// parameter. + /// + /// See also: + /// + /// * [State.setState] and [State.didUpdateWidget], which talk about widget + /// configuration changes and how they're triggered. + /// * [Element.update], the method that actually updates the widget's + /// configuration. + @protected + bool updateShouldRebuild( + covariant AbstractLayoutBuilder oldWidget, + ) => true; + + @override + RenderAbstractLayoutBuilderMixin + createRenderObject( + BuildContext context, + ); + + // updateRenderObject is redundant with the logic in the LayoutBuilderElement below. +} + +/// A specialized [AbstractLayoutBuilder] whose widget subtree depends on the +/// incoming [ConstraintType] that will be imposed on the widget. +/// +/// {@template flutter.widgets.ConstrainedLayoutBuilder} +/// The [builder] function is called in the following situations: +/// +/// * The first time the widget is laid out. +/// * When the parent widget passes different layout constraints. +/// * When the parent widget updates this widget and [updateShouldRebuild] returns `true`. +/// * When the dependencies that the [builder] function subscribes to change. +/// +/// The [builder] function is _not_ called during layout if the parent passes +/// the same constraints repeatedly. +/// +/// In the event that an ancestor skips the layout of this subtree so the +/// constraints become outdated, the `builder` rebuilds with the last known +/// constraints. +/// {@endtemplate} +abstract class ConstrainedLayoutBuilder + extends AbstractLayoutBuilder { + /// Creates a widget that defers its building until layout. + const ConstrainedLayoutBuilder({super.key, required this.builder}); + + @override + final Widget Function(BuildContext context, ConstraintType constraints) + builder; +} + +class _LayoutBuilderElement extends RenderObjectElement { + _LayoutBuilderElement(AbstractLayoutBuilder super.widget); + + @override + RenderAbstractLayoutBuilderMixin + get renderObject => + super.renderObject + as RenderAbstractLayoutBuilderMixin; + + Element? _child; + + // @override + // BuildScope get buildScope => _buildScope; + + // late final BuildScope _buildScope = BuildScope( + // scheduleRebuild: _scheduleRebuild, + // ); + + // To schedule a rebuild, markNeedsLayout needs to be called on this Element's + // render object (as the rebuilding is done in its performLayout call). However, + // the render tree should typically be kept clean during the postFrameCallbacks + // and the idle phase, so the layout data can be safely read. + // bool _deferredCallbackScheduled = false; + // void _scheduleRebuild() { + // if (_deferredCallbackScheduled) { + // return; + // } + + // final bool deferMarkNeedsLayout = + // switch (SchedulerBinding.instance.schedulerPhase) { + // SchedulerPhase.idle || SchedulerPhase.postFrameCallbacks => true, + // SchedulerPhase.transientCallbacks || + // SchedulerPhase.midFrameMicrotasks || + // SchedulerPhase.persistentCallbacks => false, + // }; + // if (!deferMarkNeedsLayout) { + // renderObject.scheduleLayoutCallback(); + // return; + // } + // _deferredCallbackScheduled = true; + // SchedulerBinding.instance.scheduleFrameCallback(_frameCallback); + // } + + // void _frameCallback(Duration timestamp) { + // _deferredCallbackScheduled = false; + // // This method is only called when the render tree is stable, if the Element + // // is deactivated it will never be reincorporated back to the tree. + // if (mounted) { + // renderObject.scheduleLayoutCallback(); + // } + // } + + @override + void visitChildren(ElementVisitor visitor) { + if (_child != null) { + visitor(_child!); + } + } + + @override + void forgetChild(Element child) { + assert(child == _child); + _child = null; + super.forgetChild(child); + } + + @override + void mount(Element? parent, Object? newSlot) { + super.mount(parent, newSlot); // Creates the renderObject. + renderObject._updateCallback(_rebuildWithConstraints); + } + + @override + void update(AbstractLayoutBuilder newWidget) { + assert(widget != newWidget); + final oldWidget = widget as AbstractLayoutBuilder; + super.update(newWidget); + assert(widget == newWidget); + + renderObject._updateCallback(_rebuildWithConstraints); + if (newWidget.updateShouldRebuild(oldWidget)) { + _needsBuild = true; + renderObject.scheduleLayoutCallback(); + } + } + + @override + void markNeedsBuild() { + // Calling super.markNeedsBuild is not needed. This Element does not need + // to performRebuild since this call already does what performRebuild does, + // So the element is clean as soon as this method returns and does not have + // to be added to the dirty list or marked as dirty. + renderObject.scheduleLayoutCallback(); + _needsBuild = true; + } + + @override + void performRebuild() { + // This gets called if markNeedsBuild() is called on us. + // That might happen if, e.g., our builder uses Inherited widgets. + + // Force the callback to be called, even if the layout constraints are the + // same. This is because that callback may depend on the updated widget + // configuration, or an inherited widget. + renderObject.scheduleLayoutCallback(); + _needsBuild = true; + super + .performRebuild(); // Calls widget.updateRenderObject (a no-op in this case). + } + + @override + void unmount() { + renderObject._callback = null; + super.unmount(); + } + + // The LayoutInfoType that was used to invoke the layout callback with last time, + // during layout. The `_previousLayoutInfo` value is compared to the new one + // to determine whether [LayoutBuilderBase.builder] needs to be called. + LayoutInfoType? _previousLayoutInfo; + bool _needsBuild = true; + + void _rebuildWithConstraints(Constraints _) { + final LayoutInfoType layoutInfo = renderObject.layoutInfo; + @pragma('vm:notify-debugger-on-exception') + void updateChildCallback() { + Widget built; + try { + assert(layoutInfo == renderObject.layoutInfo); + built = (widget as AbstractLayoutBuilder).builder( + this, + layoutInfo, + ); + debugWidgetBuilderValue(widget, built); + } catch (e, stack) { + built = ErrorWidget.builder( + _reportException( + ErrorDescription('building $widget'), + e, + stack, + informationCollector: () => [ + if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)), + ], + ), + ); + } + try { + _child = updateChild(_child, built, null); + assert(_child != null); + } catch (e, stack) { + built = ErrorWidget.builder( + _reportException( + ErrorDescription('building $widget'), + e, + stack, + informationCollector: () => [ + if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)), + ], + ), + ); + _child = updateChild(null, built, slot); + } finally { + _needsBuild = false; + _previousLayoutInfo = layoutInfo; + } + } + + final VoidCallback? callback = + _needsBuild || (layoutInfo != _previousLayoutInfo) + ? updateChildCallback + : null; + owner!.buildScope(this, callback); + } + + @override + void insertRenderObjectChild(RenderObject child, Object? slot) { + final RenderObjectWithChildMixin renderObject = + this.renderObject; + assert(slot == null); + assert(renderObject.debugValidateChild(child)); + renderObject.child = child; + assert(renderObject == this.renderObject); + } + + @override + void moveRenderObjectChild( + RenderObject child, + Object? oldSlot, + Object? newSlot, + ) { + assert(false); + } + + @override + void removeRenderObjectChild(RenderObject child, Object? slot) { + final RenderAbstractLayoutBuilderMixin + renderObject = this.renderObject; + assert(renderObject.child == child); + renderObject.child = null; + assert(renderObject == this.renderObject); + } +} + +/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with +/// the the same `LayoutInfoType`. +/// +/// Provides a [layoutCallback] implementation which, if needed, invokes +/// [AbstractLayoutBuilder]'s builder callback. +/// +/// Implementers can override the [layoutInfo] implementation with a value +/// that is safe to access in [layoutCallback], which is called in +/// [performLayout]. The default [layoutInfo] returns the incoming +/// [Constraints]. +/// +/// This mixin replaces [RenderConstrainedLayoutBuilder]. +mixin RenderAbstractLayoutBuilderMixin< + LayoutInfoType, + ChildType extends RenderObject +> + on + RenderObjectWithChildMixin, + RenderObjectWithLayoutCallbackMixin { + LayoutCallback? _callback; + + /// Change the layout callback. + void _updateCallback(LayoutCallback value) { + if (value == _callback) { + return; + } + _callback = value; + scheduleLayoutCallback(); + } + + /// Invokes the builder callback supplied via [AbstractLayoutBuilder] and + /// rebuilds the [AbstractLayoutBuilder]'s widget tree, if needed. + /// + /// No further work will be done if [layoutInfo] has not changed since the last + /// time this method was called, and [AbstractLayoutBuilder.updateShouldRebuild] + /// returned `false` when the widget was rebuilt. + /// + /// This method should typically be called as soon as possible in the class's + /// [performLayout] implementation, before any layout work is done. + @visibleForOverriding + @override + void layoutCallback() => _callback!(constraints); + + /// The information to invoke the [AbstractLayoutBuilder.builder] callback with. + /// + /// This is typically the information that are only made available in + /// [performLayout], which is inaccessible for regular [Builder] widget, + /// such as the incoming [Constraints], which are the default value. + @protected + LayoutInfoType get layoutInfo => constraints as LayoutInfoType; +} + +/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with +/// the the same `LayoutInfoType`. +/// +/// Use [RenderAbstractLayoutBuilderMixin] instead, which replaces this mixin. +typedef RenderConstrainedLayoutBuilder< + LayoutInfoType, + ChildType extends RenderObject +> = RenderAbstractLayoutBuilderMixin; + +/// Builds a widget tree that can depend on the parent widget's size. +/// +/// Similar to the [Builder] widget except that the framework calls the [builder] +/// function at layout time and provides the parent widget's constraints. This +/// is useful when the parent constrains the child's size and doesn't depend on +/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its +/// child's size. +/// +/// {@macro flutter.widgets.ConstrainedLayoutBuilder} +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw} +/// +/// If the child should be smaller than the parent, consider wrapping the child +/// in an [Align] widget. If the child might want to be bigger, consider +/// wrapping it in a [SingleChildScrollView] or [OverflowBox]. +/// +/// {@tool dartpad} +/// This example uses a [LayoutBuilder] to build a different widget depending on the available width. Resize the +/// DartPad window to see [LayoutBuilder] in action! +/// +/// ** See code in examples/api/lib/widgets/layout_builder/layout_builder.0.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [SliverLayoutBuilder], the sliver counterpart of this widget. +/// * [Builder], which calls a `builder` function at build time. +/// * [StatefulBuilder], which passes its `builder` function a `setState` callback. +/// * [CustomSingleChildLayout], which positions its child during layout. +/// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/). +class LayoutBuilder extends ConstrainedLayoutBuilder { + /// Creates a widget that defers its building until layout. + const LayoutBuilder({super.key, required super.builder}); + + @override + RenderAbstractLayoutBuilderMixin + createRenderObject( + BuildContext context, + ) => _RenderLayoutBuilder(); +} + +class _RenderLayoutBuilder extends RenderBox + with + RenderObjectWithChildMixin, + RenderObjectWithLayoutCallbackMixin, + RenderAbstractLayoutBuilderMixin { + @override + double computeMinIntrinsicWidth(double height) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMaxIntrinsicWidth(double height) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMinIntrinsicHeight(double width) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMaxIntrinsicHeight(double width) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + assert( + debugCannotComputeDryLayout( + reason: + 'Calculating the dry layout would require running the layout callback ' + 'speculatively, which might mutate the live render object tree.', + ), + ); + return Size.zero; + } + + @override + double? computeDryBaseline( + BoxConstraints constraints, + TextBaseline baseline, + ) { + assert( + debugCannotComputeDryLayout( + reason: + 'Calculating the dry baseline would require running the layout callback ' + 'speculatively, which might mutate the live render object tree.', + ), + ); + return null; + } + + @override + void performLayout() { + final BoxConstraints constraints = this.constraints; + runLayoutCallback(); + if (child != null) { + child!.layout(constraints, parentUsesSize: true); + size = constraints.constrain(child!.size); + } else { + size = constraints.biggest; + } + } + + @override + double? computeDistanceToActualBaseline(TextBaseline baseline) { + return child?.getDistanceToActualBaseline(baseline) ?? + super.computeDistanceToActualBaseline(baseline); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return child?.hitTest(result, position: position) ?? false; + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null) { + context.paintChild(child!, offset); + } + } + + bool _debugThrowIfNotCheckingIntrinsics() { + assert(() { + if (!RenderObject.debugCheckingIntrinsics) { + throw FlutterError( + 'LayoutBuilder does not support returning intrinsic dimensions.\n' + 'Calculating the intrinsic dimensions would require running the layout ' + 'callback speculatively, which might mutate the live render object tree.', + ); + } + return true; + }()); + + return true; + } +} + +FlutterErrorDetails _reportException( + DiagnosticsNode context, + Object exception, + StackTrace stack, { + InformationCollector? informationCollector, +}) { + final details = FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets library', + context: context, + informationCollector: informationCollector, + ); + FlutterError.reportError(details); + return details; +} diff --git a/lib/common/widgets/flutter/sliver_layout_builder.dart b/lib/common/widgets/flutter/sliver_layout_builder.dart new file mode 100644 index 000000000..f3f35b773 --- /dev/null +++ b/lib/common/widgets/flutter/sliver_layout_builder.dart @@ -0,0 +1,83 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart' + hide + ConstrainedLayoutBuilder, + LayoutBuilder, + RenderConstrainedLayoutBuilder; + +/// Builds a sliver widget tree that can depend on its own [SliverConstraints]. +/// +/// Similar to the [LayoutBuilder] widget except its builder should return a sliver +/// widget, and [SliverLayoutBuilder] is itself a sliver. The framework calls the +/// [builder] function at layout time and provides the current [SliverConstraints]. +/// The [SliverLayoutBuilder]'s final [SliverGeometry] will match the [SliverGeometry] +/// of its child. +/// +/// {@macro flutter.widgets.ConstrainedLayoutBuilder} +/// +/// See also: +/// +/// * [LayoutBuilder], the non-sliver version of this widget. +class SliverLayoutBuilder extends ConstrainedLayoutBuilder { + /// Creates a sliver widget that defers its building until layout. + const SliverLayoutBuilder({super.key, required super.builder}); + + @override + RenderConstrainedLayoutBuilder + createRenderObject( + BuildContext context, + ) => _RenderSliverLayoutBuilder(); +} + +class _RenderSliverLayoutBuilder extends RenderSliver + with + RenderObjectWithChildMixin, + RenderObjectWithLayoutCallbackMixin, + RenderConstrainedLayoutBuilder { + @override + double childMainAxisPosition(RenderObject child) { + assert(child == this.child); + return 0; + } + + @override + void performLayout() { + runLayoutCallback(); + child?.layout(constraints, parentUsesSize: true); + geometry = child?.geometry ?? SliverGeometry.zero; + } + + @override + void applyPaintTransform(RenderObject child, Matrix4 transform) { + assert(child == this.child); + // child's offset is always (0, 0), transform.translate(0, 0) does not mutate the transform. + } + + @override + void paint(PaintingContext context, Offset offset) { + // This renderObject does not introduce additional offset to child's position. + if (child?.geometry?.visible ?? false) { + context.paintChild(child!, offset); + } + } + + @override + bool hitTestChildren( + SliverHitTestResult result, { + required double mainAxisPosition, + required double crossAxisPosition, + }) { + return child != null && + child!.geometry!.hitTestExtent > 0 && + child!.hitTest( + result, + mainAxisPosition: mainAxisPosition, + crossAxisPosition: crossAxisPosition, + ); + } +} diff --git a/lib/common/widgets/image_grid/image_grid_builder.dart b/lib/common/widgets/image_grid/image_grid_builder.dart index 3203c89b9..0732fe07b 100644 --- a/lib/common/widgets/image_grid/image_grid_builder.dart +++ b/lib/common/widgets/image_grid/image_grid_builder.dart @@ -38,7 +38,6 @@ import 'package:flutter/rendering.dart' ContainerParentDataMixin, InformationCollector, DiagnosticsDebugCreator; -import 'package:flutter/scheduler.dart'; /// ref [LayoutBuilder] @@ -274,42 +273,42 @@ class ImageGridRenderObjectElement extends RenderObjectElement { // repeatedly to remove children. final Set _forgottenChildren = HashSet(); - @override - BuildScope get buildScope => _buildScope; + // @override + // BuildScope get buildScope => _buildScope; - late final BuildScope _buildScope = BuildScope( - scheduleRebuild: _scheduleRebuild, - ); + // late final BuildScope _buildScope = BuildScope( + // scheduleRebuild: _scheduleRebuild, + // ); - bool _deferredCallbackScheduled = false; - void _scheduleRebuild() { - if (_deferredCallbackScheduled) { - return; - } + // bool _deferredCallbackScheduled = false; + // void _scheduleRebuild() { + // if (_deferredCallbackScheduled) { + // return; + // } - final bool deferMarkNeedsLayout = - switch (SchedulerBinding.instance.schedulerPhase) { - SchedulerPhase.idle || SchedulerPhase.postFrameCallbacks => true, - SchedulerPhase.transientCallbacks || - SchedulerPhase.midFrameMicrotasks || - SchedulerPhase.persistentCallbacks => false, - }; - if (!deferMarkNeedsLayout) { - renderObject.scheduleLayoutCallback(); - return; - } - _deferredCallbackScheduled = true; - SchedulerBinding.instance.scheduleFrameCallback(_frameCallback); - } + // final bool deferMarkNeedsLayout = + // switch (SchedulerBinding.instance.schedulerPhase) { + // SchedulerPhase.idle || SchedulerPhase.postFrameCallbacks => true, + // SchedulerPhase.transientCallbacks || + // SchedulerPhase.midFrameMicrotasks || + // SchedulerPhase.persistentCallbacks => false, + // }; + // if (!deferMarkNeedsLayout) { + // renderObject.scheduleLayoutCallback(); + // return; + // } + // _deferredCallbackScheduled = true; + // SchedulerBinding.instance.scheduleFrameCallback(_frameCallback); + // } - void _frameCallback(Duration timestamp) { - _deferredCallbackScheduled = false; - // This method is only called when the render tree is stable, if the Element - // is deactivated it will never be reincorporated back to the tree. - if (mounted) { - renderObject.scheduleLayoutCallback(); - } - } + // void _frameCallback(Duration timestamp) { + // _deferredCallbackScheduled = false; + // // This method is only called when the render tree is stable, if the Element + // // is deactivated it will never be reincorporated back to the tree. + // if (mounted) { + // renderObject.scheduleLayoutCallback(); + // } + // } @override void insertRenderObjectChild(RenderObject child, IndexedSlot slot) { diff --git a/lib/common/widgets/image_viewer/gallery_viewer.dart b/lib/common/widgets/image_viewer/gallery_viewer.dart index b9dec96d8..2e324f981 100644 --- a/lib/common/widgets/image_viewer/gallery_viewer.dart +++ b/lib/common/widgets/image_viewer/gallery_viewer.dart @@ -18,6 +18,7 @@ import 'dart:io' show File, Platform; import 'package:PiliPlus/common/widgets/colored_box_transition.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart'; import 'package:PiliPlus/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/image.dart'; @@ -35,7 +36,7 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide Image, PageView; +import 'package:flutter/material.dart' hide Image, PageView, LayoutBuilder; import 'package:flutter/services.dart' show HapticFeedback; import 'package:get/get.dart'; import 'package:media_kit/media_kit.dart'; diff --git a/lib/common/widgets/video_card/video_card_h.dart b/lib/common/widgets/video_card/video_card_h.dart index 4f2cd2bdd..e833019a7 100644 --- a/lib/common/widgets/video_card/video_card_h.dart +++ b/lib/common/widgets/video_card/video_card_h.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart'; @@ -15,7 +16,7 @@ import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; // 视频卡片 - 水平布局 diff --git a/lib/common/widgets/video_card/video_card_v.dart b/lib/common/widgets/video_card/video_card_v.dart index 9f2c8c730..b1dd289f4 100644 --- a/lib/common/widgets/video_card/video_card_v.dart +++ b/lib/common/widgets/video_card/video_card_v.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; @@ -15,7 +16,7 @@ import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:intl/intl.dart'; diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index b4f90cf09..b0a2ff665 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -177,7 +177,7 @@ class _ArticlePageState extends CommonDynPageState { // if (kDebugMode) debugPrint('moduleBlocked'); final moduleBlocked = controller.opusData!.modules.moduleBlocked!; content = SliverToBoxAdapter( - child: moduleBlockedItem(context, theme, moduleBlocked, maxWidth), + child: moduleBlockedItem(context, theme, moduleBlocked), ); } else if (controller.articleData?.content != null) { if (controller.articleData?.type == 3) { diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index ae3d13be6..2771f5a19 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/cached_network_svg_image.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -20,7 +21,7 @@ import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/foundation.dart' show kDebugMode; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_navigation/src/extension_navigation.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -711,7 +712,6 @@ Widget moduleBlockedItem( BuildContext context, ThemeData theme, ModuleBlocked moduleBlocked, - double maxWidth, ) { late final isDarkMode = theme.brightness.isDark; @@ -784,35 +784,41 @@ Widget moduleBlockedItem( } if (moduleBlocked.blockedType == 1) { - maxWidth = maxWidth <= 255 ? maxWidth : math.min(400, maxWidth * 0.8); - return UnconstrainedBox( - alignment: Alignment.centerLeft, - child: Container( - width: maxWidth, - height: maxWidth, - decoration: bgImg(), - padding: const EdgeInsets.all(12), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (moduleBlocked.icon != null) icon(math.max(40, maxWidth / 7)), - if (moduleBlocked.hintMessage?.isNotEmpty == true) ...[ - const SizedBox(height: 5), - Text( - moduleBlocked.hintMessage!, - textAlign: TextAlign.center, - style: TextStyle(color: theme.colorScheme.outline), - ), - ], - if (moduleBlocked.button != null) ...[ - const SizedBox(height: 8), - btn( - context, - visualDensity: const VisualDensity(vertical: -2.5), - ), - ], - ], - ), + return Align( + alignment: .centerLeft, + child: LayoutBuilder( + builder: (context, constraints) { + var maxWidth = constraints.maxWidth; + maxWidth = maxWidth <= 255 ? maxWidth : math.min(400, maxWidth * 0.8); + return Container( + width: maxWidth, + height: maxWidth, + decoration: bgImg(), + padding: const EdgeInsets.all(12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (moduleBlocked.icon != null) + icon(math.max(40, maxWidth / 7)), + if (moduleBlocked.hintMessage?.isNotEmpty == true) ...[ + const SizedBox(height: 5), + Text( + moduleBlocked.hintMessage!, + textAlign: TextAlign.center, + style: TextStyle(color: theme.colorScheme.outline), + ), + ], + if (moduleBlocked.button != null) ...[ + const SizedBox(height: 8), + btn( + context, + visualDensity: const VisualDensity(vertical: -2.5), + ), + ], + ], + ), + ); + }, ), ); } diff --git a/lib/pages/article_list/widgets/item.dart b/lib/pages/article_list/widgets/item.dart index 2c79b560c..a7e45fa0b 100644 --- a/lib/pages/article_list/widgets/item.dart +++ b/lib/pages/article_list/widgets/item.dart @@ -1,9 +1,10 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/article/article_list/article.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class ArticleListItem extends StatelessWidget { diff --git a/lib/pages/common/slide/common_slide_page.dart b/lib/pages/common/slide/common_slide_page.dart index 501bc1b7d..58ba68b7c 100644 --- a/lib/pages/common/slide/common_slide_page.dart +++ b/lib/pages/common/slide/common_slide_page.dart @@ -1,9 +1,10 @@ import 'dart:math' show max; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/gestures.dart' show HorizontalDragGestureRecognizer; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; abstract class CommonSlidePage extends StatefulWidget { diff --git a/lib/pages/download/detail/widgets/item.dart b/lib/pages/download/detail/widgets/item.dart index 644f3ebce..583095e16 100644 --- a/lib/pages/download/detail/widgets/item.dart +++ b/lib/pages/download/detail/widgets/item.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; @@ -20,7 +21,7 @@ import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:path/path.dart' as path; diff --git a/lib/pages/download/view.dart b/lib/pages/download/view.dart index a58a616cb..c32c83b0c 100644 --- a/lib/pages/download/view.dart +++ b/lib/pages/download/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; @@ -20,7 +21,7 @@ import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart' - hide SliverGridDelegateWithMaxCrossAxisExtent; + hide SliverGridDelegateWithMaxCrossAxisExtent, LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index ba33c7b31..c50356350 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -1,10 +1,9 @@ -import 'package:PiliPlus/common/widgets/flutter/dyn/text_button.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics_repost/view.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/request_utils.dart'; -import 'package:flutter/material.dart' hide TextButton; +import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class ActionPanel extends StatelessWidget { diff --git a/lib/pages/dynamics/widgets/additional_panel.dart b/lib/pages/dynamics/widgets/additional_panel.dart index 139817c8d..c12490a2e 100644 --- a/lib/pages/dynamics/widgets/additional_panel.dart +++ b/lib/pages/dynamics/widgets/additional_panel.dart @@ -1,5 +1,4 @@ import 'package:PiliPlus/common/constants.dart'; -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/http/dynamics.dart'; @@ -9,7 +8,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:flutter/foundation.dart' show kDebugMode; -import 'package:flutter/material.dart' hide InkWell; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; Widget addWidget( diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 46d056db9..0f21528b5 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -2,7 +2,6 @@ import 'dart:math'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/dialog/report.dart'; -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -24,7 +23,7 @@ import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/foundation.dart' show kDebugMode; -import 'package:flutter/material.dart' hide InkWell; +import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; diff --git a/lib/pages/dynamics/widgets/blocked_item.dart b/lib/pages/dynamics/widgets/blocked_item.dart index c777f44ca..ee9bd528e 100644 --- a/lib/pages/dynamics/widgets/blocked_item.dart +++ b/lib/pages/dynamics/widgets/blocked_item.dart @@ -7,10 +7,9 @@ Widget blockedItem( BuildContext context, { required ThemeData theme, required ModuleBlocked blocked, - required double maxWidth, }) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 1), - child: moduleBlockedItem(context, theme, blocked, maxWidth - 26), + child: moduleBlockedItem(context, theme, blocked), ); } diff --git a/lib/pages/dynamics/widgets/content_panel.dart b/lib/pages/dynamics/widgets/content_panel.dart index 41b2a2c18..b794d66c6 100644 --- a/lib/pages/dynamics/widgets/content_panel.dart +++ b/lib/pages/dynamics/widgets/content_panel.dart @@ -15,16 +15,11 @@ Widget content( required DynamicItemModel item, required bool isSave, required bool isDetail, - required double maxWidth, }) { - if (floor == 1) { - maxWidth -= 24; - } TextSpan? richNodes = richNode( context, theme: theme, item: item, - maxWidth: maxWidth, ); final moduleDynamic = item.modules.moduleDynamic; final pics = moduleDynamic?.major?.opus?.pics; diff --git a/lib/pages/dynamics/widgets/dyn_content.dart b/lib/pages/dynamics/widgets/dyn_content.dart index 66bced1fe..e144f2f7f 100644 --- a/lib/pages/dynamics/widgets/dyn_content.dart +++ b/lib/pages/dynamics/widgets/dyn_content.dart @@ -12,7 +12,6 @@ List dynContent( required DynamicItemModel item, required bool isSave, required bool isDetail, - required double maxWidth, }) { final moduleDynamic = item.modules.moduleDynamic; return [ @@ -24,7 +23,6 @@ List dynContent( isDetail: isDetail, item: item, floor: floor, - maxWidth: maxWidth, ), module( context, @@ -33,7 +31,6 @@ List dynContent( isDetail: isDetail, item: item, floor: floor, - maxWidth: maxWidth, ), if (moduleDynamic?.additional case final additional?) addWidget( @@ -44,6 +41,6 @@ List dynContent( floor: floor, ), if (moduleDynamic?.major?.blocked case final blocked?) - blockedItem(context, theme: theme, blocked: blocked, maxWidth: maxWidth), + blockedItem(context, theme: theme, blocked: blocked), ]; } diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index 5b309bc09..36c707836 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -1,5 +1,4 @@ import 'package:PiliPlus/common/widgets/avatars.dart'; -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; @@ -10,11 +9,10 @@ import 'package:PiliPlus/pages/dynamics/widgets/interaction.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart' hide InkWell; +import 'package:flutter/material.dart'; class DynamicPanel extends StatelessWidget { final DynamicItemModel item; - final double maxWidth; final bool isDetail; final ValueChanged? onRemove; final bool isSave; @@ -30,7 +28,6 @@ class DynamicPanel extends StatelessWidget { const DynamicPanel({ super.key, required this.item, - required this.maxWidth, this.isDetail = false, this.onRemove, this.isSave = false, @@ -99,7 +96,6 @@ class DynamicPanel extends StatelessWidget { isDetail: isDetail, item: item, floor: 1, - maxWidth: maxWidth, ), const SizedBox(height: 2), if (!isDetail) ...[ diff --git a/lib/pages/dynamics/widgets/forward_panel.dart b/lib/pages/dynamics/widgets/forward_panel.dart index ba6f12ce2..efff1be3e 100644 --- a/lib/pages/dynamics/widgets/forward_panel.dart +++ b/lib/pages/dynamics/widgets/forward_panel.dart @@ -1,4 +1,3 @@ -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dyn_content.dart'; @@ -6,7 +5,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/module_panel.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart' hide InkWell; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; Widget forwardPanel( @@ -16,7 +15,6 @@ Widget forwardPanel( required DynamicItemModel orig, required bool isSave, required bool isDetail, - required double maxWidth, }) { final moduleDynamic = orig.modules.moduleDynamic; final major = moduleDynamic?.major; @@ -44,7 +42,6 @@ Widget forwardPanel( isDetail: isDetail, item: orig, floor: floor + 1, - maxWidth: maxWidth - 30, ), ], ); diff --git a/lib/pages/dynamics/widgets/live_panel.dart b/lib/pages/dynamics/widgets/live_panel.dart index 902859e24..eee426f08 100644 --- a/lib/pages/dynamics/widgets/live_panel.dart +++ b/lib/pages/dynamics/widgets/live_panel.dart @@ -8,7 +8,6 @@ Widget livePanel( required ThemeData theme, required DynamicItemModel item, required bool isDetail, - required double maxWidth, Function(List, int)? callback, }) { DynamicLive2Model? live = item.modules.moduleDynamic!.major!.live; diff --git a/lib/pages/dynamics/widgets/live_panel_sub.dart b/lib/pages/dynamics/widgets/live_panel_sub.dart index 7852f8324..f5a3233a9 100644 --- a/lib/pages/dynamics/widgets/live_panel_sub.dart +++ b/lib/pages/dynamics/widgets/live_panel_sub.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; Widget livePanelSub( BuildContext context, { @@ -12,7 +13,6 @@ Widget livePanelSub( required ThemeData theme, required DynamicItemModel item, required bool isDetail, - required double maxWidth, }) { LivePlayInfo? live = item .modules @@ -27,7 +27,6 @@ Widget livePanelSub( } EdgeInsets padding; if (floor == 1) { - maxWidth -= 24; padding = const EdgeInsets.symmetric(horizontal: 12); } else { padding = EdgeInsets.zero; @@ -40,11 +39,13 @@ Widget livePanelSub( Stack( clipBehavior: Clip.none, children: [ - NetworkImgLayer( - width: maxWidth, - height: maxWidth / StyleString.aspectRatio, - src: live.cover, - quality: 40, + LayoutBuilder( + builder: (context, constraints) => NetworkImgLayer( + width: constraints.maxWidth, + height: constraints.maxWidth / StyleString.aspectRatio, + src: live.cover, + quality: 40, + ), ), PBadge( text: live.watchedShow?.textLarge, diff --git a/lib/pages/dynamics/widgets/live_rcmd_panel.dart b/lib/pages/dynamics/widgets/live_rcmd_panel.dart index 2d438d3bb..5b39e5c57 100644 --- a/lib/pages/dynamics/widgets/live_rcmd_panel.dart +++ b/lib/pages/dynamics/widgets/live_rcmd_panel.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; Widget liveRcmdPanel( BuildContext context, { @@ -12,7 +13,6 @@ Widget liveRcmdPanel( required ThemeData theme, required DynamicItemModel item, required bool isDetail, - required double maxWidth, Function(List, int)? callback, }) { DynamicLiveModel? liveRcmd = item.modules.moduleDynamic?.major?.liveRcmd; @@ -21,7 +21,6 @@ Widget liveRcmdPanel( } EdgeInsets padding; if (floor == 1) { - maxWidth -= 24; padding = const EdgeInsets.symmetric(horizontal: 12); } else { padding = EdgeInsets.zero; @@ -34,11 +33,13 @@ Widget liveRcmdPanel( Stack( clipBehavior: Clip.none, children: [ - NetworkImgLayer( - width: maxWidth, - height: maxWidth / StyleString.aspectRatio, - src: liveRcmd.cover, - quality: 40, + LayoutBuilder( + builder: (context, constraints) => NetworkImgLayer( + width: constraints.maxWidth, + height: constraints.maxWidth / StyleString.aspectRatio, + src: liveRcmd.cover, + quality: 40, + ), ), PBadge( text: liveRcmd.watchedShow?.textLarge, diff --git a/lib/pages/dynamics/widgets/module_panel.dart b/lib/pages/dynamics/widgets/module_panel.dart index 4b28d2947..cfeaabd71 100644 --- a/lib/pages/dynamics/widgets/module_panel.dart +++ b/lib/pages/dynamics/widgets/module_panel.dart @@ -1,6 +1,5 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pbenum.dart' show PlaylistSource; @@ -15,7 +14,7 @@ import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart' hide InkWell; +import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; Widget noneWidget(ThemeData theme, String? tips) => Row( @@ -40,7 +39,6 @@ Widget module( required DynamicItemModel item, required bool isSave, required bool isDetail, - required double maxWidth, }) { final moduleDynamic = item.modules.moduleDynamic; final major = moduleDynamic?.major; @@ -77,7 +75,6 @@ Widget module( floor: floor, isSave: isSave, isDetail: isDetail, - maxWidth: maxWidth, ); // 转发 case 'DYNAMIC_TYPE_FORWARD': @@ -88,7 +85,6 @@ Widget module( orig: item.orig!, isDetail: isDetail, floor: floor + 1, - maxWidth: maxWidth, ); // 直播 case 'DYNAMIC_TYPE_LIVE_RCMD': @@ -98,7 +94,6 @@ Widget module( isDetail: isDetail, item: item, floor: floor, - maxWidth: maxWidth, ); // 直播 case 'DYNAMIC_TYPE_LIVE': @@ -108,7 +103,6 @@ Widget module( item: item, floor: floor, isDetail: isDetail, - maxWidth: maxWidth, ); // 活动 case 'DYNAMIC_TYPE_COMMON_SQUARE': @@ -315,7 +309,6 @@ Widget module( isDetail: isDetail, item: item, floor: floor, - maxWidth: maxWidth, ); default: diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index 0c9b67903..cfd55ea69 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -22,7 +22,6 @@ TextSpan? richNode( BuildContext context, { required ThemeData theme, required DynamicItemModel item, - required double maxWidth, }) { try { late final style = TextStyle(color: theme.colorScheme.primary); diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index 43f5408eb..4e107d423 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -1,4 +1,3 @@ -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -10,7 +9,7 @@ import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart' hide InkWell; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; class UpPanel extends StatefulWidget { diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart index 883728b64..6311a5857 100644 --- a/lib/pages/dynamics/widgets/video_panel.dart +++ b/lib/pages/dynamics/widgets/video_panel.dart @@ -1,12 +1,13 @@ // 视频or合集 import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/num_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; Widget videoSeasonWidget( BuildContext context, { @@ -15,7 +16,6 @@ Widget videoSeasonWidget( required DynamicItemModel item, required bool isSave, required bool isDetail, - required double maxWidth, }) { // type archive ugcSeason // archive 视频/显示发布人 @@ -36,7 +36,6 @@ Widget videoSeasonWidget( EdgeInsets padding; if (floor == 1) { - maxWidth -= 24; padding = const EdgeInsets.symmetric(horizontal: 12); } else { padding = EdgeInsets.zero; @@ -51,11 +50,13 @@ Widget videoSeasonWidget( Stack( clipBehavior: Clip.none, children: [ - NetworkImgLayer( - width: maxWidth, - height: maxWidth / StyleString.aspectRatio, - src: cover, - quality: 40, + LayoutBuilder( + builder: (context, constraints) => NetworkImgLayer( + width: constraints.maxWidth, + height: constraints.maxWidth / StyleString.aspectRatio, + src: cover, + quality: 40, + ), ), if (video.badge?.text case final badge?) PBadge( diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index b04ad142b..452a14c03 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/avatars.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/dialog/report.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -15,7 +16,7 @@ import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/num_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class VotePanel extends StatefulWidget { diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index e247734b8..c1fdfc0fc 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -251,7 +251,6 @@ class _DynamicDetailPageState extends CommonDynPageState { child: DynamicPanel( item: controller.dynItem, isDetail: true, - maxWidth: maxWidth - this.padding.horizontal - 2 * padding, isDetailPortraitW: isPortrait, onSetPubSetting: controller.onSetPubSetting, onEdit: _onEdit, @@ -284,10 +283,6 @@ class _DynamicDetailPageState extends CommonDynPageState { child: DynamicPanel( item: controller.dynItem, isDetail: true, - maxWidth: - (maxWidth - this.padding.horizontal) * - (flex / (flex + flex1)) - - padding, isDetailPortraitW: isPortrait, onSetPubSetting: controller.onSetPubSetting, onEdit: _onEdit, diff --git a/lib/pages/dynamics_tab/view.dart b/lib/pages/dynamics_tab/view.dart index 3fdbcc983..c14c2e08b 100644 --- a/lib/pages/dynamics_tab/view.dart +++ b/lib/pages/dynamics_tab/view.dart @@ -104,7 +104,6 @@ class _DynamicsTabPageState extends State onRemove: (idStr) => controller.onRemove(index, idStr), onBlock: () => controller.onBlock(index), - maxWidth: maxWidth, onUnfold: () => controller.onUnfold(item, index), ); }, @@ -122,7 +121,6 @@ class _DynamicsTabPageState extends State onRemove: (idStr) => controller.onRemove(index, idStr), onBlock: () => controller.onBlock(index), - maxWidth: maxWidth, onUnfold: () => controller.onUnfold(item, index), ); }, diff --git a/lib/pages/dynamics_topic/view.dart b/lib/pages/dynamics_topic/view.dart index 03451d572..545e52901 100644 --- a/lib/pages/dynamics_topic/view.dart +++ b/lib/pages/dynamics_topic/view.dart @@ -350,10 +350,7 @@ class _DynTopicPageState extends State with DynMixin { final item = response[index]; if (item.dynamicCardItem != null) { - return DynamicPanel( - item: item.dynamicCardItem!, - maxWidth: maxWidth, - ); + return DynamicPanel(item: item.dynamicCardItem!); } return Text(item.topicType ?? 'err'); @@ -368,10 +365,7 @@ class _DynTopicPageState extends State with DynMixin { } final item = response[index]; if (item.dynamicCardItem != null) { - return DynamicPanel( - item: item.dynamicCardItem!, - maxWidth: maxWidth, - ); + return DynamicPanel(item: item.dynamicCardItem!); } else { return Text(item.topicType ?? 'err'); } diff --git a/lib/pages/fav/article/widget/item.dart b/lib/pages/fav/article/widget/item.dart index 9ad12f7e5..934df096e 100644 --- a/lib/pages/fav/article/widget/item.dart +++ b/lib/pages/fav/article/widget/item.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/fav/fav_article/item.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class FavArticleItem extends StatelessWidget { @@ -45,17 +46,13 @@ class FavArticleItem extends StatelessWidget { AspectRatio( aspectRatio: StyleString.aspectRatio, child: LayoutBuilder( - builder: - ( - BuildContext context, - BoxConstraints boxConstraints, - ) { - return NetworkImgLayer( - src: item.cover!.url, - width: boxConstraints.maxWidth, - height: boxConstraints.maxHeight, - ); - }, + builder: (context, boxConstraints) { + return NetworkImgLayer( + src: item.cover!.url, + width: boxConstraints.maxWidth, + height: boxConstraints.maxHeight, + ); + }, ), ), const SizedBox(width: 10), diff --git a/lib/pages/fav/note/widget/item.dart b/lib/pages/fav/note/widget/item.dart index b600dfb3b..6135e61d7 100644 --- a/lib/pages/fav/note/widget/item.dart +++ b/lib/pages/fav/note/widget/item.dart @@ -1,11 +1,12 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; import 'package:PiliPlus/models_new/fav/fav_note/list.dart'; import 'package:PiliPlus/pages/fav/note/controller.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class FavNoteItem extends StatelessWidget { const FavNoteItem({ diff --git a/lib/pages/fav/pgc/widget/item.dart b/lib/pages/fav/pgc/widget/item.dart index ccfe8d468..db59d8af7 100644 --- a/lib/pages/fav/pgc/widget/item.dart +++ b/lib/pages/fav/pgc/widget/item.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; @@ -8,7 +9,7 @@ import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class FavPgcItem extends StatelessWidget { const FavPgcItem({ @@ -60,45 +61,41 @@ class FavPgcItem extends StatelessWidget { AspectRatio( aspectRatio: 3 / 4, child: LayoutBuilder( - builder: - ( - BuildContext context, - BoxConstraints boxConstraints, - ) { - return Stack( - clipBehavior: Clip.none, - children: [ - NetworkImgLayer( - src: item.cover, - width: boxConstraints.maxWidth, - height: boxConstraints.maxHeight, - borderRadius: const BorderRadius.all( - Radius.circular(4), - ), + builder: (context, boxConstraints) { + return Stack( + clipBehavior: Clip.none, + children: [ + NetworkImgLayer( + src: item.cover, + width: boxConstraints.maxWidth, + height: boxConstraints.maxHeight, + borderRadius: const BorderRadius.all( + Radius.circular(4), + ), + ), + PBadge( + right: 4, + top: 4, + text: item.badge, + size: PBadgeSize.small, + fontSize: 10, + padding: const EdgeInsets.symmetric( + horizontal: 2, + vertical: 1, + ), + ), + Positioned.fill( + child: selectMask( + theme, + item.checked, + borderRadius: const BorderRadius.all( + Radius.circular(4), ), - PBadge( - right: 4, - top: 4, - text: item.badge, - size: PBadgeSize.small, - fontSize: 10, - padding: const EdgeInsets.symmetric( - horizontal: 2, - vertical: 1, - ), - ), - Positioned.fill( - child: selectMask( - theme, - item.checked, - borderRadius: const BorderRadius.all( - Radius.circular(4), - ), - ), - ), - ], - ); - }, + ), + ), + ], + ); + }, ), ), const SizedBox(width: 10), diff --git a/lib/pages/fav/video/widgets/item.dart b/lib/pages/fav/video/widgets/item.dart index 188c136b1..76401d9b0 100644 --- a/lib/pages/fav/video/widgets/item.dart +++ b/lib/pages/fav/video/widgets/item.dart @@ -1,9 +1,10 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; import 'package:PiliPlus/utils/fav_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class FavVideoItem extends StatelessWidget { final String heroTag; diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 0c545a9b8..6a7048124 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; @@ -16,7 +17,7 @@ import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; // 收藏视频卡片 - 水平布局 diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index c821189e2..6f660ecf8 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; @@ -13,7 +14,7 @@ import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; diff --git a/lib/pages/later/widgets/video_card_h_later.dart b/lib/pages/later/widgets/video_card_h_later.dart index 1beedce04..9647a22ca 100644 --- a/lib/pages/later/widgets/video_card_h_later.dart +++ b/lib/pages/later/widgets/video_card_h_later.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; @@ -13,7 +14,7 @@ import 'package:PiliPlus/pages/later/controller.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; // 视频卡片 - 水平布局 diff --git a/lib/pages/live/widgets/live_item_app.dart b/lib/pages/live/widgets/live_item_app.dart index 9c74c1237..44da5125d 100644 --- a/lib/pages/live/widgets/live_item_app.dart +++ b/lib/pages/live/widgets/live_item_app.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/live/live_feed_index/card_data_list_item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class LiveCardVApp extends StatelessWidget { diff --git a/lib/pages/live_dm_block/view.dart b/lib/pages/live_dm_block/view.dart index f181a1f35..0271c2823 100644 --- a/lib/pages/live_dm_block/view.dart +++ b/lib/pages/live_dm_block/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; @@ -10,8 +11,8 @@ import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; +import 'package:flutter/services.dart' show FilteringTextInputFormatter; import 'package:get/get.dart'; class LiveDmBlockPage extends StatefulWidget { diff --git a/lib/pages/live_follow/widgets/live_item_follow.dart b/lib/pages/live_follow/widgets/live_item_follow.dart index ef110e9da..b0dd15859 100644 --- a/lib/pages/live_follow/widgets/live_item_follow.dart +++ b/lib/pages/live_follow/widgets/live_item_follow.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/live/live_follow/item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class LiveCardVFollow extends StatelessWidget { diff --git a/lib/pages/live_search/widgets/live_search_room.dart b/lib/pages/live_search/widgets/live_search_room.dart index 4ffe0327d..c5d44c14d 100644 --- a/lib/pages/live_search/widgets/live_search_room.dart +++ b/lib/pages/live_search/widgets/live_search_room.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/live/live_search/room_item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class LiveCardVSearch extends StatelessWidget { diff --git a/lib/pages/member_article/widget/item.dart b/lib/pages/member_article/widget/item.dart index d4d5163be..931048d58 100644 --- a/lib/pages/member_article/widget/item.dart +++ b/lib/pages/member_article/widget/item.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; @@ -6,7 +7,7 @@ import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/space/space_article/item.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class MemberArticleItem extends StatelessWidget { const MemberArticleItem({super.key, required this.item}); diff --git a/lib/pages/member_audio/widgets/item.dart b/lib/pages/member_audio/widgets/item.dart index 601c5d3e5..4892e4ed0 100644 --- a/lib/pages/member_audio/widgets/item.dart +++ b/lib/pages/member_audio/widgets/item.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; @@ -9,7 +10,7 @@ import 'package:PiliPlus/models_new/space/space_audio/item.dart'; import 'package:PiliPlus/pages/audio/view.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class MemberAudioItem extends StatelessWidget { const MemberAudioItem({super.key, required this.item}); diff --git a/lib/pages/member_cheese/widgets/item.dart b/lib/pages/member_cheese/widgets/item.dart index 9f75c8b3a..d999e48f8 100644 --- a/lib/pages/member_cheese/widgets/item.dart +++ b/lib/pages/member_cheese/widgets/item.dart @@ -1,13 +1,14 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/space/space_cheese/item.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class MemberCheeseItem extends StatelessWidget { const MemberCheeseItem({ diff --git a/lib/pages/member_coin_arc/widgets/item.dart b/lib/pages/member_coin_arc/widgets/item.dart index bfde0d3fc..b9feaa0d0 100644 --- a/lib/pages/member_coin_arc/widgets/item.dart +++ b/lib/pages/member_coin_arc/widgets/item.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; @@ -12,7 +13,7 @@ import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class MemberCoinLikeItem extends StatelessWidget { final CoinLikeArcItem item; diff --git a/lib/pages/member_comic/widgets/item.dart b/lib/pages/member_comic/widgets/item.dart index a9be6da0d..8ac6a2b86 100644 --- a/lib/pages/member_comic/widgets/item.dart +++ b/lib/pages/member_comic/widgets/item.dart @@ -1,9 +1,10 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/space/space_archive/item.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class MemberComicItem extends StatelessWidget { diff --git a/lib/pages/member_dynamics/view.dart b/lib/pages/member_dynamics/view.dart index 0457fb1ec..a24619ddd 100644 --- a/lib/pages/member_dynamics/view.dart +++ b/lib/pages/member_dynamics/view.dart @@ -93,7 +93,6 @@ class _MemberDynamicsPageState extends State item: response[index], onRemove: _memberDynamicController.onRemove, onSetTop: _memberDynamicController.onSetTop, - maxWidth: maxWidth, ); }, childCount: response.length, @@ -108,7 +107,6 @@ class _MemberDynamicsPageState extends State item: response[index], onRemove: _memberDynamicController.onRemove, onSetTop: _memberDynamicController.onSetTop, - maxWidth: maxWidth, ); }, itemCount: response.length, diff --git a/lib/pages/member_favorite/widget/item.dart b/lib/pages/member_favorite/widget/item.dart index dbd3af711..7768b0a69 100644 --- a/lib/pages/member_favorite/widget/item.dart +++ b/lib/pages/member_favorite/widget/item.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/space/space_fav/list.dart'; @@ -8,7 +9,7 @@ import 'package:PiliPlus/utils/fav_utils.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class MemberFavItem extends StatelessWidget { diff --git a/lib/pages/member_home/widgets/video_card_v_member_home.dart b/lib/pages/member_home/widgets/video_card_v_member_home.dart index e84c904a3..95be864dd 100644 --- a/lib/pages/member_home/widgets/video_card_v_member_home.dart +++ b/lib/pages/member_home/widgets/video_card_v_member_home.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/http/search.dart'; @@ -10,7 +11,7 @@ import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class VideoCardVMemberHome extends StatelessWidget { diff --git a/lib/pages/member_opus/view.dart b/lib/pages/member_opus/view.dart index 0232c127c..e04998d42 100644 --- a/lib/pages/member_opus/view.dart +++ b/lib/pages/member_opus/view.dart @@ -33,8 +33,6 @@ class _MemberOpusState extends State with AutomaticKeepAliveClientMixin { late final MemberOpusController _controller; - late double _maxWidth; - @override void initState() { super.initState(); @@ -127,7 +125,6 @@ class _MemberOpusState extends State maxCrossAxisExtent: Grid.smallCardWidth, mainAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace, - afterCalc: (value) => _maxWidth = value, ); Widget _buildBody(LoadingState?> loadingState) { @@ -148,10 +145,7 @@ class _MemberOpusState extends State if (index == response.length - 1) { _controller.onLoadMore(); } - return SpaceOpusItem( - item: response[index], - maxWidth: _maxWidth, - ); + return SpaceOpusItem(item: response[index]); }, childCount: response.length, ), diff --git a/lib/pages/member_opus/widgets/space_opus_item.dart b/lib/pages/member_opus/widgets/space_opus_item.dart index 7d375de3a..471014a42 100644 --- a/lib/pages/member_opus/widgets/space_opus_item.dart +++ b/lib/pages/member_opus/widgets/space_opus_item.dart @@ -1,21 +1,19 @@ -import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/space/space_opus/item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; -import 'package:flutter/material.dart' hide InkWell; +import 'package:flutter/material.dart' hide LayoutBuilder; class SpaceOpusItem extends StatelessWidget { const SpaceOpusItem({ super.key, required this.item, - required this.maxWidth, }); final SpaceOpusItemModel item; - final double maxWidth; @override Widget build(BuildContext context) { @@ -34,12 +32,14 @@ class SpaceOpusItem extends StatelessWidget { if (hasPic) Stack( children: [ - NetworkImgLayer( - width: maxWidth, - height: maxWidth * item.cover!.ratio, - src: item.cover!.url, - type: ImageType.emote, - quality: 60, + LayoutBuilder( + builder: (context, constraints) => NetworkImgLayer( + width: constraints.maxWidth, + height: constraints.maxWidth * item.cover!.ratio, + src: item.cover!.url, + type: ImageType.emote, + quality: 60, + ), ), Positioned( left: 0, diff --git a/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart b/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart index fd22d0391..bd41ee22f 100644 --- a/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart +++ b/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/space/space_archive/item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class PgcCardVMemberPgc extends StatelessWidget { diff --git a/lib/pages/member_search/child/view.dart b/lib/pages/member_search/child/view.dart index 0b7bd317c..611ab56cc 100644 --- a/lib/pages/member_search/child/view.dart +++ b/lib/pages/member_search/child/view.dart @@ -95,10 +95,7 @@ class _MemberSearchChildPageState extends State if (index == response.length - 1) { _controller.onLoadMore(); } - return DynamicPanel( - item: response[index], - maxWidth: maxWidth, - ); + return DynamicPanel(item: response[index]); }, childCount: response.length, ), @@ -108,10 +105,7 @@ class _MemberSearchChildPageState extends State if (index == response.length - 1) { _controller.onLoadMore(); } - return DynamicPanel( - item: response[index], - maxWidth: maxWidth, - ); + return DynamicPanel(item: response[index]); }, itemCount: response.length, ), diff --git a/lib/pages/member_season_series/widget/season_series_card.dart b/lib/pages/member_season_series/widget/season_series_card.dart index f0a81b16f..b6d9c854a 100644 --- a/lib/pages/member_season_series/widget/season_series_card.dart +++ b/lib/pages/member_season_series/widget/season_series_card.dart @@ -1,11 +1,12 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/space/space_season_series/season.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class SeasonSeriesCard extends StatelessWidget { const SeasonSeriesCard({ diff --git a/lib/pages/member_shop/view.dart b/lib/pages/member_shop/view.dart index b22081904..08c79f5b8 100644 --- a/lib/pages/member_shop/view.dart +++ b/lib/pages/member_shop/view.dart @@ -65,13 +65,10 @@ class _MemberShopState extends State @override bool get wantKeepAlive => true; - late double _maxWidth; - late final gridDelegate = SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: Grid.smallCardWidth, mainAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace, - afterCalc: (value) => _maxWidth = value, ); Widget _buildBody(LoadingState?> loadingState) { @@ -92,10 +89,7 @@ class _MemberShopState extends State gridDelegate: gridDelegate, delegate: SliverChildBuilderDelegate( (_, index) { - return MemberShopItem( - item: response[index], - maxWidth: _maxWidth, - ); + return MemberShopItem(item: response[index]); }, childCount: response.length, ), diff --git a/lib/pages/member_shop/widgets/item.dart b/lib/pages/member_shop/widgets/item.dart index b914e4d0d..b386653e2 100644 --- a/lib/pages/member_shop/widgets/item.dart +++ b/lib/pages/member_shop/widgets/item.dart @@ -1,20 +1,19 @@ import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models_new/space/space_shop/item.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class MemberShopItem extends StatelessWidget { const MemberShopItem({ super.key, required this.item, - required this.maxWidth, }); final SpaceShopItem item; - final double maxWidth; @override Widget build(BuildContext context) { @@ -34,11 +33,13 @@ class MemberShopItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - NetworkImgLayer( - type: .emote, - src: item.cover?.url, - width: maxWidth, - height: maxWidth, + LayoutBuilder( + builder: (context, constraints) => NetworkImgLayer( + type: .emote, + src: item.cover?.url, + width: constraints.maxWidth, + height: constraints.maxWidth, + ), ), Padding( padding: const EdgeInsets.all(8), diff --git a/lib/pages/member_video/widgets/video_card_h_member_video.dart b/lib/pages/member_video/widgets/video_card_h_member_video.dart index c09b3cdd5..2744d9d35 100644 --- a/lib/pages/member_video/widgets/video_card_h_member_video.dart +++ b/lib/pages/member_video/widgets/video_card_h_member_video.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart'; @@ -12,7 +13,7 @@ import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; // 视频卡片 - 水平布局 @@ -77,137 +78,118 @@ class VideoCardHMemberVideo extends StatelessWidget { horizontal: StyleString.safeSpace, vertical: 5, ), - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints boxConstraints) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: - ( - BuildContext context, - BoxConstraints boxConstraints, - ) { - final double maxWidth = boxConstraints.maxWidth; - final double maxHeight = - boxConstraints.maxHeight; - return Stack( - clipBehavior: Clip.none, - children: [ - NetworkImgLayer( - src: videoItem.cover, - width: maxWidth, - height: maxHeight, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + final double maxWidth = boxConstraints.maxWidth; + final double maxHeight = boxConstraints.maxHeight; + return Stack( + clipBehavior: Clip.none, + children: [ + NetworkImgLayer( + src: videoItem.cover, + width: maxWidth, + height: maxHeight, + ), + if (fromViewAid == videoItem.param) + const Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: StyleString.mdRadius, + color: Colors.black54, + ), + child: Center( + child: Text( + '上次观看', + style: TextStyle( + color: Colors.white, + fontSize: 15, + letterSpacing: 5, + ), ), - if (fromViewAid == videoItem.param) - const Positioned.fill( - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: StyleString.mdRadius, - color: Colors.black54, - ), - child: Center( - child: Text( - '上次观看', - style: TextStyle( - color: Colors.white, - fontSize: 15, - letterSpacing: 5, - ), - ), - ), - ), + ), + ), + ), + if (videoItem.badges?.isNotEmpty == true) + PBadge( + text: videoItem.badges! + .map((item) => item.text) + .join('|'), + right: 6.0, + top: 6.0, + type: videoItem.badges!.first.text == '充电专属' + ? PBadgeType.error + : PBadgeType.primary, + ), + if (videoItem.history != null) ...[ + Builder( + builder: (context) { + try { + return Positioned( + left: 0, + right: 0, + bottom: 0, + child: VideoProgressIndicator( + color: theme.colorScheme.primary, + backgroundColor: theme + .colorScheme + .secondaryContainer, + progress: + videoItem.history!.progress! / + videoItem.history!.duration!, ), - if (videoItem.badges?.isNotEmpty == true) - PBadge( - text: videoItem.badges! - .map((item) => item.text) - .join('|'), - right: 6.0, - top: 6.0, - type: - videoItem.badges!.first.text == - '充电专属' - ? PBadgeType.error - : PBadgeType.primary, + ); + } catch (_) { + return const SizedBox.shrink(); + } + }, + ), + Builder( + builder: (context) { + try { + return PBadge( + text: + videoItem.history!.progress == + videoItem.history!.duration + ? '已看完' + : '${DurationUtils.formatDuration(videoItem.history!.progress)}/${DurationUtils.formatDuration(videoItem.history!.duration)}', + right: 6.0, + bottom: 6.0, + type: PBadgeType.gray, + ); + } catch (_) { + return PBadge( + text: DurationUtils.formatDuration( + videoItem.duration, ), - if (videoItem.history != null) ...[ - Builder( - builder: (context) { - try { - return Positioned( - left: 0, - right: 0, - bottom: 0, - child: VideoProgressIndicator( - color: - theme.colorScheme.primary, - backgroundColor: theme - .colorScheme - .secondaryContainer, - progress: - videoItem - .history! - .progress! / - videoItem - .history! - .duration!, - ), - ); - } catch (_) { - return const SizedBox.shrink(); - } - }, - ), - Builder( - builder: (context) { - try { - return PBadge( - text: - videoItem.history!.progress == - videoItem - .history! - .duration - ? '已看完' - : '${DurationUtils.formatDuration(videoItem.history!.progress)}/${DurationUtils.formatDuration(videoItem.history!.duration)}', - right: 6.0, - bottom: 6.0, - type: PBadgeType.gray, - ); - } catch (_) { - return PBadge( - text: - DurationUtils.formatDuration( - videoItem.duration, - ), - right: 6.0, - bottom: 6.0, - type: PBadgeType.gray, - ); - } - }, - ), - ] else if (videoItem.duration > 0) - PBadge( - text: DurationUtils.formatDuration( - videoItem.duration, - ), - right: 6.0, - bottom: 6.0, - type: PBadgeType.gray, - ), - ], - ); - }, - ), - ), - const SizedBox(width: 10), - content(context, theme), - ], - ); - }, + right: 6.0, + bottom: 6.0, + type: PBadgeType.gray, + ); + } + }, + ), + ] else if (videoItem.duration > 0) + PBadge( + text: DurationUtils.formatDuration( + videoItem.duration, + ), + right: 6.0, + bottom: 6.0, + type: PBadgeType.gray, + ), + ], + ); + }, + ), + ), + const SizedBox(width: 10), + content(context, theme), + ], ), ), ), diff --git a/lib/pages/music/widget/music_video_card_h.dart b/lib/pages/music/widget/music_video_card_h.dart index 081afa752..5e7b7f834 100644 --- a/lib/pages/music/widget/music_video_card_h.dart +++ b/lib/pages/music/widget/music_video_card_h.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/marquee.dart'; @@ -11,7 +12,7 @@ import 'package:PiliPlus/models_new/music/bgm_recommend_list.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class MusicVideoCardH extends StatelessWidget { final BgmRecommend videoItem; diff --git a/lib/pages/pgc/widgets/pgc_card_v.dart b/lib/pages/pgc/widgets/pgc_card_v.dart index 1c6b1e4ea..2f3c6b559 100644 --- a/lib/pages/pgc/widgets/pgc_card_v.dart +++ b/lib/pages/pgc/widgets/pgc_card_v.dart @@ -1,12 +1,13 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class PgcCardV extends StatelessWidget { diff --git a/lib/pages/pgc/widgets/pgc_card_v_timeline.dart b/lib/pages/pgc/widgets/pgc_card_v_timeline.dart index e9413f054..85930eceb 100644 --- a/lib/pages/pgc/widgets/pgc_card_v_timeline.dart +++ b/lib/pages/pgc/widgets/pgc_card_v_timeline.dart @@ -1,12 +1,13 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models_new/pgc/pgc_timeline/episode.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class PgcCardVTimeline extends StatelessWidget { diff --git a/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart b/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart index 915e51b02..5e50491dd 100644 --- a/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart +++ b/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart @@ -1,12 +1,13 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models_new/pgc/pgc_index_result/list.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class PgcCardVPgcIndex extends StatelessWidget { diff --git a/lib/pages/rank/zone/widget/pgc_rank_item.dart b/lib/pages/rank/zone/widget/pgc_rank_item.dart index bac4d485d..f26234c80 100644 --- a/lib/pages/rank/zone/widget/pgc_rank_item.dart +++ b/lib/pages/rank/zone/widget/pgc_rank_item.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; @@ -6,7 +7,7 @@ import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/pgc/pgc_rank/pgc_rank_item_model.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class PgcRankItem extends StatelessWidget { const PgcRankItem({super.key, required this.item}); diff --git a/lib/pages/save_panel/view.dart b/lib/pages/save_panel/view.dart index 74a577e8d..2d8c121e2 100644 --- a/lib/pages/save_panel/view.dart +++ b/lib/pages/save_panel/view.dart @@ -386,7 +386,6 @@ class _SavePanelState extends State { item: dyn, isDetail: true, isSave: true, - maxWidth: maxWidth - 24, ), ), if (cover?.isNotEmpty == true && diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 58b002437..553a9c90b 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:PiliPlus/common/widgets/disabled_icon.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/search/search_rcmd/data.dart'; @@ -13,7 +14,7 @@ import 'package:PiliPlus/utils/extension/size_ext.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'hide LayoutBuilder; import 'package:get/get.dart'; class SearchPage extends StatefulWidget { diff --git a/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart b/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart index 41c0a0708..4fe53f696 100644 --- a/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart +++ b/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart @@ -1,11 +1,12 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class PgcCardVSearch extends StatelessWidget { diff --git a/lib/pages/search_panel/article/widgets/item.dart b/lib/pages/search_panel/article/widgets/item.dart index e5d725429..40c707c0f 100644 --- a/lib/pages/search_panel/article/widgets/item.dart +++ b/lib/pages/search_panel/article/widgets/item.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class SearchArticleItem extends StatelessWidget { diff --git a/lib/pages/search_panel/live/widgets/item.dart b/lib/pages/search_panel/live/widgets/item.dart index 25e09e5ec..58b42fb11 100644 --- a/lib/pages/search_panel/live/widgets/item.dart +++ b/lib/pages/search_panel/live/widgets/item.dart @@ -1,11 +1,12 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; class LiveItem extends StatelessWidget { final SearchLiveItemModel liveItem; diff --git a/lib/pages/subscription/widgets/item.dart b/lib/pages/subscription/widgets/item.dart index 49bbcb3f3..c85f49565 100644 --- a/lib/pages/subscription/widgets/item.dart +++ b/lib/pages/subscription/widgets/item.dart @@ -1,12 +1,13 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/sub/sub/list.dart'; import 'package:PiliPlus/pages/subscription_detail/view.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart index 911c63515..9d4bc46d2 100644 --- a/lib/pages/subscription_detail/widget/sub_video_card.dart +++ b/lib/pages/subscription_detail/widget/sub_video_card.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; @@ -11,7 +12,7 @@ import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; // 收藏视频卡片 - 水平布局 class SubVideoCardH extends StatelessWidget { diff --git a/lib/pages/video/reply_search_item/child/widgets/item.dart b/lib/pages/video/reply_search_item/child/widgets/item.dart index 0095bd58c..ceac7bada 100644 --- a/lib/pages/video/reply_search_item/child/widgets/item.dart +++ b/lib/pages/video/reply_search_item/child/widgets/item.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' @@ -8,7 +9,7 @@ import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/common/reply/reply_search_type.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class ReplySearchItem extends StatelessWidget { diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index ced1fca6d..2fcfeb748 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; @@ -21,7 +22,7 @@ import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; diff --git a/lib/utils/waterfall.dart b/lib/utils/waterfall.dart index c61bf2c6c..78ad40ce2 100644 --- a/lib/utils/waterfall.dart +++ b/lib/utils/waterfall.dart @@ -1,20 +1,18 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/dynamic_card.dart'; +import 'package:PiliPlus/common/widgets/flutter/sliver_layout_builder.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/grid.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide SliverLayoutBuilder; import 'package:flutter/rendering.dart' show SliverConstraints; import 'package:waterfall_flow/waterfall_flow.dart' show SliverWaterfallFlowDelegate; mixin DynMixin { - late double maxWidth; - late final dynGridDelegate = SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: Grid.smallCardWidth * 2, crossAxisSpacing: 4, - afterCalc: (value) => maxWidth = value, ); Widget buildPage(Widget child) { @@ -26,7 +24,6 @@ mixin DynMixin { final maxWidth = constraints.crossAxisExtent; final cardWidth = Grid.smallCardWidth * 2; final flag = cardWidth < maxWidth; - this.maxWidth = flag ? cardWidth : maxWidth; return SliverPadding( padding: EdgeInsets.symmetric( horizontal: flag ? (maxWidth - cardWidth) / 2 : 0, @@ -76,7 +73,6 @@ class SliverWaterfallFlowDelegateWithMaxCrossAxisExtent super.collectGarbage, super.viewportBuilder, super.closeToTrailing, - this.afterCalc, }) : assert(maxCrossAxisExtent >= 0); /// The maximum extent of tiles in the cross axis. @@ -95,8 +91,6 @@ class SliverWaterfallFlowDelegateWithMaxCrossAxisExtent int? crossAxisCount; double? crossAxisExtent; - final ValueChanged? afterCalc; - @override int getCrossAxisCount(SliverConstraints constraints) { final crossAxisExtent = constraints.crossAxisExtent; @@ -106,10 +100,6 @@ class SliverWaterfallFlowDelegateWithMaxCrossAxisExtent this.crossAxisExtent = crossAxisExtent; crossAxisCount = (crossAxisExtent / (maxCrossAxisExtent + crossAxisSpacing)) .ceil(); - afterCalc?.call( - (crossAxisExtent - ((crossAxisCount! - 1) * crossAxisSpacing)) / - crossAxisCount!, - ); return crossAxisCount!; }