Skip to content

Add CupertinoLinearActivityIndicator #170108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

ValentinVignal
Copy link
Contributor

@ValentinVignal ValentinVignal commented Jun 6, 2025

Fixes #156167

Continuation of #156287

Screenshot 2025-06-06 at 10 00 32 AM

Native version:

image

Pre-launch Checklist

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

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: cupertino flutter/packages/flutter/cupertino repository d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Jun 6, 2025
@ValentinVignal ValentinVignal force-pushed the flutter/add-cupertino-linear-activity-indicator branch from a8baca1 to 15ee6a9 Compare June 6, 2025 03:17
@victorsanni victorsanni self-requested a review June 9, 2025 20:36
Copy link
Contributor

@victorsanni victorsanni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR. Can you attach images comparing to native as well as a description of the native APIs for similar behaviors? For a design review.

@override
void paint(Canvas canvas, Size size) {
// Draw the background of the progress bar.
canvas.drawRRect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if the native version is a rounded rect or a rounded superellipse (squircle). Can you attach images to the PR description comparing the native version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to look at https://developer.apple.com/design/human-interface-guidelines/progress-indicators

and from the photo

image

it looks like rounded rect? But again, I half guessing here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The native photo attached has less-rounded corners on the edges than the flutter photo attached. But the actual blue line has the same rounding (I think) in both.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true on the picture it looks like the container has less rounded corners.

However, if I look at https://developer.apple.com/design/human-interface-guidelines/progress-indicators

image

image

it looks like the background AND the blue line are having a stadium shape, which is like the current implementation

image


Having said that, what would you prefer me to do?

I can replace the current native image attached to the PR description with the ones from https://developer.apple.com/design/human-interface-guidelines/progress-indicators

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you attach images from a running native iOS app? The goal is to match live widgets, not HIG website photos.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I create a straightforward macOS app:

Screenshot 2025-06-14 at 7 53 00 PM

It looks like a stadium shape to me. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. If the native app is stadium-shaped then it should be stadium-shaped.

@ValentinVignal
Copy link
Contributor Author

@victorsanni I have attached the picture from the issue in the description.

I might need a bit of assistance to provide what you need from me, as I'm not very familiar with Cupertino and Apple specs/environment. For context, I saw the issue #156167 and noticed that the PR to fix it #156287 was closed due to a lack of tests. So, I've added those tests.

I'm not saying I'm unwilling to apply your suggestion; I just need you to be extra explicit because I might not fully understand everything.

@ValentinVignal ValentinVignal force-pushed the flutter/add-cupertino-linear-activity-indicator branch from 15ee6a9 to 022699e Compare June 13, 2025 17:43
@ValentinVignal
Copy link
Contributor Author

Thank you for the feedbacks. I improved the documentation in Improve documentation

Copy link
Contributor

@victorsanni victorsanni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM pending reviews from others.

Copy link
Contributor

@victorsanni victorsanni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ValentinVignal,

Looking through this again I realized there is already a Material widget with similar behavior: LinearProgressIndicator. Is there an opportunity here to share logic already in that widget?

@ValentinVignal
Copy link
Contributor Author

@victorsanni, looking at LinearProgressIndicator, I think we could try to reuse the _LinearProgressIndicatorPainter.
I can try to

  1. Move _LinearProgressIndicatorPainter to progress_indicator.dart and rename it to LinearProgressIndicatorPainter
  2. Reuse it in CupertinoLinearActivityIndicator

What do you think?

@MitchellGoodwin
Copy link
Contributor

Imo, we could move the majority of ProgressIndicator down as well. The only hiccup is _getValueColor that I can see. Two options for that are:

  1. Create a RawProgressIndicator in Widgets. Have it implement everything but _getValueColor. ProgressIndicator in Material extends off of RawProgressIndicator and adds _getValueColor. The Cupertino version uses RawProgressIndicator.
  2. Move ProgressIndicator down entirely, and make _getValueColor public. Have the Widgets version default to logic that doesn't use themes. Create a MaterialProgressIndicator that overrides getValueColor to the current Material defaults. Cupertino extends off of ProgressIndicator.

I personally like option 2 a bit better. Important to point out that this PR uses "progress" where Material uses "value". We don't want that API drift.

For the painter, moving that down sounds like a good idea to me as well, but it looks like it needs to have generic, or possibly empty curve defaults.

@victorsanni
Copy link
Contributor

Create a MaterialProgressIndicator that overrides getValueColor to the current Material defaults.

Why can't LinearProgressIndicator and CircularProgressIndicator override getValueColor directly?

@MitchellGoodwin
Copy link
Contributor

Why can't LinearProgressIndicator and CircularProgressIndicator override getValueColor directly?

That's valid too, they just would both override to the same value it looks like. But overriding getValueColor twice would be less code than making a new class so that's probably better.

@victorsanni
Copy link
Contributor

Right now ProgressIndicator is just an accessibility wrapper. Can we move more logic from LinearProgressIndicator and CircularProgressIndicator into ProgressIndicator?

@victorsanni
Copy link
Contributor

make _getValueColor public.

And protected too.

@dkwingsmt
Copy link
Contributor

Hi! @ValentinVignal Are you able to address the comments above so we can work together to get this landed? This is a great addition and I'm looking forward to it!

@ValentinVignal
Copy link
Contributor Author

@dkwingsmt yes I'd be happy to resume it. However, @victorsanni asked a question to @MitchellGoodwin here. Or it was a bit unclear to me everyone agreed on the solution

@victorsanni
Copy link
Contributor

Hey @ValentinVignal, feel free to go ahead with implementing option 2 in #170108 (comment). Basically we want to share as much code as possible between material and cupertino.

@ValentinVignal ValentinVignal force-pushed the flutter/add-cupertino-linear-activity-indicator branch from 8c7c1a0 to 10d8be4 Compare August 15, 2025 16:38
@ValentinVignal
Copy link
Contributor Author

@victorsanni @MitchellGoodwin I did my best, and I created ProgressIndicator.

  1. As you mentioned, Cupertino uses progress and Material value, which brings a bit of an issue to share variables and code.
  2. MaterialProgressIndicator has a nullable value. Null means the progress indicator is indeterminate and animated. But that's not the case for CupertinoActivityIndicator. progress is non-nullable and defaulted to 1.0. Another variable bool animating indicates whether or not the widget is animated.
    1. Because of that, I had to create this getter animating in ProgressIndicatorMixin which I'm not very happy with.
    2. The documentation of widget's ProgressIndicator currently mention that value being null means the indicator is indeterminate and animated. But that's not true for Cupertino
  3. The current implementation lost the

@github-actions github-actions bot added the f: material design flutter/packages/flutter/material repository. label Aug 15, 2025
Comment on lines +43 to +40
/// If null, this progress indicator is indeterminate, which means the
/// indicator displays a predetermined animation that does not indicate how
/// much actual progress is being made.
final double? value;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is not true for Cupertino

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to belabor this, but can you explain why this is not true for cupertino again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value is never null for CupertinoActivityIndicator.

For the animated/indeterminate version, value = 1. This value is used to calculate the number of "ticks" to display:

Here is a video, which I think makes it clearer:

Screen.Recording.2025-08-23.at.1.53.27.PM.mov

Comment on lines +33 to +37
double progress = 1,
super.semanticsLabel,
super.semanticsValue,
this.color,
}) : super(value: progress);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a better way of handling progress and value?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. But does this class and MaterialProgressIndicator need to be public? In general I'm thinking this project can be done without increasing the API surface anywhere (except maybe with getValueColor), what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, makes sense! I've made CupertinoProgressIndicator and MaterialProgressIndicator private in Make _MaterialProgressIndicator and _CupertinoProgressIndicator private

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making them private means a bunch of documentation needs to be re-written for _MaterialProgressIndicator:
Screenshot 2025-08-23 at 12 01 18 AM

