diff --git a/CHANGELOG.md b/CHANGELOG.md index f688467169..ec851410c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Bug fixes +* Fix `Button(icon=...)` and other material buttons rendering an error box when `content` is not set. Icon-only buttons now render the icon centered ([#6886](https://github.com/flet-dev/flet/issues/6886), [#6889](https://github.com/flet-dev/flet/pull/6889)) by @FeodorFitsner. * Fix `flet debug`, `flet build`, `flet test`, `flet emulators` and `flet run` rejecting a positional argument typed after an option on Python 3.10, 3.11, 3.12.0-3.12.6 and 3.13.0 - `flet debug ios --device-id X my_app` failed with `unrecognized arguments: my_app` - by backporting the upstream `argparse` fix (CPython gh-59317). Also, an app path pointing to a file such as `main.py` is now rejected with a clear error instead of crashing on `main.py/build`, and a bad app path fails before the Flutter toolchain is set up ([#6840](https://github.com/flet-dev/flet/issues/6840), [#6875](https://github.com/flet-dev/flet/pull/6875)) by @ndonkoHenri. * Fix `--android-extract-packages` keeping only the values of its last occurrence when repeated. Like the other list options of `flet build`, repeating it now adds to the list ([#6875](https://github.com/flet-dev/flet/pull/6875)) by @ndonkoHenri. * Fix `--flutter-build-args` handing a value that starts with `-` to Flet instead of Flutter - `--flutter-build-args --verbose` turned on Flet's own verbose output - and overriding `build_args` from `pyproject.toml` with an empty list. Such an occurrence now fails with a hint to attach the value using `=`, e.g. `--flutter-build-args=--obfuscate` ([#6879](https://github.com/flet-dev/flet/pull/6879)) by @ndonkoHenri. diff --git a/client/pubspec.lock b/client/pubspec.lock index de454e8440..3e7de10f23 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -359,7 +359,7 @@ packages: path: "../packages/flet" relative: true source: path - version: "1.0.1" + version: "1.0.2" flet_ads: dependency: "direct main" description: diff --git a/packages/flet/CHANGELOG.md b/packages/flet/CHANGELOG.md index 16b12d8cc4..9ed14cc35a 100644 --- a/packages/flet/CHANGELOG.md +++ b/packages/flet/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.2 + +* Fix `Button`, `FilledButton`, `FilledTonalButton`, `OutlinedButton` and `TextButton` rendering an error box when `icon` is set without `content`. `ButtonControl` now uses the `.icon(...)` constructors only when both `icon` and `content` are provided; otherwise it builds the plain button with whichever one is set as its child, so icon-only buttons render the icon centered ([#6886](https://github.com/flet-dev/flet/issues/6886), [#6889](https://github.com/flet-dev/flet/pull/6889)) by @FeodorFitsner. + ## 1.0.1 _No changes in the `flet` Dart package; version bumped for release coordination with the Python-side fix for child components losing click events after wrapper re-renders ([#6857](https://github.com/flet-dev/flet/issues/6857), [#6859](https://github.com/flet-dev/flet/pull/6859))._ diff --git a/packages/flet/lib/src/controls/button.dart b/packages/flet/lib/src/controls/button.dart index 279e84e93a..db3dad68cb 100644 --- a/packages/flet/lib/src/controls/button.dart +++ b/packages/flet/lib/src/controls/button.dart @@ -7,7 +7,6 @@ import '../utils/colors.dart'; import '../utils/client_actions.dart'; import '../utils/misc.dart'; import '../utils/numbers.dart'; -import '../widgets/error.dart'; import '../widgets/flet_store_mixin.dart'; import 'base_controls.dart'; @@ -112,10 +111,7 @@ class _ButtonControlState extends State with FletStoreMixin { : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ); - Widget error = const ErrorControl("Error displaying Button", - description: "\"icon\" must be specified together with \"content\""); - - if (icon != null) { + if (icon != null && content != null) { if (isFilledButton) { button = FilledButton.icon( style: style, @@ -126,7 +122,7 @@ class _ButtonControlState extends State with FletStoreMixin { onHover: onHoverHandler, clipBehavior: clipBehavior, icon: icon, - label: content ?? error); + label: content); } else if (isFilledTonalButton) { button = FilledButton.tonalIcon( style: style, @@ -137,7 +133,7 @@ class _ButtonControlState extends State with FletStoreMixin { onHover: onHoverHandler, clipBehavior: clipBehavior, icon: icon, - label: content ?? error); + label: content); } else if (isTextButton) { button = TextButton.icon( autofocus: autofocus, @@ -148,7 +144,7 @@ class _ButtonControlState extends State with FletStoreMixin { style: style, clipBehavior: clipBehavior, icon: icon, - label: content ?? error, + label: content, ); } else if (isOutlinedButton) { button = OutlinedButton.icon( @@ -159,7 +155,7 @@ class _ButtonControlState extends State with FletStoreMixin { clipBehavior: clipBehavior, style: style, icon: icon, - label: content ?? error); + label: content); } else { button = ElevatedButton.icon( style: style, @@ -170,7 +166,7 @@ class _ButtonControlState extends State with FletStoreMixin { onHover: onHoverHandler, clipBehavior: clipBehavior, icon: icon, - label: content ?? error); + label: content); } } else { if (isFilledButton) { @@ -182,7 +178,7 @@ class _ButtonControlState extends State with FletStoreMixin { onLongPress: onLongPressHandler, onHover: onHoverHandler, clipBehavior: clipBehavior, - child: content); + child: content ?? icon); } else if (isFilledTonalButton) { button = FilledButton.tonal( style: style, @@ -192,7 +188,7 @@ class _ButtonControlState extends State with FletStoreMixin { onLongPress: onLongPressHandler, onHover: onHoverHandler, clipBehavior: clipBehavior, - child: content); + child: content ?? icon); } else if (isTextButton) { button = TextButton( autofocus: autofocus, @@ -202,7 +198,7 @@ class _ButtonControlState extends State with FletStoreMixin { onLongPress: onLongPressHandler, onHover: onHoverHandler, clipBehavior: clipBehavior, - child: content ?? const Text("")); + child: content ?? icon ?? const Text("")); } else if (isOutlinedButton) { button = OutlinedButton( autofocus: autofocus, @@ -212,7 +208,7 @@ class _ButtonControlState extends State with FletStoreMixin { clipBehavior: clipBehavior, onHover: onHoverHandler, style: style, - child: content); + child: content ?? icon); } else { button = ElevatedButton( style: style, @@ -222,7 +218,7 @@ class _ButtonControlState extends State with FletStoreMixin { onLongPress: onLongPressHandler, onHover: onHoverHandler, clipBehavior: clipBehavior, - child: content); + child: content ?? icon); } } diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 5e50506e96..f6b985066b 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -2,7 +2,7 @@ name: flet description: Write entire Flutter app in Python or add server-driven UI experience into existing Flutter app. homepage: https://flet.dev repository: https://github.com/flet-dev/flet/tree/main/packages/flet -version: 1.0.1 +version: 1.0.2 # Supported platforms platforms: diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/button/icon_only.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/button/icon_only.png new file mode 100644 index 0000000000..f252ae5b9a Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/button/icon_only.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/filled_button/icon_only.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/filled_button/icon_only.png new file mode 100644 index 0000000000..40c385fc54 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/filled_button/icon_only.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/filled_tonal_button/icon_only.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/filled_tonal_button/icon_only.png new file mode 100644 index 0000000000..6cd1dacf24 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/filled_tonal_button/icon_only.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/outlined_button/icon_only.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/outlined_button/icon_only.png new file mode 100644 index 0000000000..0c6c849e08 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/outlined_button/icon_only.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/text_button/icon_only.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/text_button/icon_only.png new file mode 100644 index 0000000000..e7c4438a5a Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/text_button/icon_only.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/test_button.py b/sdk/python/packages/flet/integration_tests/controls/material/test_button.py index 1787abc7e4..367f360dc7 100644 --- a/sdk/python/packages/flet/integration_tests/controls/material/test_button.py +++ b/sdk/python/packages/flet/integration_tests/controls/material/test_button.py @@ -72,3 +72,17 @@ async def test_style_conflicts(flet_app: ftt.FletTestApp, request): ), ), ) + + +@pytest.mark.asyncio(loop_scope="module") +async def test_icon_only(flet_app: ftt.FletTestApp, request): + flet_app.page.theme_mode = ft.ThemeMode.LIGHT + await flet_app.assert_control_screenshot( + request.node.name, + ft.Row( + controls=[ + ft.Button(icon=ft.Icons.PAUSE), + ft.Button(icon=ft.Icons.PAUSE, icon_color=ft.Colors.RED), + ], + ), + ) diff --git a/sdk/python/packages/flet/integration_tests/controls/material/test_filled_button.py b/sdk/python/packages/flet/integration_tests/controls/material/test_filled_button.py index 84ca94af5a..996fbd9d32 100644 --- a/sdk/python/packages/flet/integration_tests/controls/material/test_filled_button.py +++ b/sdk/python/packages/flet/integration_tests/controls/material/test_filled_button.py @@ -10,3 +10,17 @@ async def test_basic(flet_app: ftt.FletTestApp, request): request.node.name, ft.FilledButton("Click me"), ) + + +@pytest.mark.asyncio(loop_scope="module") +async def test_icon_only(flet_app: ftt.FletTestApp, request): + flet_app.page.theme_mode = ft.ThemeMode.LIGHT + await flet_app.assert_control_screenshot( + request.node.name, + ft.Row( + controls=[ + ft.FilledButton(icon=ft.Icons.PAUSE), + ft.FilledButton(icon=ft.Icons.PAUSE, icon_color=ft.Colors.RED), + ], + ), + ) diff --git a/sdk/python/packages/flet/integration_tests/controls/material/test_filled_tonal_button.py b/sdk/python/packages/flet/integration_tests/controls/material/test_filled_tonal_button.py index b7ec452f08..72ec2bf5ea 100644 --- a/sdk/python/packages/flet/integration_tests/controls/material/test_filled_tonal_button.py +++ b/sdk/python/packages/flet/integration_tests/controls/material/test_filled_tonal_button.py @@ -10,3 +10,17 @@ async def test_basic(flet_app: ftt.FletTestApp, request): request.node.name, ft.FilledTonalButton("Click me"), ) + + +@pytest.mark.asyncio(loop_scope="module") +async def test_icon_only(flet_app: ftt.FletTestApp, request): + flet_app.page.theme_mode = ft.ThemeMode.LIGHT + await flet_app.assert_control_screenshot( + request.node.name, + ft.Row( + controls=[ + ft.FilledTonalButton(icon=ft.Icons.PAUSE), + ft.FilledTonalButton(icon=ft.Icons.PAUSE, icon_color=ft.Colors.RED), + ], + ), + ) diff --git a/sdk/python/packages/flet/integration_tests/controls/material/test_outlined_button.py b/sdk/python/packages/flet/integration_tests/controls/material/test_outlined_button.py index d162c6f2ca..97553a4845 100644 --- a/sdk/python/packages/flet/integration_tests/controls/material/test_outlined_button.py +++ b/sdk/python/packages/flet/integration_tests/controls/material/test_outlined_button.py @@ -10,3 +10,17 @@ async def test_basic(flet_app: ftt.FletTestApp, request): request.node.name, ft.OutlinedButton("Click me"), ) + + +@pytest.mark.asyncio(loop_scope="module") +async def test_icon_only(flet_app: ftt.FletTestApp, request): + flet_app.page.theme_mode = ft.ThemeMode.LIGHT + await flet_app.assert_control_screenshot( + request.node.name, + ft.Row( + controls=[ + ft.OutlinedButton(icon=ft.Icons.PAUSE), + ft.OutlinedButton(icon=ft.Icons.PAUSE, icon_color=ft.Colors.RED), + ], + ), + ) diff --git a/sdk/python/packages/flet/integration_tests/controls/material/test_text_button.py b/sdk/python/packages/flet/integration_tests/controls/material/test_text_button.py index 12a61c379d..67f47b9fbb 100644 --- a/sdk/python/packages/flet/integration_tests/controls/material/test_text_button.py +++ b/sdk/python/packages/flet/integration_tests/controls/material/test_text_button.py @@ -10,3 +10,17 @@ async def test_basic(flet_app: ftt.FletTestApp, request): request.node.name, ft.TextButton("Click me"), ) + + +@pytest.mark.asyncio(loop_scope="module") +async def test_icon_only(flet_app: ftt.FletTestApp, request): + flet_app.page.theme_mode = ft.ThemeMode.LIGHT + await flet_app.assert_control_screenshot( + request.node.name, + ft.Row( + controls=[ + ft.TextButton(icon=ft.Icons.PAUSE), + ft.TextButton(icon=ft.Icons.PAUSE, icon_color=ft.Colors.RED), + ], + ), + )