Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
84078c85
Unverified
Commit
84078c85
authored
Mar 30, 2023
by
Mitchell Goodwin
Committed by
GitHub
Mar 30, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create CupertinoRadio Widget (#123296)
Create CupertinoRadio Widget
parent
8afd600e
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
872 additions
and
0 deletions
+872
-0
cupertino_radio.0.dart
examples/api/lib/cupertino/radio/cupertino_radio.0.dart
+75
-0
cupertino_radio.toggleable.0.dart
...api/lib/cupertino/radio/cupertino_radio.toggleable.0.dart
+78
-0
cupertino_radio.0_test.dart
...ples/api/test/cupertino/radio/cupertino_radio.0_test.dart
+32
-0
cupertino_radio.toggleable.0_test.dart
...st/cupertino/radio/cupertino_radio.toggleable.0_test.dart
+38
-0
cupertino.dart
packages/flutter/lib/cupertino.dart
+1
-0
radio.dart
packages/flutter/lib/src/cupertino/radio.dart
+308
-0
radio_test.dart
packages/flutter/test/cupertino/radio_test.dart
+340
-0
No files found.
examples/api/lib/cupertino/radio/cupertino_radio.0.dart
0 → 100644
View file @
84078c85
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/cupertino.dart'
;
/// Flutter code sample for [CupertinoRadio].
void
main
(
)
=>
runApp
(
const
CupertinoRadioApp
());
class
CupertinoRadioApp
extends
StatelessWidget
{
const
CupertinoRadioApp
({
super
.
key
});
static
const
String
_title
=
'CuptertinoRadio Example'
;
@override
Widget
build
(
BuildContext
context
)
{
return
const
CupertinoApp
(
theme:
CupertinoThemeData
(
brightness:
Brightness
.
light
),
home:
CupertinoPageScaffold
(
navigationBar:
CupertinoNavigationBar
(
middle:
Text
(
_title
),
),
child:
SafeArea
(
child:
CupertinoRadioExample
(),
),
),
);
}
}
enum
SingingCharacter
{
lafayette
,
jefferson
}
class
CupertinoRadioExample
extends
StatefulWidget
{
const
CupertinoRadioExample
({
super
.
key
});
@override
State
<
CupertinoRadioExample
>
createState
()
=>
_CupertinoRadioExampleState
();
}
class
_CupertinoRadioExampleState
extends
State
<
CupertinoRadioExample
>
{
SingingCharacter
?
_character
=
SingingCharacter
.
lafayette
;
@override
Widget
build
(
BuildContext
context
)
{
return
CupertinoListSection
(
children:
<
Widget
>[
CupertinoListTile
(
title:
const
Text
(
'Lafayette'
),
leading:
CupertinoRadio
<
SingingCharacter
>(
value:
SingingCharacter
.
lafayette
,
groupValue:
_character
,
onChanged:
(
SingingCharacter
?
value
)
{
setState
(()
{
_character
=
value
;
});
},
),
),
CupertinoListTile
(
title:
const
Text
(
'Thomas Jefferson'
),
leading:
CupertinoRadio
<
SingingCharacter
>(
value:
SingingCharacter
.
jefferson
,
groupValue:
_character
,
onChanged:
(
SingingCharacter
?
value
)
{
setState
(()
{
_character
=
value
;
});
},
),
),
],
);
}
}
examples/api/lib/cupertino/radio/cupertino_radio.toggleable.0.dart
0 → 100644
View file @
84078c85
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/cupertino.dart'
;
/// Flutter code sample for [CupertinoRadio.toggleable].
void
main
(
)
=>
runApp
(
const
CupertinoRadioApp
());
class
CupertinoRadioApp
extends
StatelessWidget
{
const
CupertinoRadioApp
({
super
.
key
});
static
const
String
_title
=
'CuptertinoRadio Toggleable Example'
;
@override
Widget
build
(
BuildContext
context
)
{
return
const
CupertinoApp
(
home:
CupertinoPageScaffold
(
navigationBar:
CupertinoNavigationBar
(
middle:
Text
(
_title
),
),
child:
SafeArea
(
child:
CupertinoRadioExample
(),
),
),
);
}
}
enum
SingingCharacter
{
mulligan
,
hamilton
}
class
CupertinoRadioExample
extends
StatefulWidget
{
const
CupertinoRadioExample
({
super
.
key
});
@override
State
<
CupertinoRadioExample
>
createState
()
=>
_CupertinoRadioExampleState
();
}
class
_CupertinoRadioExampleState
extends
State
<
CupertinoRadioExample
>
{
SingingCharacter
?
_character
=
SingingCharacter
.
mulligan
;
@override
Widget
build
(
BuildContext
context
)
{
return
CupertinoListSection
(
children:
<
Widget
>[
CupertinoListTile
(
title:
const
Text
(
'Hercules Mulligan'
),
leading:
CupertinoRadio
<
SingingCharacter
>(
value:
SingingCharacter
.
mulligan
,
groupValue:
_character
,
// TRY THIS: Try setting the toggleable value to false and
// see how that changes the behavior of the widget.
toggleable:
true
,
onChanged:
(
SingingCharacter
?
value
)
{
setState
(()
{
_character
=
value
;
});
},
),
),
CupertinoListTile
(
title:
const
Text
(
'Eliza Hamilton'
),
leading:
CupertinoRadio
<
SingingCharacter
>(
value:
SingingCharacter
.
hamilton
,
groupValue:
_character
,
toggleable:
true
,
onChanged:
(
SingingCharacter
?
value
)
{
setState
(()
{
_character
=
value
;
});
},
),
),
],
);
}
}
examples/api/test/cupertino/radio/cupertino_radio.0_test.dart
0 → 100644
View file @
84078c85
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/cupertino.dart'
;
import
'package:flutter_api_samples/cupertino/radio/cupertino_radio.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Has 2 CupertinoRadio widgets'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
CupertinoRadioApp
(),
);
expect
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>),
findsNWidgets
(
2
));
CupertinoRadio
<
example
.
SingingCharacter
>
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
first
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
lafayette
);
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
lafayette
);
await
tester
.
tap
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
await
tester
.
pumpAndSettle
();
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
jefferson
);
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
first
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
jefferson
);
});
}
examples/api/test/cupertino/radio/cupertino_radio.toggleable.0_test.dart
0 → 100644
View file @
84078c85
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/cupertino.dart'
;
import
'package:flutter_api_samples/cupertino/radio/cupertino_radio.toggleable.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Has 2 CupertinoRadio widgets that can be toggled off'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
CupertinoRadioApp
(),
);
expect
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>),
findsNWidgets
(
2
));
CupertinoRadio
<
example
.
SingingCharacter
>
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
first
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
mulligan
);
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
mulligan
);
await
tester
.
tap
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
await
tester
.
pumpAndSettle
();
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
hamilton
);
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
first
);
expect
(
radio
.
groupValue
,
example
.
SingingCharacter
.
hamilton
);
await
tester
.
tap
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
await
tester
.
pumpAndSettle
();
radio
=
tester
.
widget
(
find
.
byType
(
CupertinoRadio
<
example
.
SingingCharacter
>).
last
);
expect
(
radio
.
groupValue
,
null
);
});
}
packages/flutter/lib/cupertino.dart
View file @
84078c85
...
@@ -50,6 +50,7 @@ export 'src/cupertino/magnifier.dart';
...
@@ -50,6 +50,7 @@ export 'src/cupertino/magnifier.dart';
export
'src/cupertino/nav_bar.dart'
;
export
'src/cupertino/nav_bar.dart'
;
export
'src/cupertino/page_scaffold.dart'
;
export
'src/cupertino/page_scaffold.dart'
;
export
'src/cupertino/picker.dart'
;
export
'src/cupertino/picker.dart'
;
export
'src/cupertino/radio.dart'
;
export
'src/cupertino/refresh.dart'
;
export
'src/cupertino/refresh.dart'
;
export
'src/cupertino/route.dart'
;
export
'src/cupertino/route.dart'
;
export
'src/cupertino/scrollbar.dart'
;
export
'src/cupertino/scrollbar.dart'
;
...
...
packages/flutter/lib/src/cupertino/radio.dart
0 → 100644
View file @
84078c85
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'toggleable.dart'
;
// Examples can assume:
// late BuildContext context;
// enum SingingCharacter { lafayette }
// late SingingCharacter? _character;
// late StateSetter setState;
const
Size
_size
=
Size
(
18.0
,
18.0
);
const
double
_kOuterRadius
=
7.0
;
const
double
_kInnerRadius
=
2.975
;
// The relative values needed to transform a color to its equivilant focus
// outline color.
const
double
_kCupertinoFocusColorOpacity
=
0.80
;
const
double
_kCupertinoFocusColorBrightness
=
0.69
;
const
double
_kCupertinoFocusColorSaturation
=
0.835
;
/// A macOS-style radio button.
///
/// Used to select between a number of mutually exclusive values. When one radio
/// button in a group is selected, the other radio buttons in the group are
/// deselected. The values are of type `T`, the type parameter of the
/// [CupertinoRadio] class. Enums are commonly used for this purpose.
///
/// The radio button itself does not maintain any state. Instead, selecting the
/// radio invokes the [onChanged] callback, passing [value] as a parameter. If
/// [groupValue] and [value] match, this radio will be selected. Most widgets
/// will respond to [onChanged] by calling [State.setState] to update the
/// radio button's [groupValue].
///
/// {@tool dartpad}
/// Here is an example of CupertinoRadio widgets wrapped in CupertinoListTiles.
///
/// The currently selected character is passed into `groupValue`, which is
/// maintained by the example's `State`. In this case, the first [CupertinoRadio]
/// will start off selected because `_character` is initialized to
/// `SingingCharacter.lafayette`.
///
/// If the second radio button is pressed, the example's state is updated
/// with `setState`, updating `_character` to `SingingCharacter.jefferson`.
/// This causes the buttons to rebuild with the updated `groupValue`, and
/// therefore the selection of the second button.
///
/// ** See code in examples/api/lib/cupertino/radio/cupertino_radio.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoSlider], for selecting a value in a range.
/// * [CupertinoCheckbox] and [CupertinoSwitch], for toggling a particular value on or off.
/// * [Radio], the Material Design equivalent.
/// * <https://developer.apple.com/design/human-interface-guidelines/components/selection-and-input/toggles/>
class
CupertinoRadio
<
T
>
extends
StatefulWidget
{
/// Creates a macOS-styled radio button.
///
/// The following arguments are required:
///
/// * [value] and [groupValue] together determine whether the radio button is
/// selected.
/// * [onChanged] is called when the user selects this radio button.
const
CupertinoRadio
({
super
.
key
,
required
this
.
value
,
required
this
.
groupValue
,
required
this
.
onChanged
,
this
.
toggleable
=
false
,
this
.
activeColor
,
this
.
inactiveColor
,
this
.
fillColor
,
this
.
focusColor
,
this
.
focusNode
,
this
.
autofocus
=
false
,
});
/// The value represented by this radio button.
///
/// If this equals the [groupValue], then this radio button will appear
/// selected.
final
T
value
;
/// The currently selected value for a group of radio buttons.
///
/// This radio button is considered selected if its [value] matches the
/// [groupValue].
final
T
?
groupValue
;
/// Called when the user selects this [CupertinoRadio] button.
///
/// The radio button passes [value] as a parameter to this callback. It does
/// not actually change state until the parent widget rebuilds the radio
/// button with a new [groupValue].
///
/// If null, the radio button will be displayed as disabled.
///
/// The provided callback will not be invoked if this radio button is already
/// selected.
///
/// The callback provided to [onChanged] should update the state of the parent
/// [StatefulWidget] using the [State.setState] method, so that the parent
/// gets rebuilt; for example:
///
/// ```dart
/// CupertinoRadio<SingingCharacter>(
/// value: SingingCharacter.lafayette,
/// groupValue: _character,
/// onChanged: (SingingCharacter? newValue) {
/// setState(() {
/// _character = newValue;
/// });
/// },
/// )
/// ```
final
ValueChanged
<
T
?>?
onChanged
;
/// Set to true if this radio button is allowed to be returned to an
/// indeterminate state by selecting it again when selected.
///
/// To indicate returning to an indeterminate state, [onChanged] will be
/// called with null.
///
/// If true, [onChanged] can be called with [value] when selected while
/// [groupValue] != [value], or with null when selected again while
/// [groupValue] == [value].
///
/// If false, [onChanged] will be called with [value] when it is selected
/// while [groupValue] != [value], and only by selecting another radio button
/// in the group (i.e. changing the value of [groupValue]) can this radio
/// button be unselected.
///
/// The default is false.
///
/// {@tool dartpad}
/// This example shows how to enable deselecting a radio button by setting the
/// [toggleable] attribute.
///
/// ** See code in examples/api/lib/cupertino/radio/cupertino_radio.toggleable.0.dart **
/// {@end-tool}
final
bool
toggleable
;
/// The color to use when this radio button is selected.
///
/// Defaults to [CupertinoColors.activeBlue].
final
Color
?
activeColor
;
/// The color to use when this radio button is not selected.
///
/// Defaults to [CupertinoColors.white].
final
Color
?
inactiveColor
;
/// The color that fills the inner circle of the radio button when selected.
///
/// Defaults to [CupertinoColors.white].
final
Color
?
fillColor
;
/// The color for the radio's border when it has the input focus.
///
/// If null, then a paler form of the [activeColor] will be used.
final
Color
?
focusColor
;
/// {@macro flutter.widgets.Focus.focusNode}
final
FocusNode
?
focusNode
;
/// {@macro flutter.widgets.Focus.autofocus}
final
bool
autofocus
;
bool
get
_selected
=>
value
==
groupValue
;
@override
State
<
CupertinoRadio
<
T
>>
createState
()
=>
_CupertinoRadioState
<
T
>();
}
class
_CupertinoRadioState
<
T
>
extends
State
<
CupertinoRadio
<
T
>>
with
TickerProviderStateMixin
,
ToggleableStateMixin
{
final
_RadioPainter
_painter
=
_RadioPainter
();
bool
focused
=
false
;
void
_handleChanged
(
bool
?
selected
)
{
if
(
selected
==
null
)
{
widget
.
onChanged
!(
null
);
return
;
}
if
(
selected
)
{
widget
.
onChanged
!(
widget
.
value
);
}
}
@override
void
dispose
()
{
_painter
.
dispose
();
super
.
dispose
();
}
@override
ValueChanged
<
bool
?>?
get
onChanged
=>
widget
.
onChanged
!=
null
?
_handleChanged
:
null
;
@override
bool
get
tristate
=>
widget
.
toggleable
;
@override
bool
?
get
value
=>
widget
.
_selected
;
void
onFocusChange
(
bool
value
)
{
if
(
focused
!=
value
)
{
focused
=
value
;
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
Color
effectiveActiveColor
=
widget
.
activeColor
??
CupertinoColors
.
activeBlue
;
final
Color
effectiveInactiveColor
=
widget
.
inactiveColor
??
CupertinoColors
.
white
;
final
Color
effectiveFocusOverlayColor
=
widget
.
focusColor
??
HSLColor
.
fromColor
(
effectiveActiveColor
.
withOpacity
(
_kCupertinoFocusColorOpacity
))
.
withLightness
(
_kCupertinoFocusColorBrightness
)
.
withSaturation
(
_kCupertinoFocusColorSaturation
)
.
toColor
();
final
Color
effectiveActivePressedOverlayColor
=
HSLColor
.
fromColor
(
effectiveActiveColor
).
withLightness
(
0.45
).
toColor
();
final
Color
effectiveFillColor
=
widget
.
fillColor
??
CupertinoColors
.
white
;
return
Semantics
(
inMutuallyExclusiveGroup:
true
,
checked:
widget
.
_selected
,
child:
buildToggleable
(
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
onFocusChange:
onFocusChange
,
size:
_size
,
painter:
_painter
..
focusColor
=
effectiveFocusOverlayColor
..
downPosition
=
downPosition
..
isFocused
=
focused
..
activeColor
=
downPosition
!=
null
?
effectiveActivePressedOverlayColor
:
effectiveActiveColor
..
inactiveColor
=
effectiveInactiveColor
..
fillColor
=
effectiveFillColor
..
value
=
value
,
),
);
}
}
class
_RadioPainter
extends
ToggleablePainter
{
bool
?
get
value
=>
_value
;
bool
?
_value
;
set
value
(
bool
?
value
)
{
if
(
_value
==
value
)
{
return
;
}
_value
=
value
;
notifyListeners
();
}
Color
get
fillColor
=>
_fillColor
!;
Color
?
_fillColor
;
set
fillColor
(
Color
value
)
{
if
(
value
==
_fillColor
)
{
return
;
}
_fillColor
=
value
;
notifyListeners
();
}
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
final
Offset
center
=
(
Offset
.
zero
&
size
).
center
;
// Outer border
final
Paint
paint
=
Paint
()
..
color
=
inactiveColor
..
style
=
PaintingStyle
.
fill
..
strokeWidth
=
0.1
;
canvas
.
drawCircle
(
center
,
_kOuterRadius
,
paint
);
paint
.
style
=
PaintingStyle
.
stroke
;
paint
.
color
=
CupertinoColors
.
inactiveGray
;
canvas
.
drawCircle
(
center
,
_kOuterRadius
,
paint
);
if
(
value
??
false
)
{
paint
.
style
=
PaintingStyle
.
fill
;
paint
.
color
=
activeColor
;
canvas
.
drawCircle
(
center
,
_kOuterRadius
,
paint
);
paint
.
color
=
fillColor
;
canvas
.
drawCircle
(
center
,
_kInnerRadius
,
paint
);
}
if
(
isFocused
)
{
paint
.
style
=
PaintingStyle
.
stroke
;
paint
.
color
=
focusColor
;
paint
.
strokeWidth
=
3.0
;
canvas
.
drawCircle
(
center
,
_kOuterRadius
+
1.5
,
paint
);
}
}
}
packages/flutter/test/cupertino/radio_test.dart
0 → 100644
View file @
84078c85
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../widgets/semantics_tester.dart'
;
void
main
(
)
{
testWidgets
(
'Radio control test'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
UniqueKey
();
final
List
<
int
?>
log
=
<
int
?>[];
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
key:
key
,
value:
1
,
groupValue:
2
,
onChanged:
log
.
add
,
),
),
));
await
tester
.
tap
(
find
.
byKey
(
key
));
expect
(
log
,
equals
(<
int
>[
1
]));
log
.
clear
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
key:
key
,
value:
1
,
groupValue:
1
,
onChanged:
log
.
add
,
activeColor:
CupertinoColors
.
systemGreen
,
),
),
));
await
tester
.
tap
(
find
.
byKey
(
key
));
expect
(
log
,
isEmpty
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
key:
key
,
value:
1
,
groupValue:
2
,
onChanged:
null
,
),
),
));
await
tester
.
tap
(
find
.
byKey
(
key
));
expect
(
log
,
isEmpty
);
});
testWidgets
(
'Radio can be toggled when toggleable is set'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
UniqueKey
();
final
List
<
int
?>
log
=
<
int
?>[];
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
key:
key
,
value:
1
,
groupValue:
2
,
onChanged:
log
.
add
,
toggleable:
true
,
),
),
));
await
tester
.
tap
(
find
.
byKey
(
key
));
expect
(
log
,
equals
(<
int
>[
1
]));
log
.
clear
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
key:
key
,
value:
1
,
groupValue:
1
,
onChanged:
log
.
add
,
toggleable:
true
,
),
),
));
await
tester
.
tap
(
find
.
byKey
(
key
));
expect
(
log
,
equals
(<
int
?>[
null
]));
log
.
clear
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
key:
key
,
value:
1
,
groupValue:
null
,
onChanged:
log
.
add
,
toggleable:
true
,
),
),
));
await
tester
.
tap
(
find
.
byKey
(
key
));
expect
(
log
,
equals
(<
int
>[
1
]));
});
testWidgets
(
'Radio semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
value:
1
,
groupValue:
2
,
onChanged:
(
int
?
i
)
{
},
),
),
));
expect
(
tester
.
getSemantics
(
find
.
byType
(
Focus
).
last
),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
isEnabled:
true
,
hasTapAction:
true
,
isFocusable:
true
,
isInMutuallyExclusiveGroup:
true
,
));
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
value:
2
,
groupValue:
2
,
onChanged:
(
int
?
i
)
{
},
),
),
));
expect
(
tester
.
getSemantics
(
find
.
byType
(
Focus
).
last
),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
isEnabled:
true
,
hasTapAction:
true
,
isFocusable:
true
,
isInMutuallyExclusiveGroup:
true
,
isChecked:
true
,
));
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
value:
1
,
groupValue:
2
,
onChanged:
null
,
),
),
));
expect
(
tester
.
getSemantics
(
find
.
byType
(
Focus
).
last
),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
isFocusable:
true
,
isInMutuallyExclusiveGroup:
true
,
));
await
tester
.
pump
();
// Now the isFocusable should be gone.
expect
(
tester
.
getSemantics
(
find
.
byType
(
Focus
).
last
),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
isInMutuallyExclusiveGroup:
true
,
));
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
value:
2
,
groupValue:
2
,
onChanged:
null
,
),
),
));
expect
(
tester
.
getSemantics
(
find
.
byType
(
Focus
).
last
),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
isChecked:
true
,
isInMutuallyExclusiveGroup:
true
,
));
semantics
.
dispose
();
});
testWidgets
(
'has semantic events'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
final
Key
key
=
UniqueKey
();
dynamic
semanticEvent
;
int
?
radioValue
=
2
;
tester
.
binding
.
defaultBinaryMessenger
.
setMockDecodedMessageHandler
<
dynamic
>(
SystemChannels
.
accessibility
,
(
dynamic
message
)
async
{
semanticEvent
=
message
;
});
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoRadio
<
int
>(
key:
key
,
value:
1
,
groupValue:
radioValue
,
onChanged:
(
int
?
i
)
{
radioValue
=
i
;
},
),
),
));
await
tester
.
tap
(
find
.
byKey
(
key
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byKey
(
key
));
expect
(
radioValue
,
1
);
expect
(
semanticEvent
,
<
String
,
dynamic
>{
'type'
:
'tap'
,
'nodeId'
:
object
.
debugSemantics
!.
id
,
'data'
:
<
String
,
dynamic
>{},
});
expect
(
object
.
debugSemantics
!.
getSemanticsData
().
hasAction
(
SemanticsAction
.
tap
),
true
);
semantics
.
dispose
();
tester
.
binding
.
defaultBinaryMessenger
.
setMockDecodedMessageHandler
<
dynamic
>(
SystemChannels
.
accessibility
,
null
);
});
testWidgets
(
'Radio can be controlled by keyboard shortcuts'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
focusManager
.
highlightStrategy
=
FocusHighlightStrategy
.
alwaysTraditional
;
int
?
groupValue
=
1
;
const
Key
radioKey0
=
Key
(
'radio0'
);
const
Key
radioKey1
=
Key
(
'radio1'
);
const
Key
radioKey2
=
Key
(
'radio2'
);
final
FocusNode
focusNode2
=
FocusNode
(
debugLabel:
'radio2'
);
Widget
buildApp
({
bool
enabled
=
true
})
{
return
CupertinoApp
(
home:
Center
(
child:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
SizedBox
(
width:
200
,
height:
100
,
child:
Row
(
children:
<
Widget
>[
CupertinoRadio
<
int
>(
key:
radioKey0
,
value:
0
,
onChanged:
enabled
?
(
int
?
newValue
)
{
setState
(()
{
groupValue
=
newValue
;
});
}
:
null
,
groupValue:
groupValue
,
autofocus:
true
,
),
CupertinoRadio
<
int
>(
key:
radioKey1
,
value:
1
,
onChanged:
enabled
?
(
int
?
newValue
)
{
setState
(()
{
groupValue
=
newValue
;
});
}
:
null
,
groupValue:
groupValue
,
),
CupertinoRadio
<
int
>(
key:
radioKey2
,
value:
2
,
onChanged:
enabled
?
(
int
?
newValue
)
{
setState
(()
{
groupValue
=
newValue
;
});
}
:
null
,
groupValue:
groupValue
,
focusNode:
focusNode2
,
),
],
),
);
}),
),
);
}
await
tester
.
pumpWidget
(
buildApp
());
await
tester
.
pumpAndSettle
();
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
enter
);
await
tester
.
pumpAndSettle
();
// On web, radios don't respond to the enter key.
expect
(
groupValue
,
kIsWeb
?
equals
(
1
)
:
equals
(
0
));
focusNode2
.
requestFocus
();
await
tester
.
pumpAndSettle
();
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
expect
(
groupValue
,
equals
(
2
));
});
testWidgets
(
'Do not crash when widget disappears while pointer is down'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
UniqueKey
();
Widget
buildRadio
(
bool
show
)
{
return
CupertinoApp
(
home:
Center
(
child:
show
?
CupertinoRadio
<
bool
>(
key:
key
,
value:
true
,
groupValue:
false
,
onChanged:
(
_
)
{
})
:
Container
(),
),
);
}
await
tester
.
pumpWidget
(
buildRadio
(
true
));
final
Offset
center
=
tester
.
getCenter
(
find
.
byKey
(
key
));
// Put a pointer down on the screen.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
center
);
await
tester
.
pump
();
// While the pointer is down, the widget disappears.
await
tester
.
pumpWidget
(
buildRadio
(
false
));
expect
(
find
.
byKey
(
key
),
findsNothing
);
// Release pointer after widget disappeared.
await
gesture
.
up
();
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment