Unverified Commit 35a94f70 authored by Daniel Edrisian's avatar Daniel Edrisian Committed by GitHub

Adaptive progress indicator (#69143)

parent fb508db9
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -14,6 +15,8 @@ const double _kMinCircularProgressIndicatorSize = 36.0; ...@@ -14,6 +15,8 @@ const double _kMinCircularProgressIndicatorSize = 36.0;
const int _kIndeterminateLinearDuration = 1800; const int _kIndeterminateLinearDuration = 1800;
const int _kIndeterminateCircularDuration = 1333 * 2222; const int _kIndeterminateCircularDuration = 1333 * 2222;
enum _ActivityIndicatorType { material, adaptive }
/// A base class for material design progress indicators. /// A base class for material design progress indicators.
/// ///
/// This widget cannot be instantiated directly. For a linear progress /// This widget cannot be instantiated directly. For a linear progress
...@@ -53,11 +56,17 @@ abstract class ProgressIndicator extends StatefulWidget { ...@@ -53,11 +56,17 @@ abstract class ProgressIndicator extends StatefulWidget {
/// If null, this progress indicator is indeterminate, which means the /// If null, this progress indicator is indeterminate, which means the
/// indicator displays a predetermined animation that does not indicate how /// indicator displays a predetermined animation that does not indicate how
/// much actual progress is being made. /// much actual progress is being made.
///
/// This property is ignored if used in an adaptive constructor inside an iOS
/// environment.
final double? value; final double? value;
/// The progress indicator's background color. /// The progress indicator's background color.
/// ///
/// The current theme's [ThemeData.backgroundColor] by default. /// The current theme's [ThemeData.backgroundColor] by default.
///
/// This property is ignored if used in an adaptive constructor inside an iOS
/// environment.
final Color? backgroundColor; final Color? backgroundColor;
/// The progress indicator's color as an animated value. /// The progress indicator's color as an animated value.
...@@ -66,6 +75,9 @@ abstract class ProgressIndicator extends StatefulWidget { ...@@ -66,6 +75,9 @@ abstract class ProgressIndicator extends StatefulWidget {
/// ///
/// If null, the progress indicator is rendered with the current theme's /// If null, the progress indicator is rendered with the current theme's
/// [ThemeData.accentColor]. /// [ThemeData.accentColor].
///
/// This property is ignored if used in an adaptive constructor inside an iOS
/// environment.
final Animation<Color?>? valueColor; final Animation<Color?>? valueColor;
/// {@template flutter.material.progressIndicator.semanticsLabel} /// {@template flutter.material.progressIndicator.semanticsLabel}
...@@ -74,6 +86,9 @@ abstract class ProgressIndicator extends StatefulWidget { ...@@ -74,6 +86,9 @@ abstract class ProgressIndicator extends StatefulWidget {
/// This value indicates the purpose of the progress bar, and will be /// This value indicates the purpose of the progress bar, and will be
/// read out by screen readers to indicate the purpose of this progress /// read out by screen readers to indicate the purpose of this progress
/// indicator. /// indicator.
///
/// This property is ignored if used in an adaptive constructor inside an iOS
/// environment.
/// {@endtemplate} /// {@endtemplate}
final String? semanticsLabel; final String? semanticsLabel;
...@@ -88,6 +103,9 @@ abstract class ProgressIndicator extends StatefulWidget { ...@@ -88,6 +103,9 @@ abstract class ProgressIndicator extends StatefulWidget {
/// For determinate progress indicators, this will be defaulted to /// For determinate progress indicators, this will be defaulted to
/// [ProgressIndicator.value] expressed as a percentage, i.e. `0.1` will /// [ProgressIndicator.value] expressed as a percentage, i.e. `0.1` will
/// become '10%'. /// become '10%'.
///
/// This property is ignored if used in an adaptive constructor inside an iOS
/// environment.
/// {@endtemplate} /// {@endtemplate}
final String? semanticsValue; final String? semanticsValue;
...@@ -433,7 +451,34 @@ class CircularProgressIndicator extends ProgressIndicator { ...@@ -433,7 +451,34 @@ class CircularProgressIndicator extends ProgressIndicator {
this.strokeWidth = 4.0, this.strokeWidth = 4.0,
String? semanticsLabel, String? semanticsLabel,
String? semanticsValue, String? semanticsValue,
}) : super( }) : _indicatorType = _ActivityIndicatorType.material,
super(
key: key,
value: value,
backgroundColor: backgroundColor,
valueColor: valueColor,
semanticsLabel: semanticsLabel,
semanticsValue: semanticsValue,
);
/// Creates an adaptive progress indicator that is a
/// [CupertinoActivityIndicator] in iOS and [CircularProgressIndicator] in
/// material theme/non-iOS.
///
/// The [value], [backgroundColor], [valueColor], [strokeWidth],
/// [semanticsLabel], and [semanticsValue] will be ignored in iOS.
///
/// {@macro flutter.material.progressIndicator.parameters}
const CircularProgressIndicator.adaptive({
Key? key,
double? value,
Color? backgroundColor,
Animation<Color?>? valueColor,
this.strokeWidth = 4.0,
String? semanticsLabel,
String? semanticsValue,
}) : _indicatorType = _ActivityIndicatorType.adaptive,
super(
key: key, key: key,
value: value, value: value,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
...@@ -442,7 +487,12 @@ class CircularProgressIndicator extends ProgressIndicator { ...@@ -442,7 +487,12 @@ class CircularProgressIndicator extends ProgressIndicator {
semanticsValue: semanticsValue, semanticsValue: semanticsValue,
); );
final _ActivityIndicatorType _indicatorType;
/// The width of the line used to draw the circle. /// The width of the line used to draw the circle.
///
/// This property is ignored if used in an adaptive constructor inside an iOS
/// environment.
final double strokeWidth; final double strokeWidth;
@override @override
...@@ -494,7 +544,11 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w ...@@ -494,7 +544,11 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
super.dispose(); super.dispose();
} }
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) { Widget _buildCupertinoIndicator(BuildContext context) {
return CupertinoActivityIndicator(key: widget.key);
}
Widget _buildMaterialIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) {
return widget._buildSemanticsWrapper( return widget._buildSemanticsWrapper(
context: context, context: context,
child: Container( child: Container(
...@@ -522,7 +576,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w ...@@ -522,7 +576,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
return AnimatedBuilder( return AnimatedBuilder(
animation: _controller, animation: _controller,
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {
return _buildIndicator( return _buildMaterialIndicator(
context, context,
_strokeHeadTween.evaluate(_controller), _strokeHeadTween.evaluate(_controller),
_strokeTailTween.evaluate(_controller), _strokeTailTween.evaluate(_controller),
...@@ -535,9 +589,27 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w ...@@ -535,9 +589,27 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.value != null) switch (widget._indicatorType) {
return _buildIndicator(context, 0.0, 0.0, 0, 0.0); case _ActivityIndicatorType.material:
return _buildAnimation(); if (widget.value != null)
return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
return _buildAnimation();
case _ActivityIndicatorType.adaptive:
final ThemeData theme = Theme.of(context)!;
assert(theme.platform != null);
switch (theme.platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return _buildCupertinoIndicator(context);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
if (widget.value != null)
return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
return _buildAnimation();
}
}
} }
} }
...@@ -657,7 +729,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState { ...@@ -657,7 +729,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
} }
@override @override
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) { Widget _buildMaterialIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) {
final double arrowheadScale = widget.value == null ? 0.0 : (widget.value! * 2.0).clamp(0.0, 1.0); final double arrowheadScale = widget.value == null ? 0.0 : (widget.value! * 2.0).clamp(0.0, 1.0);
return widget._buildSemanticsWrapper( return widget._buildSemanticsWrapper(
context: context, context: context,
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../rendering/mock_canvas.dart'; import '../rendering/mock_canvas.dart';
...@@ -554,4 +555,46 @@ void main() { ...@@ -554,4 +555,46 @@ void main() {
matchesGoldenFile('material.circular_progress_indicator.indeterminate.png'), matchesGoldenFile('material.circular_progress_indicator.indeterminate.png'),
); );
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767
testWidgets(
'Adaptive CircularProgressIndicator displays CupertinoActivityIndicator in iOS',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Material(
child: CircularProgressIndicator.adaptive(),
),
),
),
);
expect(find.byType(CupertinoActivityIndicator), findsOneWidget);
}, variant: const TargetPlatformVariant(<TargetPlatform> {
TargetPlatform.iOS,
TargetPlatform.macOS,
})
);
testWidgets(
'Adaptive CircularProgressIndicator does not display CupertinoActivityIndicator in non-iOS',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Material(
child: CircularProgressIndicator.adaptive(),
),
),
),
);
expect(find.byType(CupertinoActivityIndicator), findsNothing);
}, variant: const TargetPlatformVariant(<TargetPlatform> {
TargetPlatform.android,
TargetPlatform.fuchsia,
TargetPlatform.windows,
TargetPlatform.linux,
}),
);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment