Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 3d82059
Author: Matthias Ngeo <[email protected]>
Date:   Sun Jun 2 15:31:14 2024 +0800

    Add switch (#31)

    * WIP

    * Add golden tests

    * Commit from GitHub Actions (Forui Presubmit)

    * Fix PR issues

    ---------

    Co-authored-by: Pante <[email protected]>
  • Loading branch information
Daviiddoo committed Jun 2, 2024
1 parent 97196da commit d2cd05b
Show file tree
Hide file tree
Showing 16 changed files with 265 additions and 4 deletions.
1 change: 1 addition & 0 deletions forui/lib/forui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export 'src/widgets/button/tappable.dart' hide FTappable;
export 'src/widgets/button/button.dart' hide FButtonContent;
export 'src/widgets/card/card.dart' hide FCardContent;
export 'src/widgets/separator.dart';
export 'src/widgets/switch.dart';
18 changes: 14 additions & 4 deletions forui/lib/src/theme/theme_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class FThemeData with Diagnosticable {
/// The separator styles.
final FSeparatorStyles separatorStyles;

/// The switch style.
final FSwitchStyle switchStyle;

/// Creates a [FThemeData].
FThemeData({
required this.colorScheme,
Expand All @@ -38,6 +41,7 @@ class FThemeData with Diagnosticable {
required this.buttonStyles,
required this.cardStyle,
required this.separatorStyles,
required this.switchStyle,
});

/// Creates a [FThemeData] that inherits the given arguments' properties.
Expand All @@ -50,7 +54,8 @@ class FThemeData with Diagnosticable {
boxStyle = FBoxStyle.inherit(colorScheme: colorScheme),
buttonStyles = FButtonStyles.inherit(colorScheme: colorScheme, font: font, style: style, ),
cardStyle = FCardStyle.inherit(colorScheme: colorScheme, font: font, style: style),
separatorStyles = FSeparatorStyles.inherit(colorScheme: colorScheme, style: style);
separatorStyles = FSeparatorStyles.inherit(colorScheme: colorScheme, style: style),
switchStyle = FSwitchStyle.inherit(colorScheme: colorScheme);

/// Creates a copy of this [FThemeData] with the given properties replaced.
FThemeData copyWith({
Expand All @@ -62,6 +67,7 @@ class FThemeData with Diagnosticable {
FButtonStyles? buttonStyles,
FCardStyle? cardStyle,
FSeparatorStyles? separatorStyles,
FSwitchStyle? switchStyle,
}) => FThemeData(
colorScheme: colorScheme ?? this.colorScheme,
font: font ?? this.font,
Expand All @@ -71,6 +77,7 @@ class FThemeData with Diagnosticable {
buttonStyles: buttonStyles ?? this.buttonStyles,
cardStyle: cardStyle ?? this.cardStyle,
separatorStyles: separatorStyles ?? this.separatorStyles,
switchStyle: switchStyle ?? this.switchStyle,
);

@override
Expand All @@ -84,7 +91,8 @@ class FThemeData with Diagnosticable {
..add(DiagnosticsProperty<FBoxStyle>('boxStyle', boxStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FButtonStyles>('buttonStyles', buttonStyles, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FCardStyle>('cardStyle', cardStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FSeparatorStyles>('separatorStyles', separatorStyles, level: DiagnosticLevel.debug));
..add(DiagnosticsProperty<FSeparatorStyles>('separatorStyles', separatorStyles, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FSwitchStyle>('switchStyle', switchStyle));
}

@override
Expand All @@ -99,7 +107,8 @@ class FThemeData with Diagnosticable {
boxStyle == other.boxStyle &&
buttonStyles == other.buttonStyles &&
cardStyle == other.cardStyle &&
separatorStyles == other.separatorStyles;
separatorStyles == other.separatorStyles &&
switchStyle == other.switchStyle;

@override
int get hashCode =>
Expand All @@ -110,5 +119,6 @@ class FThemeData with Diagnosticable {
boxStyle.hashCode ^
buttonStyles.hashCode ^
cardStyle.hashCode ^
separatorStyles.hashCode;
separatorStyles.hashCode ^
switchStyle.hashCode;
}
177 changes: 177 additions & 0 deletions forui/lib/src/widgets/switch.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';

import 'package:forui/forui.dart';

/// A control that allows the user to toggle between checked and not checked.
class FSwitch extends StatelessWidget {

/// The style of the switch.
final FSwitchStyle? style;

/// Whether this switch is on or off.
final bool value;

/// Called when the user toggles with switch on or off.
///
/// The switch passes the new value to the callback but does not actually
/// change state until the parent widget rebuilds the switch with the new
/// value.
///
/// If null, the switch will be displayed as disabled, which has a reduced opacity.
///
/// 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
/// FSwitch(
/// value: _giveVerse,
/// onChanged: (bool newValue) {
/// setState(() {
/// _giveVerse = newValue;
/// });
/// },
/// )
/// ```
final ValueChanged<bool>? onChanged;

/// True if this widget will be selected as the initial focus when no other node in its scope is currently focused.
///
/// Ideally, there is only one widget with autofocus set in each FocusScope. If there is more than one widget with
/// autofocus set, then the first one added to the tree will get focus.
///
/// Defaults to false.
final bool autofocus;

/// An optional focus node to use as the focus node for this widget.
///
/// If one is not supplied, then one will be automatically allocated, owned, and managed by this widget. The widget
/// will be focusable even if a [focusNode] is not supplied. If supplied, the given `focusNode` will be hosted by this
/// widget, but not owned. See [FocusNode] for more information on what being hosted and/or owned implies.
///
/// Supplying a focus node is sometimes useful if an ancestor to this widget wants to control when this widget has the
/// focus. The owner will be responsible for calling [FocusNode.dispose] on the focus node when it is done with it,
/// but this widget will attach/detach and reparent the node when needed.
final FocusNode? focusNode;

/// Handler called when the focus changes.
///
/// Called with true if this widget's node gains focus, and false if it loses focus.
final ValueChanged<bool>? onFocusChange;

/// Determines the way that drag start behavior is handled.
///
/// If set to [DragStartBehavior.start], the drag behavior used to move the
/// switch from on to off will begin at the position where the drag gesture won
/// the arena. If set to [DragStartBehavior.down] it will begin at the position
/// where a down event was first detected.
///
/// In general, setting this to [DragStartBehavior.start] will make drag
/// animation smoother and setting it to [DragStartBehavior.down] will make
/// drag behavior feel slightly more reactive.
///
/// By default, the drag start behavior is [DragStartBehavior.start].
final DragStartBehavior dragStartBehavior;

/// Creates a [FSwitch].
const FSwitch({
required this.value,
required this.onChanged,
this.style,
this.autofocus = false,
this.focusNode,
this.onFocusChange,
this.dragStartBehavior = DragStartBehavior.start,
super.key,
});

@override
Widget build(BuildContext context) {
final style = this.style ?? context.theme.switchStyle;
return CupertinoSwitch(
value: value,
onChanged: onChanged,
activeColor: style.checked,
trackColor: style.unchecked,
thumbColor: style.thumb,
focusColor: style.focus,
autofocus: autofocus,
focusNode: focusNode,
onFocusChange: onFocusChange,
dragStartBehavior: dragStartBehavior,
);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('value', value))
..add(DiagnosticsProperty('autofocus', autofocus))
..add(DiagnosticsProperty('dragStartBehavior', dragStartBehavior))
..add(DiagnosticsProperty('style', style))
..add(DiagnosticsProperty<ValueChanged<bool>>('onChanged', onChanged))
..add(DiagnosticsProperty<FocusNode>('focusNode', focusNode))
..add(DiagnosticsProperty<ValueChanged<bool>>('onFocusChange', onFocusChange));
}
}

/// The style of a [FSwitch].
final class FSwitchStyle with Diagnosticable {

/// The color of the switch when it is checked.
final Color checked;

/// The color of the switch when it is unchecked.
final Color unchecked;

/// The color of the switch's thumb.
final Color thumb;

/// The color of the switch when it is focused. Defaults to a slightly transparent [checked] color.
final Color focus;

/// Creates a [FSwitchStyle].
const FSwitchStyle({
required this.checked,
required this.unchecked,
required this.thumb,
required this.focus,
});

/// Creates a [FSwitchStyle] that inherits its properties from [colorScheme].
FSwitchStyle.inherit({required FColorScheme colorScheme})
: checked = colorScheme.primary,
unchecked = colorScheme.border,
thumb = colorScheme.background,
focus = HSLColor.fromColor(colorScheme.primary.withOpacity(0.80))
.withLightness(0.69)
.withSaturation(0.835)
.toColor();

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ColorProperty('checked', checked))
..add(ColorProperty('unchecked', unchecked))
..add(ColorProperty('thumb', thumb))
..add(ColorProperty('focus', focus));
}

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is FSwitchStyle &&
runtimeType == other.runtimeType &&
checked == other.checked &&
unchecked == other.unchecked &&
thumb == other.thumb &&
focus == other.focus;

@override
int get hashCode => checked.hashCode ^ unchecked.hashCode ^ thumb.hashCode ^ focus.hashCode;

}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions forui/test/src/widgets/switch_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:flutter/cupertino.dart';

import 'package:flutter_test/flutter_test.dart';

import 'package:forui/forui.dart';
import '../test_scaffold.dart';

void main() {
group('FSeparator', () {
for (final (name, theme, _) in TestScaffold.themes) {
for (final (checked, value) in [('checked', true), ('unchecked', false)]) {
testWidgets('$name - $checked - unfocused', (tester) async {
await tester.pumpWidget(
TestScaffold(
data: theme,
child: Center(
child: FSwitch(
value: value,
onChanged: (_) {},
),
),
),
);

await expectLater(
find.byType(TestScaffold),
matchesGoldenFile('switch/$name-$checked-unfocused.png'),
);
});

testWidgets('$name - $checked - focused', (tester) async {
await tester.pumpWidget(
TestScaffold(
data: theme,
child: Center(
child: FSwitch(
value: value,
autofocus: true,
onChanged: (_) {},
),
),
),
);

await expectLater(
find.byType(TestScaffold),
matchesGoldenFile('switch/$name-$checked-focused.png'),
);
});

testWidgets('$name - $checked - disabled', (tester) async {
await tester.pumpWidget(
TestScaffold(
data: theme,
child: Center(
child: FSwitch(
value: value,
autofocus: true,
onChanged: null,
),
),
),
);

await expectLater(
find.byType(TestScaffold),
matchesGoldenFile('switch/$name-$checked-disabled.png'),
);
});
}
}
});
}

0 comments on commit d2cd05b

Please sign in to comment.