From dfd0a3cb5f9e386b155aa88f8d7c2ccecd3b2eb2 Mon Sep 17 00:00:00 2001 From: jcheng Date: Tue, 6 May 2025 09:47:26 +0800 Subject: [PATCH] feat(RadioListTile):ensure that 'isThreeLine' can be configured through the (#166964) This PR is a continuation of [165481](https://github.com/flutter/flutter/pull/165481) Related items also include: [SwitchListTile](https://github.com/flutter/flutter/pull/166820), [CheckboxListTile](https://github.com/flutter/flutter/pull/166826) ## 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]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --------- Co-authored-by: Tong Mu --- .../lib/src/material/list_tile_theme.dart | 3 +- .../lib/src/material/radio_list_tile.dart | 15 +- .../test/material/radio_list_tile_test.dart | 235 ++++++++++++++++++ 3 files changed, 245 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/material/list_tile_theme.dart b/packages/flutter/lib/src/material/list_tile_theme.dart index e91fcdb8c3f..63623eb60cb 100644 --- a/packages/flutter/lib/src/material/list_tile_theme.dart +++ b/packages/flutter/lib/src/material/list_tile_theme.dart @@ -140,7 +140,8 @@ class ListTileThemeData with Diagnosticable { /// or [ExpansionTile.controlAffinity] or [SwitchListTile.controlAffinity] or [RadioListTile.controlAffinity]. final ListTileControlAffinity? controlAffinity; - /// If specified, overrides the default value of [ListTile.isThreeLine] or [CheckboxListTile.isThreeLine]. + /// If specified, overrides the default value of [ListTile.isThreeLine] or [CheckboxListTile.isThreeLine] + /// or [RadioListTile.isThreeLine]. final bool? isThreeLine; /// Creates a copy of this object with the given fields replaced with the diff --git a/packages/flutter/lib/src/material/radio_list_tile.dart b/packages/flutter/lib/src/material/radio_list_tile.dart index 095d7766e10..1e55eee7afe 100644 --- a/packages/flutter/lib/src/material/radio_list_tile.dart +++ b/packages/flutter/lib/src/material/radio_list_tile.dart @@ -186,7 +186,7 @@ class RadioListTile extends StatelessWidget { this.materialTapTargetSize, this.title, this.subtitle, - this.isThreeLine = false, + this.isThreeLine, this.dense, this.secondary, this.selected = false, @@ -204,7 +204,7 @@ class RadioListTile extends StatelessWidget { this.internalAddSemanticForOnTap = false, }) : _radioType = _RadioType.material, useCupertinoCheckmarkStyle = false, - assert(!isThreeLine || subtitle != null); + assert(isThreeLine != true || subtitle != null); /// Creates a combination of a list tile and a platform adaptive radio. /// @@ -227,7 +227,7 @@ class RadioListTile extends StatelessWidget { this.materialTapTargetSize, this.title, this.subtitle, - this.isThreeLine = false, + this.isThreeLine, this.dense, this.secondary, this.selected = false, @@ -245,7 +245,7 @@ class RadioListTile extends StatelessWidget { this.useCupertinoCheckmarkStyle = false, this.internalAddSemanticForOnTap = false, }) : _radioType = _RadioType.adaptive, - assert(!isThreeLine || subtitle != null); + assert(isThreeLine != true || subtitle != null); /// The value represented by this radio button. final T value; @@ -385,9 +385,10 @@ class RadioListTile extends StatelessWidget { /// Whether this list tile is intended to display three lines of text. /// - /// If false, the list tile is treated as having one line if the subtitle is - /// null and treated as having two lines if the subtitle is non-null. - final bool isThreeLine; + /// If null, the value from [ListTileThemeData.isThreeLine] is used. + /// If that is also null, the value from [ThemeData.listTileTheme] is used. + /// If still null, the default value is `false`. + final bool? isThreeLine; /// Whether this list tile is part of a vertically dense list. /// diff --git a/packages/flutter/test/material/radio_list_tile_test.dart b/packages/flutter/test/material/radio_list_tile_test.dart index 0c6047bdfda..54ec1aca070 100644 --- a/packages/flutter/test/material/radio_list_tile_test.dart +++ b/packages/flutter/test/material/radio_list_tile_test.dart @@ -1645,4 +1645,239 @@ void main() { expect(widget.transform.getMaxScaleOnAxis(), scale); }); + + testWidgets('RadioListTile isThreeLine', (WidgetTester tester) async { + const double height = 300; + const double size = 40.0; + + Widget buildFrame({bool? themeDataIsThreeLine, bool? themeIsThreeLine, bool? isThreeLine}) { + return MaterialApp( + key: UniqueKey(), + theme: + themeDataIsThreeLine != null + ? ThemeData(listTileTheme: ListTileThemeData(isThreeLine: themeDataIsThreeLine)) + : null, + home: Material( + child: ListTileTheme( + data: + themeIsThreeLine != null ? ListTileThemeData(isThreeLine: themeIsThreeLine) : null, + child: ListView( + children: [ + RadioListTile( + isThreeLine: isThreeLine, + title: const Text('A'), + subtitle: const Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'), + value: 0, + onChanged: null, + groupValue: 1, + ), + RadioListTile( + isThreeLine: isThreeLine, + title: const Text('A'), + subtitle: const Text('A'), + value: 0, + onChanged: null, + groupValue: 2, + ), + ], + ), + ), + ), + ); + } + + void expectTwoLine() { + expect( + tester.getRect(find.byType(RadioListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(Radio).at(0)), + const Rect.fromLTWH(16.0, 130.0, size, size), + ); + expect( + tester.getRect(find.byType(RadioListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 72.0), + ); + expect( + tester.getRect(find.byType(Radio).at(1)), + const Rect.fromLTWH(16.0, height + 16, size, size), + ); + } + + void expectThreeLine() { + expect( + tester.getRect(find.byType(RadioListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(Radio).at(0)), + const Rect.fromLTWH(16.0, 8.0, size, size), + ); + expect( + tester.getRect(find.byType(RadioListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 88.0), + ); + expect( + tester.getRect(find.byType(Radio).at(1)), + const Rect.fromLTWH(16.0, height + 8.0, size, size), + ); + } + + await tester.pumpWidget(buildFrame()); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeIsThreeLine: true, isThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, isThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget( + buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: true, isThreeLine: false), + ); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeIsThreeLine: false, isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget( + buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: false, isThreeLine: true), + ); + expectThreeLine(); + }); + + testWidgets('RadioListTile.adaptive isThreeLine', (WidgetTester tester) async { + const double height = 300; + const double size = 18.0; + + Widget buildFrame({bool? themeDataIsThreeLine, bool? themeIsThreeLine, bool? isThreeLine}) { + return MaterialApp( + key: UniqueKey(), + theme: ThemeData( + platform: TargetPlatform.iOS, + listTileTheme: + themeDataIsThreeLine != null + ? ListTileThemeData(isThreeLine: themeDataIsThreeLine) + : null, + ), + home: Material( + child: ListTileTheme( + data: + themeIsThreeLine != null ? ListTileThemeData(isThreeLine: themeIsThreeLine) : null, + child: ListView( + children: [ + RadioListTile.adaptive( + isThreeLine: isThreeLine, + title: const Text('A'), + subtitle: const Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'), + value: 0, + onChanged: null, + groupValue: 1, + ), + RadioListTile.adaptive( + isThreeLine: isThreeLine, + title: const Text('A'), + subtitle: const Text('A'), + value: 0, + onChanged: null, + groupValue: 2, + ), + ], + ), + ), + ), + ); + } + + void expectTwoLine() { + expect( + tester.getRect(find.byType(RadioListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(Radio).at(0)), + const Rect.fromLTWH(16.0, 141.0, size, size), + ); + expect( + tester.getRect(find.byType(RadioListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 72.0), + ); + expect( + tester.getRect(find.byType(Radio).at(1)), + const Rect.fromLTWH(16.0, height + 27.0, size, size), + ); + } + + void expectThreeLine() { + expect( + tester.getRect(find.byType(RadioListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(Radio).at(0)), + const Rect.fromLTWH(16.0, 8.0, size, size), + ); + expect( + tester.getRect(find.byType(RadioListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 88.0), + ); + expect( + tester.getRect(find.byType(Radio).at(1)), + const Rect.fromLTWH(16.0, height + 8.0, size, size), + ); + } + + await tester.pumpWidget(buildFrame()); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeIsThreeLine: true, isThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, isThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget( + buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: true, isThreeLine: false), + ); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeIsThreeLine: false, isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget( + buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: false, isThreeLine: true), + ); + expectThreeLine(); + }); }