Skip to content

Conversation

@QuncCccccc
Copy link
Contributor

@QuncCccccc QuncCccccc commented Aug 7, 2024

This PR is to improve CupertinoSlidingSegmentedControl fidelity and add a new property setEnabled so that segments can be disabled now.

Fidelity update includes:

  • small change on default thumb radius (Based on iOS 17 Figma file)
  • separator height (Based on the comparison with SegmentedControl example in Xcode)
  • segment min padding (Based on iOS 17 Figma file)
  • thumb scale alignment (Based on the comparison with SegmentedControl example in Xcode). If the thumb is on the first or last position in SegmentedControl, the alignment should be Alignment.leftCenter and Alignment.rightCenter respectively, assuming the text direction is left to right. For segments in middle, they should have center alignment as previously.
  • TextStyle update (Based on both the Figma file and the Xcode example)
  • Clipped thumb shadow if the shadow is outside of the segmented control (Based on the Xcode example)
  • Fixed the overall size shaking issue during segment switching on macOS and iOS
Alignment update demo
Screen.Recording.2024-08-08.at.1.33.19.PM.mov
Comparison before and after

Before:
Screenshot 2024-08-08 at 1 42 57 PM

After:
Screenshot 2024-08-08 at 1 43 50 PM

Shaking issue

Before:

Screen.Recording.2024-08-08.at.1.47.53.PM.mov

After:

Screen.Recording.2024-08-08.at.1.47.35.PM.mov

The disabledChildren feature can be used to disable segments. The default disabled color comes from the inspection in the Xcode example.

Disabled feature demos

Disabled segment cannot be highlighted:

Screen.Recording.2024-08-09.at.6.03.40.PM.mov

groupValue can still be highlighted programmatically if it is disabled.

Screenshot 2024-09-04 at 5 45 25 PM

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • 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.
  • All existing and new tests are passing.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: cupertino flutter/packages/flutter/cupertino repository labels Aug 7, 2024
@QuncCccccc QuncCccccc force-pushed the segmented_control_update branch from ce2986d to 7af6ff4 Compare August 7, 2024 00:52
@QuncCccccc QuncCccccc changed the title Segmented control update CupertinoSlidingSegmentedControl update Aug 7, 2024
@QuncCccccc QuncCccccc force-pushed the segmented_control_update branch from 8efda17 to 3465fb2 Compare August 8, 2024 00:21
@QuncCccccc QuncCccccc marked this pull request as ready for review August 8, 2024 20:49
@flutter-dashboard
Copy link

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #152976 at sha 3465fb2

@flutter-dashboard flutter-dashboard bot added the will affect goldens Changes to golden files label Aug 8, 2024

