diff --git a/.agents/skills/implement-flet-extension/SKILL.md b/.agents/skills/implement-flet-extension/SKILL.md index 01584b2c9d..457354e264 100644 --- a/.agents/skills/implement-flet-extension/SKILL.md +++ b/.agents/skills/implement-flet-extension/SKILL.md @@ -44,7 +44,7 @@ Implement a Flet extension around an external Flutter package using existing `fl ## Flutter-side Rules -- Use `parseEnum()` for enums. +- Use `parseEnum()` for enums — it's exported from `package:flet/flet.dart`. Call it as `parseEnum(MyEnum.values, widget.control.getString("attr"), MyEnum.defaultVal)!`. Do NOT write a custom `_parseXxx()` switch helper. - For control attributes, prefer `widget.control.getBool()/getDouble()/...` accessors. - For non-control parsing, use shared `parseSomething()` helpers. - Do not add one-off private parser utilities when standard helpers exist. @@ -65,22 +65,33 @@ Without matching defaults, Dart receives `null` and either crashes or silently u ## Integration Checklist -- Add dependency and registration to Flet client app. -- Add extension package to `sdk/python/pyproject.toml`. -- Add extension to `sdk/python/packages/flet/pyproject.toml`. -- Add extension to `sdk/python/examples/apps/flet_build_test/pyproject.toml`. +- Register in the Flet client app — two files: + 1. `client/pubspec.yaml`: add `flet_: path: ../sdk/python/packages/flet-/src/flutter/flet_` under `dependencies`. + 2. `client/lib/main.dart`: add `import 'package:flet_/flet_.dart' as flet_;` and `flet_.Extension()` to the `extensions` list. +- Add extension package to `sdk/python/pyproject.toml` in **two places**: the `dependencies` list and `[tool.uv.sources]` as `{ workspace = true }`. +- Add extension to `sdk/python/packages/flet/pyproject.toml` in the `[dependency-groups] extensions` list. +- Add extension to `sdk/python/examples/apps/flet_build_test/pyproject.toml` in **three places**: `[project] dependencies`, `[tool.uv.sources]` as `{ path = "...", editable = true }`, and `[tool.flet.dev_packages]` as a relative path string. +- Add extension to `tools/crocodocs/pyproject.toml` under `[tool.crocodocs.packages]` as ` = "../../sdk/python/packages//src"`. This fixes "Missing API entry" errors in the docs. Note: `api-data.json` is gitignored (generated at build time); only the `pyproject.toml` change needs committing. - Add extension to `.github/workflows/ci.yml` in both places: - `build_flet_extensions` -> `PACKAGES` list. - `py_publish` -> `for pkg in ...` publish loop. - Add `.gitignore` to Flutter extension project if missing. +- Remove `[tool.uv.sources]` local path overrides from example `pyproject.toml` files before opening a PR (they are development-only conveniences). ## Docs, Examples, Tests - Add control/service docs under `website/docs/controls/` for controls and `website/docs/services/` for services. +- Always create one doc page per control, even for extensions with many similar controls. Use `index.md` for the overview (install instructions, examples, list of links) and individual `.md` files for each control — consistent with `flet-color-pickers` and other extensions. +- Use `` and `` JSX from `@site/src/components/crocodocs` to render API docs. Do NOT add `image=`, `imageCaption=`, or `imageWidth=` props to `` when no screenshots exist yet. +- In the `## Examples` section, do NOT add `###` subtitles above `` blocks — titles are injected automatically from the example file itself. - Add all custom enums/types docs and update `website/sidebars.yml` navigation. - Use markdown filenames without underscores (`codeeditor.md`, not `code_editor.md`). -- Add examples under `sdk/python/examples` in the appropriate category for control vs service. -- Add integration tests under `packages/flet/integration_tests` in the appropriate category for control vs service. +- Add examples under `sdk/python/examples/extensions//` for extension controls. +- Use `import flet_ as ` in examples (e.g., `import flet_spinkit as spins`). Keep alias short but readable. +- Use `ft.Colors.SURFACE_CONTAINER_HIGHEST` for card/cell backgrounds in showcase examples — it adapts to light and dark system themes automatically. +- Do NOT set an explicit dark theme in examples; let the app use system theme (no `page.theme_mode`). +- Add integration tests under `packages/flet/integration_tests/extensions//` — **not** inside the extension package's own directory (no `tests/` folder in the package itself, matching the pattern of `flet-code-editor`, `flet-color-pickers`, etc.). +- For controls with continuously-running animations, do NOT use `assert_control_screenshot` or `pump_and_settle` — they will timeout waiting for animations to settle. Instead use `await flet_app.tester.pump(duration=ft.Duration(milliseconds=500))` which advances the clock by a fixed amount. This still runs real Flutter rendering and catches crashes, without screenshot comparison. - Ensure generated screenshots are suitable for docs usage when visual examples are added. ## Upgrade and Compatibility Guardrails diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f3fa41a97..485e503842 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -672,6 +672,7 @@ jobs: flet-permission-handler flet-rive flet-secure-storage + flet-spinkit flet-video flet-webview ) @@ -836,6 +837,7 @@ jobs: flet_permission_handler \ flet_rive \ flet_secure_storage \ + flet_spinkit \ flet_video \ flet_webview; do uv publish dist/**/${pkg}-* diff --git a/CHANGELOG.md b/CHANGELOG.md index 35380e6d0f..b5e0e0f885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### New features -* Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0), and Emscripten wheel platform tag are all resolved from `flet-dev/python-build`'s date-keyed manifest. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 3.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0), and Emscripten wheel platform tag are all resolved from `flet-dev/python-build`'s date-keyed manifest. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 4.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Add `ft.DataChannel`: dedicated byte channels for widgets that move bulk binary data (image frames, audio buffers, ML tensors) between Dart and Python, bypassing the MsgPack control protocol. The Dart side opens a channel via `FletBackend.of(context).openDataChannel()` and announces it to Python by firing a `data_channel_open` control event with `{channel_name, channel_id}`; the Python side declares `on_data_channel_open: Optional[ft.EventHandler[ft.DataChannelOpenEvent]]` and captures the channel via `self.get_data_channel(e.channel_id)`. Backed by a dedicated `PythonBridge` per channel in embedded native mode (4–7 GiB/s on M2 Pro) and by the default `ProtocolMuxedDataChannelFactory` in dev / web modes (raw-byte frames muxed over the active Flet protocol transport with a 1-byte type discriminator). Pyodide gets zero-copy outbound sends via `postMessage` Transferable ArrayBuffer. First consumer: `flet-charts` `MatplotlibChartCanvas`, migrated from `_invoke_method` PNG dispatch to a 1-byte-opcode data channel by @FeodorFitsner. * **In-process Python transport (`dart_bridge` FFI).** `package:flet` gains a third protocol transport alongside the UDS / TCP socket servers: it can run Flet's MsgPack protocol over an in-process `dart_bridge` byte channel via a `FletApp(channelBuilder: …)` seam (the `flet` package stays Python-independent — it doesn't depend on `serious_python` or know about `PythonBridge`; the embedder wires the channel). `serious_python` >= 3.0.0 uses this seam to embed the Python interpreter **in-process** instead of talking to it over a localhost socket, and the `flet build` template migrates from sockets to the FFI transport. On Android, where the OS may keep the process alive across a back-button quit and restart only the Dart VM, the transport rebinds to the new VM's `dart_bridge` ports on a session-restart signal (`libdart_bridge` >= 1.3.0) — the Python process and its in-memory state are preserved and the Flet session is rebuilt from `REGISTER_CLIENT` by @FeodorFitsner. * Add `flet clean` command that deletes the `build` directory of a Flet app — the Flutter bootstrap project, cached artifacts, and generated output — in a single step ([#6233](https://github.com/flet-dev/flet/issues/6233)) by @ndonkoHenri. @@ -10,6 +10,7 @@ ### Improvements +* **Swift Package Manager for iOS/macOS builds (on by default).** `flet build` / `flet debug` now integrate the embedded Python runtime via SPM instead of CocoaPods (CocoaPods goes read-only in December 2026). Flet **auto-falls back to CocoaPods** when the app depends on a package that isn't SPM-ready — currently `flet-video` (media_kit) — since Flutter then builds the whole app with CocoaPods. Force CocoaPods for other non-SPM packages with `--no-swift-package-manager` (or `swift_package_manager = false` under `[tool.flet]`). Flet does **not** change Flutter's global SPM configuration; the setting only selects how `serious_python` stages the runtime to match. When SPM is used (it has no `pod install` hook), `flet build` sets `SERIOUS_PYTHON_DARWIN_SPM` so `serious_python`'s `package` step stages the runtime (Python/dart_bridge xcframeworks, the iOS native extensions, and the stdlib/site-packages/app resources) into the plugin's `Package.swift` layout on the host before `flutter build`, and exports the `SP_NATIVE_SET` cache-bust key into the build. Requires the SPM-capable `serious_python` release. * **Smaller Android apps with no native-packaging config.** `flet build apk`/`aab` consume serious_python's new Android packaging: Python extension modules load **memory-mapped directly from the APK** (no extraction to disk), and pure Python ships in stored asset zips read via `zipimport`, so the standard library is no longer duplicated per ABI. Apps no longer need `useLegacyPackaging` / `keepDebugSymbols` — the `flet build` Android template drops them; just use `minSdk 23+`. New `--android-extract-packages` flag and `[tool.flet.android].extract_packages` ship "path-hungry" packages — those that read bundled data via `__file__` / `pkg_resources` instead of `importlib.resources` — extracted to disk instead of inside the zip (most packages, including `certifi`, are zip-safe and need no entry). Requires `serious_python` with the native-mmap packaging (dart_bridge 1.4.0). * Pyodide is no longer pre-baked into the `flet build` template. Each `flet build web` / `flet publish` run downloads the matching `pyodide-core-.tar.bz2` (plus the runtime `micropip` and `packaging` wheels) into a per-version cache at `~/.flet/pyodide//` and copies the files into the build output. Subsequent builds reuse the cache; the older `0.27.5` bundle previously shipped in the cookiecutter template is gone ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * The supported Python / Pyodide / dart_bridge versions are loaded on demand from `flet-dev/python-build`'s date-keyed `manifest.json` (fetched once and cached under `~/.flet/cache`), the single source of truth shared with `serious_python` — replacing flet's hand-mirrored version table. `flet build` forwards only `SERIOUS_PYTHON_VERSION` and lets `serious_python` derive the full version / build date / dart_bridge version from its own committed snapshot. The module exposes `get_supported_python_versions()` / `get_default_python_version()` (the previous `SUPPORTED_PYTHON_VERSIONS` / `DEFAULT_PYTHON_VERSION` constants are removed) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. @@ -22,6 +23,7 @@ ### Breaking changes +* **App files now ship unpacked in a read-only bundle, and the storage directories were reworked** (requires `serious_python` >= 4.0.0, now pinned in the `flet build` template). Your Python sources ship **unpacked inside the app bundle** next to the stdlib/site-packages (no first-launch `app.zip` extraction) on macOS/iOS/Windows/Linux; on Android they ship as a *stored* `app.zip` asset unpacked once on first launch; web is unchanged. The app directory is now **read-only**, so the Python program's **working directory** moved to a writable, app-private data dir. `FLET_APP_STORAGE_DATA` now maps to the OS *application support* dir (a `data` subdir) instead of the user's Documents folder and is the cwd; `FLET_APP_STORAGE_TEMP` now points to the OS temp dir (was the cache dir) and a new `FLET_APP_STORAGE_CACHE` exposes the cache dir. `flet run` sets the dev cwd to a hidden, git-ignored `/.flet/storage/data`. Relative **reads** of bundled files (`open("seed.json")`) must move to `__file__`/`importlib.resources` or `assets/`. See the [app files unpacked / storage dirs](/docs/updates/breaking-changes/v0-86-0-app-files-unpacked-read-only-bundle) guide by @FeodorFitsner. * `flet build` and `flet publish` now bundle CPython 3.14 by default (previously 3.12, implicit via the old single-version `serious_python`). Existing apps that depend on native wheels without 3.14 binaries should pin explicitly with `--python-version 3.12` (CLI), `requires-python = ">=3.12,<3.13"` (pyproject), or `SERIOUS_PYTHON_VERSION=3.12` in the build environment ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Android builds now include only the ABIs the bundled Python supports: for Python 3.13+, `armeabi-v7a` is no longer supported nor packaged by default. An explicit `--arch armeabi-v7a` fails with a clear error unless combined with `--python-version 3.12`, which is the only Python version supporting it. The per-version ABI set is sourced from python-build's manifest (`pythons..android_abis`), not hardcoded in flet ([#6578](https://github.com/flet-dev/flet/pull/6578)) by @ndonkoHenri. * `flet build` / `flet publish` now **compile your app and packages to `.pyc` by default** (previously off). This removes per-launch bytecode recompilation — a significant cold-start win, especially on mobile where pure Python is imported from a stored zip (`zipimport`) and can't cache bytecode back to disk, so every module would otherwise recompile from source on each launch. The CLI flags gain `--no-compile-app` / `--no-compile-packages` (via `argparse.BooleanOptionalAction`; the existing `--compile-app` / `--compile-packages` still work), and `[tool.flet.compile].app` / `.packages` now default to `true`. Pass `--no-compile-*` or set them to `false` to restore the old behavior (faster iterative builds, or keeping `.py` source in the bundle). Compiled web builds were verified to load in Pyodide (bundled CPython and Pyodide share the same minor version). See the [compile-on-by-default](/docs/updates/breaking-changes/v0-86-0-compile-on-by-default) guide ([#6598](https://github.com/flet-dev/flet/pull/6598)) by @FeodorFitsner. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd2ad45cd3..5d931423c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,13 +70,12 @@ uv sync Create `hello.py` file with a minimal Flet program: ```python -import flet -from flet import Page, Text +import flet as ft -def main(page: Page): - page.add(Text("Hello, world!")) +def main(page: ft.Page): + page.add(ft.Text("Hello, world!")) -flet.app(target=main) +ft.run(main) ``` and then run it: @@ -169,12 +168,12 @@ First, run `printenv | grep FLET` (or `gci env:* | findstr FLET` on Windows) in - To build the Flutter client for MacOS, run: ``` - flutter build macos + fvm flutter build macos ``` When the build is complete, you should see the Flet bundle in the `FLET_VIEW_PATH`. (Running it will open a blank window.) - To build the Flutter client for Web, run the below command: ``` - flutter build web + fvm flutter build web --wasm ``` When the build is complete, a directory `client/build/web` will be created. @@ -263,79 +262,82 @@ For patches to the current stable release, branch directly from `main`, fix, ope ## New macOS environment for Flet developer -* **Homebrew**: https://brew.sh/ +This section outlines how to prepare a fresh macOS environment for Flet development. -After installing homebrew, install xz libraries with it: -``` -brew install xz -``` +### Prerequisites -* **Pyenv**. Install with `brew`: https://github.com/pyenv/pyenv?tab=readme-ov-file#unixmacos - * Install and switch to the latest Python 3.12: -``` -pyenv install 3.12.6 -pyenv global 3.12.6 -``` +* **uv**: https://docs.astral.sh/uv/getting-started/installation/ +* **git**: https://git-scm.com/downloads +* **Flutter**: https://docs.flutter.dev/get-started/install/macos +* **Xcode**: Install from the Mac App Store, then open it, agree to license, and install command line tools with `xcode-select --install`. +* **Android Studio** (optional for Android development): https://developer.android.com/studio +* **FVM** - Flutter Version Manager: https://fvm.app/documentation/getting-started/installation +* **CocoaPods**: `sudo gem install cocoapods` +* **Visual Studio Code**: https://code.visualstudio.com/ -Setup your shell environment: https://github.com/pyenv/pyenv#set-up-your-shell-environment-for-pyenv +### Clone the repository -``` -echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zprofile -echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zprofile -echo 'eval "$(pyenv init -)"' >> ~/.zprofile +```bash +git clone https://github.com/flet-dev/flet.git +cd flet ``` -Ensure Python version is 3.12.6 and location is `/Users/{user}/.pyenv/shims/python`: +### Install Flutter -``` -python --version -which python -``` +Follow the [official guide](https://docs.flutter.dev/get-started/install/macos) to install Flutter. Ensure Flutter is in your PATH and run `fvm flutter doctor` to verify. -* **Rbenv**. Install with `brew`: https://github.com/rbenv/rbenv?tab=readme-ov-file#homebrew - * Install and switch to the latest Ruby: -``` -rbenv install 3.3.5 -rbenv global 3.3.5 +### Install uv + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh ``` -Ensure Ruby version is 3.3.5 and location is `/Users/{user}/.rbenv/shims/ruby`: +### Set up Python SDK -``` -ruby --version -which ruby +```bash +cd sdk/python +uv sync ``` -* **VS Code**. Install "Apple silicon" release: https://code.visualstudio.com/download +### Set up Flutter client -* **GitHub Desktop**: https://desktop.github.com/download/ -Open GitHub Desktop app, install Rosetta. +```bash +cd client +fvm flutter pub get +``` -* **uv**: https://docs.astral.sh/uv/getting-started/installation/ +### Set environment variables -After installing uv, set PATH: -``` -echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.zprofile +Add the following to your shell profile (e.g., `~/.zshrc`): + +```bash +export FLET_VIEW_PATH="$HOME/flet/client/build/macos/Build/Products/Release" +export FLET_WEB_PATH="$HOME/flet/client/build/web" ``` -Check `uv` version and make sure it's in PATH: +Then reload: `source ~/.zshrc` -``` -uv --version -``` +### Build Flutter client -* **Android Studio** for Android SDK required by Flutter: https://developer.android.com/studio -* **XCode** for macOS and iOS SDKs: https://apps.apple.com/ca/app/xcode/id497799835?mt=12 -* **FVM** - Flutter Version Manager: https://fvm.app/documentation/getting-started/installation -Install flutter with fvm: -``` -fvm install 3.24.3 -fvm global 3.24.3 +```bash +cd client +fvm flutter build macos +fvm flutter build web --wasm ``` -Set PATH: -``` -echo 'export PATH=$HOME/fvm/default/bin:$PATH' >> ~/.zprofile +### Verify installation + +Create a `hello.py` file with the updated example and run it with `uv run python hello.py`. You should see a window with "Hello, world!". + +### Running tests + +```bash +cd sdk/python +uv run pytest ``` -* **cocoapods**: https://guides.cocoapods.org/using/getting-started.html#installation +### Additional notes + +* For Android development, set up an Android emulator or connect a physical device. +* For iOS development, you need Xcode and a Mac with Apple Silicon or an Intel Mac. +* Refer to the official Flutter documentation for any platform-specific setup. diff --git a/client/lib/main.dart b/client/lib/main.dart index 131338e54a..1e5e1b6abb 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -24,6 +24,7 @@ import 'package:flet_rive/flet_rive.dart' as flet_rive; // --FAT_CLIENT_END-- import 'package:flet_secure_storage/flet_secure_storage.dart' as flet_secure_storage; +import 'package:flet_spinkit/flet_spinkit.dart' as flet_spinkit; // --FAT_CLIENT_START-- import 'package:flet_video/flet_video.dart' as flet_video; // --FAT_CLIENT_END-- @@ -59,6 +60,7 @@ void main([List? args]) async { flet_map.Extension(), flet_permission_handler.Extension(), flet_secure_storage.Extension(), + flet_spinkit.Extension(), flet_webview.Extension(), // --FAT_CLIENT_START-- diff --git a/client/linux/flutter/generated_plugin_registrant.cc b/client/linux/flutter/generated_plugin_registrant.cc index 486c5f4f54..49c3757b48 100644 --- a/client/linux/flutter/generated_plugin_registrant.cc +++ b/client/linux/flutter/generated_plugin_registrant.cc @@ -16,7 +16,6 @@ #include #include #include -#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = @@ -49,7 +48,4 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); - g_autoptr(FlPluginRegistrar) window_to_front_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "WindowToFrontPlugin"); - window_to_front_plugin_register_with_registrar(window_to_front_registrar); } diff --git a/client/linux/flutter/generated_plugins.cmake b/client/linux/flutter/generated_plugins.cmake index 692e9fcd42..b30e4c610a 100644 --- a/client/linux/flutter/generated_plugins.cmake +++ b/client/linux/flutter/generated_plugins.cmake @@ -13,7 +13,6 @@ list(APPEND FLUTTER_PLUGIN_LIST screen_retriever_linux url_launcher_linux window_manager - window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index 62e3cfcb94..634e2b35b4 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -26,7 +26,6 @@ import url_launcher_macos import wakelock_plus import webview_flutter_wkwebview import window_manager -import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) @@ -50,5 +49,4 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) - WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) } diff --git a/client/macos/Runner.xcodeproj/project.pbxproj b/client/macos/Runner.xcodeproj/project.pbxproj index 7f045dcd00..2cccb3d5db 100644 --- a/client/macos/Runner.xcodeproj/project.pbxproj +++ b/client/macos/Runner.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 4C5C567116C0F37A5F3A163A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB9EEDA013737D623D55462A /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -74,6 +75,7 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; BB9EEDA013737D623D55462A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EDAD244E5F1A9F15957004F8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -81,6 +83,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 4C5C567116C0F37A5F3A163A /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -132,6 +135,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -175,6 +179,9 @@ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( @@ -200,6 +207,9 @@ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; @@ -647,6 +657,18 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 16d4c22e3f..d1579dfd00 100644 --- a/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + =3.11.0 <4.0.0" - flutter: ">=3.41.0" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 79ebf5030c..6de1369774 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -82,6 +82,9 @@ dependencies: flet_secure_storage: path: ../sdk/python/packages/flet-secure-storage/src/flutter/flet_secure_storage + flet_spinkit: + path: ../sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit + flet_webview: path: ../sdk/python/packages/flet-webview/src/flutter/flet_webview @@ -94,6 +97,7 @@ dependencies: dependency_overrides: flet: path: ../packages/flet + screen_retriever: 0.2.1 # this one migrated to SPM, but 0.2.0 is used by some other packages dev_dependencies: flutter_test: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index c4d28c3ed8..5042c5c7cf 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -22,7 +22,6 @@ #include #include #include -#include void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( @@ -57,6 +56,4 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); - WindowToFrontPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("WindowToFrontPlugin")); } diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index c04e69f80e..1aab211945 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -19,7 +19,6 @@ list(APPEND FLUTTER_PLUGIN_LIST share_plus url_launcher_windows window_manager - window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/packages/flet/lib/src/utils/desktop.dart b/packages/flet/lib/src/utils/desktop.dart index 34d44b696b..fba99767c2 100644 --- a/packages/flet/lib/src/utils/desktop.dart +++ b/packages/flet/lib/src/utils/desktop.dart @@ -1,7 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:window_manager/window_manager.dart'; -import 'package:window_to_front/window_to_front.dart'; import '../models/window_state.dart'; import 'platform.dart'; @@ -243,7 +242,9 @@ Future focusWindow() async { Future windowToFront() async { if (isDesktopPlatform()) { - await WindowToFront.activate(); + // Bring the window to the front, activating the app over other apps + // (window_manager.show() calls NSApp.activate(ignoringOtherApps: true)). + await windowManager.show(); } } diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 257f6f9c97..41ca2cded8 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: package_info_plus: ^9.0.0 path: ^1.9.0 path_provider: ^2.1.5 - pasteboard: ^0.4.0 + pasteboard: ^0.5.0 provider: ^6.1.2 screenshot: ^3.0.0 screen_brightness: ^2.1.11 @@ -52,7 +52,6 @@ dependencies: web: ^1.1.1 web_socket_channel: ^3.0.2 window_manager: ^0.5.1 - window_to_front: ^0.0.3 wakelock_plus: ^1.4.0 dev_dependencies: diff --git a/sdk/python/.pre-commit-config.yaml b/sdk/python/.pre-commit-config.yaml index 9a0bd650d0..cb79e5c469 100644 --- a/sdk/python/.pre-commit-config.yaml +++ b/sdk/python/.pre-commit-config.yaml @@ -28,4 +28,6 @@ repos: hooks: - id: typos args: [ --config, sdk/python/_typos.toml ] - exclude: /templates/ + # Generated *.pbxproj files contain random hex UUIDs that typos + # misreads as misspelled words. + exclude: (/templates/|\.pbxproj$) diff --git a/sdk/python/_typos.toml b/sdk/python/_typos.toml index b503150fa2..8092cfbc0d 100644 --- a/sdk/python/_typos.toml +++ b/sdk/python/_typos.toml @@ -1,3 +1,8 @@ +[files] +# Generated Xcode project files contain random hex UUIDs that typos +# misreads as misspelled words. +extend-exclude = ["*.pbxproj"] + [default.extend-words] # AAC High Efficiency audio codec enum member name AACHE = "AACHE" diff --git a/sdk/python/examples/apps/flet_build_test/pyproject.toml b/sdk/python/examples/apps/flet_build_test/pyproject.toml index aeba0ac742..ffdda16f17 100644 --- a/sdk/python/examples/apps/flet_build_test/pyproject.toml +++ b/sdk/python/examples/apps/flet_build_test/pyproject.toml @@ -14,6 +14,8 @@ dependencies = [ "flet-audio-recorder", "flet-camera", "flet-charts", + "flet-code-editor", + "flet-color-pickers", "flet-datatable2", "flet-flashlight", "flet-geolocator", @@ -22,6 +24,7 @@ dependencies = [ "flet-permission-handler", "flet-rive", "flet-secure-storage", + "flet-spinkit", "flet-video", "flet-webview", ] @@ -43,6 +46,8 @@ flet-audio = { path = "../../../packages/flet-audio", editable = true } flet-audio-recorder = { path = "../../../packages/flet-audio-recorder", editable = true } flet-camera = { path = "../../../packages/flet-camera", editable = true } flet-charts = { path = "../../../packages/flet-charts", editable = true } +flet-code-editor = { path = "../../../packages/flet-code-editor", editable = true } +flet-color-pickers = { path = "../../../packages/flet-color-pickers", editable = true } flet-datatable2 = { path = "../../../packages/flet-datatable2", editable = true } flet-flashlight = { path = "../../../packages/flet-flashlight", editable = true } flet-geolocator = { path = "../../../packages/flet-geolocator", editable = true } @@ -51,6 +56,7 @@ flet-map = { path = "../../../packages/flet-map", editable = true } flet-permission-handler = { path = "../../../packages/flet-permission-handler", editable = true } flet-rive = { path = "../../../packages/flet-rive", editable = true } flet-secure-storage = { path = "../../../packages/flet-secure-storage", editable = true } +flet-spinkit = { path = "../../../packages/flet-spinkit", editable = true } flet-video = { path = "../../../packages/flet-video", editable = true } flet-webview = { path = "../../../packages/flet-webview", editable = true } @@ -61,6 +67,8 @@ flet-audio = "../../../packages/flet-audio" flet-audio-recorder = "../../../packages/flet-audio-recorder" flet-camera = "../../../packages/flet-camera" flet-charts = "../../../packages/flet-charts" +flet-code-editor = "../../../packages/flet-code-editor" +flet-color-pickers = "../../../packages/flet-color-pickers" flet-datatable2 = "../../../packages/flet-datatable2" flet-flashlight = "../../../packages/flet-flashlight" flet-geolocator = "../../../packages/flet-geolocator" @@ -69,6 +77,7 @@ flet-map = "../../../packages/flet-map" flet-permission-handler = "../../../packages/flet-permission-handler" flet-rive = "../../../packages/flet-rive" flet-secure-storage = "../../../packages/flet-secure-storage" +flet-spinkit = "../../../packages/flet-spinkit" flet-video = "../../../packages/flet-video" flet-webview = "../../../packages/flet-webview" @@ -94,8 +103,8 @@ path = "src" [tool.flet.android.meta_data] "com.google.android.gms.ads.APPLICATION_ID" = "ca-app-pub-3940256099942544~3347511713" -[tool.flet.android.provider] -"rikka.shizuku.ShizukuProvider" = { authorities = "${applicationId}.shizuku", multiprocess = "false", enabled = "true", exported = "true", permission = "android.permission.INTERACT_ACROSS_USERS_FULL" } +# [tool.flet.android.provider] +# "rikka.shizuku.ShizukuProvider" = { authorities = "${applicationId}.shizuku", multiprocess = "false", enabled = "true", exported = "true", permission = "android.permission.INTERACT_ACROSS_USERS_FULL" } [tool.flet.ios.info] GADApplicationIdentifier = "ca-app-pub-3940256099942544~3347511713" diff --git a/sdk/python/examples/apps/flet_build_test/src/main.py b/sdk/python/examples/apps/flet_build_test/src/main.py index 5eb53b1e05..63c35edd28 100644 --- a/sdk/python/examples/apps/flet_build_test/src/main.py +++ b/sdk/python/examples/apps/flet_build_test/src/main.py @@ -1,5 +1,7 @@ import sys +import flet_code_editor # noqa: F401 +import flet_spinkit # noqa: F401 import numpy import PIL from modules.utils import greet @@ -10,6 +12,7 @@ import flet_audio # noqa: F401 import flet_audio_recorder # noqa: F401 import flet_charts # noqa: F401 +import flet_color_pickers # noqa: F401 import flet_datatable2 # noqa: F401 import flet_flashlight # noqa: F401 import flet_geolocator # noqa: F401 diff --git a/sdk/python/examples/extensions/spinkit/spinkit_props/main.py b/sdk/python/examples/extensions/spinkit/spinkit_props/main.py new file mode 100644 index 0000000000..4fbb386c7a --- /dev/null +++ b/sdk/python/examples/extensions/spinkit/spinkit_props/main.py @@ -0,0 +1,250 @@ +import flet_spinkit as spins + +import flet as ft + + +def section(title: str, controls: list) -> ft.Column: + return ft.Column( + [ + ft.Text(title, size=14, weight=ft.FontWeight.BOLD), + ft.Row(controls, spacing=24, wrap=True), + ], + spacing=12, + ) + + +def labeled(spinner, label: str) -> ft.Column: + return ft.Column( + [ + ft.Container( + content=spinner, + alignment=ft.Alignment.CENTER, + width=80, + height=80, + ), + ft.Text( + label, + size=10, + text_align=ft.TextAlign.CENTER, + ), + ], + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + spacing=4, + ) + + +def main(page: ft.Page): + page.title = "SpinKit Properties" + page.scroll = ft.ScrollMode.AUTO + page.padding = 24 + + page.add( + ft.Text("SpinKit Properties", size=24, weight=ft.FontWeight.BOLD), + ft.Divider(height=16, color=ft.Colors.TRANSPARENT), + # Colors + section( + "color", + [ + labeled(spins.RotatingCircle(color=ft.Colors.BLUE, size=50), "BLUE"), + labeled(spins.RotatingCircle(color=ft.Colors.RED, size=50), "RED"), + labeled(spins.RotatingCircle(color=ft.Colors.GREEN, size=50), "GREEN"), + labeled( + spins.RotatingCircle(color=ft.Colors.ORANGE, size=50), "ORANGE" + ), + labeled( + spins.RotatingCircle(color=ft.Colors.PURPLE, size=50), "PURPLE" + ), + labeled(spins.RotatingCircle(color=ft.Colors.TEAL, size=50), "TEAL"), + ], + ), + ft.Divider(height=8), + # Sizes + section( + "size", + [ + labeled(spins.ThreeBounce(color=ft.Colors.BLUE, size=20), "20"), + labeled(spins.ThreeBounce(color=ft.Colors.BLUE, size=30), "30"), + labeled(spins.ThreeBounce(color=ft.Colors.BLUE, size=40), "40"), + labeled(spins.ThreeBounce(color=ft.Colors.BLUE, size=50), "50"), + labeled(spins.ThreeBounce(color=ft.Colors.BLUE, size=70), "70"), + ], + ), + ft.Divider(height=8), + # Durations + section( + "duration", + [ + labeled( + spins.Pulse( + color=ft.Colors.BLUE, + size=50, + duration=ft.Duration(milliseconds=400), + ), + "400ms", + ), + labeled( + spins.Pulse( + color=ft.Colors.BLUE, + size=50, + duration=ft.Duration(milliseconds=700), + ), + "700ms", + ), + labeled( + spins.Pulse( + color=ft.Colors.BLUE, + size=50, + duration=ft.Duration(milliseconds=1000), + ), + "1000ms", + ), + labeled( + spins.Pulse( + color=ft.Colors.BLUE, + size=50, + duration=ft.Duration(milliseconds=2000), + ), + "2000ms", + ), + labeled( + spins.Pulse( + color=ft.Colors.BLUE, + size=50, + duration=ft.Duration(milliseconds=4000), + ), + "4000ms", + ), + ], + ), + ft.Divider(height=8), + # Wave — wave_type + section( + "Wave · wave_type", + [ + labeled( + spins.Wave( + color=ft.Colors.BLUE, size=50, wave_type=spins.WaveType.START + ), + "START", + ), + labeled( + spins.Wave( + color=ft.Colors.BLUE, size=50, wave_type=spins.WaveType.CENTER + ), + "CENTER", + ), + labeled( + spins.Wave( + color=ft.Colors.BLUE, size=50, wave_type=spins.WaveType.END + ), + "END", + ), + ], + ), + ft.Divider(height=8), + # Wave — item_count + section( + "Wave · item_count", + [ + labeled(spins.Wave(color=ft.Colors.BLUE, size=50, item_count=3), "3"), + labeled(spins.Wave(color=ft.Colors.BLUE, size=50, item_count=5), "5"), + labeled(spins.Wave(color=ft.Colors.BLUE, size=50, item_count=7), "7"), + labeled(spins.Wave(color=ft.Colors.BLUE, size=50, item_count=10), "10"), + ], + ), + ft.Divider(height=8), + # Ring — line_width + section( + "Ring · line_width", + [ + labeled(spins.Ring(color=ft.Colors.BLUE, size=50, line_width=2), "2"), + labeled(spins.Ring(color=ft.Colors.BLUE, size=50, line_width=5), "5"), + labeled(spins.Ring(color=ft.Colors.BLUE, size=50, line_width=8), "8"), + labeled(spins.Ring(color=ft.Colors.BLUE, size=50, line_width=12), "12"), + ], + ), + ft.Divider(height=8), + # DualRing — line_width + section( + "DualRing · line_width", + [ + labeled( + spins.DualRing(color=ft.Colors.BLUE, size=50, line_width=2), "2" + ), + labeled( + spins.DualRing(color=ft.Colors.BLUE, size=50, line_width=5), "5" + ), + labeled( + spins.DualRing(color=ft.Colors.BLUE, size=50, line_width=8), "8" + ), + labeled( + spins.DualRing(color=ft.Colors.BLUE, size=50, line_width=12), "12" + ), + ], + ), + ft.Divider(height=8), + # Ripple — border_width + section( + "Ripple · border_width", + [ + labeled( + spins.Ripple(color=ft.Colors.BLUE, size=50, border_width=2), "2" + ), + labeled( + spins.Ripple(color=ft.Colors.BLUE, size=50, border_width=4), "4" + ), + labeled( + spins.Ripple(color=ft.Colors.BLUE, size=50, border_width=8), "8" + ), + labeled( + spins.Ripple(color=ft.Colors.BLUE, size=50, border_width=14), "14" + ), + ], + ), + ft.Divider(height=8), + # SpinningLines — line_width + section( + "SpinningLines · line_width", + [ + labeled( + spins.SpinningLines(color=ft.Colors.BLUE, size=50, line_width=1), + "1", + ), + labeled( + spins.SpinningLines(color=ft.Colors.BLUE, size=50, line_width=2), + "2", + ), + labeled( + spins.SpinningLines(color=ft.Colors.BLUE, size=50, line_width=4), + "4", + ), + labeled( + spins.SpinningLines(color=ft.Colors.BLUE, size=50, line_width=6), + "6", + ), + ], + ), + ft.Divider(height=8), + # PianoWave — item_count + section( + "PianoWave · item_count", + [ + labeled( + spins.PianoWave(color=ft.Colors.BLUE, size=50, item_count=3), "3" + ), + labeled( + spins.PianoWave(color=ft.Colors.BLUE, size=50, item_count=5), "5" + ), + labeled( + spins.PianoWave(color=ft.Colors.BLUE, size=50, item_count=8), "8" + ), + labeled( + spins.PianoWave(color=ft.Colors.BLUE, size=50, item_count=12), "12" + ), + ], + ), + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/extensions/spinkit/spinkit_props/pyproject.toml b/sdk/python/examples/extensions/spinkit/spinkit_props/pyproject.toml new file mode 100644 index 0000000000..3d7405e92b --- /dev/null +++ b/sdk/python/examples/extensions/spinkit/spinkit_props/pyproject.toml @@ -0,0 +1,29 @@ +[project] +name = "spinkit-props" +version = "1.0.0" +description = "Demonstrates SpinKit control properties: color, size, duration, line_width, border_width, wave_type, item_count." +requires-python = ">=3.10" +keywords = ["spinkit", "loading", "spinner", "animation", "properties"] +authors = [{ name = "Flet team", email = "hello@flet.dev" }] +dependencies = ["flet", "flet-spinkit"] + +[dependency-groups] +dev = ["flet-cli", "flet-desktop", "flet-web"] + +[tool.flet.gallery] +categories = ["Animation/SpinKit"] + +[tool.flet.metadata] +title = "SpinKit Properties" +controls = [ + "RotatingCircle", "ThreeBounce", "Pulse", "Wave", + "Ring", "DualRing", "Ripple", "SpinningLines", "PianoWave", +] +layout_pattern = "sections" +complexity = "beginner" +features = ["color", "size", "duration", "wave_type", "item_count", "line_width", "border_width"] + +[tool.flet] +org = "dev.flet" +company = "Flet" +copyright = "Copyright (C) 2023-2026 by Flet" diff --git a/sdk/python/examples/extensions/spinkit/spinkit_showcase/main.py b/sdk/python/examples/extensions/spinkit/spinkit_showcase/main.py new file mode 100644 index 0000000000..8ee70b1b1b --- /dev/null +++ b/sdk/python/examples/extensions/spinkit/spinkit_showcase/main.py @@ -0,0 +1,82 @@ +import flet_spinkit as spins + +import flet as ft + +SPINNERS = [ + ("RotatingPlain", spins.RotatingPlain), + ("DoubleBounce", spins.DoubleBounce), + ("Wave", spins.Wave), + ("WanderingCubes", spins.WanderingCubes), + ("FadingFour", spins.FadingFour), + ("FadingCube", spins.FadingCube), + ("Pulse", spins.Pulse), + ("ChasingDots", spins.ChasingDots), + ("ThreeBounce", spins.ThreeBounce), + ("Circle", spins.Circle), + ("CubeGrid", spins.CubeGrid), + ("FadingCircle", spins.FadingCircle), + ("RotatingCircle", spins.RotatingCircle), + ("FoldingCube", spins.FoldingCube), + ("PumpingHeart", spins.PumpingHeart), + ("HourGlass", spins.HourGlass), + ("PouringHourGlass", spins.PouringHourGlass), + ("PouringHourGlassRefined", spins.PouringHourGlassRefined), + ("FadingGrid", spins.FadingGrid), + ("Ring", spins.Ring), + ("Ripple", spins.Ripple), + ("DualRing", spins.DualRing), + ("SpinningCircle", spins.SpinningCircle), + ("SpinningLines", spins.SpinningLines), + ("SquareCircle", spins.SquareCircle), + ("ThreeInOut", spins.ThreeInOut), + ("DancingSquare", spins.DancingSquare), + ("PianoWave", spins.PianoWave), + ("PulsingGrid", spins.PulsingGrid), + ("WaveSpinner", spins.WaveSpinner), +] + + +def main(page: ft.Page): + page.title = "SpinKit Showcase" + page.scroll = ft.ScrollMode.AUTO + page.padding = 20 + + color = ft.Colors.BLUE + + page.add( + ft.GridView( + runs_count=4, + max_extent=160, + child_aspect_ratio=0.9, + spacing=12, + run_spacing=12, + controls=[ + ft.Container( + content=ft.Column( + [ + ft.Container( + content=cls(color=color, size=50), + alignment=ft.Alignment.CENTER, + expand=True, + ), + ft.Text( + name, + size=11, + text_align=ft.TextAlign.CENTER, + ), + ], + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + spacing=8, + ), + bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST, + border_radius=12, + padding=12, + ) + for name, cls in SPINNERS + ], + ) + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/extensions/spinkit/spinkit_showcase/pyproject.toml b/sdk/python/examples/extensions/spinkit/spinkit_showcase/pyproject.toml new file mode 100644 index 0000000000..caaf23184f --- /dev/null +++ b/sdk/python/examples/extensions/spinkit/spinkit_showcase/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "spinkit-showcase" +version = "1.0.0" +description = "Showcases all 30 SpinKit loading animations." +requires-python = ">=3.10" +keywords = ["spinkit", "loading", "spinner", "animation", "indicator"] +authors = [{ name = "Flet team", email = "hello@flet.dev" }] +dependencies = ["flet", "flet-spinkit"] + +[dependency-groups] +dev = ["flet-cli", "flet-desktop", "flet-web"] + +[tool.flet.gallery] +categories = ["Animation/SpinKit"] + +[tool.flet.metadata] +title = "SpinKit Showcase" +controls = [ + "SafeArea", "Column", "Row", "GridView", "Container", "Text", + "Slider", "SegmentedButton", "ButtonSegment", + "SpinKitRotatingCircle", "SpinKitWave", "SpinKitThreeBounce", + "SpinKitFadingCircle", "SpinKitPulse", "SpinKitRing", + "SpinKitRipple", "SpinKitDualRing", "SpinKitCubeGrid", + "SpinKitFoldingCube", "SpinKitHourGlass", "SpinKitPianoWave", +] +layout_pattern = "grid" +complexity = "intermediate" +features = ["spinner grid", "color selection", "size slider", "all 31 spinner types"] + +[tool.flet] +org = "dev.flet" +company = "Flet" +copyright = "Copyright (C) 2023-2026 by Flet" diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index 529dd2e804..2bc6d2ebd9 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -486,6 +486,18 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: help="Pre-compile site packages' `.py` files to `.pyc` (on by default; " "use --no-compile-packages to disable)", ) + parser.add_argument( + "--swift-package-manager", + dest="swift_package_manager", + action=argparse.BooleanOptionalAction, + default=None, + help="Integrate the embedded Python runtime via Swift Package Manager " + "(default) or CocoaPods for iOS/macOS builds. On by default, matching " + "Flutter 3.44+ which uses SPM by default (other non-SPM plugins still " + "build with CocoaPods alongside it). Use --no-swift-package-manager (or " + "`swift_package_manager = false` under [tool.flet]) only if you've " + "disabled Swift Package Manager in Flutter.", + ) parser.add_argument( "--cleanup-app", dest="cleanup_app", @@ -1507,10 +1519,15 @@ def register_flutter_extensions(self): assert self.template_data assert self.build_dir + # Replace the permanent flutter-packages copy with this build's set. The + # temp dir is populated by serious_python's package step and is ABSENT + # when the app has no Flutter extensions — so always clear the old copy + # first, otherwise an extension removed since the previous build (e.g. + # dropping flet-video) would linger here and stay in the built app. + if self.flutter_packages_dir.exists(): + shutil.rmtree(self.flutter_packages_dir, ignore_errors=True) if self.flutter_packages_temp_dir.exists(): # copy packages from temp to permanent location - if self.flutter_packages_dir.exists(): - shutil.rmtree(self.flutter_packages_dir, ignore_errors=True) shutil.move(self.flutter_packages_temp_dir, self.flutter_packages_dir) if self.flutter_packages_dir.exists(): @@ -1972,6 +1989,25 @@ def fallback_image(self, pubspec, yaml_path: str, images: list, images_dir: str) d[pp[-1]] = f"{images_dir}/{image}" return + def _darwin_spm_active(self) -> bool: + """Whether to stage serious_python for Swift Package Manager (vs CocoaPods). + + On by default, matching Flutter 3.44+ (SPM enabled by default). Because + `serious_python_darwin` ships a `Package.swift`, Flutter always builds it + as an SPM plugin when SPM is enabled — even in a hybrid app where other, + non-SPM plugins (e.g. `flet-video`/media_kit) build with CocoaPods at the + same time. So serious_python must stage for SPM to match; it is NOT tied + to whether the app also pulls in non-SPM plugins. Users force CocoaPods + with `--no-swift-package-manager` (or `swift_package_manager = false` under + `[tool.flet]`) only when they've disabled SPM in Flutter itself. Flet does + not change Flutter's global SPM configuration. + """ + if self.package_platform not in ("iOS", "Darwin"): + return False + return self.get_bool_setting( + self.options.swift_package_manager, "swift_package_manager", True + ) + def package_python_app(self): """ Package Python app and dependencies into Flutter-consumable app archive. @@ -2088,6 +2124,23 @@ def package_python_app(self): package_env["SERIOUS_PYTHON_SITE_PACKAGES"] = str( self.build_dir / "site-packages" ) + # app staging dir: serious_python's `package` places the processed + # app here (no app.zip on native); the platform native build copies + # it into the bundle (Android zips it as a stored asset). + package_env["SERIOUS_PYTHON_APP"] = str(self.build_dir / "python-app") + + # Swift Package Manager (darwin): tell serious_python's package command to + # do the host-side SPM staging (the podspec prepare_command doesn't run + # under SPM) and write the SP_NATIVE_SET cache-bust key to this file. + # serious_python defaults to SPM staging, so be explicit either way — set + # it false for the CocoaPods cases (e.g. an app using flet-video). + if self.package_platform in ("iOS", "Darwin"): + spm = self._darwin_spm_active() + package_env["SERIOUS_PYTHON_DARWIN_SPM"] = "true" if spm else "false" + if spm: + package_env["SERIOUS_PYTHON_SPM_KEY_FILE"] = str( + self.build_dir / ".serious_python_spm_key" + ) # flutter-packages variable if self.flutter_packages_temp_dir.exists(): @@ -2234,10 +2287,18 @@ def package_python_app(self): hash.commit() - # make sure app/app.zip exists - app_zip_path = self.flutter_dir.joinpath("app", "app.zip") - if not os.path.exists(app_zip_path): - self.cleanup(1, "Flet app package app/app.zip was not created.") + # verify the package output: web ships app/app.zip; native platforms + # stage the unpacked app to build/app for the native build to bundle. + if self.package_platform == "Emscripten": + app_zip_path = self.flutter_dir.joinpath("app", "app.zip") + if not os.path.exists(app_zip_path): + self.cleanup(1, "Flet app package app/app.zip was not created.") + else: + app_staging_dir = self.build_dir / "python-app" + if not app_staging_dir.exists(): + self.cleanup( + 1, f"Flet app package was not staged to {app_staging_dir}." + ) console.log(f"Packaged Python app {self.emojis['checkmark']}") @@ -2337,6 +2398,18 @@ def _run_flutter_command(self): build_env["SERIOUS_PYTHON_SITE_PACKAGES"] = str( self.build_dir / "site-packages" ) + # app staging dir: read by the platform native build (CMake / + # podspec / Android Gradle) at `flutter build` time to place the + # unpacked app into the bundle. + build_env["SERIOUS_PYTHON_APP"] = str(self.build_dir / "python-app") + + # Swift Package Manager (darwin): export the cache-bust key the package + # step computed so the plugin's Package.swift re-resolves when the staged + # native set changes (SwiftPM caches its graph on manifest text + env). + if self._darwin_spm_active(): + spm_key_file = self.build_dir / ".serious_python_spm_key" + if spm_key_file.exists(): + build_env["SP_NATIVE_SET"] = spm_key_file.read_text().strip() # Path-hungry packages to ship extracted to disk: consumed by the # serious_python_android Gradle split during `flutter build`. diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py index 2be23587a1..e0217aa2e2 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py @@ -220,8 +220,67 @@ def handle(self, options: argparse.Namespace) -> None: else [] ) - flet_app_data_dir = project_dir / "storage" / "data" - flet_app_temp_dir = project_dir / "storage" / "temp" + # Dev-mode app storage under a hidden, Flet-namespaced `.flet/` dir so + # it stays out of the way and is git-ignored. Mirrors a built app: the + # data dir becomes the process cwd; data/cache/temp map to the three + # FLET_APP_STORAGE_* env vars. + flet_storage_dir = project_dir / ".flet" / "storage" + flet_app_data_dir = flet_storage_dir / "data" + flet_app_cache_dir = flet_storage_dir / "cache" + flet_app_temp_dir = flet_storage_dir / "temp" + for d in (flet_app_data_dir, flet_app_cache_dir, flet_app_temp_dir): + d.mkdir(parents=True, exist_ok=True) + # Self-ignore the whole `.flet/` dir in any repo, independent of the + # project's root .gitignore. + flet_dir = project_dir / ".flet" + flet_gitignore = flet_dir / ".gitignore" + if not flet_gitignore.exists(): + flet_gitignore.write_text("*\n", encoding="utf-8") + # Drop a short note explaining what this dir is, next to .gitignore. + flet_readme = flet_dir / "README.md" + if not flet_readme.exists(): + flet_readme.write_text( + "# `.flet/`\n" + "\n" + "This directory is created by `flet run` to hold per-project " + "development state. It is local to your machine, safe to delete " + "(it will be recreated on the next run), and git-ignored via the " + "`.gitignore` next to this file.\n" + "\n" + "While `flet run` is active, your app's working directory is " + "`storage/data` and the three storage locations are exposed to " + "your Python code through the `FLET_APP_STORAGE_*` environment " + "variables — matching how a packaged app behaves on a device.\n" + "\n" + "## `storage/`\n" + "\n" + "- **`data/`** (`FLET_APP_STORAGE_DATA`) — durable application " + "data: databases, settings, user files. This is also the " + "process current working directory, so relative paths " + '(e.g. `sqlite3.connect("my.db")`) land here. Persists across ' + "runs; the equivalent of this dir is preserved across app " + "updates on a device.\n" + "- **`cache/`** (`FLET_APP_STORAGE_CACHE`) — regenerable cache " + "data. The OS may purge the equivalent dir on a device under " + "storage pressure, so only store things you can rebuild.\n" + "- **`temp/`** (`FLET_APP_STORAGE_TEMP`) — throwaway scratch " + "space. Python's `tempfile` is pointed here too. May be cleared " + "at any time.\n", + encoding="utf-8", + ) + # On Windows a leading dot doesn't hide a dir (that's a POSIX + # convention); set the FILE_ATTRIBUTE_HIDDEN attribute explicitly so + # `.flet/` stays out of Explorer / file pickers like it does elsewhere. + if is_windows(): + try: + import ctypes + + FILE_ATTRIBUTE_HIDDEN = 0x02 + ctypes.windll.kernel32.SetFileAttributesW( + str(flet_dir), FILE_ATTRIBUTE_HIDDEN + ) + except Exception: + pass my_event_handler = Handler( args=[sys.executable, "-u"] @@ -240,6 +299,7 @@ def handle(self, options: argparse.Namespace) -> None: assets_dir=assets_dir, ignore_dirs=ignore_dirs, flet_app_data_dir=str(flet_app_data_dir), + flet_app_cache_dir=str(flet_app_cache_dir), flet_app_temp_dir=str(flet_app_temp_dir), ) @@ -284,6 +344,7 @@ def __init__( assets_dir, ignore_dirs, flet_app_data_dir, + flet_app_cache_dir, flet_app_temp_dir, ) -> None: super().__init__() @@ -307,6 +368,7 @@ def __init__( self.page_url_prefix = f"PAGE_URL_{time.time()}" self.page_url = None self.flet_app_data_dir = flet_app_data_dir + self.flet_app_cache_dir = flet_app_cache_dir self.flet_app_temp_dir = flet_app_temp_dir self.terminate = threading.Event() self.start_process() @@ -339,13 +401,37 @@ def start_process(self): p_env["FLET_DISPLAY_URL_PREFIX"] = self.page_url_prefix p_env["FLET_APP_STORAGE_DATA"] = self.flet_app_data_dir + p_env["FLET_APP_STORAGE_CACHE"] = self.flet_app_cache_dir p_env["FLET_APP_STORAGE_TEMP"] = self.flet_app_temp_dir + # Point Python's temp machinery at the project-local temp dir so + # tempfile.gettempdir() agrees with FLET_APP_STORAGE_TEMP and dev scratch + # stays inside the git-ignored .flet/. + p_env["TMPDIR"] = self.flet_app_temp_dir + p_env["TEMP"] = self.flet_app_temp_dir + p_env["TMP"] = self.flet_app_temp_dir + + # We launch the app with cwd = the data dir (matches a built app, where + # the app bundle is read-only and writes go to app-support). Keep the + # app importable by putting the original working directory — where + # `flet run` was invoked and where `-m` modules resolve — on PYTHONPATH. + invocation_cwd = os.getcwd() + existing_pythonpath = p_env.get("PYTHONPATH", "") + p_env["PYTHONPATH"] = ( + invocation_cwd + os.pathsep + existing_pythonpath + if existing_pythonpath + else invocation_cwd + ) + p_env["PYTHONIOENCODING"] = "utf-8" p_env["PYTHONWARNINGS"] = "default::DeprecationWarning" self.p = subprocess.Popen( - self.args, env=p_env, stdout=subprocess.PIPE, encoding="utf-8" + self.args, + env=p_env, + cwd=self.flet_app_data_dir, + stdout=subprocess.PIPE, + encoding="utf-8", ) self.is_running = True diff --git a/sdk/python/packages/flet-flashlight/src/flutter/flet_flashlight/pubspec.yaml b/sdk/python/packages/flet-flashlight/src/flutter/flet_flashlight/pubspec.yaml index 1ca7a12bdf..83045edaf0 100644 --- a/sdk/python/packages/flet-flashlight/src/flutter/flet_flashlight/pubspec.yaml +++ b/sdk/python/packages/flet-flashlight/src/flutter/flet_flashlight/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: sdk: flutter collection: ^1.16.0 - torch_light: 1.1.0 + torch_light: 2.0.0 flet: path: ../../../../../../../packages/flet diff --git a/sdk/python/packages/flet-spinkit/CHANGELOG.md b/sdk/python/packages/flet-spinkit/CHANGELOG.md new file mode 100644 index 0000000000..621a0dbdaa --- /dev/null +++ b/sdk/python/packages/flet-spinkit/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.1.0 + +- Initial release. diff --git a/sdk/python/packages/flet-spinkit/LICENSE b/sdk/python/packages/flet-spinkit/LICENSE new file mode 100644 index 0000000000..94edbe67ce --- /dev/null +++ b/sdk/python/packages/flet-spinkit/LICENSE @@ -0,0 +1,200 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by their Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or exemplary damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sdk/python/packages/flet-spinkit/README.md b/sdk/python/packages/flet-spinkit/README.md new file mode 100644 index 0000000000..acbe47ca0a --- /dev/null +++ b/sdk/python/packages/flet-spinkit/README.md @@ -0,0 +1,42 @@ +# flet-spinkit + +[![pypi](https://img.shields.io/pypi/v/flet-spinkit.svg)](https://pypi.python.org/pypi/flet-spinkit) +[![downloads](https://static.pepy.tech/badge/flet-spinkit/month)](https://pepy.tech/project/flet-spinkit) +[![python](https://img.shields.io/badge/python-%3E%3D3.10-%2334D058)](https://pypi.org/project/flet-spinkit) +[![docstring coverage](https://flet.dev/docs/assets/badges/docs-coverage/flet-spinkit.svg)](https://flet.dev/docs/assets/badges/docs-coverage/flet-spinkit.svg) +[![license](https://img.shields.io/badge/License-Apache_2.0-green.svg)](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-spinkit/LICENSE) + +A [Flet](https://flet.dev) extension package with 30 animated loading spinner controls. + +It is based on the [flutter_spinkit](https://pub.dev/packages/flutter_spinkit) Flutter package. + +## Documentation + +Detailed documentation to this package can be found [here](https://flet.dev/docs/controls/spinkit/). + +## Platform Support + +| Platform | Windows | macOS | Linux | iOS | Android | Web | +|----------|---------|-------|-------|-----|---------|-----| +| Supported| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | + +## Usage + +### Installation + +To install the `flet-spinkit` package and add it to your project dependencies: + +- Using `uv`: + ```bash + uv add flet-spinkit + ``` + +- Using `pip`: + ```bash + pip install flet-spinkit + ``` + After this, you will have to manually add this package to your `requirements.txt` or `pyproject.toml`. + +### Examples + +For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/extensions/spinkit). diff --git a/sdk/python/packages/flet-spinkit/pyproject.toml b/sdk/python/packages/flet-spinkit/pyproject.toml new file mode 100644 index 0000000000..ecc20d0d27 --- /dev/null +++ b/sdk/python/packages/flet-spinkit/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "flet-spinkit" +version = "0.1.0" +description = "Loading spinner animations for Flet apps." +readme = "README.md" +authors = [{ name = "Flet contributors", email = "hello@flet.dev" }] +license = "Apache-2.0" +requires-python = ">=3.10" +dependencies = [ + "flet", +] + +[project.urls] +Homepage = "https://flet.dev" +Documentation = "https://flet.dev/docs/controls/spinkit" +Repository = "https://github.com/flet-dev/flet/tree/main/sdk/python/packages/flet-spinkit" +Issues = "https://github.com/flet-dev/flet/issues" + +[tool.setuptools.package-data] +"flutter.flet_spinkit" = [ + "pubspec.yaml", + "lib/**", +] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/sdk/python/packages/flet-spinkit/src/flet_spinkit/__init__.py b/sdk/python/packages/flet-spinkit/src/flet_spinkit/__init__.py new file mode 100644 index 0000000000..80969d6e34 --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flet_spinkit/__init__.py @@ -0,0 +1,67 @@ +from flet_spinkit.spinkit import ( + ChasingDots, + Circle, + CubeGrid, + DancingSquare, + DoubleBounce, + DualRing, + FadingCircle, + FadingCube, + FadingFour, + FadingGrid, + FoldingCube, + HourGlass, + PianoWave, + PouringHourGlass, + PouringHourGlassRefined, + Pulse, + PulsingGrid, + PumpingHeart, + Ring, + Ripple, + RotatingCircle, + RotatingPlain, + SpinningCircle, + SpinningLines, + SquareCircle, + ThreeBounce, + ThreeInOut, + WanderingCubes, + Wave, + WaveSpinner, + WaveType, +) + +__all__ = [ + "ChasingDots", + "Circle", + "CubeGrid", + "DancingSquare", + "DoubleBounce", + "DualRing", + "FadingCircle", + "FadingCube", + "FadingFour", + "FadingGrid", + "FoldingCube", + "HourGlass", + "PianoWave", + "PouringHourGlass", + "PouringHourGlassRefined", + "Pulse", + "PulsingGrid", + "PumpingHeart", + "Ring", + "Ripple", + "RotatingCircle", + "RotatingPlain", + "SpinningCircle", + "SpinningLines", + "SquareCircle", + "ThreeBounce", + "ThreeInOut", + "WanderingCubes", + "Wave", + "WaveSpinner", + "WaveType", +] diff --git a/sdk/python/packages/flet-spinkit/src/flet_spinkit/spinkit.py b/sdk/python/packages/flet-spinkit/src/flet_spinkit/spinkit.py new file mode 100644 index 0000000000..6da8056525 --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flet_spinkit/spinkit.py @@ -0,0 +1,495 @@ +from enum import Enum +from typing import Optional + +import flet as ft + +__all__ = [ + "ChasingDots", + "Circle", + "CubeGrid", + "DancingSquare", + "DoubleBounce", + "DualRing", + "FadingCircle", + "FadingCube", + "FadingFour", + "FadingGrid", + "FoldingCube", + "HourGlass", + "PianoWave", + "PouringHourGlass", + "PouringHourGlassRefined", + "Pulse", + "PulsingGrid", + "PumpingHeart", + "Ring", + "Ripple", + "RotatingCircle", + "RotatingPlain", + "SpinningCircle", + "SpinningLines", + "SquareCircle", + "ThreeBounce", + "ThreeInOut", + "WanderingCubes", + "Wave", + "WaveSpinner", + "WaveType", +] + + +class WaveType(Enum): + """Controls how the wave animation is aligned.""" + + START = "start" + """Wave animation starts from the first item.""" + + CENTER = "center" + """Wave animation radiates from the center.""" + + END = "end" + """Wave animation starts from the last item.""" + + +@ft.control("SpinKitRotatingPlain") +class RotatingPlain(ft.LayoutControl): + """A plain rotating square spinner.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitDoubleBounce") +class DoubleBounce(ft.LayoutControl): + """Two overlapping circles that bounce in and out.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitWave") +class Wave(ft.LayoutControl): + """A row of bars that animate in a wave pattern.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + wave_type: Optional[WaveType] = None + """Controls the wave propagation direction. + + Defaults to :attr:`WaveType.START`. + """ + + item_count: Optional[int] = None + """Number of bars in the wave. Defaults to 5.""" + + +@ft.control("SpinKitWanderingCubes") +class WanderingCubes(ft.LayoutControl): + """Two cubes that rotate and wander around each other.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitFadingFour") +class FadingFour(ft.LayoutControl): + """Four dots arranged in a square that fade in sequence.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitFadingCube") +class FadingCube(ft.LayoutControl): + """A cube made of four smaller cubes that fade and rotate.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitPulse") +class Pulse(ft.LayoutControl): + """A circle that pulses in and out.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitChasingDots") +class ChasingDots(ft.LayoutControl): + """Two dots that chase each other in a circular path.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitThreeBounce") +class ThreeBounce(ft.LayoutControl): + """Three dots that bounce sequentially.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitCircle") +class Circle(ft.LayoutControl): + """A ring of dots that fade sequentially around a circle.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitCubeGrid") +class CubeGrid(ft.LayoutControl): + """A 3x3 grid of cubes that scale and fade in a wave.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitFadingCircle") +class FadingCircle(ft.LayoutControl): + """A ring of dots that fade sequentially.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitRotatingCircle") +class RotatingCircle(ft.LayoutControl): + """A circle that rotates with a color gradient effect.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitFoldingCube") +class FoldingCube(ft.LayoutControl): + """Four cubes that fold and unfold in sequence.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitPumpingHeart") +class PumpingHeart(ft.LayoutControl): + """A heart shape that pumps in and out.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitHourGlass") +class HourGlass(ft.LayoutControl): + """An hourglass shape that rotates.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitPouringHourGlass") +class PouringHourGlass(ft.LayoutControl): + """An hourglass that pours dots from top to bottom.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitPouringHourGlassRefined") +class PouringHourGlassRefined(ft.LayoutControl): + """A refined version of the pouring hourglass with smoother animation.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitFadingGrid") +class FadingGrid(ft.LayoutControl): + """A 3x3 grid of dots that fade in a spiral sequence.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitRing") +class Ring(ft.LayoutControl): + """A ring that spins with a gap.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + line_width: Optional[ft.Number] = None + """The stroke width of the ring. Defaults to 7.""" + + +@ft.control("SpinKitRipple") +class Ripple(ft.LayoutControl): + """Two concentric circles that expand outward like a ripple.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + border_width: Optional[ft.Number] = None + """The stroke width of the ripple rings. Defaults to 6.""" + + +@ft.control("SpinKitDualRing") +class DualRing(ft.LayoutControl): + """Two concentric rings that spin in opposite directions.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + line_width: Optional[ft.Number] = None + """The stroke width of the rings. Defaults to 7.""" + + +@ft.control("SpinKitSpinningCircle") +class SpinningCircle(ft.LayoutControl): + """A circle that spins continuously.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitSpinningLines") +class SpinningLines(ft.LayoutControl): + """Multiple lines radiating from the center that spin.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + line_width: Optional[ft.Number] = None + """The stroke width of the lines. Defaults to 2.""" + + +@ft.control("SpinKitSquareCircle") +class SquareCircle(ft.LayoutControl): + """A square and a circle that morph between shapes.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitThreeInOut") +class ThreeInOut(ft.LayoutControl): + """Three dots that move in and out of view.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitDancingSquare") +class DancingSquare(ft.LayoutControl): + """A square that dances by rotating and scaling.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitPianoWave") +class PianoWave(ft.LayoutControl): + """A row of bars that animate like piano keys.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + item_count: Optional[int] = None + """Number of bars. Defaults to 5.""" + + +@ft.control("SpinKitPulsingGrid") +class PulsingGrid(ft.LayoutControl): + """A 3x3 grid of dots that pulse in sequence.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" + + +@ft.control("SpinKitWaveSpinner") +class WaveSpinner(ft.LayoutControl): + """A circular wave spinner.""" + + color: Optional[ft.ColorValue] = None + """The color of the spinner.""" + + size: Optional[ft.Number] = None + """The size of the spinner in pixels. Defaults to 50.""" + + duration: Optional[ft.DurationValue] = None + """The duration of one animation cycle.""" diff --git a/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/.gitignore b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/.gitignore new file mode 100644 index 0000000000..ed7794f2ab --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/.gitignore @@ -0,0 +1,34 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ +.flutter-plugins +.flutter-plugins-dependencies + +# override parent rules +!lib/ diff --git a/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/analysis_options.yaml b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/analysis_options.yaml new file mode 100644 index 0000000000..a5744c1cfb --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/flet_spinkit.dart b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/flet_spinkit.dart new file mode 100644 index 0000000000..a5cb6448f4 --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/flet_spinkit.dart @@ -0,0 +1,3 @@ +library flet_spinkit; + +export "src/extension.dart" show Extension; diff --git a/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/src/extension.dart b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/src/extension.dart new file mode 100644 index 0000000000..50f1e02500 --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/src/extension.dart @@ -0,0 +1,45 @@ +import 'package:flet/flet.dart'; +import 'package:flutter/widgets.dart'; + +import 'spinkit.dart'; + +class Extension extends FletExtension { + @override + Widget? createWidget(Key? key, Control control) { + switch (control.type) { + case "SpinKitRotatingPlain": + case "SpinKitDoubleBounce": + case "SpinKitWave": + case "SpinKitWanderingCubes": + case "SpinKitFadingFour": + case "SpinKitFadingCube": + case "SpinKitPulse": + case "SpinKitChasingDots": + case "SpinKitThreeBounce": + case "SpinKitCircle": + case "SpinKitCubeGrid": + case "SpinKitFadingCircle": + case "SpinKitRotatingCircle": + case "SpinKitFoldingCube": + case "SpinKitPumpingHeart": + case "SpinKitHourGlass": + case "SpinKitPouringHourGlass": + case "SpinKitPouringHourGlassRefined": + case "SpinKitFadingGrid": + case "SpinKitRing": + case "SpinKitRipple": + case "SpinKitDualRing": + case "SpinKitSpinningCircle": + case "SpinKitSpinningLines": + case "SpinKitSquareCircle": + case "SpinKitThreeInOut": + case "SpinKitDancingSquare": + case "SpinKitPianoWave": + case "SpinKitPulsingGrid": + case "SpinKitWaveSpinner": + return SpinKitControl(key: key, control: control); + default: + return null; + } + } +} diff --git a/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/src/spinkit.dart b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/src/spinkit.dart new file mode 100644 index 0000000000..e1d93a8753 --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/lib/src/spinkit.dart @@ -0,0 +1,260 @@ +import 'package:flet/flet.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +class SpinKitControl extends StatefulWidget { + final Control control; + + const SpinKitControl({super.key, required this.control}); + + @override + State createState() => _SpinKitControlState(); +} + +class _SpinKitControlState extends State { + @override + Widget build(BuildContext context) { + debugPrint("SpinKitControl build: ${widget.control.id} (${widget.control.type})"); + + final color = + widget.control.getColor("color", context) ?? Theme.of(context).primaryColor; + final size = widget.control.getDouble("size", 50.0)!; + final duration = widget.control.getDuration("duration"); + + final lineWidth = widget.control.getDouble("line_width"); + final borderWidth = widget.control.getDouble("border_width"); + final itemCount = widget.control.getInt("item_count"); + final waveType = parseEnum(SpinKitWaveType.values, + widget.control.getString("wave_type"), SpinKitWaveType.start)!; + + Widget spinner; + + switch (widget.control.type) { + case "SpinKitRotatingPlain": + spinner = SpinKitRotatingPlain( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitDoubleBounce": + spinner = SpinKitDoubleBounce( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 2000), + ); + break; + case "SpinKitWave": + spinner = SpinKitWave( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + itemCount: itemCount ?? 5, + type: waveType, + ); + break; + case "SpinKitWanderingCubes": + spinner = SpinKitWanderingCubes( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1800), + ); + break; + case "SpinKitFadingFour": + spinner = SpinKitFadingFour( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitFadingCube": + spinner = SpinKitFadingCube( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitPulse": + spinner = SpinKitPulse( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1000), + ); + break; + case "SpinKitChasingDots": + spinner = SpinKitChasingDots( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 2000), + ); + break; + case "SpinKitThreeBounce": + spinner = SpinKitThreeBounce( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1400), + ); + break; + case "SpinKitCircle": + spinner = SpinKitCircle( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitCubeGrid": + spinner = SpinKitCubeGrid( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitFadingCircle": + spinner = SpinKitFadingCircle( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitRotatingCircle": + spinner = SpinKitRotatingCircle( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitFoldingCube": + spinner = SpinKitFoldingCube( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 2400), + ); + break; + case "SpinKitPumpingHeart": + spinner = SpinKitPumpingHeart( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1000), + ); + break; + case "SpinKitHourGlass": + spinner = SpinKitHourGlass( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitPouringHourGlass": + spinner = SpinKitPouringHourGlass( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 2400), + ); + break; + case "SpinKitPouringHourGlassRefined": + spinner = SpinKitPouringHourGlassRefined( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 2400), + ); + break; + case "SpinKitFadingGrid": + spinner = SpinKitFadingGrid( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitRing": + spinner = SpinKitRing( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + lineWidth: lineWidth ?? 7.0, + ); + break; + case "SpinKitRipple": + spinner = SpinKitRipple( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1800), + borderWidth: borderWidth ?? 6.0, + ); + break; + case "SpinKitDualRing": + spinner = SpinKitDualRing( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + lineWidth: lineWidth ?? 7.0, + ); + break; + case "SpinKitSpinningCircle": + spinner = SpinKitSpinningCircle( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitSpinningLines": + spinner = SpinKitSpinningLines( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + lineWidth: lineWidth ?? 2.0, + ); + break; + case "SpinKitSquareCircle": + spinner = SpinKitSquareCircle( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 500), + ); + break; + case "SpinKitThreeInOut": + spinner = SpinKitThreeInOut( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1500), + ); + break; + case "SpinKitDancingSquare": + spinner = SpinKitDancingSquare( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitPianoWave": + spinner = SpinKitPianoWave( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + itemCount: itemCount ?? 5, + ); + break; + case "SpinKitPulsingGrid": + spinner = SpinKitPulsingGrid( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + case "SpinKitWaveSpinner": + spinner = SpinKitWaveSpinner( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + break; + default: + spinner = SpinKitRotatingCircle( + color: color, + size: size, + duration: duration ?? const Duration(milliseconds: 1200), + ); + } + + return LayoutControl(control: widget.control, child: spinner); + } +} diff --git a/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/pubspec.yaml b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/pubspec.yaml new file mode 100644 index 0000000000..df490172cb --- /dev/null +++ b/sdk/python/packages/flet-spinkit/src/flutter/flet_spinkit/pubspec.yaml @@ -0,0 +1,22 @@ +name: flet_spinkit +description: Flet SpinKit controls +version: 0.1.0 +publish_to: none + +environment: + sdk: '>=3.2.3 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + + flutter_spinkit: ^5.2.2 + + flet: + path: ../../../../../../../packages/flet + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 diff --git a/sdk/python/packages/flet-webview/pyproject.toml b/sdk/python/packages/flet-webview/pyproject.toml index e9b72025a1..4f0d34a9ad 100644 --- a/sdk/python/packages/flet-webview/pyproject.toml +++ b/sdk/python/packages/flet-webview/pyproject.toml @@ -8,6 +8,7 @@ license = "Apache-2.0" requires-python = ">=3.10" dependencies = [ "flet", + "typing-extensions; python_version < '3.11'", ] [project.urls] diff --git a/sdk/python/packages/flet-webview/src/flet_webview/webview.py b/sdk/python/packages/flet-webview/src/flet_webview/webview.py index 91da17d8df..c35fbbda3e 100644 --- a/sdk/python/packages/flet-webview/src/flet_webview/webview.py +++ b/sdk/python/packages/flet-webview/src/flet_webview/webview.py @@ -1,6 +1,10 @@ +import sys from typing import Optional -from typing_extensions import Self +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self import flet as ft from flet_webview.types import ( diff --git a/sdk/python/packages/flet/integration_tests/extensions/spinkit/test_spinkit.py b/sdk/python/packages/flet/integration_tests/extensions/spinkit/test_spinkit.py new file mode 100644 index 0000000000..dc9c2d2c5c --- /dev/null +++ b/sdk/python/packages/flet/integration_tests/extensions/spinkit/test_spinkit.py @@ -0,0 +1,148 @@ +import flet_spinkit as spins +import pytest + +import flet as ft +import flet.testing as ftt + + +@pytest.mark.asyncio(loop_scope="module") +async def test_rotating_circle(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add(spins.RotatingCircle(color=ft.Colors.BLUE, size=50, key="ctrl")) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_wave_default(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add(spins.Wave(color=ft.Colors.BLUE, size=50, key="ctrl")) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_wave_type(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add( + spins.Wave( + color=ft.Colors.BLUE, + size=50, + wave_type=spins.WaveType.CENTER, + item_count=7, + key="ctrl", + ) + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_ring_line_width(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add( + spins.Ring(color=ft.Colors.BLUE, size=50, line_width=10, key="ctrl") + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_dual_ring_line_width(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add( + spins.DualRing(color=ft.Colors.BLUE, size=50, line_width=4, key="ctrl") + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_ripple_border_width(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add( + spins.Ripple(color=ft.Colors.BLUE, size=50, border_width=8, key="ctrl") + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_spinning_lines_line_width(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add( + spins.SpinningLines(color=ft.Colors.BLUE, size=50, line_width=3, key="ctrl") + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_piano_wave_item_count(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add( + spins.PianoWave(color=ft.Colors.BLUE, size=50, item_count=8, key="ctrl") + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_custom_duration(flet_app: ftt.FletTestApp): + flet_app.page.clean() + flet_app.page.add( + spins.Pulse( + color=ft.Colors.BLUE, + size=50, + duration=ft.Duration(milliseconds=400), + key="ctrl", + ) + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("ctrl")).count == 1 + + +@pytest.mark.asyncio(loop_scope="module") +async def test_all_controls_render(flet_app: ftt.FletTestApp): + controls = [ + spins.ChasingDots, + spins.Circle, + spins.CubeGrid, + spins.DancingSquare, + spins.DoubleBounce, + spins.DualRing, + spins.FadingCircle, + spins.FadingCube, + spins.FadingFour, + spins.FadingGrid, + spins.FoldingCube, + spins.HourGlass, + spins.PianoWave, + spins.PouringHourGlass, + spins.PouringHourGlassRefined, + spins.Pulse, + spins.PulsingGrid, + spins.PumpingHeart, + spins.Ring, + spins.Ripple, + spins.RotatingCircle, + spins.RotatingPlain, + spins.SpinningCircle, + spins.SpinningLines, + spins.SquareCircle, + spins.ThreeBounce, + spins.ThreeInOut, + spins.WanderingCubes, + spins.Wave, + spins.WaveSpinner, + ] + flet_app.page.clean() + flet_app.page.add( + ft.Row( + wrap=True, + key="row", + controls=[cls(color=ft.Colors.BLUE, size=40) for cls in controls], + ) + ) + await flet_app.tester.pump(duration=ft.Duration(milliseconds=500)) + assert (await flet_app.tester.find_by_key("row")).count == 1 diff --git a/sdk/python/packages/flet/pyproject.toml b/sdk/python/packages/flet/pyproject.toml index c6a20b80f2..55539efe6e 100644 --- a/sdk/python/packages/flet/pyproject.toml +++ b/sdk/python/packages/flet/pyproject.toml @@ -53,6 +53,7 @@ extensions = [ "flet-permission-handler", "flet-rive", "flet-secure-storage", + "flet-spinkit", "flet-video", "flet-webview", ] diff --git a/sdk/python/packages/flet/src/flet/components/hooks/use_context.py b/sdk/python/packages/flet/src/flet/components/hooks/use_context.py index db1830e342..bc6a1449b3 100644 --- a/sdk/python/packages/flet/src/flet/components/hooks/use_context.py +++ b/sdk/python/packages/flet/src/flet/components/hooks/use_context.py @@ -1,11 +1,6 @@ from collections.abc import Callable from dataclasses import dataclass -from typing import TypeVar, cast - -try: - from typing import Protocol -except ImportError: - from typing_extensions import Protocol +from typing import Protocol, TypeVar, cast from flet.components.hooks.hook import Hook from flet.components.observable import Observable diff --git a/sdk/python/packages/flet/src/flet/controls/page.py b/sdk/python/packages/flet/src/flet/controls/page.py index 977ec72f04..fa813abf1c 100644 --- a/sdk/python/packages/flet/src/flet/controls/page.py +++ b/sdk/python/packages/flet/src/flet/controls/page.py @@ -13,6 +13,7 @@ Any, Callable, Optional, + ParamSpec, TypeVar, Union, ) @@ -83,12 +84,6 @@ def _default_authorization_impl() -> "type[Authorization]": return Authorization -try: - from typing import ParamSpec -except ImportError: - from typing_extensions import ParamSpec - - logger = logging.getLogger("flet") diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 8a805efb35..66bed0b632 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ "flet-permission-handler", "flet-rive", "flet-secure-storage", + "flet-spinkit", "flet-video", "flet-webview", ] @@ -47,6 +48,7 @@ flet-map = { workspace = true } flet-permission-handler = { workspace = true } flet-rive = { workspace = true } flet-secure-storage = { workspace = true } +flet-spinkit = { workspace = true } flet-video = { workspace = true } flet-webview = { workspace = true } flet-code-editor = { workspace = true } diff --git a/sdk/python/templates/app/app/{{cookiecutter.out_dir}}/.gitignore b/sdk/python/templates/app/app/{{cookiecutter.out_dir}}/.gitignore index adb31b9c42..13e37d0cb1 100644 --- a/sdk/python/templates/app/app/{{cookiecutter.out_dir}}/.gitignore +++ b/sdk/python/templates/app/app/{{cookiecutter.out_dir}}/.gitignore @@ -160,4 +160,4 @@ cython_debug/ #.idea/ # Flet -storage/ +.flet/ diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart index 73b8557f6d..0701306297 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart @@ -6,7 +6,6 @@ import 'package:flet/flet.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart' as path_provider; import 'package:flutter_web_plugins/url_strategy.dart'; @@ -50,7 +49,6 @@ hide_window_on_start: {{ hide_window_on_start }} const bool isRelease = bool.fromEnvironment('dart.vm.product'); -const assetPath = "app/app.zip"; const pythonModuleName = "{{ cookiecutter.python_module_name }}"; final showAppBootScreen = bool.tryParse("{{ show_boot_screen }}".toLowerCase()) ?? false; const appBootScreenMessage = '{{ boot_screen_message | default("Preparing the app for its first launch…", true) }}'; @@ -183,35 +181,35 @@ Future prepareApp() async { } } else { // production mode - // extract app from asset - appDir = await nrt.extractAppAssets(assetPath, checkHash: true); - - // set current directory to app path - Directory.current = appDir; + // resolve the app dir from the bundle (Android unpacks app.zip on first launch) + appDir = await nrt.getAppDir(); assetsDir = path.join(appDir, "assets"); - // configure apps DATA and TEMP directories + // configure the app's storage directories WidgetsFlutterBinding.ensureInitialized(); - var appTempPath = (await path_provider.getApplicationCacheDirectory()).path; - var appDataPath = - (await path_provider.getApplicationDocumentsDirectory()).path; - - if (defaultTargetPlatform != TargetPlatform.iOS && - defaultTargetPlatform != TargetPlatform.android) { - // append app name to the path and create dir - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - appDataPath = path.join(appDataPath, "flet", packageInfo.packageName); - if (!await Directory(appDataPath).exists()) { - await Directory(appDataPath).create(recursive: true); - } + // FLET_APP_STORAGE_DATA — durable, app-private; also the cwd. Lives under + // the OS application-support dir (NOT the app bundle, which is read-only), + // so relative file writes / SQLite work and persist across app updates. + var appDataPath = path.join( + (await path_provider.getApplicationSupportDirectory()).path, "data"); + if (!await Directory(appDataPath).exists()) { + await Directory(appDataPath).create(recursive: true); } + Directory.current = appDataPath; + + // FLET_APP_STORAGE_CACHE — regenerable; the OS may purge it. + var appCachePath = (await path_provider.getApplicationCacheDirectory()).path; + // FLET_APP_STORAGE_TEMP — volatile OS temp; may vanish between launches. + var appTempPath = (await path_provider.getTemporaryDirectory()).path; environmentVariables.putIfAbsent("FLET_APP_STORAGE_DATA", () => appDataPath); + environmentVariables.putIfAbsent( + "FLET_APP_STORAGE_CACHE", () => appCachePath); environmentVariables.putIfAbsent("FLET_APP_STORAGE_TEMP", () => appTempPath); - outLogFilename = path.join(appTempPath, "console.log"); + outLogFilename = path.join(appCachePath, "console.log"); environmentVariables.putIfAbsent("FLET_APP_CONSOLE", () => outLogFilename); environmentVariables.putIfAbsent( diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart index ab281e4901..edb09ff6a9 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart @@ -70,11 +70,11 @@ bool get bridgesActive => _bridge != null; /// process, and false when running against pre-1.3.0 libdart_bridge. bool get pythonAlreadyRunning => DartBridge.instance.isPythonInitialized; -/// Extract the bundled app.zip into a fresh app directory and return its -/// path. Wraps `serious_python.extractAssetZip` so main.dart doesn't have -/// to import the FFI-touching package directly. -Future extractAppAssets(String assetPath, {bool checkHash = false}) => - extractAssetZip(assetPath, checkHash: checkHash); +/// Resolve the bundled app directory (Android unpacks `app.zip` on first +/// launch; desktop/iOS read it in place from the bundle). Wraps +/// `SeriousPython.prepareApp()` so main.dart doesn't have to import the +/// FFI-touching package directly. +Future getAppDir() => SeriousPython.prepareApp(); /// FletApp's `channelBuilder` for the embedded-Python protocol — wraps the /// in-process [PythonBridge] in a [FletBackendChannel]. Returns null until diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart index 95cacb5acc..dbfc67f390 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart @@ -17,8 +17,8 @@ bool get bridgesActive => false; /// process-reuse check compiles for the web target. bool get pythonAlreadyRunning => false; -Future extractAppAssets(String assetPath, {bool checkHash = false}) => - throw UnsupportedError("Asset extraction not available on web"); +Future getAppDir() => + throw UnsupportedError("App directory not available on web"); FletBackendChannelBuilder? get channelBuilder => null; diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index 22472a5acd..441db56117 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -17,7 +17,8 @@ dependencies: flet: path: ../../../../../packages/flet - serious_python: 3.0.0 + + serious_python: 4.0.0 # MsgPack codec used by the dart_bridge FletBackendChannel implementation # in lib/main.dart — matches the wire format flet's existing socket @@ -37,6 +38,7 @@ dependencies: dependency_overrides: flet: path: ../../../../../packages/flet + screen_retriever: 0.2.1 # this one migrated to SPM, but 0.2.0 is used by some other packages dev_dependencies: flutter_launcher_icons: ^0.14.1 @@ -47,9 +49,14 @@ flutter: uses-material-design: true +# On native platforms the app ships unpacked inside the bundle (placed by +# serious_python's platform build), so there is no app.zip Flutter asset. Only +# the web (Pyodide) build still loads app/app.zip. +#{% if cookiecutter.options.config_platform == "web" %} assets: - app/app.zip - app/app.zip.hash +#{% endif %} # dart run flutter_launcher_icons flutter_launcher_icons: diff --git a/tools/crocodocs/pyproject.toml b/tools/crocodocs/pyproject.toml index 5d89833ce9..34bfeba0e4 100644 --- a/tools/crocodocs/pyproject.toml +++ b/tools/crocodocs/pyproject.toml @@ -74,6 +74,7 @@ flet_map = "../../sdk/python/packages/flet-map/src" flet_permission_handler = "../../sdk/python/packages/flet-permission-handler/src" flet_rive = "../../sdk/python/packages/flet-rive/src" flet_secure_storage = "../../sdk/python/packages/flet-secure-storage/src" +flet_spinkit = "../../sdk/python/packages/flet-spinkit/src" flet_video = "../../sdk/python/packages/flet-video/src" flet_web = "../../sdk/python/packages/flet-web/src" flet_webview = "../../sdk/python/packages/flet-webview/src" diff --git a/website/docs/controls/spinkit/chasingdots.md b/website/docs/controls/spinkit/chasingdots.md new file mode 100644 index 0000000000..49563c4b65 --- /dev/null +++ b/website/docs/controls/spinkit/chasingdots.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.ChasingDots" +title: "ChasingDots" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/circle.md b/website/docs/controls/spinkit/circle.md new file mode 100644 index 0000000000..42a9a773c1 --- /dev/null +++ b/website/docs/controls/spinkit/circle.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.Circle" +title: "Circle" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/cubegrid.md b/website/docs/controls/spinkit/cubegrid.md new file mode 100644 index 0000000000..8a09a76e04 --- /dev/null +++ b/website/docs/controls/spinkit/cubegrid.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.CubeGrid" +title: "CubeGrid" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/dancingsquare.md b/website/docs/controls/spinkit/dancingsquare.md new file mode 100644 index 0000000000..2a2d57ba60 --- /dev/null +++ b/website/docs/controls/spinkit/dancingsquare.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.DancingSquare" +title: "DancingSquare" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/doublebounce.md b/website/docs/controls/spinkit/doublebounce.md new file mode 100644 index 0000000000..559e4b4891 --- /dev/null +++ b/website/docs/controls/spinkit/doublebounce.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.DoubleBounce" +title: "DoubleBounce" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/dualring.md b/website/docs/controls/spinkit/dualring.md new file mode 100644 index 0000000000..aa1f304d4d --- /dev/null +++ b/website/docs/controls/spinkit/dualring.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.DualRing" +title: "DualRing" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/fadingcircle.md b/website/docs/controls/spinkit/fadingcircle.md new file mode 100644 index 0000000000..f4d7b73dbb --- /dev/null +++ b/website/docs/controls/spinkit/fadingcircle.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.FadingCircle" +title: "FadingCircle" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/fadingcube.md b/website/docs/controls/spinkit/fadingcube.md new file mode 100644 index 0000000000..1cfa5a9a56 --- /dev/null +++ b/website/docs/controls/spinkit/fadingcube.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.FadingCube" +title: "FadingCube" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/fadingfour.md b/website/docs/controls/spinkit/fadingfour.md new file mode 100644 index 0000000000..3c0267ebde --- /dev/null +++ b/website/docs/controls/spinkit/fadingfour.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.FadingFour" +title: "FadingFour" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/fadinggrid.md b/website/docs/controls/spinkit/fadinggrid.md new file mode 100644 index 0000000000..c08a637ffc --- /dev/null +++ b/website/docs/controls/spinkit/fadinggrid.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.FadingGrid" +title: "FadingGrid" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/foldingcube.md b/website/docs/controls/spinkit/foldingcube.md new file mode 100644 index 0000000000..ebd16fba28 --- /dev/null +++ b/website/docs/controls/spinkit/foldingcube.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.FoldingCube" +title: "FoldingCube" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/hourglass.md b/website/docs/controls/spinkit/hourglass.md new file mode 100644 index 0000000000..4ccefe1673 --- /dev/null +++ b/website/docs/controls/spinkit/hourglass.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.HourGlass" +title: "HourGlass" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/index.md b/website/docs/controls/spinkit/index.md new file mode 100644 index 0000000000..a48356dc91 --- /dev/null +++ b/website/docs/controls/spinkit/index.md @@ -0,0 +1,78 @@ +--- +examples: "extensions/spinkit" +title: "SpinKit" +--- + +import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; +import {CodeExample} from '@site/src/components/crocodocs'; + +# SpinKit + +30 animated loading spinner controls built on Flutter's +[`flutter_spinkit`](https://pub.dev/packages/flutter_spinkit) package. + +## Usage + +Add `flet-spinkit` to your project dependencies: + + + +```bash +uv add flet-spinkit +``` + + + +```bash +pip install flet-spinkit # (1)! +``` + +1. After this, you will have to manually add this package to your `requirements.txt` or `pyproject.toml`. + + + +Then import the package in your Flet app: + +```python +import flet_spinkit as spins +``` + +## Examples + + + + + +## Available controls + +- [ChasingDots](chasingdots.md) +- [Circle](circle.md) +- [CubeGrid](cubegrid.md) +- [DancingSquare](dancingsquare.md) +- [DoubleBounce](doublebounce.md) +- [DualRing](dualring.md) +- [FadingCircle](fadingcircle.md) +- [FadingCube](fadingcube.md) +- [FadingFour](fadingfour.md) +- [FadingGrid](fadinggrid.md) +- [FoldingCube](foldingcube.md) +- [HourGlass](hourglass.md) +- [PianoWave](pianowave.md) +- [PouringHourGlass](pouringhourglass.md) +- [PouringHourGlassRefined](pouringhourglassrefined.md) +- [Pulse](pulse.md) +- [PulsingGrid](pulsinggrid.md) +- [PumpingHeart](pumpingheart.md) +- [Ring](ring.md) +- [Ripple](ripple.md) +- [RotatingCircle](rotatingcircle.md) +- [RotatingPlain](rotatingplain.md) +- [SpinningCircle](spinningcircle.md) +- [SpinningLines](spinninglines.md) +- [SquareCircle](squarecircle.md) +- [ThreeBounce](threebounce.md) +- [ThreeInOut](threeinout.md) +- [WanderingCubes](wanderingcubes.md) +- [Wave](wave.md) +- [WaveSpinner](wavespinner.md) diff --git a/website/docs/controls/spinkit/pianowave.md b/website/docs/controls/spinkit/pianowave.md new file mode 100644 index 0000000000..dd16b1ae40 --- /dev/null +++ b/website/docs/controls/spinkit/pianowave.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.PianoWave" +title: "PianoWave" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/pouringhourglass.md b/website/docs/controls/spinkit/pouringhourglass.md new file mode 100644 index 0000000000..3472120af1 --- /dev/null +++ b/website/docs/controls/spinkit/pouringhourglass.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.PouringHourGlass" +title: "PouringHourGlass" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/pouringhourglassrefined.md b/website/docs/controls/spinkit/pouringhourglassrefined.md new file mode 100644 index 0000000000..9ec760daa4 --- /dev/null +++ b/website/docs/controls/spinkit/pouringhourglassrefined.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.PouringHourGlassRefined" +title: "PouringHourGlassRefined" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/pulse.md b/website/docs/controls/spinkit/pulse.md new file mode 100644 index 0000000000..222ad5bf8f --- /dev/null +++ b/website/docs/controls/spinkit/pulse.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.Pulse" +title: "Pulse" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/pulsinggrid.md b/website/docs/controls/spinkit/pulsinggrid.md new file mode 100644 index 0000000000..843e12f962 --- /dev/null +++ b/website/docs/controls/spinkit/pulsinggrid.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.PulsingGrid" +title: "PulsingGrid" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/pumpingheart.md b/website/docs/controls/spinkit/pumpingheart.md new file mode 100644 index 0000000000..1c542ab4ad --- /dev/null +++ b/website/docs/controls/spinkit/pumpingheart.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.PumpingHeart" +title: "PumpingHeart" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/ring.md b/website/docs/controls/spinkit/ring.md new file mode 100644 index 0000000000..a6367979ad --- /dev/null +++ b/website/docs/controls/spinkit/ring.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.Ring" +title: "Ring" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/ripple.md b/website/docs/controls/spinkit/ripple.md new file mode 100644 index 0000000000..424b0b8559 --- /dev/null +++ b/website/docs/controls/spinkit/ripple.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.Ripple" +title: "Ripple" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/rotatingcircle.md b/website/docs/controls/spinkit/rotatingcircle.md new file mode 100644 index 0000000000..522c6d31e3 --- /dev/null +++ b/website/docs/controls/spinkit/rotatingcircle.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.RotatingCircle" +title: "RotatingCircle" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/rotatingplain.md b/website/docs/controls/spinkit/rotatingplain.md new file mode 100644 index 0000000000..919a482e30 --- /dev/null +++ b/website/docs/controls/spinkit/rotatingplain.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.RotatingPlain" +title: "RotatingPlain" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/spinningcircle.md b/website/docs/controls/spinkit/spinningcircle.md new file mode 100644 index 0000000000..6cf1c0e336 --- /dev/null +++ b/website/docs/controls/spinkit/spinningcircle.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.SpinningCircle" +title: "SpinningCircle" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/spinninglines.md b/website/docs/controls/spinkit/spinninglines.md new file mode 100644 index 0000000000..8ea7031d48 --- /dev/null +++ b/website/docs/controls/spinkit/spinninglines.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.SpinningLines" +title: "SpinningLines" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/squarecircle.md b/website/docs/controls/spinkit/squarecircle.md new file mode 100644 index 0000000000..31f04276df --- /dev/null +++ b/website/docs/controls/spinkit/squarecircle.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.SquareCircle" +title: "SquareCircle" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/threebounce.md b/website/docs/controls/spinkit/threebounce.md new file mode 100644 index 0000000000..1e9d35b88b --- /dev/null +++ b/website/docs/controls/spinkit/threebounce.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.ThreeBounce" +title: "ThreeBounce" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/threeinout.md b/website/docs/controls/spinkit/threeinout.md new file mode 100644 index 0000000000..a9a3aa2774 --- /dev/null +++ b/website/docs/controls/spinkit/threeinout.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.ThreeInOut" +title: "ThreeInOut" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/wanderingcubes.md b/website/docs/controls/spinkit/wanderingcubes.md new file mode 100644 index 0000000000..142f08cb4e --- /dev/null +++ b/website/docs/controls/spinkit/wanderingcubes.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.WanderingCubes" +title: "WanderingCubes" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/wave.md b/website/docs/controls/spinkit/wave.md new file mode 100644 index 0000000000..6ee58341be --- /dev/null +++ b/website/docs/controls/spinkit/wave.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.Wave" +title: "Wave" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/wavespinner.md b/website/docs/controls/spinkit/wavespinner.md new file mode 100644 index 0000000000..e90a96ccae --- /dev/null +++ b/website/docs/controls/spinkit/wavespinner.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.WaveSpinner" +title: "WaveSpinner" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/controls/spinkit/wavetype.md b/website/docs/controls/spinkit/wavetype.md new file mode 100644 index 0000000000..cd70f80a56 --- /dev/null +++ b/website/docs/controls/spinkit/wavetype.md @@ -0,0 +1,10 @@ +--- +class_name: "flet_spinkit.WaveType" +title: "WaveType" +--- + +import {ClassMembers, ClassSummary} from '@site/src/components/crocodocs'; + + + + diff --git a/website/docs/cookbook/read-and-write-files.md b/website/docs/cookbook/read-and-write-files.md index 330f231a1f..e2eb53cf1d 100644 --- a/website/docs/cookbook/read-and-write-files.md +++ b/website/docs/cookbook/read-and-write-files.md @@ -9,19 +9,33 @@ Flet makes it easy to work with files and directories on the mobile/desktop devi ### Storage Paths -Flet provides two directory paths for data storage, available as environment variables: -[`FLET_APP_STORAGE_DATA`](../reference/environment-variables.md#flet_app_storage_data) and -[`FLET_APP_STORAGE_TEMP`](../reference/environment-variables.md#flet_app_storage_temp). +Flet provides three pre-created, app-private directory paths as environment variables: -Their values can be gotten as follows: +- [`FLET_APP_STORAGE_DATA`](../reference/environment-variables.md#flet_app_storage_data) — **durable** + data (databases, state, config). Preserved across app updates and backed up; the OS never deletes it. + This is also the program's current working directory, so relative writes land here. +- [`FLET_APP_STORAGE_CACHE`](../reference/environment-variables.md#flet_app_storage_cache) — + **regenerable** cache. The OS may purge it; only store things you can rebuild. +- [`FLET_APP_STORAGE_TEMP`](../reference/environment-variables.md#flet_app_storage_temp) — **throwaway** + scratch (the OS temp dir). May vanish between launches. + +Use `FLET_APP_STORAGE_DATA` for anything you must keep — putting a database in cache or temp risks +silent data loss. + +Their values can be read as follows: ```python import os app_data_path = os.getenv("FLET_APP_STORAGE_DATA") +app_cache_path = os.getenv("FLET_APP_STORAGE_CACHE") app_temp_path = os.getenv("FLET_APP_STORAGE_TEMP") ``` +Because `FLET_APP_STORAGE_DATA` is the working directory, a relative path like `"counter.txt"` resolves +inside it; use `os.path.join(app_data_path, ...)` if you want to be explicit. The same behavior applies +under `flet run` (the dev working directory is `/.flet/storage/data`). + ### Writing to a File To write data to a new/existing file, you can use the built-in [`open`](https://docs.python.org/3/library/functions.html#open) function. diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index d85c18f22f..a8e223d90a 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -9,19 +9,39 @@ Any other value will be interpreted as `False`. ### `FLET_APP_CONSOLE` -The path to the application's console log file (`console.log`) in the temporary storage directory. +The path to the application's console log file (`console.log`) in the cache storage directory +([`FLET_APP_STORAGE_CACHE`](#flet_app_storage_cache)). Its value is set in production mode. ### `FLET_APP_STORAGE_DATA` -A directory for the storage of persistent application data that is preserved between app updates. -It is already pre-created and its location depends on the platform the app is running on. +A directory for **durable** application data — databases, state, config — that is preserved between +app updates, backed up by the OS, and never auto-deleted. It is pre-created, app-private, and its +location depends on the platform (it maps to the platform's *application support* directory: +`%APPDATA%\\\data` on Windows, `~/Library/Application Support//data` on +macOS, `~/.local/share//data` on Linux, the app-sandboxed support dir on iOS/Android). + +In production this directory is also the Python program's **current working directory**, so relative +file writes (e.g. `open("app.db", "w")`, `sqlite3.connect("app.db")`) land here. In `flet run` (dev +mode) it is `/.flet/storage/data`. + +### `FLET_APP_STORAGE_CACHE` + +A directory for **regenerable** cached data. The OS may purge it under storage pressure (and the +platform "clear cache" action wipes it), so only store things you can rebuild. Pre-created and +app-private; it maps to the platform's *caches* directory (`%LOCALAPPDATA%\\` on +Windows, `~/Library/Caches/` on macOS, `~/.cache/` on Linux, the app cache dir on +iOS/Android). In `flet run` it is `/.flet/storage/cache`. ### `FLET_APP_STORAGE_TEMP` -A directory for the storage of temporary application files, i.e. cache. -It is already pre-created and its location depends on the platform the app is running on. +A directory for **throwaway** temporary files — the OS temporary directory (`getTemporaryDirectory()`). +It is the most volatile of the three and may be cleared between launches, so don't rely on its contents +persisting. (On Android it resolves to the same directory as +[`FLET_APP_STORAGE_CACHE`](#flet_app_storage_cache).) In `flet run` it is `/.flet/storage/temp`, +and Python's `tempfile` is pointed here too (via `TMPDIR`). Equivalent to Python's +`tempfile.gettempdir()`. ### `FLET_APP_USER_MODEL_ID` diff --git a/website/docs/services/storagepaths.md b/website/docs/services/storagepaths.md index 9e8e7c2670..ee6f3ef20b 100644 --- a/website/docs/services/storagepaths.md +++ b/website/docs/services/storagepaths.md @@ -8,6 +8,16 @@ import {ClassMembers, ClassSummary, CodeExample} from '@site/src/components/croc +:::note Relationship to the `FLET_APP_STORAGE_*` environment variables +`StoragePaths` exposes the raw platform directories. The pre-created +[`FLET_APP_STORAGE_*`](../reference/environment-variables.md#flet_app_storage_data) env vars map onto +them: `FLET_APP_STORAGE_CACHE` = `get_application_cache_directory()`, `FLET_APP_STORAGE_TEMP` = +`get_temporary_directory()`, and `FLET_APP_STORAGE_DATA` = a flet-owned **`data` subdirectory** of +`get_application_support_directory()` (which is also the app's current working directory). Prefer the +env vars for app storage; use `StoragePaths` when you need a different platform directory (e.g. +Documents or Downloads). +::: + ## Examples diff --git a/website/docs/updates/breaking-changes/v0-86-0-app-files-unpacked-read-only-bundle.md b/website/docs/updates/breaking-changes/v0-86-0-app-files-unpacked-read-only-bundle.md new file mode 100644 index 0000000000..8ca4d4867a --- /dev/null +++ b/website/docs/updates/breaking-changes/v0-86-0-app-files-unpacked-read-only-bundle.md @@ -0,0 +1,121 @@ +--- +title: "App files ship unpacked in a read-only bundle; storage dirs reworked" +--- + +# App files ship unpacked in a read-only bundle; storage dirs reworked + +:::note +This guide is accurate as of Flet 0.86.0. Later releases might add new APIs or +additional migration paths. + +The [breaking changes and deprecations index](.) lists the guides created for each release. +::: + +## Summary + +Flet 0.86.0 (with `serious_python` 4.0.0) changes how a packaged app's Python files are shipped and +where the app reads and writes data: + +- **App files ship unpacked inside the app bundle**, next to the Python standard library and + site-packages, on macOS / iOS / Windows / Linux. There is no first-launch `app.zip` extraction. On + **Android** the app ships as a *stored* `app.zip` asset inside the APK and is unpacked once + (version-keyed) to the app's files directory on the first launch after an install/update. Web + (Pyodide) is unchanged. +- The app directory is now **read-only** (signed `.app`, iOS bundle, `Program Files`, the APK). +- The Python program's **current working directory** moved from the app directory to a writable, + app-private data directory, exposed as `FLET_APP_STORAGE_DATA`. +- `FLET_APP_STORAGE_DATA` now maps to the OS *application support* directory (a `data` subdir), + **not** the user's Documents folder. +- The storage env vars are now a clean three-tier set: `FLET_APP_STORAGE_DATA` (durable), + the new **`FLET_APP_STORAGE_CACHE`** (regenerable), and `FLET_APP_STORAGE_TEMP` (now the OS temp + dir, previously the cache dir). +- `flet run` (dev mode) now sets the working directory to a hidden, git-ignored + `/.flet/storage/data`, mirroring a built app. + +## Background + +Previously your Python sources were zipped into `app/app.zip`, shipped as a Flutter asset, and +extracted at runtime on first launch — while the stdlib and site-packages already shipped unpacked +inside the bundle. Lifting app-file handling into `serious_python` removes the first-launch unzip and +the hash bookkeeping, and puts your code in the same place as the stdlib. Because that place is +read-only, the working directory has to move somewhere writable — and the natural home for app data +is the OS *application support* directory, which is app-private on every platform (no Documents +clutter, no manual scoping). + +## Migration guide + +### Write data to the storage dirs, not next to your code + +The app directory is read-only. Relative writes resolve against the **current working directory**, +which is now `FLET_APP_STORAGE_DATA` (a writable, durable, app-private dir). `open("app.db", "w")` and +`sqlite3.connect("app.db")` keep working — the file just lands in the data dir instead of next to your +`.py` files, and now persists across app updates. + +### Read shipped files via `__file__`, not relative paths + +Relative **reads** of files you bundle with your app (e.g. `open("seed.json")`) no longer resolve +against your app folder. Read bundled data relative to your module instead: + +```python +from pathlib import Path + +SEED = Path(__file__).parent / "seed.json" +data = SEED.read_text() +``` + +or use `importlib.resources`, or place the file under your Flet `assets/` directory and resolve it via +`FLET_ASSETS_DIR`. + +### Storage environment variables + +| Variable | Meaning | Lifetime | +| --- | --- | --- | +| `FLET_APP_STORAGE_DATA` | Durable app data (= the working directory) | Never auto-deleted; backed up | +| `FLET_APP_STORAGE_CACHE` | Regenerable cache (**new**) | OS may purge under storage pressure | +| `FLET_APP_STORAGE_TEMP` | OS temp / scratch (**changed** — was the cache dir) | Most volatile; may vanish between launches | + +If you relied on `FLET_APP_STORAGE_TEMP` for data that should survive (it used to be the cache dir), +switch to `FLET_APP_STORAGE_CACHE` (or `FLET_APP_STORAGE_DATA` for must-keep data). + +### Physical locations + +`FLET_APP_STORAGE_DATA` (= the working directory): + +| Platform | Location | +| --- | --- | +| Windows | `%APPDATA%\\\data` (e.g. `C:\Users\\AppData\Roaming\\\data`) | +| macOS | `~/Library/Application Support//data` (sandboxed apps: redirected into the app container) | +| Linux | `${XDG_DATA_HOME:-~/.local/share}//data` | +| iOS | `/Library/Application Support//data` | +| Android | `/data/data//files/data` | +| `flet run` (dev) | `/.flet/storage/data` | + +`FLET_APP_STORAGE_CACHE` and `FLET_APP_STORAGE_TEMP`: + +| Platform | `FLET_APP_STORAGE_CACHE` | `FLET_APP_STORAGE_TEMP` | +| --- | --- | --- | +| Windows | `%LOCALAPPDATA%\\` | `%TEMP%` (`…\AppData\Local\Temp`) | +| macOS | `~/Library/Caches/` | `$TMPDIR` (e.g. `/var/folders/.../T/`) | +| Linux | `${XDG_CACHE_HOME:-~/.cache}/` | system temp (`$TMPDIR`, fallback `/tmp`) | +| iOS | `/Library/Caches/` | `/tmp` | +| Android | `getCacheDir()` (`/data/data//cache`) | same as `CACHE` (`getCacheDir()`) | +| `flet run` (dev) | `/.flet/storage/cache` | `/.flet/storage/temp` | + +### `flet run` dev storage + +`flet run` now creates a hidden `/.flet/` directory (with `storage/{data,cache,temp}`) and +runs your app with the working directory set to `.flet/storage/data`, so dev behaves like a built app +(including the read-shipped-files change above). The directory writes its own `.gitignore` and is +ignored by Git automatically; the old visible `storage/` directory is no longer used. + +## Timeline + +- Changed in: `0.86.0` + +## References + +- Environment variables: [`FLET_APP_STORAGE_DATA`](../../reference/environment-variables.md#flet_app_storage_data), + [`FLET_APP_STORAGE_CACHE`](../../reference/environment-variables.md#flet_app_storage_cache), + [`FLET_APP_STORAGE_TEMP`](../../reference/environment-variables.md#flet_app_storage_temp) +- Cookbook: [Read and write files](../../cookbook/read-and-write-files.md) +- Release notes: [Flet 0.86.0](../release-notes.md) diff --git a/website/sidebars.js b/website/sidebars.js new file mode 100644 index 0000000000..b052322b52 --- /dev/null +++ b/website/sidebars.js @@ -0,0 +1,4111 @@ +// Generated by CrocoDocs from website/sidebars.yml. +// Do not edit by hand. + +module.exports = { + "docs": [ + { + "type": "doc", + "id": "index", + "label": "Introduction" + }, + { + "type": "category", + "label": "Getting started", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "getting-started/installation" + }, + { + "type": "doc", + "id": "getting-started/create-flet-app" + }, + { + "type": "doc", + "id": "getting-started/running-app" + }, + { + "type": "doc", + "id": "getting-started/testing-on-mobile" + } + ] + }, + { + "type": "category", + "label": "Tutorials", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "tutorials/calculator", + "label": "Calculator" + }, + { + "type": "doc", + "id": "tutorials/todo", + "label": "ToDo" + }, + { + "type": "doc", + "id": "tutorials/chat", + "label": "Chat" + }, + { + "type": "doc", + "id": "tutorials/solitaire", + "label": "Solitaire" + } + ] + }, + { + "type": "category", + "label": "Cookbook", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "cookbook/expanding-controls", + "label": "Expanding Controls" + }, + { + "type": "doc", + "id": "cookbook/colors", + "label": "Colors" + }, + { + "type": "doc", + "id": "cookbook/fonts", + "label": "Fonts" + }, + { + "type": "doc", + "id": "cookbook/theming", + "label": "Theming" + }, + { + "type": "doc", + "id": "cookbook/assets", + "label": "Assets" + }, + { + "type": "doc", + "id": "cookbook/animations", + "label": "Animations" + }, + { + "type": "doc", + "id": "cookbook/adaptive-apps", + "label": "Adaptive apps" + }, + { + "type": "doc", + "id": "cookbook/keyboard-shortcuts", + "label": "Keyboard Shortcuts" + }, + { + "type": "doc", + "id": "cookbook/drag-and-drop", + "label": "Drag and Drop" + }, + { + "type": "doc", + "id": "cookbook/large-lists", + "label": "Large Lists" + }, + { + "type": "doc", + "id": "cookbook/accessibility", + "label": "Accessibility" + }, + { + "type": "doc", + "id": "cookbook/navigation-and-routing", + "label": "Navigation and Routing" + }, + { + "type": "doc", + "id": "cookbook/router", + "label": "Router" + }, + { + "type": "doc", + "id": "cookbook/control-refs", + "label": "Control Refs" + }, + { + "type": "doc", + "id": "cookbook/custom-controls", + "label": "Custom Controls" + }, + { + "type": "doc", + "id": "cookbook/declarative-vs-imperative-crud-app", + "label": "Declarative vs Imperative CRUD app" + }, + { + "type": "doc", + "id": "cookbook/async-apps", + "label": "Async apps" + }, + { + "type": "doc", + "id": "cookbook/read-and-write-files", + "label": "Read and Write Files" + }, + { + "type": "doc", + "id": "cookbook/client-storage", + "label": "Client Storage" + }, + { + "type": "doc", + "id": "cookbook/session-storage", + "label": "Session Storage" + }, + { + "type": "doc", + "id": "cookbook/pub-sub", + "label": "PubSub" + }, + { + "type": "doc", + "id": "cookbook/subprocess", + "label": "Subprocess" + }, + { + "type": "doc", + "id": "cookbook/logging", + "label": "Logging" + }, + { + "type": "doc", + "id": "cookbook/authentication", + "label": "Authentication" + }, + { + "type": "doc", + "id": "cookbook/encrypting-sensitive-data", + "label": "Encrypting sensitive data" + }, + { + "type": "doc", + "id": "cookbook/declarative-dialogs", + "label": "Declarative dialogs" + } + ] + }, + { + "type": "category", + "label": "Publishing Flet app", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "publish/android", + "label": "Android" + }, + { + "type": "doc", + "id": "publish/ios", + "label": "iOS" + }, + { + "type": "doc", + "id": "publish/linux", + "label": "Linux" + }, + { + "type": "doc", + "id": "publish/macos", + "label": "macOS" + }, + { + "type": "doc", + "id": "publish/windows", + "label": "Windows" + }, + { + "type": "category", + "label": "Web", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "Dynamic Website", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "Hosting", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "publish/web/dynamic-website/hosting/fly-io", + "label": "Fly.io" + }, + { + "type": "doc", + "id": "publish/web/dynamic-website/hosting/replit", + "label": "Replit" + }, + { + "type": "doc", + "id": "publish/web/dynamic-website/hosting/self-hosting", + "label": "Self-Hosting" + } + ], + "link": { + "type": "doc", + "id": "publish/web/dynamic-website/hosting/index" + } + } + ], + "link": { + "type": "doc", + "id": "publish/web/dynamic-website/index" + } + }, + { + "type": "category", + "label": "Static Website", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "Hosting", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "publish/web/static-website/hosting/cloudflare", + "label": "Cloudflare" + }, + { + "type": "doc", + "id": "publish/web/static-website/hosting/github-pages", + "label": "GitHub Pages" + } + ] + } + ], + "link": { + "type": "doc", + "id": "publish/web/static-website/index" + } + } + ], + "link": { + "type": "doc", + "id": "publish/web/index" + } + } + ], + "link": { + "type": "doc", + "id": "publish/index" + } + }, + { + "type": "category", + "label": "Extending Flet", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "extend/user-extensions" + }, + { + "type": "doc", + "id": "extend/built-in-extensions" + } + ] + }, + { + "type": "category", + "label": "Stay up to date", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "updates/release-notes", + "label": "Release notes" + }, + { + "type": "category", + "label": "Breaking changes and deprecations", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "v0.85.0", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "updates/breaking-changes/removed-spacing-border-helpers", + "label": "Deprecated spacing and border helper functions removed" + }, + { + "type": "doc", + "id": "updates/breaking-changes/deprecated-drag-target-event-coordinates", + "label": "DragTargetEvent coordinate fields deprecated" + }, + { + "type": "doc", + "id": "updates/breaking-changes/deprecated-video-apis", + "label": "Video control APIs deprecated" + } + ] + } + ], + "link": { + "type": "doc", + "id": "updates/breaking-changes/index" + } + }, + { + "type": "doc", + "id": "updates/compatibility-policy", + "label": "Compatibility policy" + } + ], + "link": { + "type": "doc", + "id": "updates/index" + } + }, + { + "type": "category", + "label": "Reference", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "Controls", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "Ads", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/ads/bannerad", + "label": "BannerAd" + }, + { + "type": "doc", + "id": "controls/ads/basead", + "label": "BaseAd" + }, + { + "type": "doc", + "id": "controls/ads/interstitialad", + "label": "InterstitialAd" + } + ], + "link": { + "type": "doc", + "id": "controls/ads/index" + } + }, + { + "type": "doc", + "id": "controls/alertdialog", + "label": "AlertDialog" + }, + { + "type": "doc", + "id": "controls/animatedswitcher", + "label": "AnimatedSwitcher" + }, + { + "type": "doc", + "id": "controls/appbar", + "label": "AppBar" + }, + { + "type": "doc", + "id": "controls/autocomplete", + "label": "AutoComplete" + }, + { + "type": "doc", + "id": "controls/autofillgroup", + "label": "AutofillGroup" + }, + { + "type": "doc", + "id": "types/badge", + "label": "Badge" + }, + { + "type": "doc", + "id": "controls/banner", + "label": "Banner" + }, + { + "type": "doc", + "id": "controls/bottomappbar", + "label": "BottomAppBar" + }, + { + "type": "doc", + "id": "controls/bottomsheet", + "label": "BottomSheet" + }, + { + "type": "doc", + "id": "controls/button", + "label": "Button" + }, + { + "type": "doc", + "id": "controls/camera/index", + "label": "Camera" + }, + { + "type": "category", + "label": "Canvas", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/canvas/arc", + "label": "Arc" + }, + { + "type": "doc", + "id": "controls/canvas/circle", + "label": "Circle" + }, + { + "type": "doc", + "id": "controls/canvas/color", + "label": "Color" + }, + { + "type": "doc", + "id": "controls/canvas/fill", + "label": "Fill" + }, + { + "type": "doc", + "id": "controls/canvas/image", + "label": "Image" + }, + { + "type": "doc", + "id": "controls/canvas/line", + "label": "Line" + }, + { + "type": "doc", + "id": "controls/canvas/oval", + "label": "Oval" + }, + { + "type": "doc", + "id": "controls/canvas/path", + "label": "Path" + }, + { + "type": "doc", + "id": "controls/canvas/points", + "label": "Points" + }, + { + "type": "doc", + "id": "controls/canvas/rect", + "label": "Rect" + }, + { + "type": "doc", + "id": "controls/canvas/shadow", + "label": "Shadow" + }, + { + "type": "doc", + "id": "controls/canvas/shape", + "label": "Shape" + }, + { + "type": "doc", + "id": "controls/canvas/text", + "label": "Text" + } + ], + "link": { + "type": "doc", + "id": "controls/canvas/index" + } + }, + { + "type": "doc", + "id": "controls/card", + "label": "Card" + }, + { + "type": "category", + "label": "Charts", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/charts/barchart", + "label": "BarChart" + }, + { + "type": "doc", + "id": "controls/charts/candlestickchart", + "label": "CandlestickChart" + }, + { + "type": "doc", + "id": "controls/charts/linechart", + "label": "LineChart" + }, + { + "type": "doc", + "id": "controls/charts/matplotlibchart", + "label": "MatplotlibChart" + }, + { + "type": "doc", + "id": "controls/charts/matplotlibchartwithtoolbar", + "label": "MatplotlibChartWithToolbar" + }, + { + "type": "doc", + "id": "controls/charts/piechart", + "label": "PieChart" + }, + { + "type": "doc", + "id": "controls/charts/plotlychart", + "label": "PlotlyChart" + }, + { + "type": "doc", + "id": "controls/charts/radarchart", + "label": "RadarChart" + }, + { + "type": "doc", + "id": "controls/charts/scatterchart", + "label": "ScatterChart" + } + ], + "link": { + "type": "doc", + "id": "controls/charts/index" + } + }, + { + "type": "doc", + "id": "controls/checkbox", + "label": "Checkbox" + }, + { + "type": "doc", + "id": "controls/chip", + "label": "Chip" + }, + { + "type": "doc", + "id": "controls/circleavatar", + "label": "CircleAvatar" + }, + { + "type": "doc", + "id": "controls/codeeditor/index", + "label": "CodeEditor" + }, + { + "type": "category", + "label": "Color pickers", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/colorpickers/colorpicker", + "label": "ColorPicker" + }, + { + "type": "doc", + "id": "controls/colorpickers/blockpicker", + "label": "BlockPicker" + }, + { + "type": "doc", + "id": "controls/colorpickers/hueringpicker", + "label": "HueRingPicker" + }, + { + "type": "doc", + "id": "controls/colorpickers/materialpicker", + "label": "MaterialPicker" + }, + { + "type": "doc", + "id": "controls/colorpickers/multiplechoiceblockpicker", + "label": "MultipleChoiceBlockPicker" + }, + { + "type": "doc", + "id": "controls/colorpickers/slidepicker", + "label": "SlidePicker" + } + ], + "link": { + "type": "doc", + "id": "controls/colorpickers/index" + } + }, + { + "type": "doc", + "id": "controls/column", + "label": "Column" + }, + { + "type": "doc", + "id": "controls/container", + "label": "Container" + }, + { + "type": "doc", + "id": "controls/contextmenu", + "label": "ContextMenu" + }, + { + "type": "category", + "label": "CupertinoActionSheet", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/cupertinoactionsheetaction", + "label": "CupertinoActionSheetAction" + } + ], + "link": { + "type": "doc", + "id": "controls/cupertinoactionsheet/index" + } + }, + { + "type": "doc", + "id": "controls/cupertinoactivityindicator", + "label": "CupertinoActivityIndicator" + }, + { + "type": "doc", + "id": "controls/cupertinoalertdialog", + "label": "CupertinoAlertDialog" + }, + { + "type": "doc", + "id": "controls/cupertinoappbar", + "label": "CupertinoAppBar" + }, + { + "type": "doc", + "id": "controls/cupertinobottomsheet", + "label": "CupertinoBottomSheet" + }, + { + "type": "doc", + "id": "controls/cupertinobutton", + "label": "CupertinoButton" + }, + { + "type": "doc", + "id": "controls/cupertinocheckbox", + "label": "CupertinoCheckbox" + }, + { + "type": "category", + "label": "CupertinoContextMenu", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/cupertinocontextmenuaction", + "label": "CupertinoContextMenuAction" + } + ], + "link": { + "type": "doc", + "id": "controls/cupertinocontextmenu/index" + } + }, + { + "type": "doc", + "id": "controls/cupertinodatepicker", + "label": "CupertinoDatePicker" + }, + { + "type": "doc", + "id": "controls/cupertinodialogaction", + "label": "CupertinoDialogAction" + }, + { + "type": "doc", + "id": "controls/cupertinofilledbutton", + "label": "CupertinoFilledButton" + }, + { + "type": "doc", + "id": "controls/cupertinolisttile", + "label": "CupertinoListTile" + }, + { + "type": "doc", + "id": "controls/cupertinonavigationbar", + "label": "CupertinoNavigationBar" + }, + { + "type": "doc", + "id": "controls/cupertinopicker", + "label": "CupertinoPicker" + }, + { + "type": "doc", + "id": "controls/cupertinoradio", + "label": "CupertinoRadio" + }, + { + "type": "doc", + "id": "controls/cupertinosegmentedbutton", + "label": "CupertinoSegmentedButton" + }, + { + "type": "doc", + "id": "controls/cupertinoslider", + "label": "CupertinoSlider" + }, + { + "type": "doc", + "id": "controls/cupertinoslidingsegmentedbutton", + "label": "CupertinoSlidingSegmentedButton" + }, + { + "type": "doc", + "id": "controls/cupertinoswitch", + "label": "CupertinoSwitch" + }, + { + "type": "doc", + "id": "controls/cupertinotextfield", + "label": "CupertinoTextField" + }, + { + "type": "doc", + "id": "controls/cupertinotimerpicker", + "label": "CupertinoTimerPicker" + }, + { + "type": "doc", + "id": "controls/cupertinotintedbutton", + "label": "CupertinoTintedButton" + }, + { + "type": "doc", + "id": "controls/datepicker", + "label": "DatePicker" + }, + { + "type": "doc", + "id": "controls/daterangepicker", + "label": "DateRangePicker" + }, + { + "type": "category", + "label": "DataTable", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/datacell", + "label": "DataCell" + }, + { + "type": "doc", + "id": "controls/datacolumn", + "label": "DataColumn" + }, + { + "type": "doc", + "id": "controls/datarow", + "label": "DataRow" + } + ], + "link": { + "type": "doc", + "id": "controls/datatable/index" + } + }, + { + "type": "category", + "label": "DataTable2", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/datatable2/datacolumn2", + "label": "DataColumn2" + }, + { + "type": "doc", + "id": "controls/datatable2/datarow2", + "label": "DataRow2" + } + ], + "link": { + "type": "doc", + "id": "controls/datatable2/index" + } + }, + { + "type": "doc", + "id": "controls/dismissible", + "label": "Dismissible" + }, + { + "type": "doc", + "id": "controls/divider", + "label": "Divider" + }, + { + "type": "doc", + "id": "controls/dragtarget", + "label": "DragTarget" + }, + { + "type": "doc", + "id": "controls/draggable", + "label": "Draggable" + }, + { + "type": "category", + "label": "Dropdown", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/dropdownoption", + "label": "DropdownOption" + } + ], + "link": { + "type": "doc", + "id": "controls/dropdown/index" + } + }, + { + "type": "doc", + "id": "controls/expansionpanel", + "label": "ExpansionPanel" + }, + { + "type": "doc", + "id": "controls/expansionpanellist", + "label": "ExpansionPanelList" + }, + { + "type": "doc", + "id": "controls/expansiontile", + "label": "ExpansionTile" + }, + { + "type": "doc", + "id": "controls/filledbutton", + "label": "FilledButton" + }, + { + "type": "doc", + "id": "controls/fillediconbutton", + "label": "FilledIconButton" + }, + { + "type": "doc", + "id": "controls/filledtonalbutton", + "label": "FilledTonalButton" + }, + { + "type": "doc", + "id": "controls/filledtonaliconbutton", + "label": "FilledTonalIconButton" + }, + { + "type": "doc", + "id": "controls/fletapp", + "label": "FletApp" + }, + { + "type": "doc", + "id": "controls/floatingactionbutton", + "label": "FloatingActionButton" + }, + { + "type": "doc", + "id": "controls/gesturedetector", + "label": "GestureDetector" + }, + { + "type": "doc", + "id": "controls/gridview", + "label": "GridView" + }, + { + "type": "doc", + "id": "controls/hero", + "label": "Hero" + }, + { + "type": "doc", + "id": "controls/icon", + "label": "Icon" + }, + { + "type": "doc", + "id": "controls/iconbutton", + "label": "IconButton" + }, + { + "type": "doc", + "id": "controls/image", + "label": "Image" + }, + { + "type": "doc", + "id": "controls/interactiveviewer", + "label": "InteractiveViewer" + }, + { + "type": "doc", + "id": "controls/keyboardlistener", + "label": "KeyboardListener" + }, + { + "type": "doc", + "id": "controls/listtile", + "label": "ListTile" + }, + { + "type": "doc", + "id": "controls/listview", + "label": "ListView" + }, + { + "type": "doc", + "id": "controls/lottie/index", + "label": "Lottie" + }, + { + "type": "category", + "label": "Map", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/map/mapcontrol", + "label": "Map" + }, + { + "type": "category", + "label": "Layers", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/map/maplayer" + }, + { + "type": "doc", + "id": "controls/map/tilelayer" + }, + { + "type": "doc", + "id": "controls/map/markerlayer" + }, + { + "type": "doc", + "id": "controls/map/overlayimagelayer" + }, + { + "type": "doc", + "id": "controls/map/circlelayer" + }, + { + "type": "doc", + "id": "controls/map/polygonlayer" + }, + { + "type": "doc", + "id": "controls/map/polylinelayer" + } + ] + }, + { + "type": "category", + "label": "Markers", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/map/marker" + }, + { + "type": "doc", + "id": "controls/map/circlemarker" + }, + { + "type": "doc", + "id": "controls/map/polygonmarker" + }, + { + "type": "doc", + "id": "controls/map/polylinemarker" + } + ] + }, + { + "type": "category", + "label": "Overlays", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/map/baseoverlayimage" + }, + { + "type": "doc", + "id": "controls/map/overlayimage" + }, + { + "type": "doc", + "id": "controls/map/rotatedoverlayimage" + } + ] + }, + { + "type": "category", + "label": "Attributions", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/map/sourceattribution" + }, + { + "type": "doc", + "id": "controls/map/simpleattribution" + }, + { + "type": "doc", + "id": "controls/map/richattribution" + }, + { + "type": "doc", + "id": "controls/map/textsourceattribution" + }, + { + "type": "doc", + "id": "controls/map/imagesourceattribution" + } + ] + } + ], + "link": { + "type": "doc", + "id": "controls/map/index" + } + }, + { + "type": "doc", + "id": "controls/markdown", + "label": "Markdown" + }, + { + "type": "doc", + "id": "controls/menubar", + "label": "MenuBar" + }, + { + "type": "doc", + "id": "controls/menuitembutton", + "label": "MenuItemButton" + }, + { + "type": "doc", + "id": "controls/mergesemantics", + "label": "MergeSemantics" + }, + { + "type": "doc", + "id": "controls/multiview", + "label": "MultiView" + }, + { + "type": "category", + "label": "NavigationBar", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/navigationbardestination", + "label": "NavigationBarDestination" + } + ], + "link": { + "type": "doc", + "id": "controls/navigationbar/index" + } + }, + { + "type": "category", + "label": "NavigationDrawer", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/navigationdrawerdestination", + "label": "NavigationDrawerDestination" + } + ], + "link": { + "type": "doc", + "id": "controls/navigationdrawer/index" + } + }, + { + "type": "category", + "label": "NavigationRail", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/navigationraildestination", + "label": "NavigationRailDestination" + } + ], + "link": { + "type": "doc", + "id": "controls/navigationrail/index" + } + }, + { + "type": "doc", + "id": "controls/outlinedbutton", + "label": "OutlinedButton" + }, + { + "type": "doc", + "id": "controls/outlinediconbutton", + "label": "OutlinedIconButton" + }, + { + "type": "doc", + "id": "controls/page", + "label": "Page" + }, + { + "type": "doc", + "id": "controls/pagelet", + "label": "Pagelet" + }, + { + "type": "doc", + "id": "controls/pageview", + "label": "PageView" + }, + { + "type": "doc", + "id": "controls/placeholder", + "label": "Placeholder" + }, + { + "type": "doc", + "id": "controls/popupmenubutton", + "label": "PopupMenuButton" + }, + { + "type": "doc", + "id": "controls/progressbar", + "label": "ProgressBar" + }, + { + "type": "doc", + "id": "controls/progressring", + "label": "ProgressRing" + }, + { + "type": "doc", + "id": "controls/radio", + "label": "Radio" + }, + { + "type": "doc", + "id": "controls/radiogroup", + "label": "RadioGroup" + }, + { + "type": "doc", + "id": "controls/rangeslider", + "label": "RangeSlider" + }, + { + "type": "doc", + "id": "controls/reorderabledraghandle", + "label": "ReorderableDragHandle" + }, + { + "type": "doc", + "id": "controls/reorderablelistview", + "label": "ReorderableListView" + }, + { + "type": "doc", + "id": "controls/responsiverow", + "label": "ResponsiveRow" + }, + { + "type": "doc", + "id": "controls/rotatedbox", + "label": "RotatedBox" + }, + { + "type": "doc", + "id": "controls/router", + "label": "Router" + }, + { + "type": "doc", + "id": "controls/row", + "label": "Row" + }, + { + "type": "doc", + "id": "controls/rive/index", + "label": "Rive" + }, + { + "type": "doc", + "id": "controls/safearea", + "label": "SafeArea" + }, + { + "type": "doc", + "id": "controls/searchbar", + "label": "SearchBar" + }, + { + "type": "category", + "label": "SegmentedButton", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/segment", + "label": "Segment" + } + ], + "link": { + "type": "doc", + "id": "controls/segmentedbutton/index" + } + }, + { + "type": "doc", + "id": "controls/selectionarea", + "label": "SelectionArea" + }, + { + "type": "doc", + "id": "controls/semantics", + "label": "Semantics" + }, + { + "type": "doc", + "id": "controls/screenshot", + "label": "Screenshot" + }, + { + "type": "doc", + "id": "controls/shadermask", + "label": "ShaderMask" + }, + { + "type": "doc", + "id": "controls/shimmer", + "label": "Shimmer" + }, + { + "type": "category", + "label": "SpinKit", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/spinkit/chasingdots", + "label": "ChasingDots" + }, + { + "type": "doc", + "id": "controls/spinkit/circle", + "label": "Circle" + }, + { + "type": "doc", + "id": "controls/spinkit/cubegrid", + "label": "CubeGrid" + }, + { + "type": "doc", + "id": "controls/spinkit/dancingsquare", + "label": "DancingSquare" + }, + { + "type": "doc", + "id": "controls/spinkit/doublebounce", + "label": "DoubleBounce" + }, + { + "type": "doc", + "id": "controls/spinkit/dualring", + "label": "DualRing" + }, + { + "type": "doc", + "id": "controls/spinkit/fadingcircle", + "label": "FadingCircle" + }, + { + "type": "doc", + "id": "controls/spinkit/fadingcube", + "label": "FadingCube" + }, + { + "type": "doc", + "id": "controls/spinkit/fadingfour", + "label": "FadingFour" + }, + { + "type": "doc", + "id": "controls/spinkit/fadinggrid", + "label": "FadingGrid" + }, + { + "type": "doc", + "id": "controls/spinkit/foldingcube", + "label": "FoldingCube" + }, + { + "type": "doc", + "id": "controls/spinkit/hourglass", + "label": "HourGlass" + }, + { + "type": "doc", + "id": "controls/spinkit/pianowave", + "label": "PianoWave" + }, + { + "type": "doc", + "id": "controls/spinkit/pouringhourglass", + "label": "PouringHourGlass" + }, + { + "type": "doc", + "id": "controls/spinkit/pouringhourglassrefined", + "label": "PouringHourGlassRefined" + }, + { + "type": "doc", + "id": "controls/spinkit/pulse", + "label": "Pulse" + }, + { + "type": "doc", + "id": "controls/spinkit/pulsinggrid", + "label": "PulsingGrid" + }, + { + "type": "doc", + "id": "controls/spinkit/pumpingheart", + "label": "PumpingHeart" + }, + { + "type": "doc", + "id": "controls/spinkit/ring", + "label": "Ring" + }, + { + "type": "doc", + "id": "controls/spinkit/ripple", + "label": "Ripple" + }, + { + "type": "doc", + "id": "controls/spinkit/rotatingcircle", + "label": "RotatingCircle" + }, + { + "type": "doc", + "id": "controls/spinkit/rotatingplain", + "label": "RotatingPlain" + }, + { + "type": "doc", + "id": "controls/spinkit/spinningcircle", + "label": "SpinningCircle" + }, + { + "type": "doc", + "id": "controls/spinkit/spinninglines", + "label": "SpinningLines" + }, + { + "type": "doc", + "id": "controls/spinkit/squarecircle", + "label": "SquareCircle" + }, + { + "type": "doc", + "id": "controls/spinkit/threebounce", + "label": "ThreeBounce" + }, + { + "type": "doc", + "id": "controls/spinkit/threeinout", + "label": "ThreeInOut" + }, + { + "type": "doc", + "id": "controls/spinkit/wanderingcubes", + "label": "WanderingCubes" + }, + { + "type": "doc", + "id": "controls/spinkit/wave", + "label": "Wave" + }, + { + "type": "doc", + "id": "controls/spinkit/wavespinner", + "label": "WaveSpinner" + }, + { + "type": "doc", + "id": "controls/spinkit/wavetype", + "label": "WaveType" + } + ], + "link": { + "type": "doc", + "id": "controls/spinkit/index" + } + }, + { + "type": "doc", + "id": "controls/slider", + "label": "Slider" + }, + { + "type": "doc", + "id": "controls/snackbar", + "label": "SnackBar" + }, + { + "type": "doc", + "id": "controls/stack", + "label": "Stack" + }, + { + "type": "doc", + "id": "controls/submenubutton", + "label": "SubmenuButton" + }, + { + "type": "doc", + "id": "controls/switch", + "label": "Switch" + }, + { + "type": "category", + "label": "Tabs", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/tab", + "label": "Tab" + }, + { + "type": "doc", + "id": "controls/tabbar", + "label": "TabBar" + }, + { + "type": "doc", + "id": "controls/tabbarview", + "label": "TabBarView" + } + ], + "link": { + "type": "doc", + "id": "controls/tabs/index" + } + }, + { + "type": "doc", + "id": "controls/text", + "label": "Text" + }, + { + "type": "doc", + "id": "controls/textbutton", + "label": "TextButton" + }, + { + "type": "doc", + "id": "controls/textfield", + "label": "TextField" + }, + { + "type": "doc", + "id": "controls/timepicker", + "label": "TimePicker" + }, + { + "type": "doc", + "id": "controls/transparentpointer", + "label": "TransparentPointer" + }, + { + "type": "doc", + "id": "controls/verticaldivider", + "label": "VerticalDivider" + }, + { + "type": "doc", + "id": "controls/video/index", + "label": "Video" + }, + { + "type": "doc", + "id": "controls/view", + "label": "View" + }, + { + "type": "doc", + "id": "controls/webview/index", + "label": "WebView" + }, + { + "type": "doc", + "id": "controls/windowdragarea", + "label": "WindowDragArea" + } + ], + "link": { + "type": "generated-index", + "title": "Controls", + "slug": "/controls", + "description": "Browse the complete catalog of controls available in Flet." + } + }, + { + "type": "category", + "label": "Services", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "services/accelerometer", + "label": "Accelerometer" + }, + { + "type": "doc", + "id": "services/audio/index", + "label": "Audio" + }, + { + "type": "doc", + "id": "services/audiorecorder/index", + "label": "AudioRecorder" + }, + { + "type": "doc", + "id": "services/barometer", + "label": "Barometer" + }, + { + "type": "doc", + "id": "services/battery", + "label": "Battery" + }, + { + "type": "doc", + "id": "services/browsercontextmenu", + "label": "BrowserContextMenu" + }, + { + "type": "doc", + "id": "services/clipboard", + "label": "Clipboard" + }, + { + "type": "doc", + "id": "services/connectivity", + "label": "Connectivity" + }, + { + "type": "doc", + "id": "services/filepicker", + "label": "FilePicker" + }, + { + "type": "doc", + "id": "services/flashlight/index", + "label": "Flashlight" + }, + { + "type": "doc", + "id": "services/geolocator/index", + "label": "Geolocator" + }, + { + "type": "doc", + "id": "services/gyroscope", + "label": "Gyroscope" + }, + { + "type": "doc", + "id": "services/hapticfeedback", + "label": "HapticFeedback" + }, + { + "type": "doc", + "id": "services/magnetometer", + "label": "Magnetometer" + }, + { + "type": "doc", + "id": "services/permissionhandler/index", + "label": "PermissionHandler" + }, + { + "type": "doc", + "id": "services/screenbrightness", + "label": "ScreenBrightness" + }, + { + "type": "doc", + "id": "services/semanticsservice", + "label": "SemanticsService" + }, + { + "type": "doc", + "id": "services/shakedetector", + "label": "ShakeDetector" + }, + { + "type": "doc", + "id": "services/share", + "label": "Share" + }, + { + "type": "doc", + "id": "services/securestorage/index", + "label": "SecureStorage" + }, + { + "type": "doc", + "id": "services/sharedpreferences", + "label": "SharedPreferences" + }, + { + "type": "doc", + "id": "services/storagepaths", + "label": "StoragePaths" + }, + { + "type": "doc", + "id": "services/urllauncher", + "label": "UrlLauncher" + }, + { + "type": "doc", + "id": "services/useraccelerometer", + "label": "UserAccelerometer" + }, + { + "type": "doc", + "id": "services/wakelock", + "label": "Wakelock" + } + ], + "link": { + "type": "generated-index", + "title": "Services", + "slug": "/services", + "description": "Browse the complete catalog of services available in Flet." + } + }, + { + "type": "category", + "label": "Types", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/aliases", + "label": "Aliases" + }, + { + "type": "category", + "label": "Authentication", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/auth/authorization" + }, + { + "type": "doc", + "id": "types/auth/authorizationservice" + }, + { + "type": "doc", + "id": "types/auth/group" + }, + { + "type": "doc", + "id": "types/auth/oauthtoken" + }, + { + "type": "category", + "label": "OAuthProvider", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/auth/auth0oauthprovider", + "label": "Auth0OAuthProvider" + }, + { + "type": "doc", + "id": "types/auth/azureoauthprovider", + "label": "AzureOAuthProvider" + }, + { + "type": "doc", + "id": "types/auth/githuboauthprovider", + "label": "GitHubOAuthProvider" + }, + { + "type": "doc", + "id": "types/auth/googleoauthprovider", + "label": "GoogleOAuthProvider" + } + ], + "link": { + "type": "doc", + "id": "types/auth/oauthprovider/index" + } + }, + { + "type": "doc", + "id": "types/auth/user" + } + ] + }, + { + "type": "category", + "label": "Base Controls", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/adaptivecontrol" + }, + { + "type": "doc", + "id": "controls/basecontrol" + }, + { + "type": "doc", + "id": "controls/basepage" + }, + { + "type": "doc", + "id": "controls/control" + }, + { + "type": "doc", + "id": "controls/dialogcontrol" + }, + { + "type": "doc", + "id": "controls/formfieldcontrol" + }, + { + "type": "doc", + "id": "controls/layoutcontrol" + }, + { + "type": "doc", + "id": "controls/scrollablecontrol" + }, + { + "type": "doc", + "id": "controls/service" + }, + { + "type": "doc", + "id": "types/window" + } + ] + }, + { + "type": "category", + "label": "Classes", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "Ads", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/ads/types/adrequest" + } + ] + }, + { + "type": "doc", + "id": "types/alignment" + }, + { + "type": "doc", + "id": "types/androidbuildversion" + }, + { + "type": "doc", + "id": "types/animation" + }, + { + "type": "doc", + "id": "types/animationstyle" + }, + { + "type": "category", + "label": "Audio", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "services/audio/types/audiodurationchangeevent" + }, + { + "type": "doc", + "id": "services/audio/types/audiopositionchangeevent" + }, + { + "type": "doc", + "id": "services/audio/types/audiostatechangeevent" + }, + { + "type": "doc", + "id": "services/audio/types/audiostate" + }, + { + "type": "doc", + "id": "services/audio/types/releasemode" + } + ] + }, + { + "type": "category", + "label": "AudioRecorder", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "services/audiorecorder/types/androidaudiosource" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/androidrecorderconfiguration" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/audioencoder" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/audiorecorderconfiguration" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/audiorecorderstatechangeevent" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/audiorecorderstreamevent" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/audiorecorderstate" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/audiorecorderuploadevent" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/audiorecorderuploadsettings" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/inputdevice" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/iosaudiocategoryoption" + }, + { + "type": "doc", + "id": "services/audiorecorder/types/iosrecorderconfiguration" + } + ] + }, + { + "type": "doc", + "id": "types/autocompletesuggestion" + }, + { + "type": "doc", + "id": "types/blur" + }, + { + "type": "doc", + "id": "types/border" + }, + { + "type": "doc", + "id": "types/borderradius" + }, + { + "type": "doc", + "id": "types/borderside" + }, + { + "type": "doc", + "id": "types/boxconstraints" + }, + { + "type": "doc", + "id": "types/boxdecoration" + }, + { + "type": "doc", + "id": "types/boxshadow" + }, + { + "type": "doc", + "id": "types/buttonstyle" + }, + { + "type": "category", + "label": "Camera", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/camera/types/cameradescription" + }, + { + "type": "doc", + "id": "controls/camera/types/cameraimageevent" + }, + { + "type": "doc", + "id": "controls/camera/types/cameralensdirection" + }, + { + "type": "doc", + "id": "controls/camera/types/cameralenstype" + }, + { + "type": "doc", + "id": "controls/camera/types/camerapreviewsize" + }, + { + "type": "doc", + "id": "controls/camera/types/camerastateevent" + }, + { + "type": "doc", + "id": "controls/camera/types/exposuremode" + }, + { + "type": "doc", + "id": "controls/camera/types/flashmode" + }, + { + "type": "doc", + "id": "controls/camera/types/focusmode" + }, + { + "type": "doc", + "id": "controls/camera/types/imageformatgroup" + }, + { + "type": "doc", + "id": "controls/camera/types/resolutionpreset" + } + ] + }, + { + "type": "category", + "label": "Charts", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/charts/types/barchartevent" + }, + { + "type": "doc", + "id": "controls/charts/types/barchartgroup" + }, + { + "type": "doc", + "id": "controls/charts/types/barchartrod" + }, + { + "type": "doc", + "id": "controls/charts/types/barchartrodstackitem" + }, + { + "type": "doc", + "id": "controls/charts/types/barchartrodtooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/barcharttooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/barcharttooltipdirection" + }, + { + "type": "doc", + "id": "controls/charts/types/candlestickchartevent" + }, + { + "type": "doc", + "id": "controls/charts/types/candlestickchartspot" + }, + { + "type": "doc", + "id": "controls/charts/types/candlestickchartspottooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/candlestickcharttooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/chartaxis" + }, + { + "type": "doc", + "id": "controls/charts/types/chartaxislabel" + }, + { + "type": "doc", + "id": "controls/charts/types/chartcirclepoint" + }, + { + "type": "doc", + "id": "controls/charts/types/chartcrosspoint" + }, + { + "type": "doc", + "id": "controls/charts/types/chartdatapointtooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/charteventtype" + }, + { + "type": "doc", + "id": "controls/charts/types/chartgridlines" + }, + { + "type": "doc", + "id": "controls/charts/types/chartpointline" + }, + { + "type": "doc", + "id": "controls/charts/types/chartpointshape" + }, + { + "type": "doc", + "id": "controls/charts/types/chartsquarepoint" + }, + { + "type": "doc", + "id": "controls/charts/types/horizontalalignment" + }, + { + "type": "doc", + "id": "controls/charts/types/linechartdata" + }, + { + "type": "doc", + "id": "controls/charts/types/linechartdatapoint" + }, + { + "type": "doc", + "id": "controls/charts/types/linechartdatapointtooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/linechartevent" + }, + { + "type": "doc", + "id": "controls/charts/types/linecharteventspot" + }, + { + "type": "doc", + "id": "controls/charts/types/linecharttooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/piechartevent" + }, + { + "type": "doc", + "id": "controls/charts/types/piechartsection" + }, + { + "type": "doc", + "id": "controls/charts/types/radarchartevent" + }, + { + "type": "doc", + "id": "controls/charts/types/radarcharttitle" + }, + { + "type": "doc", + "id": "controls/charts/types/radardataset" + }, + { + "type": "doc", + "id": "controls/charts/types/radardatasetentry" + }, + { + "type": "doc", + "id": "controls/charts/types/radarshape" + }, + { + "type": "doc", + "id": "controls/charts/types/scatterchartevent" + }, + { + "type": "doc", + "id": "controls/charts/types/scatterchartspot" + }, + { + "type": "doc", + "id": "controls/charts/types/scatterchartspottooltip" + }, + { + "type": "doc", + "id": "controls/charts/types/scattercharttooltip" + } + ] + }, + { + "type": "doc", + "id": "types/colorfilter" + }, + { + "type": "doc", + "id": "types/context" + }, + { + "type": "doc", + "id": "types/controlstate" + }, + { + "type": "doc", + "id": "types/decorationimage" + }, + { + "type": "category", + "label": "DeviceInfo", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/androiddeviceinfo", + "label": "AndroidDeviceInfo" + }, + { + "type": "doc", + "id": "types/iosdeviceinfo", + "label": "IosDeviceInfo" + }, + { + "type": "doc", + "id": "types/linuxdeviceinfo", + "label": "LinuxDeviceInfo" + }, + { + "type": "doc", + "id": "types/macosdeviceinfo", + "label": "MacOsDeviceInfo" + }, + { + "type": "doc", + "id": "types/webdeviceinfo", + "label": "WebDeviceInfo" + }, + { + "type": "doc", + "id": "types/windowsdeviceinfo", + "label": "WindowsDeviceInfo" + } + ], + "link": { + "type": "doc", + "id": "types/deviceinfo/index" + } + }, + { + "type": "doc", + "id": "types/duration" + }, + { + "type": "doc", + "id": "types/filepickerfile" + }, + { + "type": "doc", + "id": "types/filepickeruploadfile" + }, + { + "type": "doc", + "id": "types/finder" + }, + { + "type": "doc", + "id": "types/flip" + }, + { + "type": "doc", + "id": "types/flettestapp" + }, + { + "type": "category", + "label": "Geolocator", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "services/geolocator/types/foregroundnotificationconfiguration" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatorandroidconfiguration" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatorconfiguration" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatoriosactivitytype" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatoriosconfiguration" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatorpermissionstatus" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatorposition" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatorpositionaccuracy" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatorpositionchangeevent" + }, + { + "type": "doc", + "id": "services/geolocator/types/geolocatorwebconfiguration" + } + ] + }, + { + "type": "category", + "label": "Gradient", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/lineargradient", + "label": "LinearGradient" + }, + { + "type": "doc", + "id": "types/radialgradient", + "label": "RadialGradient" + }, + { + "type": "doc", + "id": "types/sweepgradient", + "label": "SweepGradient" + } + ], + "link": { + "type": "doc", + "id": "types/gradient/index" + } + }, + { + "type": "doc", + "id": "types/inputborder" + }, + { + "type": "doc", + "id": "types/inputfilter" + }, + { + "type": "doc", + "id": "types/iosutsname" + }, + { + "type": "category", + "label": "Key", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/scrollkey", + "label": "ScrollKey" + }, + { + "type": "doc", + "id": "types/valuekey", + "label": "ValueKey" + } + ], + "link": { + "type": "doc", + "id": "types/key/index" + } + }, + { + "type": "doc", + "id": "types/locale" + }, + { + "type": "doc", + "id": "types/localeconfiguration" + }, + { + "type": "doc", + "id": "types/locationinfo" + }, + { + "type": "category", + "label": "Map", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/map/types/attributionalignment" + }, + { + "type": "doc", + "id": "controls/map/types/camera" + }, + { + "type": "doc", + "id": "controls/map/types/camerafit" + }, + { + "type": "doc", + "id": "controls/map/types/cursorkeyboardrotationconfiguration" + }, + { + "type": "doc", + "id": "controls/map/types/cursorrotationbehaviour" + }, + { + "type": "doc", + "id": "controls/map/types/dashedstrokepattern" + }, + { + "type": "doc", + "id": "controls/map/types/dottedstrokepattern" + }, + { + "type": "doc", + "id": "controls/map/types/fadeintiledisplay" + }, + { + "type": "doc", + "id": "controls/map/types/instantaneoustiledisplay" + }, + { + "type": "doc", + "id": "controls/map/types/interactionconfiguration" + }, + { + "type": "doc", + "id": "controls/map/types/interactionflag" + }, + { + "type": "doc", + "id": "controls/map/types/keyboardconfiguration" + }, + { + "type": "doc", + "id": "controls/map/types/mapevent" + }, + { + "type": "doc", + "id": "controls/map/types/mapeventtype" + }, + { + "type": "doc", + "id": "controls/map/types/mapeventsource" + }, + { + "type": "doc", + "id": "controls/map/types/maphoverevent" + }, + { + "type": "doc", + "id": "controls/map/types/maplatitudelongitude" + }, + { + "type": "doc", + "id": "controls/map/types/maplatitudelongitudebounds" + }, + { + "type": "doc", + "id": "controls/map/types/mappointerevent" + }, + { + "type": "doc", + "id": "controls/map/types/mappositionchangeevent" + }, + { + "type": "doc", + "id": "controls/map/types/maptapevent" + }, + { + "type": "doc", + "id": "controls/map/types/multifingergesture" + }, + { + "type": "doc", + "id": "controls/map/types/patternfit" + }, + { + "type": "doc", + "id": "controls/map/types/solidstrokepattern" + }, + { + "type": "doc", + "id": "controls/map/types/strokepattern" + }, + { + "type": "doc", + "id": "controls/map/types/tiledisplay" + }, + { + "type": "doc", + "id": "controls/map/types/tilelayerevicterrortilestrategy" + }, + { + "type": "doc", + "id": "controls/map/types/wmstilelayerconfiguration" + } + ] + }, + { + "type": "doc", + "id": "types/margin" + }, + { + "type": "doc", + "id": "types/matrix4" + }, + { + "type": "doc", + "id": "types/markdowncustomcodetheme" + }, + { + "type": "doc", + "id": "types/markdownstylesheet" + }, + { + "type": "doc", + "id": "types/menustyle" + }, + { + "type": "category", + "label": "NotchShape", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/automaticnotchshape", + "label": "AutomaticNotchShape" + }, + { + "type": "doc", + "id": "types/circularrectanglenotchshape", + "label": "CircularRectangleNotchShape" + } + ], + "link": { + "type": "doc", + "id": "types/notchshape/index" + } + }, + { + "type": "doc", + "id": "types/observable" + }, + { + "type": "doc", + "id": "types/offset" + }, + { + "type": "category", + "label": "OutlinedBorder", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/beveledrectangleborder", + "label": "BeveledRectangleBorder" + }, + { + "type": "doc", + "id": "types/circleborder", + "label": "CircleBorder" + }, + { + "type": "doc", + "id": "types/continuousrectangleborder", + "label": "ContinuousRectangleBorder" + }, + { + "type": "doc", + "id": "types/roundedrectangleborder", + "label": "RoundedRectangleBorder" + }, + { + "type": "doc", + "id": "types/stadiumborder", + "label": "StadiumBorder" + } + ], + "link": { + "type": "doc", + "id": "types/outlinedborder/index" + } + }, + { + "type": "doc", + "id": "types/padding" + }, + { + "type": "doc", + "id": "types/paint" + }, + { + "type": "category", + "label": "PaintGradient", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/paintlineargradient", + "label": "PaintLinearGradient" + }, + { + "type": "doc", + "id": "types/paintradialgradient", + "label": "PaintRadialGradient" + }, + { + "type": "doc", + "id": "types/paintsweepgradient", + "label": "PaintSweepGradient" + } + ], + "link": { + "type": "doc", + "id": "types/paintgradient/index" + } + }, + { + "type": "category", + "label": "PermissionHandler", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "services/permissionhandler/types/permission" + }, + { + "type": "doc", + "id": "services/permissionhandler/types/permissionstatus" + } + ] + }, + { + "type": "doc", + "id": "types/pubsub/pubsubclient" + }, + { + "type": "doc", + "id": "types/pubsub/pubsubhub" + }, + { + "type": "doc", + "id": "types/rect" + }, + { + "type": "doc", + "id": "types/ref" + }, + { + "type": "doc", + "id": "types/responsiverowbreakpoint" + }, + { + "type": "doc", + "id": "types/rotate" + }, + { + "type": "doc", + "id": "types/scale" + }, + { + "type": "category", + "label": "SecureStorage", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "services/securestorage/types/windowsoptions" + }, + { + "type": "doc", + "id": "services/securestorage/types/androidoptions" + }, + { + "type": "doc", + "id": "services/securestorage/types/appleoptions" + }, + { + "type": "doc", + "id": "services/securestorage/types/macosoptions" + }, + { + "type": "doc", + "id": "services/securestorage/types/iosoptions" + }, + { + "type": "doc", + "id": "services/securestorage/types/weboptions" + } + ] + }, + { + "type": "doc", + "id": "types/shapeborder" + }, + { + "type": "doc", + "id": "types/size" + }, + { + "type": "doc", + "id": "types/strutstyle" + }, + { + "type": "doc", + "id": "types/templateroute" + }, + { + "type": "doc", + "id": "types/tester" + }, + { + "type": "doc", + "id": "types/textdecoration" + }, + { + "type": "doc", + "id": "types/textselection" + }, + { + "type": "doc", + "id": "types/textspan" + }, + { + "type": "doc", + "id": "types/textstyle" + }, + { + "type": "doc", + "id": "types/textthemestyle" + }, + { + "type": "doc", + "id": "types/transform" + }, + { + "type": "category", + "label": "Theme", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/appbartheme", + "label": "AppBarTheme" + }, + { + "type": "doc", + "id": "types/badgetheme", + "label": "BadgeTheme" + }, + { + "type": "doc", + "id": "types/bannertheme", + "label": "BannerTheme" + }, + { + "type": "doc", + "id": "types/bottomappbartheme", + "label": "BottomAppBarTheme" + }, + { + "type": "doc", + "id": "types/bottomsheettheme", + "label": "BottomSheetTheme" + }, + { + "type": "doc", + "id": "types/buttontheme", + "label": "ButtonTheme" + }, + { + "type": "doc", + "id": "types/cardtheme", + "label": "CardTheme" + }, + { + "type": "doc", + "id": "types/checkboxtheme", + "label": "CheckboxTheme" + }, + { + "type": "doc", + "id": "types/chiptheme", + "label": "ChipTheme" + }, + { + "type": "doc", + "id": "types/colorscheme", + "label": "ColorScheme" + }, + { + "type": "doc", + "id": "types/datatabletheme", + "label": "DataTableTheme" + }, + { + "type": "doc", + "id": "types/datepickertheme", + "label": "DatePickerTheme" + }, + { + "type": "doc", + "id": "types/dialogtheme", + "label": "DialogTheme" + }, + { + "type": "doc", + "id": "types/dividertheme", + "label": "DividerTheme" + }, + { + "type": "doc", + "id": "types/dropdowntheme", + "label": "DropdownTheme" + }, + { + "type": "doc", + "id": "types/expansiontiletheme", + "label": "ExpansionTileTheme" + }, + { + "type": "doc", + "id": "types/filledbuttontheme", + "label": "FilledButtonTheme" + }, + { + "type": "doc", + "id": "types/floatingactionbuttontheme", + "label": "FloatingActionButtonTheme" + }, + { + "type": "doc", + "id": "types/iconbuttontheme", + "label": "IconButtonTheme" + }, + { + "type": "doc", + "id": "types/icontheme", + "label": "IconTheme" + }, + { + "type": "doc", + "id": "types/listtiletheme", + "label": "ListTileTheme" + }, + { + "type": "doc", + "id": "types/navigationbartheme", + "label": "NavigationBarTheme" + }, + { + "type": "doc", + "id": "types/navigationdrawertheme", + "label": "NavigationDrawerTheme" + }, + { + "type": "doc", + "id": "types/navigationrailtheme", + "label": "NavigationRailTheme" + }, + { + "type": "doc", + "id": "types/outlinedbuttontheme", + "label": "OutlinedButtonTheme" + }, + { + "type": "doc", + "id": "types/pagetransitionstheme", + "label": "PageTransitionsTheme" + }, + { + "type": "doc", + "id": "types/pagetransitiontheme", + "label": "PageTransitionTheme" + }, + { + "type": "doc", + "id": "types/popupmenutheme", + "label": "PopupMenuTheme" + }, + { + "type": "doc", + "id": "types/progressindicatortheme", + "label": "ProgressIndicatorTheme" + }, + { + "type": "doc", + "id": "types/radiotheme", + "label": "RadioTheme" + }, + { + "type": "doc", + "id": "types/scrollbartheme", + "label": "ScrollBarTheme" + }, + { + "type": "doc", + "id": "types/searchbartheme", + "label": "SearchBarTheme" + }, + { + "type": "doc", + "id": "types/searchviewtheme", + "label": "SearchViewTheme" + }, + { + "type": "doc", + "id": "types/segmentedbuttontheme", + "label": "SegmentedButtonTheme" + }, + { + "type": "doc", + "id": "types/slidertheme", + "label": "SliderTheme" + }, + { + "type": "doc", + "id": "types/snackbartheme", + "label": "SnackBarTheme" + }, + { + "type": "doc", + "id": "types/switchtheme", + "label": "SwitchTheme" + }, + { + "type": "doc", + "id": "types/systemoverlaystyle", + "label": "SystemOverlayStyle" + }, + { + "type": "doc", + "id": "types/tabbartheme", + "label": "TabBarTheme" + }, + { + "type": "doc", + "id": "types/textbuttontheme", + "label": "TextButtonTheme" + }, + { + "type": "doc", + "id": "types/texttheme", + "label": "TextTheme" + }, + { + "type": "doc", + "id": "types/timepickertheme", + "label": "TimePickerTheme" + }, + { + "type": "doc", + "id": "types/tooltiptheme", + "label": "TooltipTheme" + } + ], + "link": { + "type": "doc", + "id": "types/theme/index" + } + }, + { + "type": "doc", + "id": "types/browserconfiguration" + }, + { + "type": "doc", + "id": "types/tooltip" + }, + { + "type": "doc", + "id": "types/url" + }, + { + "type": "doc", + "id": "types/webviewconfiguration" + }, + { + "type": "doc", + "id": "types/underlinetabindicator" + }, + { + "type": "category", + "label": "CodeEditor", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/codeeditor/types/codelanguage" + }, + { + "type": "doc", + "id": "controls/codeeditor/types/codetheme" + }, + { + "type": "doc", + "id": "controls/codeeditor/types/customcodetheme" + }, + { + "type": "doc", + "id": "controls/codeeditor/types/gutterstyle" + } + ] + }, + { + "type": "category", + "label": "Video", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/video/types/adaptivevideocontrols" + }, + { + "type": "doc", + "id": "controls/video/types/materialdesktopvideocontrols" + }, + { + "type": "doc", + "id": "controls/video/types/materialvideocontrols" + }, + { + "type": "doc", + "id": "controls/video/types/playlistmode" + }, + { + "type": "doc", + "id": "controls/video/types/videobaritem" + }, + { + "type": "doc", + "id": "controls/video/types/videoconfiguration" + }, + { + "type": "doc", + "id": "controls/video/types/videocontrols" + }, + { + "type": "doc", + "id": "controls/video/types/videocontrolsmode" + }, + { + "type": "doc", + "id": "controls/video/types/videofullscreenbutton" + }, + { + "type": "doc", + "id": "controls/video/types/videomedia" + }, + { + "type": "doc", + "id": "controls/video/types/videoplayorpausebutton" + }, + { + "type": "doc", + "id": "controls/video/types/videopositionindicator" + }, + { + "type": "doc", + "id": "controls/video/types/videoskipnextbutton" + }, + { + "type": "doc", + "id": "controls/video/types/videoskippreviousbutton" + }, + { + "type": "doc", + "id": "controls/video/types/videospacer" + }, + { + "type": "doc", + "id": "controls/video/types/videosubtitleconfiguration" + }, + { + "type": "doc", + "id": "controls/video/types/videosubtitletrack" + }, + { + "type": "doc", + "id": "controls/video/types/videovolumebutton" + } + ] + }, + { + "type": "category", + "label": "WebView", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/webview/types/requestmethod" + }, + { + "type": "doc", + "id": "controls/webview/types/loglevelseverity" + }, + { + "type": "doc", + "id": "controls/webview/types/webviewconsolemessageevent" + }, + { + "type": "doc", + "id": "controls/webview/types/webviewjavascriptevent" + }, + { + "type": "doc", + "id": "controls/webview/types/webviewscrollevent" + } + ] + } + ] + }, + { + "type": "category", + "label": "Decorators", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/component" + }, + { + "type": "doc", + "id": "types/control" + }, + { + "type": "doc", + "id": "types/observabledecorator" + }, + { + "type": "doc", + "id": "types/value" + } + ] + }, + { + "type": "category", + "label": "Enums", + "collapsed": true, + "items": [ + { + "type": "category", + "label": "Ads", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/ads/types/precisiontype" + } + ] + }, + { + "type": "doc", + "id": "types/animatedswitchertransition" + }, + { + "type": "doc", + "id": "types/animationcurve" + }, + { + "type": "doc", + "id": "types/applifecyclestate" + }, + { + "type": "doc", + "id": "types/appview" + }, + { + "type": "doc", + "id": "types/assertiveness" + }, + { + "type": "doc", + "id": "types/autofillgroupdisposeaction" + }, + { + "type": "doc", + "id": "types/autofillhint" + }, + { + "type": "doc", + "id": "types/axis" + }, + { + "type": "doc", + "id": "types/blendmode" + }, + { + "type": "doc", + "id": "types/blurstyle" + }, + { + "type": "doc", + "id": "types/blurtilemode" + }, + { + "type": "doc", + "id": "types/bordersidestrokealign" + }, + { + "type": "doc", + "id": "types/borderstyle" + }, + { + "type": "doc", + "id": "types/boxfit" + }, + { + "type": "doc", + "id": "types/boxshape" + }, + { + "type": "doc", + "id": "types/brightness" + }, + { + "type": "doc", + "id": "types/cardvariant" + }, + { + "type": "doc", + "id": "types/clipbehavior" + }, + { + "type": "doc", + "id": "types/colors" + }, + { + "type": "doc", + "id": "types/contextmenutrigger" + }, + { + "type": "doc", + "id": "types/crossaxisalignment" + }, + { + "type": "doc", + "id": "types/cupertinobuttonsize" + }, + { + "type": "doc", + "id": "types/cupertinocolors" + }, + { + "type": "doc", + "id": "types/cupertinodatepickerdateorder" + }, + { + "type": "doc", + "id": "types/cupertinodatepickermode" + }, + { + "type": "doc", + "id": "types/cupertinoicons" + }, + { + "type": "doc", + "id": "types/cupertinotimerpickermode" + }, + { + "type": "category", + "label": "DataTable2", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/datatable2/types/datacolumnsize" + } + ] + }, + { + "type": "doc", + "id": "types/datepickerentrymode" + }, + { + "type": "doc", + "id": "types/datepickermode" + }, + { + "type": "doc", + "id": "types/dismissdirection" + }, + { + "type": "doc", + "id": "types/filepickerfiletype" + }, + { + "type": "doc", + "id": "types/filterquality" + }, + { + "type": "doc", + "id": "types/floatingactionbuttonlocation" + }, + { + "type": "doc", + "id": "types/fontweight" + }, + { + "type": "doc", + "id": "types/gradienttilemode" + }, + { + "type": "doc", + "id": "types/icons" + }, + { + "type": "doc", + "id": "types/imagerepeat" + }, + { + "type": "doc", + "id": "types/keyboardtype" + }, + { + "type": "doc", + "id": "types/labelposition" + }, + { + "type": "doc", + "id": "types/listtilestyle" + }, + { + "type": "doc", + "id": "types/listtiletitlealignment" + }, + { + "type": "doc", + "id": "types/mainaxisalignment" + }, + { + "type": "doc", + "id": "types/mousecursor" + }, + { + "type": "doc", + "id": "types/markdowncodetheme" + }, + { + "type": "doc", + "id": "types/markdownextensionset" + }, + { + "type": "doc", + "id": "types/navigationbarlabelbehavior" + }, + { + "type": "doc", + "id": "types/navigationraillabeltype" + }, + { + "type": "doc", + "id": "types/deviceorientation" + }, + { + "type": "doc", + "id": "types/orientation" + }, + { + "type": "doc", + "id": "types/overlayvisibilitymode" + }, + { + "type": "doc", + "id": "types/pageplatform" + }, + { + "type": "doc", + "id": "types/paintingstyle" + }, + { + "type": "doc", + "id": "types/pointerdevicetype" + }, + { + "type": "doc", + "id": "types/pointmode" + }, + { + "type": "doc", + "id": "types/popupmenuposition" + }, + { + "type": "doc", + "id": "types/route" + }, + { + "type": "doc", + "id": "types/routeurlstrategy" + }, + { + "type": "doc", + "id": "types/scrollbar" + }, + { + "type": "doc", + "id": "types/scrollbarorientation" + }, + { + "type": "doc", + "id": "types/scrolldirection" + }, + { + "type": "doc", + "id": "types/scrollmode" + }, + { + "type": "doc", + "id": "types/scrolltype" + }, + { + "type": "doc", + "id": "types/sliderinteraction" + }, + { + "type": "doc", + "id": "types/snackbarbehavior" + }, + { + "type": "doc", + "id": "types/stackfit" + }, + { + "type": "doc", + "id": "types/strokecap" + }, + { + "type": "doc", + "id": "types/strokejoin" + }, + { + "type": "doc", + "id": "types/tabalignment" + }, + { + "type": "doc", + "id": "types/tabbarindicatorsize" + }, + { + "type": "doc", + "id": "types/tabindicatoranimation" + }, + { + "type": "doc", + "id": "types/textaffinity" + }, + { + "type": "doc", + "id": "types/textalign" + }, + { + "type": "doc", + "id": "types/textbaseline" + }, + { + "type": "doc", + "id": "types/textcapitalization" + }, + { + "type": "doc", + "id": "types/textdecorationstyle" + }, + { + "type": "doc", + "id": "types/textoverflow" + }, + { + "type": "doc", + "id": "types/textselectionchangecause" + }, + { + "type": "doc", + "id": "types/thememode" + }, + { + "type": "doc", + "id": "types/tileaffinity" + }, + { + "type": "doc", + "id": "types/timepickerentrymode" + }, + { + "type": "doc", + "id": "types/timepickerhourformat" + }, + { + "type": "doc", + "id": "types/tooltiptriggermode" + }, + { + "type": "doc", + "id": "types/launchmode" + }, + { + "type": "doc", + "id": "types/urltarget" + }, + { + "type": "doc", + "id": "types/verticalalignment" + }, + { + "type": "doc", + "id": "types/visualdensity" + }, + { + "type": "doc", + "id": "types/webbrowsername" + }, + { + "type": "doc", + "id": "types/webrenderer" + }, + { + "type": "doc", + "id": "types/windoweventtype" + }, + { + "type": "doc", + "id": "types/windowresizeedge" + }, + { + "type": "doc", + "id": "services/securestorage/types/accesscontrolflag" + }, + { + "type": "doc", + "id": "services/securestorage/types/keychainaccessibility" + }, + { + "type": "doc", + "id": "services/securestorage/types/keycipheralgorithm" + }, + { + "type": "doc", + "id": "services/securestorage/types/storagecipheralgorithm" + } + ] + }, + { + "type": "category", + "label": "Events", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/accelerometerreadingevent" + }, + { + "type": "category", + "label": "Ads", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/ads/types/paidadevent" + } + ] + }, + { + "type": "doc", + "id": "types/applifecyclestatechangeevent" + }, + { + "type": "doc", + "id": "types/autocompleteselectevent" + }, + { + "type": "doc", + "id": "types/barometerreadingevent" + }, + { + "type": "doc", + "id": "types/batterystate" + }, + { + "type": "doc", + "id": "types/batterystatechangeevent" + }, + { + "type": "doc", + "id": "types/connectivitytype" + }, + { + "type": "doc", + "id": "types/connectivitychangeevent" + }, + { + "type": "doc", + "id": "types/canvasresizeevent" + }, + { + "type": "doc", + "id": "types/contextmenudismissevent" + }, + { + "type": "doc", + "id": "types/contextmenuselectevent" + }, + { + "type": "doc", + "id": "types/datacolumnsortevent" + }, + { + "type": "doc", + "id": "types/datepickerentrymodechangeevent" + }, + { + "type": "doc", + "id": "types/dismissibledismissevent" + }, + { + "type": "doc", + "id": "types/dismissibleupdateevent" + }, + { + "type": "doc", + "id": "types/dragendevent" + }, + { + "type": "doc", + "id": "types/dragstartevent" + }, + { + "type": "doc", + "id": "types/dragtargetevent" + }, + { + "type": "doc", + "id": "types/dragtargetleaveevent" + }, + { + "type": "doc", + "id": "types/dragupdateevent" + }, + { + "type": "doc", + "id": "types/dragwillacceptevent" + }, + { + "type": "doc", + "id": "types/event" + }, + { + "type": "doc", + "id": "types/expansionpanellistchangeevent" + }, + { + "type": "doc", + "id": "types/filepickeruploadevent" + }, + { + "type": "doc", + "id": "types/gyroscopereadingevent" + }, + { + "type": "doc", + "id": "types/hoverevent" + }, + { + "type": "doc", + "id": "types/keyboardevent" + }, + { + "type": "doc", + "id": "types/keydownevent" + }, + { + "type": "doc", + "id": "types/keyrepeatevent" + }, + { + "type": "doc", + "id": "types/keyupevent" + }, + { + "type": "doc", + "id": "types/localechangeevent" + }, + { + "type": "doc", + "id": "types/loginevent" + }, + { + "type": "doc", + "id": "types/longpressendevent" + }, + { + "type": "doc", + "id": "types/longpressstartevent" + }, + { + "type": "doc", + "id": "types/magnetometerreadingevent" + }, + { + "type": "doc", + "id": "types/multitapevent" + }, + { + "type": "doc", + "id": "types/multiviewaddevent" + }, + { + "type": "doc", + "id": "types/multiviewremoveevent" + }, + { + "type": "doc", + "id": "types/onreorderevent" + }, + { + "type": "doc", + "id": "types/onscrollevent" + }, + { + "type": "doc", + "id": "types/pagemediadata" + }, + { + "type": "doc", + "id": "types/pageresizeevent" + }, + { + "type": "doc", + "id": "types/platformbrightnesschangeevent" + }, + { + "type": "doc", + "id": "types/pointerevent" + }, + { + "type": "doc", + "id": "types/routechangeevent" + }, + { + "type": "doc", + "id": "types/sensorerrorevent" + }, + { + "type": "doc", + "id": "types/scaleendevent" + }, + { + "type": "doc", + "id": "types/scalestartevent" + }, + { + "type": "doc", + "id": "types/scaleupdateevent" + }, + { + "type": "doc", + "id": "types/screenbrightnesschangeevent" + }, + { + "type": "doc", + "id": "types/shareresult" + }, + { + "type": "doc", + "id": "types/shareresultstatus" + }, + { + "type": "doc", + "id": "types/sharefile" + }, + { + "type": "doc", + "id": "types/sharecupertinoactivitytype" + }, + { + "type": "doc", + "id": "types/scrollevent" + }, + { + "type": "doc", + "id": "services/securestorage/types/securestorageevent" + }, + { + "type": "doc", + "id": "types/tabbarhoverevent" + }, + { + "type": "doc", + "id": "types/tapevent" + }, + { + "type": "doc", + "id": "types/textselectionchangeevent" + }, + { + "type": "doc", + "id": "types/timepickerentrymodechangeevent" + }, + { + "type": "doc", + "id": "types/useraccelerometerreadingevent" + }, + { + "type": "doc", + "id": "types/viewpopevent" + }, + { + "type": "doc", + "id": "types/windowevent" + } + ] + }, + { + "type": "category", + "label": "Exceptions", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/fletpagedisconnectedexception", + "label": "FletPageDisconnectedException" + }, + { + "type": "doc", + "id": "types/fletunimplementedplatformexception", + "label": "FletUnimplementedPlatformException" + }, + { + "type": "doc", + "id": "types/fletunsupportedplatformexception", + "label": "FletUnsupportedPlatformException" + } + ], + "link": { + "type": "doc", + "id": "types/fletexception/index" + } + }, + { + "type": "category", + "label": "FastAPI", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "fastapi/app" + }, + { + "type": "doc", + "id": "fastapi/app_manager" + }, + { + "type": "doc", + "id": "fastapi/fastapi" + }, + { + "type": "doc", + "id": "fastapi/fletapp" + }, + { + "type": "doc", + "id": "fastapi/fletoauth" + }, + { + "type": "doc", + "id": "fastapi/fletstaticfiles" + }, + { + "type": "doc", + "id": "fastapi/fletupload" + } + ] + }, + { + "type": "category", + "label": "Methods", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "types/createcontext" + }, + { + "type": "doc", + "id": "types/is_route_active" + }, + { + "type": "doc", + "id": "types/memo" + }, + { + "type": "doc", + "id": "types/onmounted" + }, + { + "type": "doc", + "id": "types/onunmounted" + }, + { + "type": "doc", + "id": "types/onupdated" + }, + { + "type": "doc", + "id": "types/usecallback" + }, + { + "type": "doc", + "id": "types/usecontext" + }, + { + "type": "doc", + "id": "types/useeffect" + }, + { + "type": "doc", + "id": "types/usememo" + }, + { + "type": "doc", + "id": "types/useref" + }, + { + "type": "doc", + "id": "types/use_route_loader_data" + }, + { + "type": "doc", + "id": "types/use_route_location" + }, + { + "type": "doc", + "id": "types/use_route_outlet" + }, + { + "type": "doc", + "id": "types/use_route_params" + }, + { + "type": "doc", + "id": "types/use_view_path" + }, + { + "type": "doc", + "id": "types/usestate" + } + ] + } + ], + "link": { + "type": "doc", + "id": "types/index" + } + }, + { + "type": "category", + "label": "CLI", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "cli/flet-build", + "label": "flet build" + }, + { + "type": "doc", + "id": "cli/flet-create", + "label": "flet create" + }, + { + "type": "doc", + "id": "cli/flet-debug", + "label": "flet debug" + }, + { + "type": "doc", + "id": "cli/flet-devices", + "label": "flet devices" + }, + { + "type": "doc", + "id": "cli/flet-doctor", + "label": "flet doctor" + }, + { + "type": "doc", + "id": "cli/flet-emulators", + "label": "flet emulators" + }, + { + "type": "doc", + "id": "cli/flet-pack", + "label": "flet pack" + }, + { + "type": "doc", + "id": "cli/flet-publish", + "label": "flet publish" + }, + { + "type": "doc", + "id": "cli/flet-run", + "label": "flet run" + }, + { + "type": "doc", + "id": "cli/flet-serve", + "label": "flet serve" + } + ], + "link": { + "type": "doc", + "id": "cli/index" + } + }, + { + "type": "doc", + "id": "reference/binary-packages-android-ios", + "label": "Python packages built in Flet" + }, + { + "type": "doc", + "id": "reference/environment-variables", + "label": "Environment Variables" + } + ], + "link": { + "type": "doc", + "id": "reference/index" + } + } + ], + "studio": [ + { + "type": "doc", + "id": "studio/index", + "label": "Introduction" + }, + { + "type": "doc", + "id": "studio/whats-new", + "label": "What's new" + }, + { + "type": "doc", + "id": "studio/terms-of-service", + "label": "Terms of Service" + }, + { + "type": "doc", + "id": "studio/privacy-policy", + "label": "Privacy Policy" + } + ] +}; diff --git a/website/sidebars.yml b/website/sidebars.yml index 361f2e80af..0eaee9b702 100644 --- a/website/sidebars.yml +++ b/website/sidebars.yml @@ -69,6 +69,7 @@ docs: Breaking changes and deprecations: _index: updates/breaking-changes/index.md v0.86.0: + App files ship unpacked in a read-only bundle; storage dirs reworked: updates/breaking-changes/v0-86-0-app-files-unpacked-read-only-bundle.md Default bundled Python version is now 3.14: updates/breaking-changes/v0-86-0-default-bundled-python-3-14.md App and packages are compiled to .pyc by default: updates/breaking-changes/v0-86-0-compile-on-by-default.md flet.version.pyodide_version and PYODIDE_VERSION removed: updates/breaking-changes/v0-86-0-removed-pyodide-version-export.md @@ -278,6 +279,39 @@ docs: Screenshot: controls/screenshot.md ShaderMask: controls/shadermask.md Shimmer: controls/shimmer.md + SpinKit: + _index: controls/spinkit/index.md + ChasingDots: controls/spinkit/chasingdots.md + Circle: controls/spinkit/circle.md + CubeGrid: controls/spinkit/cubegrid.md + DancingSquare: controls/spinkit/dancingsquare.md + DoubleBounce: controls/spinkit/doublebounce.md + DualRing: controls/spinkit/dualring.md + FadingCircle: controls/spinkit/fadingcircle.md + FadingCube: controls/spinkit/fadingcube.md + FadingFour: controls/spinkit/fadingfour.md + FadingGrid: controls/spinkit/fadinggrid.md + FoldingCube: controls/spinkit/foldingcube.md + HourGlass: controls/spinkit/hourglass.md + PianoWave: controls/spinkit/pianowave.md + PouringHourGlass: controls/spinkit/pouringhourglass.md + PouringHourGlassRefined: controls/spinkit/pouringhourglassrefined.md + Pulse: controls/spinkit/pulse.md + PulsingGrid: controls/spinkit/pulsinggrid.md + PumpingHeart: controls/spinkit/pumpingheart.md + Ring: controls/spinkit/ring.md + Ripple: controls/spinkit/ripple.md + RotatingCircle: controls/spinkit/rotatingcircle.md + RotatingPlain: controls/spinkit/rotatingplain.md + SpinningCircle: controls/spinkit/spinningcircle.md + SpinningLines: controls/spinkit/spinninglines.md + SquareCircle: controls/spinkit/squarecircle.md + ThreeBounce: controls/spinkit/threebounce.md + ThreeInOut: controls/spinkit/threeinout.md + WanderingCubes: controls/spinkit/wanderingcubes.md + Wave: controls/spinkit/wave.md + WaveSpinner: controls/spinkit/wavespinner.md + WaveType: controls/spinkit/wavetype.md Slider: controls/slider.md SnackBar: controls/snackbar.md Stack: controls/stack.md