Knowing that, should I leave MaterialProgressIndicator public?
Or should I not reference it in the documentation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can reference the widgets themselves. Adding new public APIs should be done only when necessary, not just for documentation. This is because any later changes to that public API will be breaking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the widgets that extend the private classes, like LinearActivityIndicator etc. And where appropriate just use ProgressIndicator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! I updated the documentation in Update documentation, tell me what you think of it :)

@@ -1727,7 +1727,7 @@ void main() {
),
);
expect(
tester.widget<CupertinoActivityIndicator>(find.byType(CupertinoActivityIndicator)).progress,
tester.widget<CupertinoActivityIndicator>(find.byType(CupertinoActivityIndicator)).value,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that considered a breaking change? Is it acceptable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't progress be used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To share code with ProgressIndicator, I've made _CupertinoProgressIndicator this way:

  const _CupertinoProgressIndicator({
    super.key,
    double progress = 1,
    super.semanticsLabel,
    super.semanticsValue,
    this.color,
  }) : super(value: progress);

  @override
  double get value => super.value!;

So CupertinoActivityIndicator extends _CupertinoProgressIndicator no longer has the property progress, it has the property value

Do you prefer me to change the override of the getter value into

  double get progress => super.value!;

?

@@ -31,10 +31,10 @@ enum _ActivityIndicatorType { material, adaptive }
/// See also:
///
/// * <https://material.io/components/progress-indicators>
abstract class ProgressIndicator extends StatefulWidget {
abstract class MaterialProgressIndicator extends ProgressIndicator {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also technically a breaking change. What do you think about it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change? It seems to me that it is just introducing a new class between ProgressIndicator and LinearProgressIndicator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user implemented class MyClass extends ProgressIndicator, this code will now break because Color getValueColor(BuildContext context, {Color? defaultColor}); is abstract now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But getValueColor was previously private. and I think ProgressIndicator can implement getValueColor so it is not abstract? Could return color ?? defaultColor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any field color in ProgressIndicator. I felt like it was better to keep it in the Material and Cupertino implementations.

Would you prefer me to add color to the widget.ProgressIndicator class?

@ValentinVignal ValentinVignal force-pushed the flutter/add-cupertino-linear-activity-indicator branch 2 times, most recently from 50ea149 to ebcebaf Compare August 19, 2025 01:59
Copy link
Contributor

@victorsanni victorsanni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple clarifying questions to understand the api design better.

@@ -31,10 +31,10 @@ enum _ActivityIndicatorType { material, adaptive }
/// See also:
///
/// * <https://material.io/components/progress-indicators>
abstract class ProgressIndicator extends StatefulWidget {
abstract class MaterialProgressIndicator extends ProgressIndicator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But getValueColor was previously private. and I think ProgressIndicator can implement getValueColor so it is not abstract? Could return color ?? defaultColor.

Comment on lines +33 to +37
double progress = 1,
super.semanticsLabel,
super.semanticsValue,
this.color,
}) : super(value: progress);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. But does this class and MaterialProgressIndicator need to be public? In general I'm thinking this project can be done without increasing the API surface anywhere (except maybe with getValueColor), what do you think?

@@ -1727,7 +1727,7 @@ void main() {
),
);
expect(
tester.widget<CupertinoActivityIndicator>(find.byType(CupertinoActivityIndicator)).progress,
tester.widget<CupertinoActivityIndicator>(find.byType(CupertinoActivityIndicator)).value,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't progress be used here?

}
}

