Updated Material 3 Progress Indicators Samples (#158925)

Fixes [Update both `ProgressIndicator` for Material 3
redesign](https://github.com/flutter/flutter/issues/141340)

> [!IMPORTANT]  
> ~~This to be merged after
https://github.com/flutter/flutter/pull/158104.~~ Merged.

### Description 

This updates progress indicator samples to include toggle to opt in to
the updated Material 3 appearance .

### Preview

<img width="753" alt="Screenshot 2024-12-04 at 15 54 50"
src="https://github.com/user-attachments/assets/285f2803-1a12-470a-9afe-2abcf0548ff4">

<img width="753" alt="Screenshot 2024-12-04 at 15 58 35"
src="https://github.com/user-attachments/assets/9caebec9-f65e-4baa-8e39-9a4a4a72b205">


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Taha Tesser 2024-12-05 19:54:54 +02:00 committed by GitHub
parent 1856940f66
commit a6d3bb5cb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 153 additions and 69 deletions

View File

@ -6,10 +6,10 @@ import 'package:flutter/material.dart';
/// Flutter code sample for [CircularProgressIndicator]. /// Flutter code sample for [CircularProgressIndicator].
void main() => runApp(const ProgressIndicatorApp()); void main() => runApp(const ProgressIndicatorExampleApp());
class ProgressIndicatorApp extends StatelessWidget { class ProgressIndicatorExampleApp extends StatelessWidget {
const ProgressIndicatorApp({super.key}); const ProgressIndicatorExampleApp({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -28,19 +28,18 @@ class ProgressIndicatorExample extends StatefulWidget {
class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample> with TickerProviderStateMixin { class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample> with TickerProviderStateMixin {
late AnimationController controller; late AnimationController controller;
bool year2023 = true;
@override @override
void initState() { void initState() {
super.initState();
controller = AnimationController( controller = AnimationController(
/// [AnimationController]s can be created with `vsync: this` because of
/// [TickerProviderStateMixin].
vsync: this, vsync: this,
duration: const Duration(seconds: 5), duration: const Duration(seconds: 5),
)..addListener(() { )..addListener(() {
setState(() {}); setState(() {});
}); })
controller.repeat(reverse: true); ..repeat(reverse: true);
super.initState();
} }
@override @override
@ -52,18 +51,32 @@ class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample> wit
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Padding( body: Center(
padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, spacing: 16.0,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text( const Text('Determinate CircularProgressIndicator'),
'Circular progress indicator with a fixed color', Padding(
style: Theme.of(context).textTheme.titleLarge, padding: const EdgeInsets.symmetric(horizontal: 16),
child: CircularProgressIndicator(
year2023: year2023,
value: controller.value,
),
), ),
CircularProgressIndicator( const Text('Indeterminate CircularProgressIndicator'),
value: controller.value, Padding(
semanticsLabel: 'Circular progress indicator', padding: const EdgeInsets.symmetric(horizontal: 16),
child: CircularProgressIndicator(year2023: year2023),
),
SwitchListTile(
value: year2023,
title: year2023 ? const Text('Switch to latest M3 style') : const Text('Switch to year2023 M3 style'),
onChanged: (bool value) {
setState(() {
year2023 = !year2023;
});
},
), ),
], ],
), ),

View File

@ -6,10 +6,10 @@ import 'package:flutter/material.dart';
/// Flutter code sample for [CircularProgressIndicator]. /// Flutter code sample for [CircularProgressIndicator].
void main() => runApp(const ProgressIndicatorApp()); void main() => runApp(const ProgressIndicatorExampleApp());
class ProgressIndicatorApp extends StatelessWidget { class ProgressIndicatorExampleApp extends StatelessWidget {
const ProgressIndicatorApp({super.key}); const ProgressIndicatorExampleApp({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -57,18 +57,17 @@ class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample> wit
body: Padding( body: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
spacing: 16.0,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text( Text(
'Circular progress indicator', 'Circular progress indicator',
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
const SizedBox(height: 30),
CircularProgressIndicator( CircularProgressIndicator(
value: controller.value, value: controller.value,
semanticsLabel: 'Circular progress indicator', semanticsLabel: 'Circular progress indicator',
), ),
const SizedBox(height: 10),
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(

View File

@ -27,9 +27,9 @@ class ProgressIndicatorExample extends StatefulWidget {
_ProgressIndicatorExampleState(); _ProgressIndicatorExampleState();
} }
class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample> class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample> with TickerProviderStateMixin {
with TickerProviderStateMixin {
late AnimationController controller; late AnimationController controller;
bool year2023 = true;
@override @override
void initState() { void initState() {
@ -54,18 +54,33 @@ class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Padding( body: Column(
padding: const EdgeInsets.all(20.0), spacing: 16.0,
child: Column( mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.0, children: <Widget>[
mainAxisAlignment: MainAxisAlignment.center, const Text('Determinate LinearProgressIndicator'),
children: <Widget>[ Padding(
const Text('Determinate LinearProgressIndicator'), padding: const EdgeInsets.symmetric(horizontal: 16),
LinearProgressIndicator(value: controller.value), child: LinearProgressIndicator(
const Text('Indeterminate LinearProgressIndicator'), year2023: year2023,
const LinearProgressIndicator(), value: controller.value,
], ),
), ),
const Text('Indeterminate LinearProgressIndicator'),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: LinearProgressIndicator(year2023: year2023),
),
SwitchListTile(
value: year2023,
title: year2023 ? const Text('Switch to latest M3 style') : const Text('Switch to year2023 M3 style'),
onChanged: (bool value) {
setState(() {
year2023 = !year2023;
});
},
),
],
), ),
); );
} }

View File

@ -58,18 +58,17 @@ class _ProgressIndicatorExampleState extends State<ProgressIndicatorExample>
body: Padding( body: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
spacing: 16.0,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
const Text( const Text(
'Linear progress indicator', 'Linear progress indicator',
style: TextStyle(fontSize: 20), style: TextStyle(fontSize: 20),
), ),
const SizedBox(height: 30),
LinearProgressIndicator( LinearProgressIndicator(
value: determinate ? controller.value : null, value: determinate ? controller.value : null,
semanticsLabel: 'Linear progress indicator', semanticsLabel: 'Linear progress indicator',
), ),
const SizedBox(height: 10),
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(

View File

@ -2,23 +2,58 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/progress_indicator/circular_progress_indicator.0.dart' import 'package:flutter_api_samples/material/progress_indicator/circular_progress_indicator.0.dart'
as example; as example;
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('Finds CircularProgressIndicator', (WidgetTester tester) async { testWidgets('Determinate CircularProgressIndicator uses the provided value', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const example.ProgressIndicatorApp(), const example.ProgressIndicatorExampleApp(),
);
await tester.pump(const Duration(milliseconds: 2500));
final Finder indicatorFinder = find.byType(CircularProgressIndicator).first;
final CircularProgressIndicator progressIndicator = tester.widget(indicatorFinder);
expect(progressIndicator.value, equals(0.5));
});
testWidgets('Indeterminate CircularProgressIndicator does not have a value', (WidgetTester tester) async {
await tester.pumpWidget(
const example.ProgressIndicatorExampleApp(),
);
await tester.pump(const Duration(milliseconds: 2500));
final Finder indicatorFinder = find.byType(CircularProgressIndicator).last;
final CircularProgressIndicator progressIndicator = tester.widget(indicatorFinder);
expect(progressIndicator.value, null);
});
testWidgets('Progress indicators year2023 flag can be toggled', (WidgetTester tester) async {
await tester.pumpWidget(
const example.ProgressIndicatorExampleApp(),
); );
expect( CircularProgressIndicator determinateIndicator = tester.widget<CircularProgressIndicator>(
find.bySemanticsLabel('Circular progress indicator'), find.byType(CircularProgressIndicator).first,
findsOneWidget,
); );
expect(determinateIndicator.year2023, true);
CircularProgressIndicator indeterminateIndicator = tester.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator).last,
);
expect(indeterminateIndicator.year2023, true);
// Test if CircularProgressIndicator is animating. await tester.tap(find.byType(SwitchListTile));
await tester.pump(const Duration(seconds: 2)); await tester.pump();
expect(tester.hasRunningAnimations, isTrue);
determinateIndicator = tester.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator).first,
);
expect(determinateIndicator.year2023, false);
indeterminateIndicator = tester.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator).last,
);
expect(indeterminateIndicator.year2023, false);
}); });
} }

View File

@ -10,7 +10,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('Finds CircularProgressIndicator', (WidgetTester tester) async { testWidgets('Finds CircularProgressIndicator', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const example.ProgressIndicatorApp(), const example.ProgressIndicatorExampleApp(),
); );
expect( expect(

View File

@ -8,33 +8,52 @@ import 'package:flutter_api_samples/material/progress_indicator/linear_progress_
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('Determinate and Indeterminate LinearProgressIndicators', testWidgets('Determinate LinearProgressIndicator uses the provided value', (WidgetTester tester) async {
(WidgetTester tester) async { await tester.pumpWidget(
const example.ProgressIndicatorExampleApp(),
);
await tester.pump(const Duration(milliseconds: 2500));
final Finder indicatorFinder = find.byType(LinearProgressIndicator).first;
final LinearProgressIndicator progressIndicator = tester.widget(indicatorFinder);
expect(progressIndicator.value, equals(0.5));
});
testWidgets('Indeterminate LinearProgressIndicator does not have a value', (WidgetTester tester) async {
await tester.pumpWidget(
const example.ProgressIndicatorExampleApp(),
);
await tester.pump(const Duration(milliseconds: 2500));
final Finder indicatorFinder = find.byType(LinearProgressIndicator).last;
final LinearProgressIndicator progressIndicator = tester.widget(indicatorFinder);
expect(progressIndicator.value, null);
});
testWidgets('Progress indicators year2023 flag can be toggled', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const example.ProgressIndicatorExampleApp(), const example.ProgressIndicatorExampleApp(),
); );
expect(find.text('Determinate LinearProgressIndicator'), findsOneWidget); LinearProgressIndicator determinateIndicator = tester.widget<LinearProgressIndicator>(
expect(find.text('Indeterminate LinearProgressIndicator'), findsOneWidget);
expect(find.byType(LinearProgressIndicator), findsNWidgets(2));
// Test determinate LinearProgressIndicator.
LinearProgressIndicator determinateIndicator = tester.firstWidget(
find.byType(LinearProgressIndicator).first, find.byType(LinearProgressIndicator).first,
); );
expect(determinateIndicator.value, equals(0.0)); expect(determinateIndicator.year2023, true);
LinearProgressIndicator indeterminateIndicator = tester.widget<LinearProgressIndicator>(
// Advance the animation by 2 seconds.
await tester.pump(const Duration(seconds: 2));
determinateIndicator = tester.firstWidget(
find.byType(LinearProgressIndicator).first,
);
expect(determinateIndicator.value, equals(0.4));
// Test indeterminate LinearProgressIndicator.
final LinearProgressIndicator indeterminateIndicator = tester.firstWidget(
find.byType(LinearProgressIndicator).last, find.byType(LinearProgressIndicator).last,
); );
expect(indeterminateIndicator.value, null); expect(indeterminateIndicator.year2023, true);
await tester.tap(find.byType(SwitchListTile));
await tester.pump();
determinateIndicator = tester.widget<LinearProgressIndicator>(
find.byType(LinearProgressIndicator).first,
);
expect(determinateIndicator.year2023, false);
indeterminateIndicator = tester.widget<LinearProgressIndicator>(
find.byType(LinearProgressIndicator).last,
);
expect(indeterminateIndicator.year2023, false);
}); });
} }

View File

@ -312,6 +312,8 @@ class _LinearProgressIndicatorPainter extends CustomPainter {
/// ///
/// {@tool dartpad} /// {@tool dartpad}
/// This example showcases determinate and indeterminate [LinearProgressIndicator]s. /// This example showcases determinate and indeterminate [LinearProgressIndicator]s.
/// The [LinearProgressIndicator]s will use the ![updated Material 3 Design appearance](https://m3.material.io/components/progress-indicators/overview)
/// when setting the [LinearProgressIndicator.year2023] flag to false.
/// ///
/// ** See code in examples/api/lib/material/progress_indicator/linear_progress_indicator.0.dart ** /// ** See code in examples/api/lib/material/progress_indicator/linear_progress_indicator.0.dart **
/// {@end-tool} /// {@end-tool}
@ -704,7 +706,9 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
/// specify a constant color use: `AlwaysStoppedAnimation<Color>(color)`. /// specify a constant color use: `AlwaysStoppedAnimation<Color>(color)`.
/// ///
/// {@tool dartpad} /// {@tool dartpad}
/// This example shows a [CircularProgressIndicator] with a changing value. /// This example showcases determinate and indeterminate [CircularProgressIndicator]s.
/// The [CircularProgressIndicator]s will use the ![updated Material 3 Design appearance](https://m3.material.io/components/progress-indicators/overview)
/// when setting the [CircularProgressIndicator.year2023] flag to false.
/// ///
/// ** See code in examples/api/lib/material/progress_indicator/circular_progress_indicator.0.dart ** /// ** See code in examples/api/lib/material/progress_indicator/circular_progress_indicator.0.dart **
/// {@end-tool} /// {@end-tool}