-
Notifications
You must be signed in to change notification settings - Fork 29.1k
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
base: master
Are you sure you want to change the base?
Add CupertinoLinearActivityIndicator
#170108
Conversation
a8baca1
to
15ee6a9
Compare
There was a problem hiding this 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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
it looks like rounded rect? But again, I half guessing here
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
it looks like the background AND the blue line are having a stadium shape, which is like the current implementation
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
@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. |
15ee6a9
to
022699e
Compare
Thank you for the feedbacks. I improved the documentation in Improve documentation |
There was a problem hiding this 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.
There was a problem hiding this 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?
@victorsanni, looking at
What do you think? |
Imo, we could move the majority of
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. |
Why can't |
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. |
Right now |
And |
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! |
@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 |
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. |
8c7c1a0
to
10d8be4
Compare
@victorsanni @MitchellGoodwin I did my best, and I created
|
/// 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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
double progress = 1, | ||
super.semanticsLabel, | ||
super.semanticsValue, | ||
this.color, | ||
}) : super(value: progress); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
50ea149
to
ebcebaf
Compare
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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
.
double progress = 1, | ||
super.semanticsLabel, | ||
super.semanticsValue, | ||
this.color, | ||
}) : super(value: progress); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
ebcebaf
to
caa9477
Compare
double progress = 1, | ||
super.semanticsLabel, | ||
super.semanticsValue, | ||
this.color, | ||
}) : super(value: progress); |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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 ProgressIndicator
s.
} | ||
} | ||
|
||
/// A mixin that provides common functionality for progress indicator widgets. |
There was a problem hiding this comment.
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.
/// 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; |
There was a problem hiding this comment.
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?
caa9477
to
91b5ea5
Compare
Fixes #156167
Continuation of #156287
Native version:
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.