Skip to content
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

Add InkResponse, Material and fix Opacity #6199

Merged
merged 12 commits into from
Feb 26, 2024
Merged
6 changes: 6 additions & 0 deletions packages/rfw/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.0.24
* Adds `InkResponse` material widget.
* Adds `Material` material widget.
* Adds the `child` to `Opacity` core widget.
* Implements more `InkWell` parameters.

## 1.0.23

* Replaces usage of deprecated Flutter APIs.
Expand Down
1 change: 1 addition & 0 deletions packages/rfw/lib/src/flutter/core_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ Map<String, LocalWidgetBuilder> get _coreWidgetsDefinitions => <String, LocalWid
opacity: source.v<double>(['opacity']) ?? 0.0,
onEnd: source.voidHandler(['onEnd']),
alwaysIncludeSemantics: source.v<bool>(['alwaysIncludeSemantics']) ?? true,
child: source.optionalChild(['child']),
);
},

Expand Down
65 changes: 64 additions & 1 deletion packages/rfw/lib/src/flutter/material_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ import 'runtime.dart';
/// * [DropdownButton]
/// * [ElevatedButton]
/// * [FloatingActionButton]
/// * [InkResponse]
/// * [InkWell]
/// * [LinearProgressIndicator]
/// * [ListTile]
/// * [Material]
/// * [OutlinedButton]
/// * [Scaffold]
/// * [TextButton]
Expand Down Expand Up @@ -337,14 +339,58 @@ Map<String, LocalWidgetBuilder> get _materialWidgetsDefinitions => <String, Loca
);
},

'InkResponse': (BuildContext context, DataSource source) {
// not implemented: mouseCursor, overlayColor, splashFactory, focusNode.
return InkResponse(
onTap: source.voidHandler(['onTap']),
onTapDown: source.handler(['onTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()),
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want to expose the details in the event?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed the issue #144175. We should expose the details for all the tap events.

onTapUp: source.handler(['onTapUp'], (VoidCallback trigger) => (TapUpDetails details) => trigger()),
onTapCancel: source.voidHandler(['onTapCancel']),
onDoubleTap: source.voidHandler(['onDoubleTap']),
onLongPress: source.voidHandler(['onLongPress']),
onSecondaryTap: source.voidHandler(['onSecondaryTap']),
onSecondaryTapUp: source.handler(['onSecondaryTapUp'], (VoidCallback trigger) => (TapUpDetails details) => trigger()),
onSecondaryTapDown: source.handler(['onSecondaryTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()),
onSecondaryTapCancel: source.voidHandler(['onSecondaryTapCancel']),
onHighlightChanged: source.handler(['onHighlightChanged'], (VoidCallback trigger) => (bool highlighted) => trigger()),
onHover: source.handler(['onHover'], (VoidCallback trigger) => (bool hovered) => trigger()),
containedInkWell: source.v<bool>(['containedInkWell']) ?? false,
highlightShape: ArgumentDecoders.enumValue<BoxShape>(BoxShape.values, source, ['highlightShape']) ?? BoxShape.circle,
radius: source.v<double>(['radius']),
borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius'])?.resolve(Directionality.of(context)),
customBorder: ArgumentDecoders.shapeBorder(source, ['customBorder']),
focusColor: ArgumentDecoders.color(source, ['focusColor']),
hoverColor: ArgumentDecoders.color(source, ['hoverColor']),
highlightColor: ArgumentDecoders.color(source, ['highlightColor']),
splashColor: ArgumentDecoders.color(source, ['splashColor']),
enableFeedback: source.v<bool>(['enableFeedback']) ?? true,
excludeFromSemantics: source.v<bool>(['excludeFromSemantics']) ?? false,
canRequestFocus: source.v<bool>(['canRequestFocus']) ?? true,
onFocusChange: source.handler(['onFocusChange'], (VoidCallback trigger) => (bool focus) => trigger()),
autofocus: source.v<bool>(['autofocus']) ?? false,
hoverDuration: ArgumentDecoders.duration(source, ['hoverDuration'], context),
child: source.optionalChild(['child']),
);
},

'InkWell': (BuildContext context, DataSource source) {
// not implemented: onHighlightChanged, onHover; mouseCursor; focusColor, hoverColor, highlightColor, overlayColor, splashColor; splashFactory; focusNode, onFocusChange
// not implemented: mouseCursor; overlayColor, splashFactory; focusNode, onFocusChange
return InkWell(
onTap: source.voidHandler(['onTap']),
onDoubleTap: source.voidHandler(['onDoubleTap']),
onLongPress: source.voidHandler(['onLongPress']),
onTapDown: source.handler(['onTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()),
onTapCancel: source.voidHandler(['onTapCancel']),
onSecondaryTap: source.voidHandler(['onSecondaryTap']),
onSecondaryTapUp: source.handler(['onSecondaryTapUp'], (VoidCallback trigger) => (TapUpDetails details) => trigger()),
onSecondaryTapDown: source.handler(['onSecondaryTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()),
onSecondaryTapCancel: source.voidHandler(['onSecondaryTapCancel']),
onHighlightChanged: source.handler(['onHighlightChanged'], (VoidCallback trigger) => (bool highlighted) => trigger()),
onHover: source.handler(['onHover'], (VoidCallback trigger) => (bool hovered) => trigger()),
focusColor: ArgumentDecoders.color(source, ['focusColor']),
hoverColor: ArgumentDecoders.color(source, ['hoverColor']),
highlightColor: ArgumentDecoders.color(source, ['highlightColor']),
splashColor: ArgumentDecoders.color(source, ['splashColor']),
radius: source.v<double>(['radius']),
borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius'])?.resolve(Directionality.of(context)),
customBorder: ArgumentDecoders.shapeBorder(source, ['customBorder']),
Expand Down Expand Up @@ -395,6 +441,23 @@ Map<String, LocalWidgetBuilder> get _materialWidgetsDefinitions => <String, Loca
);
},

'Material': (BuildContext context, DataSource source) {
return Material(
type: ArgumentDecoders.enumValue<MaterialType>(MaterialType.values,source, ['type']) ?? MaterialType.canvas,
elevation: source.v<double>(['elevation']) ?? 0.0,
color: ArgumentDecoders.color(source, ['color']),
shadowColor: ArgumentDecoders.color(source, ['shadowColor']),
surfaceTintColor: ArgumentDecoders.color(source, ['surfaceTintColor']),
textStyle: ArgumentDecoders.textStyle(source, ['textStyle']),
borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius']),
shape: ArgumentDecoders.shapeBorder(source, ['shape']),
borderOnForeground: source.v<bool>(['borderOnForeground']) ?? true,
clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
animationDuration: ArgumentDecoders.duration(source, ['animationDuration'], context),
child: source.child(['child']),
);
},

'OutlinedButton': (BuildContext context, DataSource source) {
// not implemented: buttonStyle, focusNode
return OutlinedButton(
Expand Down
2 changes: 1 addition & 1 deletion packages/rfw/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: rfw
description: "Remote Flutter widgets: a library for rendering declarative widget description files at runtime."
repository: https://github.com/flutter/packages/tree/main/packages/rfw
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+rfw%22
version: 1.0.23
version: 1.0.24

environment:
sdk: ^3.2.0
Expand Down
11 changes: 9 additions & 2 deletions packages/rfw/test/core_widgets_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ void main() {

runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
import core;
widget root = Opacity(onEnd: event 'end' {});
widget root = Opacity(
onEnd: event 'end' {},
child: Placeholder(),
);
'''));
await tester.pump();
expect(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).onEnd, isNot(isNull));
Expand Down Expand Up @@ -226,7 +229,10 @@ void main() {
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.8,
child: Text(text: "test"),
child: Text(
text: "test",
textScaleFactor: 3.0,
),
),
);
'''));
Expand All @@ -235,6 +241,7 @@ void main() {
final Size childSize = tester.getSize(find.text('test'));
expect(childSize.width, fractionallySizedBoxSize.width * 0.5);
expect(childSize.height, fractionallySizedBoxSize.height * 0.8);
expect(tester.widget<Text>(find.text('test')).textScaler, const TextScaler.linear(3));
expect(tester.widget<FractionallySizedBox>(find.byType(FractionallySizedBox)).alignment, Alignment.center);
});

Expand Down
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.
148 changes: 148 additions & 0 deletions packages/rfw/test/material_widgets_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rfw/formats.dart' show parseLibraryFile;
Expand Down Expand Up @@ -438,4 +439,151 @@ void main() {
skip: !runGoldens,
);
});

testWidgets('Implement InkResponse properties', (WidgetTester tester) async {
final Runtime runtime = setupRuntime();
final DynamicContent data = DynamicContent();
final List<String> eventLog = <String>[];
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: RemoteWidget(
runtime: runtime,
data: data,
widget: const FullyQualifiedWidgetName(testName, 'root'),
onEvent: (String eventName, DynamicMap eventArguments) {
eventLog.add('$eventName $eventArguments');
},
),
),
);
expect(
tester.takeException().toString(),
contains('Could not find remote widget named'),
);

runtime.update(testName, parseLibraryFile('''
import core;
import material;
widget root = Scaffold(
body: Center(
child: InkResponse(
onTap: event 'onTap' {},
onHover: event 'onHover' {},
borderRadius: [{x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}],
hoverColor: 0xFF00FF00,
splashColor: 0xAA0000FF,
highlightColor: 0xAAFF0000,
containedInkWell: true,
highlightShape: 'circle',
child: Text(text: 'InkResponse'),
),
),
);
'''));
await tester.pump();

expect(find.byType(InkResponse), findsOneWidget);

// Hover
final Offset center = tester.getCenter(find.byType(InkResponse));
final TestGesture gesture =
await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(center);
await tester.pumpAndSettle();

await expectLater(
find.byType(RemoteWidget),
matchesGoldenFile('goldens/material_test.ink_response_hover.png'),
skip: !runGoldens,
);
expect(eventLog, contains('onHover {}'));

// Tap
await gesture.down(center);
await tester.pump(); // start gesture
await tester.pump(const Duration(
milliseconds: 200)); // wait for splash to be well under way

await expectLater(
find.byType(RemoteWidget),
matchesGoldenFile('goldens/material_test.ink_response_tap.png'),
skip: !runGoldens,
);
await gesture.up();
await tester.pump();

expect(eventLog, contains('onTap {}'));
});

testWidgets('Implement Material properties', (WidgetTester tester) async {
final Runtime runtime = setupRuntime();
final DynamicContent data = DynamicContent();
final List<String> eventLog = <String>[];
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: RemoteWidget(
runtime: runtime,
data: data,
widget: const FullyQualifiedWidgetName(testName, 'root'),
onEvent: (String eventName, DynamicMap eventArguments) {
eventLog.add('$eventName $eventArguments');
},
),
),
);
expect(
tester.takeException().toString(),
contains('Could not find remote widget named'),
);

runtime.update(testName, parseLibraryFile('''
import core;
import material;
widget root = Material(
type: 'circle',
elevation: 6.0,
color: 0xFF0000FF,
shadowColor: 0xFF00FF00,
surfaceTintColor: 0xff0000ff,
animationDuration: 300,
borderOnForeground: false,
child: SizedBox(
width: 20.0,
height: 20.0,
),
);
'''));
await tester.pump();

expect(tester.widget<Material>(find.byType(Material)).animationDuration,
const Duration(milliseconds: 300));
expect(tester.widget<Material>(find.byType(Material)).borderOnForeground,
false);
await expectLater(
find.byType(RemoteWidget),
matchesGoldenFile('goldens/material_test.material_properties.png'),
skip: !runGoldens,
);

runtime.update(testName, parseLibraryFile('''
import core;
import material;
widget root = Material(
clipBehavior: 'antiAlias',
shape: { type: 'circle', side: { width: 10.0, color: 0xFF0066FF } },
child: SizedBox(
width: 20.0,
height: 20.0,
),
);
'''));
await tester.pump();

expect(tester.widget<Material>(find.byType(Material)).clipBehavior,
Clip.antiAlias);
});
}
4 changes: 2 additions & 2 deletions packages/rfw/test_coverage/bin/test_coverage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import 'package:meta/meta.dart';
// Please update these targets when you update this package.
// Please ensure that test coverage continues to be 100%.
// Don't forget to update the lastUpdate date too!
const int targetLines = 3273;
const int targetLines = 3333;
const String targetPercent = '100';
const String lastUpdate = '2024-01-30';
const String lastUpdate = '2024-02-26';

@immutable
/* final */ class LcovLine {
Expand Down