@override
Widget build(BuildContext context) {
Alignment scaleAlignment;
Copy link
Contributor

Choose a reason for hiding this comment

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

final Alignment scaleAlignment;

Copy link
Contributor

Choose a reason for hiding this comment

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

There's a fourth state where both isLeft and isRight are true that seems unhandled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry not sure I understand this correctly, how to be both left and right segment? For the segment that isLeft, it is the leftmost segment in the segmented control, and for isRight, it is the rightmost segment (assuming text direction is ltr).

bool enabled = true;
if (widget.setEnabled != null && widget.setEnabled!.containsKey(entry.key)) {
enabled = widget.setEnabled![entry.key]!;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

final bool enabled = widget.setEnabled?.[entry.key] ?? true.

Also you can use a set to store disabled segments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated!

.merge(TextStyle(fontWeight: widget.highlighted ? FontWeight.w500 : FontWeight.normal)),
.merge(TextStyle(
fontWeight: widget.highlighted ? FontWeight.w600 : FontWeight.w500,
fontSize: 13,
Copy link
Contributor

Choose a reason for hiding this comment

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

Use constants for hardcoded values and document the provenance?

// The minimum opacity of an unselected segment, when the user presses on the
// segment and it starts to fadeout.
//
// Inspected from iOS 13.2 simulator.
Copy link
Contributor

Choose a reason for hiding this comment

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

consider documenting the source of this const: #21302

);

// The height of the separator.
const double _kSeparatorHeight = 18.0;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: If both the height and the width are const, turn them into a Size const?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are several places that width and height are used separately, maybe just keep them as is?

Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't realize those are only used directly by the widgets / render objects. They are used to construct other constants it looks like?

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Aug 8, 2024

Choose a reason for hiding this comment

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

I'm a bit confused by _kSeparatorHeight. It looks like it's not used directly but used to compute _kSeparatorInset. So the real meaning of _kSeparatorHeight is "the height of the separator when the segmented control is at the min height"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah you are right. I was thinking providing a const height value might be more straightforward to understand. But it seems only "inset" doesn't change, when segments height have some larger values, the separator height is also getting larger. Updated.

final List<RenderBox> children = getChildrenAsList();

// Children contains both segment and separator and the order is segment ->
// separator -> segment. So to paint separators, indes should start from 1 and
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: index?

currentThumbRect = unscaledThumbRect;

double delta = 0;
if (isLeadingChild) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what if it's both leading and trailing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah the same explanation as above:) This should mean leftmost and rightmost ones

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I didn't realize assert(children.length >= 2). Nvm.

Copy link
Contributor

Choose a reason for hiding this comment

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

But in that case they're logically mutually exclusive. Maybe use an enum to model that instead of 2 bools.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just pushed a commit, does this look okay? 51277b6

}

final int? highlightedChildIndex = highlightedIndex;
final bool isLeadingChild = highlightedChildIndex != null
Copy link
Contributor

Choose a reason for hiding this comment

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

uber nit: the null check is not needed?

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 would be possible that groupValue is null, and we don't have highlighted child index, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

if highlightedChildIndex == 0 then it can't be null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right! Thanks for catching this! Updated.

Copy link
Contributor Author

@QuncCccccc QuncCccccc left a comment

Choose a reason for hiding this comment

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

Thanks so much for the review! I fixed all the comments except the setEnabled-related comments. Will push separate commits for these.

);

// The height of the separator.
const double _kSeparatorHeight = 18.0;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are several places that width and height are used separately, maybe just keep them as is?


@override
Widget build(BuildContext context) {
Alignment scaleAlignment;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry not sure I understand this correctly, how to be both left and right segment? For the segment that isLeft, it is the leftmost segment in the segmented control, and for isRight, it is the rightmost segment (assuming text direction is ltr).

child: widget.child,
),
),
widget.child,
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 tired to keep it but seems only removing both Offstage and DefaultTextStyle can solve the shaking issue. Keeping DefaultTextStyle still make the overall size shakes a little during thumb switch.

}

final int? highlightedChildIndex = highlightedIndex;
final bool isLeadingChild = highlightedChildIndex != null
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 would be possible that groupValue is null, and we don't have highlighted child index, right?

currentThumbRect = unscaledThumbRect;

double delta = 0;
if (isLeadingChild) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah the same explanation as above:) This should mean leftmost and rightmost ones

@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #152976 at sha a685b0c

@QuncCccccc QuncCccccc force-pushed the segmented_control_update branch from 51277b6 to 5a59efc Compare August 10, 2024 01:40
@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #152976 at sha a8169f2

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

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

Adding an enabled state for segments introduces a corner case where a segment is disabled (by rebuild) during a drag gesture, and it looks like it's not handled in the implementation? Could you add tests for that corner case?

Also what's the expected behavior of onValueChanged, if the current selection is disabled?

///
/// Disabled children cannot be highlighted and don't show any animation
/// when they are tapped or long pressed. So if this set contains [groupValue],
/// there is no segment getting highlighted until it is switched to an enabled
Copy link
Contributor

Choose a reason for hiding this comment

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

does that mean the original selected segment, if any, is unselected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is my understanding. If we have groupValue non null initially and it is disabled, we should not show the "highlighted" segment. This is what I saw it in Xcode experiment:)
Screenshot 2024-08-12 at 5 47 06 PM

final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
final LongPressGestureRecognizer longPress = LongPressGestureRecognizer();

T? get groupValue => widget.disabledChildren.contains(widget.groupValue) ? null : widget.groupValue;
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Aug 12, 2024

Choose a reason for hiding this comment

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

nit: Well this works but usually this will become a problem in the didUpdateWidget in the implementation becasue now you can't compare groupValue from the old widget (if that's need that is).

