mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-20 09:06:36 +08:00
789
lib/common/widgets/dyn/button.dart
Normal file
789
lib/common/widgets/dyn/button.dart
Normal file
@@ -0,0 +1,789 @@
|
|||||||
|
// 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/dyn/ink_well.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart' hide 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.
|
||||||
|
/// * <https://m3.material.io/components/buttons/overview>, 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<bool>? 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<bool>? 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.ButtonStyleButton.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<ButtonStyleButton> createState() => _ButtonStyleState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties
|
||||||
|
..add(
|
||||||
|
FlagProperty('enabled', value: enabled, ifFalse: 'disabled'),
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null),
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
DiagnosticsProperty<FocusNode>(
|
||||||
|
'focusNode',
|
||||||
|
focusNode,
|
||||||
|
defaultValue: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns null if [value] is null, otherwise `WidgetStatePropertyAll<T>(value)`.
|
||||||
|
///
|
||||||
|
/// A convenience method for subclasses.
|
||||||
|
static WidgetStateProperty<T>? allOrNull<T>(T? value) =>
|
||||||
|
value == null ? null : WidgetStatePropertyAll<T>(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<Color?>? defaultColor(
|
||||||
|
Color? enabled,
|
||||||
|
Color? disabled,
|
||||||
|
) {
|
||||||
|
if ((enabled ?? disabled) == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return WidgetStateProperty<Color?>.fromMap(<WidgetStatesConstraint, Color?>{
|
||||||
|
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<ButtonStyleButton>
|
||||||
|
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>(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<T>(
|
||||||
|
WidgetStateProperty<T>? 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<double?>(
|
||||||
|
(ButtonStyle? style) => style?.elevation,
|
||||||
|
);
|
||||||
|
final TextStyle? resolvedTextStyle = resolve<TextStyle?>(
|
||||||
|
(ButtonStyle? style) => style?.textStyle,
|
||||||
|
);
|
||||||
|
Color? resolvedBackgroundColor = resolve<Color?>(
|
||||||
|
(ButtonStyle? style) => style?.backgroundColor,
|
||||||
|
);
|
||||||
|
final Color? resolvedForegroundColor = resolve<Color?>(
|
||||||
|
(ButtonStyle? style) => style?.foregroundColor,
|
||||||
|
);
|
||||||
|
final Color? resolvedShadowColor = resolve<Color?>(
|
||||||
|
(ButtonStyle? style) => style?.shadowColor,
|
||||||
|
);
|
||||||
|
final Color? resolvedSurfaceTintColor = resolve<Color?>(
|
||||||
|
(ButtonStyle? style) => style?.surfaceTintColor,
|
||||||
|
);
|
||||||
|
final EdgeInsetsGeometry? resolvedPadding = resolve<EdgeInsetsGeometry?>(
|
||||||
|
(ButtonStyle? style) => style?.padding,
|
||||||
|
);
|
||||||
|
final Size? resolvedMinimumSize = resolve<Size?>(
|
||||||
|
(ButtonStyle? style) => style?.minimumSize,
|
||||||
|
);
|
||||||
|
final Size? resolvedFixedSize = resolve<Size?>(
|
||||||
|
(ButtonStyle? style) => style?.fixedSize,
|
||||||
|
);
|
||||||
|
final Size? resolvedMaximumSize = resolve<Size?>(
|
||||||
|
(ButtonStyle? style) => style?.maximumSize,
|
||||||
|
);
|
||||||
|
final Color? resolvedIconColor = effectiveIconColor();
|
||||||
|
final double? resolvedIconSize = resolve<double?>(
|
||||||
|
(ButtonStyle? style) => style?.iconSize,
|
||||||
|
);
|
||||||
|
final BorderSide? resolvedSide = resolve<BorderSide?>(
|
||||||
|
(ButtonStyle? style) => style?.side,
|
||||||
|
);
|
||||||
|
final OutlinedBorder? resolvedShape = resolve<OutlinedBorder?>(
|
||||||
|
(ButtonStyle? style) => style?.shape,
|
||||||
|
);
|
||||||
|
|
||||||
|
final WidgetStateMouseCursor mouseCursor = _MouseCursor(
|
||||||
|
(Set<WidgetState> states) => effectiveValue(
|
||||||
|
(ButtonStyle? style) => style?.mouseCursor?.resolve(states),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final WidgetStateProperty<Color?> overlayColor =
|
||||||
|
WidgetStateProperty.resolveWith<Color?>(
|
||||||
|
(Set<WidgetState> 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<MouseCursor?> resolveCallback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MouseCursor resolve(Set<WidgetState> 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 height = math.max(childSize.width, minSize.width);
|
||||||
|
final double width = math.max(childSize.height, minSize.height);
|
||||||
|
return constraints.constrain(Size(height, width));
|
||||||
|
}
|
||||||
|
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 BoxParentData 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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1307
lib/common/widgets/dyn/ink_well.dart
Normal file
1307
lib/common/widgets/dyn/ink_well.dart
Normal file
File diff suppressed because it is too large
Load Diff
676
lib/common/widgets/dyn/text_button.dart
Normal file
676
lib/common/widgets/dyn/text_button.dart
Normal file
@@ -0,0 +1,676 @@
|
|||||||
|
// 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/dyn/button.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart' hide InkWell, ButtonStyleButton;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
/// * <https://material.io/design/components/buttons.html>
|
||||||
|
/// * <https://m3.material.io/components/buttons>
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 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, will create a [TextButton] instead.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||||||
|
///
|
||||||
|
factory TextButton.icon({
|
||||||
|
Key? key,
|
||||||
|
required VoidCallback? onPressed,
|
||||||
|
VoidCallback? onLongPress,
|
||||||
|
ValueChanged<bool>? onHover,
|
||||||
|
ValueChanged<bool>? onFocusChange,
|
||||||
|
ButtonStyle? style,
|
||||||
|
FocusNode? focusNode,
|
||||||
|
bool? autofocus,
|
||||||
|
Clip? clipBehavior,
|
||||||
|
WidgetStatesController? statesController,
|
||||||
|
Widget? icon,
|
||||||
|
required Widget label,
|
||||||
|
IconAlignment? iconAlignment,
|
||||||
|
}) {
|
||||||
|
if (icon == null) {
|
||||||
|
return TextButton(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _TextButtonWithIcon(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<Color?>? backgroundColorProp = switch ((
|
||||||
|
backgroundColor,
|
||||||
|
disabledBackgroundColor,
|
||||||
|
)) {
|
||||||
|
(_?, null) => WidgetStatePropertyAll<Color?>(backgroundColor),
|
||||||
|
(_, _) => ButtonStyleButton.defaultColor(
|
||||||
|
backgroundColor,
|
||||||
|
disabledBackgroundColor,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
final WidgetStateProperty<Color?>? iconColorProp = switch ((
|
||||||
|
iconColor,
|
||||||
|
disabledIconColor,
|
||||||
|
)) {
|
||||||
|
(_?, null) => WidgetStatePropertyAll<Color?>(iconColor),
|
||||||
|
(_, _) => ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
|
||||||
|
};
|
||||||
|
final WidgetStateProperty<Color?>? overlayColorProp = switch ((
|
||||||
|
foregroundColor,
|
||||||
|
overlayColor,
|
||||||
|
)) {
|
||||||
|
(null, null) => null,
|
||||||
|
(_, Color(a: 0.0)) => WidgetStatePropertyAll<Color?>(overlayColor),
|
||||||
|
(_, final Color color) || (final Color color, _) =>
|
||||||
|
WidgetStateProperty<Color?>.fromMap(<WidgetState, Color?>{
|
||||||
|
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>(textStyle),
|
||||||
|
foregroundColor: ButtonStyleButton.defaultColor(
|
||||||
|
foregroundColor,
|
||||||
|
disabledForegroundColor,
|
||||||
|
),
|
||||||
|
backgroundColor: backgroundColorProp,
|
||||||
|
overlayColor: overlayColorProp,
|
||||||
|
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
||||||
|
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||||
|
iconColor: iconColorProp,
|
||||||
|
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
elevation: ButtonStyleButton.allOrNull<double>(elevation),
|
||||||
|
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||||
|
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||||
|
fixedSize: ButtonStyleButton.allOrNull<Size>(fixedSize),
|
||||||
|
maximumSize: ButtonStyleButton.allOrNull<Size>(maximumSize),
|
||||||
|
side: ButtonStyleButton.allOrNull<BorderSide>(side),
|
||||||
|
shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
|
||||||
|
mouseCursor: WidgetStateProperty<MouseCursor?>.fromMap(
|
||||||
|
<WidgetStatesConstraint, MouseCursor?>{
|
||||||
|
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`
|
||||||
|
/// * disabled - SystemMouseCursors.basic
|
||||||
|
/// * others - SystemMouseCursors.click
|
||||||
|
/// * `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`
|
||||||
|
/// * disabled - SystemMouseCursors.basic
|
||||||
|
/// * others - SystemMouseCursors.click
|
||||||
|
/// * `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;
|
||||||
|
|
||||||
|
return Theme.of(context).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: SystemMouseCursors.click,
|
||||||
|
disabledMouseCursor: SystemMouseCursors.basic,
|
||||||
|
visualDensity: theme.visualDensity,
|
||||||
|
tapTargetSize: theme.materialTapTargetSize,
|
||||||
|
animationDuration: kThemeChangeDuration,
|
||||||
|
enableFeedback: true,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
splashFactory: InkRipple.splashFactory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 _TextButtonWithIcon extends TextButton {
|
||||||
|
_TextButtonWithIcon({
|
||||||
|
super.key,
|
||||||
|
required super.onPressed,
|
||||||
|
super.onLongPress,
|
||||||
|
super.onHover,
|
||||||
|
super.onFocusChange,
|
||||||
|
super.style,
|
||||||
|
super.focusNode,
|
||||||
|
bool? autofocus,
|
||||||
|
super.clipBehavior,
|
||||||
|
super.statesController,
|
||||||
|
required Widget icon,
|
||||||
|
required Widget label,
|
||||||
|
IconAlignment? iconAlignment,
|
||||||
|
}) : super(
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
child: _TextButtonWithIconChild(
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
buttonStyle: style,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ButtonStyle defaultStyleOf(BuildContext context) {
|
||||||
|
final bool useMaterial3 = Theme.of(context).useMaterial3;
|
||||||
|
final ButtonStyle buttonStyle = super.defaultStyleOf(context);
|
||||||
|
final double defaultFontSize =
|
||||||
|
buttonStyle.textStyle?.resolve(const <WidgetState>{})?.fontSize ?? 14.0;
|
||||||
|
final double effectiveTextScale =
|
||||||
|
MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0;
|
||||||
|
final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
|
||||||
|
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<EdgeInsetsGeometry>(scaledPadding),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <WidgetState>{})?.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
|
||||||
|
? <Widget>[icon, Flexible(child: label)]
|
||||||
|
: <Widget>[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<TextStyle?> get textStyle =>
|
||||||
|
WidgetStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<Color?>? get backgroundColor =>
|
||||||
|
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<Color?>? get foregroundColor =>
|
||||||
|
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return _colors.onSurface.withValues(alpha: 0.38);
|
||||||
|
}
|
||||||
|
return _colors.primary;
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<Color?>? get overlayColor =>
|
||||||
|
WidgetStateProperty.resolveWith((Set<WidgetState> 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<Color>? get shadowColor =>
|
||||||
|
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<Color>? get surfaceTintColor =>
|
||||||
|
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<double>? get elevation =>
|
||||||
|
const WidgetStatePropertyAll<double>(0.0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<EdgeInsetsGeometry>? get padding =>
|
||||||
|
WidgetStatePropertyAll<EdgeInsetsGeometry>(_scaledPadding(context));
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<Size>? get minimumSize =>
|
||||||
|
const WidgetStatePropertyAll<Size>(Size(64.0, 40.0));
|
||||||
|
|
||||||
|
// No default fixedSize
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<double>? get iconSize =>
|
||||||
|
const WidgetStatePropertyAll<double>(18.0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<Color>? get iconColor {
|
||||||
|
return WidgetStateProperty.resolveWith((Set<WidgetState> 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<Size>? get maximumSize =>
|
||||||
|
const WidgetStatePropertyAll<Size>(Size.infinite);
|
||||||
|
|
||||||
|
// No default side
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<OutlinedBorder>? get shape =>
|
||||||
|
const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder());
|
||||||
|
|
||||||
|
@override
|
||||||
|
WidgetStateProperty<MouseCursor?>? get mouseCursor =>
|
||||||
|
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return SystemMouseCursors.basic;
|
||||||
|
}
|
||||||
|
return SystemMouseCursors.click;
|
||||||
|
});
|
||||||
|
|
||||||
|
@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
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import 'package:PiliPlus/common/widgets/dyn/text_button.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
||||||
import 'package:PiliPlus/utils/num_util.dart';
|
import 'package:PiliPlus/utils/num_util.dart';
|
||||||
import 'package:PiliPlus/utils/page_utils.dart';
|
import 'package:PiliPlus/utils/page_utils.dart';
|
||||||
import 'package:PiliPlus/utils/request_utils.dart';
|
import 'package:PiliPlus/utils/request_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide TextButton;
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
class ActionPanel extends StatelessWidget {
|
class ActionPanel extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/dyn/ink_well.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/http/dynamics.dart';
|
import 'package:PiliPlus/http/dynamics.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
@@ -8,7 +9,7 @@ import 'package:PiliPlus/utils/app_scheme.dart';
|
|||||||
import 'package:PiliPlus/utils/num_util.dart';
|
import 'package:PiliPlus/utils/num_util.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide InkWell;
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/common/widgets/dialog/report.dart';
|
import 'package:PiliPlus/common/widgets/dialog/report.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/dyn/ink_well.dart';
|
||||||
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
||||||
import 'package:PiliPlus/http/constants.dart';
|
import 'package:PiliPlus/http/constants.dart';
|
||||||
import 'package:PiliPlus/http/user.dart';
|
import 'package:PiliPlus/http/user.dart';
|
||||||
@@ -18,7 +19,7 @@ import 'package:PiliPlus/utils/page_utils.dart';
|
|||||||
import 'package:PiliPlus/utils/request_utils.dart';
|
import 'package:PiliPlus/utils/request_utils.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide InkWell;
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart' hide ContextExtensionss;
|
import 'package:get/get.dart' hide ContextExtensionss;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:PiliPlus/common/widgets/dyn/ink_well.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics/widgets/action_panel.dart';
|
import 'package:PiliPlus/pages/dynamics/widgets/action_panel.dart';
|
||||||
@@ -8,7 +9,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/content_panel.dart';
|
|||||||
import 'package:PiliPlus/pages/dynamics/widgets/module_panel.dart';
|
import 'package:PiliPlus/pages/dynamics/widgets/module_panel.dart';
|
||||||
import 'package:PiliPlus/utils/context_ext.dart';
|
import 'package:PiliPlus/utils/context_ext.dart';
|
||||||
import 'package:PiliPlus/utils/page_utils.dart';
|
import 'package:PiliPlus/utils/page_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide InkWell;
|
||||||
|
|
||||||
class DynamicPanel extends StatelessWidget {
|
class DynamicPanel extends StatelessWidget {
|
||||||
final DynamicItemModel item;
|
final DynamicItemModel item;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// 转发
|
// 转发
|
||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/dyn/ink_well.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/image_save.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/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
@@ -15,7 +16,7 @@ import 'package:PiliPlus/utils/date_util.dart';
|
|||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/page_utils.dart';
|
import 'package:PiliPlus/utils/page_utils.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide InkWell;
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user