/// A mixin that provides common functionality for progress indicator widgets.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this logic in a new public mixin and not in ProgressIndicator itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the implementation of the different classes LinearProgressIndicator, CircularProgressIndicator, CupertinoActivityIndicator, they were implemented the same things:

  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: duration, vsync: this);
    if (animating) {
      _controller.repeat();
    }
  }

  @override
  void didUpdateWidget(T oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.value == null && !_controller.isAnimating) {
      _controller.repeat();
    } else if (widget.value != null && _controller.isAnimating) {
      _controller.stop();
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

Since you were suggesting to share as much code as possible in

#170108 (comment)

Basically we want to share as much code as possible between material and cupertino.

I took the initiative to share the same code for all the widgets. This is what I came up with
Would you prefer me to do it another way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have this logic in ProgressIndicator itself? without needing to create a new public mixin.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic needs to be in a state of a widget (initState, dispose, didUpdateWidget, storing _controller), so it cannot be in ProgressIndicator.
Or maybe I misunderstood your suggestion?

@ValentinVignal ValentinVignal force-pushed the flutter/add-cupertino-linear-activity-indicator branch from ebcebaf to caa9477 Compare August 22, 2025 16:00
Comment on lines +33 to +37
double progress = 1,
super.semanticsLabel,
super.semanticsValue,
this.color,
}) : super(value: progress);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the widgets that extend the private classes, like LinearActivityIndicator etc. And where appropriate just use ProgressIndicator.

@@ -309,11 +309,11 @@ class ProgressIndicatorThemeData with Diagnosticable {
/// ```
/// {@end-tool}
class ProgressIndicatorTheme extends InheritedTheme {
/// Creates a theme that controls the configurations for [ProgressIndicator]
/// Creates a theme that controls the configurations for [_MaterialProgressIndicator]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here and otherwise you can even revert the documentation changes entirely. After all, LinearProgressIndicator and the others are all ProgressIndicators.

}
}

/// A mixin that provides common functionality for progress indicator widgets.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have this logic in ProgressIndicator itself? without needing to create a new public mixin.

Comment on lines +43 to +40
/// If null, this progress indicator is indeterminate, which means the
/// indicator displays a predetermined animation that does not indicate how
/// much actual progress is being made.
final double? value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to belabor this, but can you explain why this is not true for cupertino again?

@ValentinVignal ValentinVignal force-pushed the flutter/add-cupertino-linear-activity-indicator branch from caa9477 to 91b5ea5 Compare August 23, 2025 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Cupertino] Support Linear Progress View style
4 participants