if (highlighted == newValue) {
return;
}
// If `disabledChildren` set contains the `groupValue`, then no update
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if a drag is started, and highlighted becomes disabled and the gesture lands in highlighed? (Consider adding a test case).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I think I include the demo in the PR description. When we are dragging the thumb to the disabled segment, the thumb will stop and still highlight the previous one. Does this make sense? Will create one test!

Screen.Recording.2024-08-12.at.6.27.11.PM.mov

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Aug 13, 2024

Choose a reason for hiding this comment

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

I meant that the parent widget rebuilds so the currently selected segment becomes disabled. See also: #152976 (review)

Copy link
Contributor Author

@QuncCccccc QuncCccccc Aug 15, 2024

Choose a reason for hiding this comment

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

Thanks so much for all the review suggestions! Added unit tests for both highlight behavior and onValueChanged behavior. If the highlighted is added to disabledChildren during drag, then when dragging is completed, the highlight will disappear, but onValueChanged will still be called. The onValueChanged behavior is simulated from XCode example.

final T segment = segmentForXPosition(details.localPosition.dx);
onPressedChangedByGesture(null);
if (segment != widget.groupValue) {
if (segment != groupValue) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: combine the nested ifs using &&?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or do you need this enable check? groupValue is null if disabled no?

@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #152976 at sha b3f6206

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

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

IMO it makes more sense to allow selecting a disabled segment programmatically. It works better with the onValueChanged callback and it gives developers full control over the behavior. You can select a disabled segment in UISegmentedControl programmatically too.

child: DefaultTextStyle.merge(
style: const TextStyle(fontWeight: FontWeight.w500),
child: widget.child,
DefaultTextStyle.merge(
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: consider adding a comment for why the indexed stack and the widget replica.

/// there is no segment getting highlighted until it is switched to an enabled
/// segment.
///
/// [onValueChanged] will not be called if the segment is disabled. However, if
Copy link
Contributor

Choose a reason for hiding this comment

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

The [onValueChanged] callback will not be called if the currently selected segment becomes disabled,

///
/// [onValueChanged] will not be called if the segment is disabled. However, if
/// an enabled segment is "highlighted" by dragging gesture and becomes disabled
/// before dragging stops, [onValueChanged] will be triggered when release finger.
Copy link
Contributor

Choose a reason for hiding this comment

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

This sounds a bit confusing. What will be selected?

@QuncCccccc QuncCccccc force-pushed the segmented_control_update branch from 5b64f18 to 4e982a8 Compare August 29, 2024 22:20
@QuncCccccc QuncCccccc added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 10, 2024
@auto-submit auto-submit bot merged commit b872a27 into flutter:master Sep 10, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 12, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 12, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 12, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 12, 2024
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Sep 12, 2024
Roll Flutter from 2e221e7 to 303f222 (77 revisions)

flutter/flutter@2e221e7...303f222

2024-09-12 [email protected] Manual roll to 48ddaf578fb0c8326d5b4b680b0f49ea72e33216 (flutter/flutter#155070)
2024-09-12 [email protected] Externalize and update onboarding instructions (flutter/flutter#154730)
2024-09-12 [email protected] when setting up the log reader for a device during `flutter run`, discard any `RPCError` thrown due to the device being disconnected (flutter/flutter#155049)
2024-09-12 98614782+auto-submit[bot]@users.noreply.github.com Reverts "iOS: update provisioning profile for 2024-2025 cert (#155052)" (flutter/flutter#155059)
2024-09-12 [email protected] iOS: update provisioning profile for 2024-2025 cert (flutter/flutter#155052)
2024-09-11 [email protected] Factor out `Container` objects (flutter/flutter#153619)
2024-09-11 [email protected] Move (`dev/tools`), complete v0 of `native_driver` (Android) (flutter/flutter#154843)
2024-09-11 [email protected] Roll Flutter Engine from ade8ef293bc6 to ee5adf6d2ee1 (2 revisions) (flutter/flutter#155046)
2024-09-11 [email protected] Fix `flutter run` on Mac x64 hosts if Swift Package Manager is enabled (flutter/flutter#154645)
2024-09-11 [email protected] Roll Packages from bb53e5d to 4c18648 (1 revision) (flutter/flutter#155033)
2024-09-11 [email protected] Roll Flutter Engine from 4eb729b7a5c4 to ade8ef293bc6 (3 revisions) (flutter/flutter#155031)
2024-09-11 [email protected] fix: Dropdown menu trying to access highlight element which doesn't exist when search and filters both are enabled (flutter/flutter#151969)
2024-09-11 [email protected] Marks Linux build_tests_3_5 to be unflaky (flutter/flutter#154993)
2024-09-11 [email protected] Add 'direction' allow to 'SegmentedButton' oriented vertically (flutter/flutter#150903)
2024-09-11 [email protected] Marks Linux build_tests_5_5 to be unflaky (flutter/flutter#154995)
2024-09-11 [email protected] Update the signature of DDS launcher callback. (flutter/flutter#154949)
2024-09-11 [email protected] Migrate Color.toString() test, improves `equalsIgnoringHashCodes` (flutter/flutter#154934)
2024-09-11 [email protected] Update material and cupertino localizations (flutter/flutter#154959)
2024-09-11 [email protected] Marks Linux build_tests_1_5 to be unflaky (flutter/flutter#154991)
2024-09-11 [email protected] Marks Linux build_tests_2_5 to be unflaky (flutter/flutter#154992)
2024-09-11 [email protected] Fix `flutter create` warning regarding Java compatibility (flutter/flutter#152836)
2024-09-11 [email protected] Roll Flutter Engine from 54757dab9462 to 4eb729b7a5c4 (1 revision) (flutter/flutter#155022)
2024-09-11 [email protected] Fix java version used by `build_aar_module_test` (flutter/flutter#154967)
2024-09-11 [email protected] Roll Flutter Engine from 0a14c519ea4f to 54757dab9462 (1 revision) (flutter/flutter#155015)
2024-09-11 [email protected] Roll Flutter Engine from 35a3171b72c5 to 0a14c519ea4f (1 revision) (flutter/flutter#154984)
2024-09-11 [email protected] Roll Flutter Engine from b9c0b96c3316 to 35a3171b72c5 (1 revision) (flutter/flutter#154980)
2024-09-11 [email protected] Roll Flutter Engine from 52eeea075767 to b9c0b96c3316 (1 revision) (flutter/flutter#154976)
2024-09-11 [email protected] Roll Flutter Engine from a26075f9b1e6 to 52eeea075767 (1 revision) (flutter/flutter#154973)
2024-09-11 [email protected] Roll Flutter Engine from 60c15bc0f40e to a26075f9b1e6 (6 revisions) (flutter/flutter#154969)
2024-09-11 [email protected] Migrate `apple-mobile-web-*` to `mobile-web-*`. (flutter/flutter#154964)
2024-09-11 [email protected] Roll Flutter Engine from 8a038a6f7099 to 60c15bc0f40e (15 revisions) (flutter/flutter#154960)
2024-09-10 [email protected] Adds dart fixes for Color opacity functions (flutter/flutter#154953)
2024-09-10 [email protected] Missing benchmarks for `foundation/all_elements_bench.dart` (flutter/flutter#154954)
2024-09-10 [email protected] Update color assertions (flutter/flutter#154752)
2024-09-10 [email protected] handle EAGAIN (macOS) in ErrorHandlingProcessManager (flutter/flutter#154306)
2024-09-10 [email protected] fix unpack freezing app with animation duration zero  (flutter/flutter#153890)
2024-09-10 [email protected] Remove last `--disable-dart-dev` in `flutter/flutter`. (flutter/flutter#154948)
2024-09-10 [email protected] Remove scheduler: luci from new `build_aar_module_test` (flutter/flutter#154945)
2024-09-10 [email protected] Roll pub packages (flutter/flutter#154939)
2024-09-10 [email protected] `CupertinoSlidingSegmentedControl` update (flutter/flutter#152976)
2024-09-10 [email protected] Roll pub packages (flutter/flutter#154933)
2024-09-10 [email protected] fix test `chrome.close can recover if getTab throws a StateError` (flutter/flutter#154889)
2024-09-10 [email protected] SearchBar context menu (flutter/flutter#154833)
2024-09-10 [email protected] Fix `flutter build aar` for modules that use a plugin (flutter/flutter#154757)
2024-09-10 [email protected] Roll Packages from b4e0fc1 to bb53e5d (4 revisions) (flutter/flutter#154926)
2024-09-10 [email protected] Clean up `SnackBar` inherit theme data test (flutter/flutter#154921)
...
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels. will affect goldens Changes to golden files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants