Skip to content

Commit

Permalink
Add InkResponse, Material and fix Opacity (flutter#6199)
Browse files Browse the repository at this point in the history
Related issue: flutter#141668
  • Loading branch information
peixinli authored Feb 26, 2024
1 parent 5e03e0f commit 91d11d6
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 6 deletions.
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()),
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

0 comments on commit 91d11d6

Please sign in to comment.