Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ client/devtools_options.yaml

# Actual screenshots written by failed golden comparisons in integration tests
*_actual.png

# Files uploaded while running the file_picker examples, which pass
# upload_dir="examples" to ft.run() and so write under the example's own folder
sdk/python/examples/**/examples/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

### New features

* **Client actions: an `action` property that performs gesture-gated work without a round trip to Python.** Browsers only let a page open a file picker, write to the clipboard, show a share sheet or open a new tab while they are still handling the user's click or key press. Sending that click to your Python code and acting on the reply takes longer than the permission lasts, so on iOS Safari those operations were silently ignored while Android and desktop browsers let them through - which made a browser rule look like a Flet bug. `Button`, `Container`, `IconButton`, `ListTile`, `CupertinoButton`, `CupertinoListTile`, `FloatingActionButton`, `OutlinedButton`, `TextButton` and `TextSpan` now accept `action` - a single `ClientAction` or a list of them - which the client performs inside the original gesture, before your `on_click` handler is even notified: `ft.Button("Open", action=ft.OpenUrl("https://flet.dev", target=ft.UrlTarget.BLANK))`. Because an action runs before your code sees the click, its arguments have to be known in advance; to act on a value computed at click time, set it on the control ahead of the click. `url` is unchanged and keeps working exactly as before by @FeodorFitsner.
* **App icon and desktop integration for `flet build linux`.** Linux was the one platform `flet build` shipped without an icon — `flutter_launcher_icons` has no Linux generator, so `assets/icon.png` was silently ignored and built apps ran with a generic icon. The resolved icon (`icon_linux.png`, falling back to `icon.png` or the default Flet icon) is now bundled as `data/app_icon.png` and set as the window icon on startup, which taskbars and window switchers pick up on X11 and XWayland. Because Wayland has no window-icon protocol — desktops resolve icons from an installed desktop entry matching the app id — the bundle also ships a ready-to-install freedesktop tree: `share/applications/<bundle_id>.desktop` (name from `--product`, comment from `--description`, `StartupWMClass` set) plus the icon at `share/icons/hicolor/<size>/apps/<bundle_id>.png`. The entry's application categories default to `Utility` and are configurable with `--linux-categories` or `[tool.flet.linux].categories`, and the runner now sets its program name to the bundle ID (matching upstream Flutter's runner template) so the running app maps to that entry on both Wayland and X11. See the new [App icon](https://flet.dev/docs/publish/linux#app-icon) docs ([#2269](https://github.com/flet-dev/flet/issues/2269)) by @ndonkoHenri.

* **macOS code signing, notarization, and Mac App Store builds in `flet build macos`.** Select a distribution lane with `--macos-distribution` (or `[tool.flet.macos.signing].distribution`): `developer-id` signs every bundled binary with your Developer ID certificate — hardened runtime, entitlements, secure timestamp — then notarizes and staples the app for direct distribution, while `app-store` produces a sandboxed app with your provisioning profile embedded, packaged into an installer-signed `.pkg` ready for App Store Connect and TestFlight. Signing identities are auto-discovered from the keychain when not explicitly configured (via CLI options, `pyproject.toml` — including per-lane `[tool.flet.macos.signing.<lane>]` subtables — or environment variables), and the whole configuration is validated before the build starts, so a typo'd identity, expired certificate, or missing store prerequisite fails in seconds instead of after the full build. See the new [Code signing](https://flet.dev/docs/publish/macos#code-signing), [Notarization](https://flet.dev/docs/publish/macos#notarization), and [Mac App Store](https://flet.dev/docs/publish/macos#mac-app-store) docs ([#2347](https://github.com/flet-dev/flet/issues/2347), [#4543](https://github.com/flet-dev/flet/issues/4543), [#6702](https://github.com/flet-dev/flet/pull/6702)) by @ndonkoHenri.

### Improvements

* `FilePicker` gained an `on_result` event, called when files are selected through a `PickFiles` action. A `PickFiles` action opens the dialog on the client before your code sees the click, so the selection cannot be returned to the caller the way `pick_files()` returns it - it arrives here instead. The picked files stay associated with the `FilePicker`, so they can be passed straight to `upload()` by @FeodorFitsner.
* `flet build ipa` now validates the configured provisioning profile before the build starts, instead of letting Xcode fail at the signing step minutes later with "No profile for team 'X' matching 'Y' found" — a message that cannot say what *is* installed. The profile is resolved the same way Xcode's `PROVISIONING_PROFILE_SPECIFIER` does (by name **or** UUID, across both directories Xcode reads), and is additionally checked for expiry, team match, and bundle-id coverage. A name that matches nothing now fails in seconds, listing the installed profiles with their teams and UUIDs so a typo — or a profile that was downloaded but never installed — is immediately obvious ([#5100](https://github.com/flet-dev/flet/issues/5100), [#6796](https://github.com/flet-dev/flet/pull/6796)) by @ndonkoHenri.
* `flet build ipa` now reports the artifact it actually produced. An unsigned build yields only an `.xcarchive`, yet the command announced "Successfully built your .ipa bundle" and pointed at an output directory holding no `.ipa`; it now names the `.xcarchive` and explains that Xcode exports an `.ipa` only for a signed app. A failed export is also caught properly: `flutter build ipa` exits 0 when Xcode's export step fails, and the existing check for it inspected captured output — which is empty whenever `-v` is used, so verbose builds reported the failure as success. The check now looks for the `.ipa` itself ([#6796](https://github.com/flet-dev/flet/pull/6796)) by @ndonkoHenri.
* Web builds and `flet publish` now read the `FLET_WEB_RENDERER`, `FLET_WEB_ROUTE_URL_STRATEGY`, and `FLET_WEB_NO_CDN` environment variables as fallbacks behind the CLI options and `[tool.flet.web]` pyproject keys, matching the `[env: ...]` notation the options already advertised, by @ndonkoHenri.
Expand Down Expand Up @@ -48,6 +50,8 @@

### Bug fixes

* Fix `UrlTarget.BLANK` not actually opening a new tab per link on the web. Its value was `"blank"` while `SELF`, `PARENT` and `TOP` all carry the leading underscore the HTML spec defines, so the value reached `window.open()` as an ordinary window *name* rather than the reserved `_blank` keyword: the first such link opened a tab called `blank` and every later one reused that same tab instead of opening its own. The `LaunchMode.externalApplication` upgrade that `openWebBrowser()` applies to `_blank` never fired either, so on non-web platforms `BLANK` did not force an external browser as intended. The enum value is now `"_blank"` by @FeodorFitsner.
* Fix `FilePicker.pick_files()` never opening a dialog in a web app on iOS, and `Clipboard.set()`, `Clipboard.set_image()` and the `Share` methods doing nothing there either. A browser opens a file picker, writes to the clipboard or shows a share sheet only while it is handling the user's click or key press, and a service method called from an event handler misses that window entirely: the click travels to Python, the handler runs, and the instruction travels back long after the permission has lapsed. WebKit enforces this and reports nothing, while Chrome and Firefox allow the same calls, so an app worked on Android and on the desktop and silently did nothing on an iPhone or iPad - which made a browser rule look like a Flet bug, and made `FilePicker` in particular look broken since `save_file()` kept working (it clicks a download link, which is not gated). These operations are now available as client actions - `ft.PickFiles`, `ft.CopyToClipboard`, `ft.ShareText`, `ft.OpenUrl` - assigned to a control's `action` property and performed by the client inside the original gesture. `pick_files()` also no longer hangs for its full one-hour timeout when a browser has already reported it will not open the dialog: it raises straight away, pointing at `ft.PickFiles` ([#3710](https://github.com/flet-dev/flet/issues/3710)) by @FeodorFitsner.
* Fix `flet build --description` (and `project.description` / `tool.poetry.description` from `pyproject.toml`) never reaching the built app. The value was passed to the build template under a key no file consumed (`description`) while every template reads `project_description`, so it always rendered as its empty default — a web app's `<meta name="description">` and PWA `manifest.json` description have been silently blank since the option was introduced. Both now receive it, as does the new Linux desktop entry's `Comment=`, and the value is escaped per format, so a description containing quotes, newlines or backslashes can no longer produce an unparsable `pubspec.yaml`/`manifest.json` or a desktop entry the desktop environment discards. The option is now documented under [Description](https://flet.dev/docs/publish#description) ([#2269](https://github.com/flet-dev/flet/issues/2269)) by @ndonkoHenri.
* Fix Linux apps packaged with `flet pack` appearing in the taskbar as "flet", grouped together with every other Flet app and unable to carry an icon. `flet pack` runs the shared prebuilt client binary, and the Linux desktop keys a window's identity on its X11 `WM_CLASS` or Wayland `app_id` — both of which GTK derives from the client's `argv[0]`, so every packed app inherited that binary's own name. The client is now launched under the app's own identity instead, taken from the new `FLET_APP_ID` environment variable that the PyInstaller runtime hook sets to `--bundle-id` when one is given and to the executable's name otherwise — so an executable named something a desktop entry should not be keyed on, such as a versioned `my-app-1.2.3`, can be given a stable identity; the binary is unchanged, so this needs no client rebuild and works with clients already cached. A Linux app's display name and icon come from an installed desktop entry rather than from the executable, so `flet pack` now writes one next to the binary — with `StartupWMClass` already matching the app's identity, which is the part that is impossible to guess — plus the icon itself when `--icon` is a `.png`, closing the other half of the report. Neither is installed for you, since that would change your application menu as a side effect of building; [Linux taskbar identity](https://flet.dev/docs/publish/using-pyinstaller#linux-taskbar-identity) shows the two `cp` commands ([#5422](https://github.com/flet-dev/flet/issues/5422), [#6800](https://github.com/flet-dev/flet/pull/6800)) by @ndonkoHenri.
* Fix Windows apps packaged with `flet pack` showing a second taskbar identity named "Flet description", whose right-click entry and pin launch a blank Flet client window instead of the app. Two defects stacked up: the PyInstaller runtime hook carrying the AppUserModelID fix from [#6403](https://github.com/flet-dev/flet/pull/6403) was never bundled into packed apps (its `rthooks.dat` manifest was missing from the `flet-cli` wheel, and PyInstaller skips a missing manifest silently), and a process-level AppUserModelID only fixes taskbar *grouping* anyway — the taskbar name, icon and pin target resolve through the shell's relaunch properties, which were never set, so they fell back to the cached `flet.exe`. The wheel now ships the manifest, and `flet_desktop` stamps `System.AppUserModel.ID`/`RelaunchCommand`/`RelaunchDisplayNameResource`/`RelaunchIconResource` on the client window right after launch (new `flet_desktop.win_taskbar` module, pure ctypes), driven by environment variables the runtime hook sets — and settable manually when packaging by other means, such as Nuitka. Apps started hidden (`AppView.FLET_APP_HIDDEN`) get their taskbar identity as well, and executable paths containing spaces or longer than 128 characters are supported ([#6767](https://github.com/flet-dev/flet/discussions/6767), [#6793](https://github.com/flet-dev/flet/pull/6793)) by @ndonkoHenri.
Expand Down
2 changes: 2 additions & 0 deletions packages/flet/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## 1.0.0

* Add `runClientActions()` and `runControlActions()` in `utils/client_actions.dart`, which perform a control's `url` and `action` properties on the client, synchronously, from inside the gesture callback that triggered them. `runClientActions()` resolves each action's target service through `FletBackend.controlsIndex` and calls `Control.invokeMethod()` on it without awaiting, because browsers grant gesture-gated APIs - opening a file picker, writing to the clipboard, `navigator.share`, `window.open` - only while user activation is live, and activation does not survive an async gap. The new `Control.hasInvokeMethodListeners` getter guards that path: `Control.invokeMethod()` waits for a listener when the target service is not mounted yet, and awaiting that wait would silently consume the gesture, so an unresolvable action is skipped and logged instead. A new `Control.hasControlActions` extension getter reports whether a control has anything to run, for controls that only install a tap handler when something is wired to them. `openWebBrowser()` is no longer called directly by `Button`, `Container`, `IconButton`, `ListTile`, `CupertinoListTile`, `CupertinoButton` and `FloatingActionButton`, which now dispatch through `runControlActions()`; `parseTextSpans()` and `parseInlineSpan()` take an optional trailing `BuildContext` so a `TextSpan` can carry actions too.
* Client actions add `"_from_gesture": true` to the arguments they invoke a service method with, so a service can tell the two entry points apart. `FilePickerService` uses it to emit a `result` event only for the gesture path - a gesture-triggered pick has no caller to return to - and to skip the new `isGestureGatedDialogBlocked()` check, added to `utils/platform_utils_web.dart` and its non-web counterpart. That check reports `true` only for an Apple/WebKit browser whose `navigator.userActivation` says no activation is live, so a `pick_files()` call that cannot possibly open a dialog fails immediately instead of waiting out its timeout; Chrome and Firefox permit programmatic file input clicks outside a gesture and are never reported as blocked.
* Replace the `FormFieldInputBorder` enum and its `parseFormFieldInputBorder()` / `Control.getFormFieldInputBorder()` helpers with `parseInputBorder()`, which builds an `InputBorder` from a serialized border object. `parseFormFieldBorders()` maps a control's `border` property — a single border or a map of control states — onto the `InputDecoration` border slots, including `errorBorder`, `focusedErrorBorder` and `disabledBorder`, and `parseFormFieldBoxBorder()` translates the same property for controls decorated with a `BoxDecoration` instead ([#6773](https://github.com/flet-dev/flet/pull/6773)) by @ndonkoHenri.
* `_onPatchControl` now reports a `PATCH_CONTROL` whose target id is missing from `controlsIndex` instead of silently discarding it. A dropped patch means the client and server have diverged and the screen is stale from that point on; previously nothing was logged anywhere, making the resulting "app stopped responding" impossible to diagnose. Uses `print` rather than `debugPrint`, which `main.dart` nulls in release builds - exactly where the message needs to be visible.
* `FletJS.canvasKitBaseUrl` is now `String?`. `flutter_bootstrap.js` applies `flet.canvasKitBaseUrl` and `flet.fontFallbackBaseUrl` whenever they are set rather than only when `flet.noCdn` is true, and both default to `null` in CDN mode — so a host serving its own copy of the runtime can point them anywhere without also claiming a no-CDN build. The getter has no readers in this package; the annotation now matches the value it can carry.
Expand Down
7 changes: 2 additions & 5 deletions packages/flet/lib/src/controls/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import '../extensions/control.dart';
import '../models/control.dart';
import '../utils/buttons.dart';
import '../utils/colors.dart';
import '../utils/launch_url.dart';
import '../utils/client_actions.dart';
import '../utils/misc.dart';
import '../utils/numbers.dart';
import '../widgets/error.dart';
Expand Down Expand Up @@ -63,7 +63,6 @@ class _ButtonControlState extends State<ButtonControl> with FletStoreMixin {
bool isTextButton = widget.control.type == "TextButton";
bool isOutlinedButton = widget.control.type == "OutlinedButton";

var url = widget.control.getUrl("url");
var iconColor = widget.control.getColor("icon_color", context);
var clipBehavior =
widget.control.getClipBehavior("clip_behavior", Clip.none)!;
Expand All @@ -74,9 +73,7 @@ class _ButtonControlState extends State<ButtonControl> with FletStoreMixin {

Function()? onPressed = !widget.control.disabled
? () {
if (url != null) {
openWebBrowser(url);
}
runControlActions(context, widget.control);
widget.control.triggerEvent("click");
}
: null;
Expand Down
Loading
Loading