From 7f223c826ce85250d650eda3600a5ced16612cb5 Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Mon, 29 Jan 2024 17:27:12 +0200 Subject: [PATCH 1/2] [rfw] Add `OverflowBar` widget and Update `ButtonBar` widget implementation --- packages/rfw/CHANGELOG.md | 7 + .../rfw/lib/src/flutter/material_widgets.dart | 85 ++++++++-- packages/rfw/pubspec.yaml | 2 +- ..._test.overflow_bar_properties.overflow.png | Bin 0 -> 2413 bytes .../material_test.overflow_bar_properties.png | Bin 0 -> 4109 bytes ...test.overflow_bar_resembles_button_bar.png | Bin 0 -> 4524 bytes packages/rfw/test/material_widgets_test.dart | 150 +++++++++++++++++- 7 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 packages/rfw/test/goldens/material_test.overflow_bar_properties.overflow.png create mode 100644 packages/rfw/test/goldens/material_test.overflow_bar_properties.png create mode 100644 packages/rfw/test/goldens/material_test.overflow_bar_resembles_button_bar.png diff --git a/packages/rfw/CHANGELOG.md b/packages/rfw/CHANGELOG.md index 1998c2c9a73..65db8bc2c48 100644 --- a/packages/rfw/CHANGELOG.md +++ b/packages/rfw/CHANGELOG.md @@ -1,7 +1,14 @@ +## 1.0.20 + +* Adds OverflowBox material widget. +* Updates ButtonBar material widget implementation. + ## 1.0.19 + * Add `DropdownButton` and `ClipRRect` widgets to rfw widget library. ## 1.0.18 + * Exposes `WidgetLibrary`s registered in `Runtime`. * Exposes widgets map in `LocalWidgetLibrary`. diff --git a/packages/rfw/lib/src/flutter/material_widgets.dart b/packages/rfw/lib/src/flutter/material_widgets.dart index 59fb2903464..b564029d0ff 100644 --- a/packages/rfw/lib/src/flutter/material_widgets.dart +++ b/packages/rfw/lib/src/flutter/material_widgets.dart @@ -37,6 +37,7 @@ import 'runtime.dart'; /// * [Scaffold] /// * [TextButton] /// * [VerticalDivider] +/// * [OverflowBar] /// /// For each, every parameter is implemented using the same name. Parameters /// that take structured types are represented using maps, with each named @@ -50,6 +51,22 @@ import 'runtime.dart'; /// * [VisualDensity] is represented in the manner described in the documentation /// of the [ArgumentDecoders.visualDensity] method. /// +/// Some features have changed in the underlying Material library and are +/// therefore no longer supported, including: +/// +/// * The ButtonBar widget in package:flutter/material.dart is planned to be +/// deprecated in favor of the OverflowBar widget. The ButtonBar widget in +/// rfw package uses the OverflowBar widget internally for backward compatibility. +/// The ButtonBar widget in rfw package is not deprecated and will continue to +/// be supported. As a result, the following ButtonBar parameters are no longer +/// supported: +/// +/// * buttonMinWidth +/// * buttonHeight +/// * buttonAlignedDropdown +/// +/// It is recommended to use the OverflowBar widget. +/// /// Some features are not supported: /// /// * [AppBar]s do not support [AppBar.bottom], [AppBar.flexibleSpace], and @@ -123,18 +140,66 @@ Map get _materialWidgetsDefinitions => (ButtonBarLayoutBehavior.values, source, ['layoutBehavior']) + ?? ButtonBarLayoutBehavior.padded; + + Widget overflowBar = OverflowBar( alignment: ArgumentDecoders.enumValue(MainAxisAlignment.values, source, ['alignment']) ?? MainAxisAlignment.start, - mainAxisSize: ArgumentDecoders.enumValue(MainAxisSize.values, source, ['mainAxisSize']) ?? MainAxisSize.max, - buttonMinWidth: source.v(['buttonMinWidth']), - buttonHeight: source.v(['buttonHeight']), - buttonPadding: ArgumentDecoders.edgeInsets(source, ['buttonPadding']), - buttonAlignedDropdown: source.v(['buttonAlignedDropdown']) ?? false, - layoutBehavior: ArgumentDecoders.enumValue(ButtonBarLayoutBehavior.values, source, ['layoutBehavior']), - overflowDirection: ArgumentDecoders.enumValue(VerticalDirection.values, source, ['overflowDirection']), - overflowButtonSpacing: source.v(['overflowButtonSpacing']), + spacing: buttonPadding.horizontal / 2, + overflowDirection: ArgumentDecoders.enumValue(VerticalDirection.values, source, ['overflowDirection']) ?? VerticalDirection.down, + overflowSpacing: source.v(['overflowButtonSpacing']) ?? 0.0, + children: source.childList(['children']), + ); + + switch (layoutBehavior) { + case ButtonBarLayoutBehavior.padded: + overflowBar = Padding( + padding: EdgeInsets.symmetric( + vertical: 2.0 * (buttonPadding.horizontal / 4.0), + horizontal: buttonPadding.horizontal / 2.0, + ), + child: overflowBar, + ); + case ButtonBarLayoutBehavior.constrained: + overflowBar = Container( + padding: EdgeInsets.symmetric(horizontal: buttonPadding.horizontal / 2.0), + constraints: const BoxConstraints(minHeight: 52.0), + alignment: Alignment.center, + child: overflowBar, + ); + } + + if (ArgumentDecoders.enumValue(MainAxisSize.values, source, ['mainAxisSize']) == MainAxisSize.min) { + return IntrinsicWidth(child: overflowBar); + } + + return overflowBar; + }, + + 'OverflowBar': (BuildContext context, DataSource source) { + return OverflowBar( + spacing: source.v(['spacing']) ?? 0.0, + alignment: ArgumentDecoders.enumValue(MainAxisAlignment.values, source, ['alignment']), + overflowSpacing: source.v(['overflowSpacing']) ?? 0.0, + overflowAlignment: ArgumentDecoders.enumValue(OverflowBarAlignment.values, source, ['overflowAlignment']) + ?? OverflowBarAlignment.start, + overflowDirection: ArgumentDecoders.enumValue(VerticalDirection.values, source, ['overflowDirection']) + ?? VerticalDirection.down, + textDirection: ArgumentDecoders.enumValue(TextDirection.values, source, ['textDirection']), children: source.childList(['children']), ); }, diff --git a/packages/rfw/pubspec.yaml b/packages/rfw/pubspec.yaml index b683d0dc5a6..f2f8c72ab92 100644 --- a/packages/rfw/pubspec.yaml +++ b/packages/rfw/pubspec.yaml @@ -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.19 +version: 1.0.20 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/rfw/test/goldens/material_test.overflow_bar_properties.overflow.png b/packages/rfw/test/goldens/material_test.overflow_bar_properties.overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..b2b9c64dfb894ab85e37f7b094eb2c436b1a76d9 GIT binary patch literal 2413 zcmeAS@N?(olHy`uVBq!ia0vp^Cm0x*A~@K9EcJyezW^!5;vjb?hIQv;UIIBR#ZI0f z96(URk zTsNfT{_iV$+vL&0!tE5ejYIQ5OGD#Q=c!ARPfl8~ZPu-r(`D>WmS)`-TR3law(WWQ z57vjj|C;=Eb@er|?xPnO{;mC*D$g)4g{e_NfMb*x4FhVXgAZ3W@qbuS{c`X7>9sBY zSO0!*xA%>*&BZ;A3LGp=4!DUmx?h+(W$30`X6EL{|NZ;-$;}j`(1a_{ek+9|L-oJwma1Sh?hVRBtEq}yE|(>Tk6)zW+c-DI9gsd z>b$lNi_C3rZ%?kR{dfNJ%Wi3D>E!(U{BJtic|a;BC&%ahT)7k1u3fA6^5SB}pC2DB zEG%Z&p6{}>u#o7Nx0maevy}n{)aCEjfd$4F>kDP&<;TB$`*!30{c=|6%-Y&opbTohRv@;2N`|ATefNP34}i z*P-PO`{p;=jeO$i#@z(2c(&g{( z&E01IwtT|(`}P0ZE%f%HJoe+~)%)jWJ!!G5E9;xTRB{>Xr>o)dy@tnb{5a~pS9O-mOkKwU@&A*{8oE~3S33B1R%I9+pk4e=3 z{=yHHkv7lsxjm*>Mf_*kq|f_lnQN;Vu*t;W>FVdQ&MBb@0756>Q~&?~ literal 0 HcmV?d00001 diff --git a/packages/rfw/test/goldens/material_test.overflow_bar_properties.png b/packages/rfw/test/goldens/material_test.overflow_bar_properties.png new file mode 100644 index 0000000000000000000000000000000000000000..fad2df99e9d1c081c8ad367ca52221489f75ba4e GIT binary patch literal 4109 zcmeHJX-wN?7=Jmk!pMM0!ad4Foe2a|qR@e*h|Ebiw@xA1IA%G-DXe4g2iBHCwjff5 zQIVr9&D1gGEKnH~#!(>Hx^XnJ!U`=%`E$1ADDbCHptYzU%wn8hO#IOI%bVwyH&34T z_xzrtBqr+Yv7^pM0RYFs-U~esfSEf0rg0WW3`k8*(N)7`l6C%U2xu5~T{alzSs`H; zEDVQik^C6|HoUOVw=d)}S7i87R*Gq{JS~b@g&9aLOVWMq_u)xTzw|Rj-^cx$LwNn$ zrn-21vGhquj7!Ux?w=etZ?S8!wYTDqj||!iSiRWhw5n=O-P4N5s8#>0Yw_qAxp08c zsX5JT_GOD~4B-SP`5}e?W^^Zm0ie-=2>`yP<^cR+VGDq}hY>UqVibe15R9GS|JW66 zB5;5EoW8MPLlWCQqPw9+#aa(iN>MYNcX|QwhG8WXcQ^| z+R66x^n`cRFp)x`{K(-%;?kBaZGpXFg*WZi$k%Cemuw1L({%g2;w)^D4&mX;*m|{w zgeXSUBjG-iV@^`36fQsZokKQ>1tDT@6mVtadAFg!fGqZXot9_4Sn9J1<* z4?!i}X^8Le6F9;e(Lu`WtFG@CLyPO)leak>4oSPUOxlD7<^#!Hs-Jt?UrO> zq9%WM#LgXfi5(Kj7=a@ZQNeaT^7??zz{G+Bh|*Cg6fq?w<=j_o(SxbYTs|L<(C?ml!5}@%B%nUVH|}RBEM2RmFmd_OS7h!XdEJTB+@6#)?Q2KJC~gc)l#*~(ja1XcD72T z^7{BCO$m#|;y!v5@CR9O^|6VRY&u53$Lo~+;n*Ln*M=)RV>9;X2!fC{7lug6F%Fl< zC*?Jl+d?z}=`Cjb}qAOnCU&Ue7 z;fuzM_vqFUodD(B9pmxbacQoJxXhSh99GBbaeF4 z|Jc}AU2XH0fgmd$my1cro3BtmKE;l}-SSwP{kasLgr-jz%5h0dpmhY(z}07+dfgs8 z(Q*ZT(Ae$lpCPNtx7IXmE{e*n9K7ic3AvXqb(8ZHr+eaG`8~1dJ7&-G{kKUlg3qba pNQjY;f9_jGg%}m`-zj9*=SlbxS<}sj0>i)rVP~R38$yzbegiu^puhkC literal 0 HcmV?d00001 diff --git a/packages/rfw/test/goldens/material_test.overflow_bar_resembles_button_bar.png b/packages/rfw/test/goldens/material_test.overflow_bar_resembles_button_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..caba08310f0e30d2ce12f6a6b0aa3b6f672fb2ac GIT binary patch literal 4524 zcmeH}Z&Z_29LMhlILto;b|wmpbttKmIB*&$j3H^n0U@mf8UZ=}6e+NlV`DHTWmIeo zD(7rof?;~jLy>#;yiFI(O%i*R$Al8S&T!rLh4l6apBHLhJ$&bRc^;w?d%?{O z-Tv*%E?+Ohz7JXs+HRd#b(QkjPqS65X?8Oir0syw97{JYTq$2>m&dQ=&9xKj+>2vmC}7ut-cqqm5!qk&R$C@xM`9viw#MTfY0t6^}E=?S!M zc$cy^HH#@AFln6@Z3i?WUNX|}oh?qk>IBT}sETb|T-UM;t{02}u zeREzhdn3V*cK_!Kl=*u<>AO+h{h5&fE$J$-Nb8!qcGI-VUAhC{w203_P4%Aifo`6C zO_b=ycFMVT^Q?X#Sl7>73$#eQ%`sK}jUQB*-LRWUP!Re(C3K}Y;^=0f~=`*~i$uf_;Nc7@vJ6_h3G zB7-9#+`S~jcucfqds|F%9u~8%+8hLcHF}ooj7z-YpOiOSqu0y42Q#CzJ{`F_jI7kl zgVdH)WqwIQlyIF9?mp|wyn<;Qe8vC=RcM&F^!<#PY$M0=G~7DTxX|e`?b!D7GlSf| z3q;NNO}v(tmJbeU_sQjQa#~tiePd%O*CTL% zDLCO92HbttmYs{>l{%o-b5P4hwRCuSp*rDcU%}M!vPCWX8h+_jOdU5aiJ{3(q8jfH z>1}2;uh^SKKQemp{d!W{HsL8|9~@8$PcC&KTKzGMDOcCWYc^m3$Cb6(26y)+bM!Zb z%k{0P-OAm;zA=I;b34oXQje%Q8Arx1N9I4uZGe{ze}1?kMQRo=$R>Lb@aUk5!gJ}_V$fObs+TbY+jVL-%pNX;o( zRaL>ZkH>FR2Fh;TI=Itq&0z*(%zOf1?}lOl2z#9kW4j4~1R&MX8Gtg^-$g!Znjnca z8g;ClI@g6wLMxO!P3ZBOoHI}|3@ldsyaBHh<3LHb@9#tYGS0ser;jsS9=%0al!^P9 zj_A=5v1O<;;x=a53MA^84S&yVG#U%%%!|yqL^H}VqtN{gBE!9NJeBE;Mx(jZ+Ion4 zy(peU@{r3_JOn%3^i{{viPiwA`v@){-rl12V>DrbX>1}%)ZDzi@x6|f?pJ7Y^IRV?Mo;3XyfBd7SNz<93p&^R??hlm3#~KPGw+m zF&lA7o-kwv3<}!vKl8cU-q@9zUtdNl^(KbK2!aAa zEe}O2xF#GOMCMubGMQ;^LRNz73Nq=`L>+})y@w6_r2?cQ+rk5gl(l9A?5yGc;H-EB z7;8?G!`Aq#Bdz$&a06+@P;!s+SM}ltL+}Gt!GZD@5%FMVpvq&S^=KuWb){4KW^9vq z=}A!j!lSJfjwuMw(0saQ2t7wd200r~f5?6~CdvpgE2(6i>MGRF$3wSjqC|-pHnX=T zszS=gXGm37nXrunOX2u41UGoTn%PhCsKX^J<4_#5Vw2~1p_|nO=E?dB)NRb8>l)o;{NJn tx5H(J%MKTK%DvZ4hn['core']); + const LibraryName materialName = LibraryName(['material']); + const LibraryName testName = LibraryName(['test']); + + Runtime setupRuntime() { + return Runtime() + ..update(coreName, createCoreWidgets()) + ..update(materialName, createMaterialWidgets()); + } + testWidgets('Material widgets', (WidgetTester tester) async { - final Runtime runtime = Runtime() - ..update(const LibraryName(['core']), createCoreWidgets()) - ..update( - const LibraryName(['material']), createMaterialWidgets()); + final Runtime runtime = setupRuntime(); final DynamicContent data = DynamicContent(); final List eventLog = []; await tester.pumpWidget( @@ -24,8 +31,7 @@ void main() { home: RemoteWidget( runtime: runtime, data: data, - widget: const FullyQualifiedWidgetName( - LibraryName(['test']), 'root'), + widget: const FullyQualifiedWidgetName(testName, 'root'), onEvent: (String eventName, DynamicMap eventArguments) { eventLog.add('$eventName $eventArguments'); }, @@ -218,4 +224,136 @@ void main() { skip: !runGoldens, ); }); + + testWidgets('OverflowBar configured to resemble ButtonBar', + (WidgetTester tester) async { + final Runtime runtime = setupRuntime(); + final DynamicContent data = DynamicContent(); + final List eventLog = []; + 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: Card( + margin: [20.0], + child: Padding( + padding: [8.0], + child: OverflowBar( + spacing: 8.0, + children: [ + ElevatedButton( + onPressed: event 'button' { }, + child: Text(text: 'Elevated'), + ), + OutlinedButton( + onPressed: event 'button' { }, + child: Text(text: 'Outlined'), + ), + TextButton( + onPressed: event 'button' { }, + child: Text(text: 'Text'), + ), + ], + ), + ), + ), + ); + ''')); + await tester.pump(); + await expectLater( + find.byType(RemoteWidget), + matchesGoldenFile( + 'goldens/material_test.overflow_bar_resembles_button_bar.png'), + skip: !runGoldens, + ); + }); + + testWidgets('Implement OverflowBar properties', (WidgetTester tester) async { + final Runtime runtime = setupRuntime(); + final DynamicContent data = DynamicContent(); + final List eventLog = []; + 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')); + + addTearDown(() async { + await tester.binding.setSurfaceSize(null); + }); + + runtime.update(testName, parseLibraryFile(''' + import core; + import material; + widget root = Scaffold( + body: Center( + child: OverflowBar( + spacing: 16.0, + alignment: 'end', + overflowSpacing: 4.0, + overflowAlignment: 'center', + overflowDirection: 'up', + children: [ + ElevatedButton( + onPressed: event 'button' { }, + child: Text(text: 'Elevated'), + ), + OutlinedButton( + onPressed: event 'button' { }, + child: Text(text: 'Outlined'), + ), + TextButton( + onPressed: event 'button' { }, + child: Text(text: 'Text'), + ), + ], + ), + ), + ); + ''')); + await tester.pump(); + + await expectLater( + find.byType(RemoteWidget), + matchesGoldenFile('goldens/material_test.overflow_bar_properties.png'), + skip: !runGoldens, + ); + + // Update the surface size for OverflowBar to overflow. + await tester.binding.setSurfaceSize(const Size(200.0, 600.0)); + await tester.pump(); + + await expectLater( + find.byType(RemoteWidget), + matchesGoldenFile( + 'goldens/material_test.overflow_bar_properties.overflow.png'), + skip: !runGoldens, + ); + }); } From b555ba995fd380b9793cbf8d7604c06bc0579846 Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Tue, 30 Jan 2024 11:28:54 +0200 Subject: [PATCH 2/2] Update docs --- .../rfw/lib/src/flutter/material_widgets.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/rfw/lib/src/flutter/material_widgets.dart b/packages/rfw/lib/src/flutter/material_widgets.dart index b564029d0ff..b21bc8c2e63 100644 --- a/packages/rfw/lib/src/flutter/material_widgets.dart +++ b/packages/rfw/lib/src/flutter/material_widgets.dart @@ -51,21 +51,21 @@ import 'runtime.dart'; /// * [VisualDensity] is represented in the manner described in the documentation /// of the [ArgumentDecoders.visualDensity] method. /// -/// Some features have changed in the underlying Material library and are +/// Some features have changed in the underlying Flutter's material library and are /// therefore no longer supported, including: /// -/// * The ButtonBar widget in package:flutter/material.dart is planned to be -/// deprecated in favor of the OverflowBar widget. The ButtonBar widget in -/// rfw package uses the OverflowBar widget internally for backward compatibility. -/// The ButtonBar widget in rfw package is not deprecated and will continue to -/// be supported. As a result, the following ButtonBar parameters are no longer +/// * The [ButtonBar] widget in the Flutter's material library is planned to be +/// deprecated in favor of the [OverflowBar] widget. The [ButtonBar] widget in +/// `rfw` package uses the [OverflowBar] widget internally for backward compatibility. +/// The [ButtonBar] widget in `rfw` package is not deprecated and will continue to +/// be supported. As a result, the following [ButtonBar] parameters are no longer /// supported: /// -/// * buttonMinWidth -/// * buttonHeight -/// * buttonAlignedDropdown +/// * `buttonMinWidth` +/// * `buttonHeight` +/// * `buttonAlignedDropdown` /// -/// It is recommended to use the OverflowBar widget. +/// It is recommended to use the [OverflowBar] widget. /// /// Some features are not supported: ///