Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codex/skills/prepare-flet-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ description: Use when asked to prepare new Flet release by bumping versions and
Use PR author login for PR-based items. For issue-only direct-commit items, use the commit author GitHub login if available.
If one item groups multiple PRs by different authors, attribute all relevant authors:
`by [@user1](https://github.com/user1), [@user2](https://github.com/user2)`.
Ensure that all infered PRs and issues in the changelog have `{version}` milestone attached on GitHub.
Ensure that all inferred PRs and issues in the changelog have `{version}` milestone attached on GitHub.
As it's a Flutter package prefer items having changes on Flutter side.
* Add a new entry into /CHANGELOG.md. Do not add chore/trivial/duplicate items, add "worth while" items with related issue or PR.
Every changelog item must include both related issue link(s) and PR link(s) when available (issue first, PR second). If no issue exists, include PR link(s) only.
Expand Down
16 changes: 14 additions & 2 deletions packages/flet/lib/src/services/shared_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,21 @@ class SharedPreferencesService extends FletService {
var prefs = await SharedPreferences.getInstance();
switch (name) {
case "set":
return prefs.setString(args["key"]!, args["value"]!);
var key = args["key"]!;
var value = args["value"]!;

if (value is String) return prefs.setString(key, value);
if (value is bool) return prefs.setBool(key, value);
if (value is int) return prefs.setInt(key, value);
if (value is double) return prefs.setDouble(key, value);
if (value is List && value.every((item) => item is String)) {
return prefs.setStringList(key, value.cast<String>());
}
throw UnsupportedError(
"Unsupported SharedPreferences value type: ${value.runtimeType}. "
"Supported: String, bool, int, double, List<String>.");
case "get":
return prefs.getString(args["key"]!);
return prefs.get(args["key"]!);
case "contains_key":
return prefs.containsKey(args["key"]!);
case "get_keys":
Expand Down
9 changes: 3 additions & 6 deletions sdk/python/packages/flet-ads/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## 0.82.2
## 0.82.0

### Improvements
### Changed

- Refactored `BaseAd` class to be based on `flet.BaseControl`.
- Refactored `InterstitialAd` to a service-based control (`flet.Service`).
- Refactored `BannerAd` to a layout control (`flet.LayoutControl`).
- Updated Example app.
- Refactored ads controls: `BaseAd` is now based on `flet.BaseControl`, `InterstitialAd` is now a `flet.Service`, `BannerAd` is now a `flet.LayoutControl`, and examples were updated ([#6194](https://github.com/flet-dev/flet/issues/6194), [#6235](https://github.com/flet-dev/flet/pull/6235)).

## 0.80.0

Expand Down
2 changes: 2 additions & 0 deletions sdk/python/packages/flet-cli/src/flet_cli/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class Command(BaseBuildCommand):
installable bundle. It supports building for desktop (macOS, Linux, Windows), web,
Android (APK/AAB), and iOS (IPA and simulator .app), with a wide range of
customization options for metadata, assets, splash screens, and signing.

Detailed guide with usage examples: https://docs.flet.dev/publish
"""

def __init__(self, parser: argparse.ArgumentParser) -> None:
Expand Down
75 changes: 58 additions & 17 deletions sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
verbose2_style,
warning_style,
)
from flet_cli.utils.cli import parse_cli_bool_value
from flet_cli.utils.hash_stamp import HashStamp
from flet_cli.utils.merge import merge_dict
from flet_cli.utils.plist import is_supported_plist_value, parse_cli_plist_value
from flet_cli.utils.project_dependencies import (
get_poetry_dependencies,
get_project_dependencies,
Expand Down Expand Up @@ -250,8 +252,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
action="extend",
nargs="+",
default=[],
help="Files and/or directories to exclude from the package "
"(can be used multiple times)",
help="Files and/or directories to exclude from the package"
"; can be used multiple times",
)
parser.add_argument(
"--clear-cache",
Expand Down Expand Up @@ -499,42 +501,49 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--info-plist",
dest="info_plist",
action="extend",
nargs="+",
default=[],
help="The list of `<key>=<value>|True|False` pairs to add to Info.plist "
"for macOS and iOS builds (macos, ipa and ios-simulator only)",
help="The list of `<key>=<value>` pairs to add to Info.plist. Values can "
"be booleans, strings, numbers, TOML arrays, or TOML inline tables "
"(macos, ipa and ios-simulator only); can be used multiple times",
)
parser.add_argument(
"--macos-entitlements",
dest="macos_entitlements",
action="extend",
nargs="+",
default=[],
help="The list of `<key>=<value>|True|False` entitlements for "
"macOS builds (macos only)",
help="The list of `<key>=<value>` entitlements. Values can be booleans, "
"strings, numbers, TOML arrays, or TOML inline tables "
"(macos only); can be used multiple times",
)
parser.add_argument(
"--android-features",
dest="android_features",
action="extend",
nargs="+",
default=[],
help="The list of `<feature_name>=True|False` features to add to "
"AndroidManifest.xml for Android builds (android only)",
help="The list of `<feature_name>=true|false` features to add to "
"AndroidManifest.xml (android only); can be used multiple times",
)
parser.add_argument(
"--android-permissions",
dest="android_permissions",
action="extend",
nargs="+",
default=[],
help="The list of `<permission_name>=True|False` permissions to add to "
"AndroidManifest.xml for Android builds (android only)",
help="The list of `<permission_name>=true|false` permissions to add to "
"AndroidManifest.xml (android only); can be used multiple times",
)
parser.add_argument(
"--android-meta-data",
dest="android_meta_data",
action="extend",
nargs="+",
default=[],
help="The list of `<name>=<value>` app meta-data entries to add to "
"AndroidManifest.xml for Android builds (android only)",
"AndroidManifest.xml (android only); can be used multiple times",
)
parser.add_argument(
"--permissions",
Expand Down Expand Up @@ -853,12 +862,20 @@ def setup_template_data(self):
if i > -1:
k = p[:i]
v = p[i + 1 :]
info_plist[k] = (
(v.lower() == "true") if v.lower() in {"true", "false"} else v
)
info_plist[k] = parse_cli_plist_value(v)
else:
self.cleanup(1, f"Invalid Info.plist option: {p}")

for key, value in info_plist.items():
if not is_supported_plist_value(value):
self.cleanup(
1,
"Unsupported Info.plist value type for "
f"{key}: {type(value).__name__}. Supported types are "
"string, boolean, integer, float, dictionary, and arrays "
"containing those values.",
)

macos_entitlements = merge_dict(
macos_entitlements,
self.get_pyproject("tool.flet.macos.entitlement") or {},
Expand All @@ -868,10 +885,20 @@ def setup_template_data(self):
for p in self.options.macos_entitlements:
i = p.find("=")
if i > -1:
macos_entitlements[p[:i]] = p[i + 1 :] == "True"
macos_entitlements[p[:i]] = parse_cli_plist_value(p[i + 1 :])
else:
self.cleanup(1, f"Invalid macOS entitlement option: {p}")

for key, value in macos_entitlements.items():
if not is_supported_plist_value(value):
self.cleanup(
1,
"Unsupported macOS entitlement value type for "
f"{key}: {type(value).__name__}. Supported types are "
"string, boolean, integer, float, dictionary, and arrays "
"containing those values.",
)

android_permissions = merge_dict(
android_permissions,
self.get_pyproject("tool.flet.android.permission") or {},
Expand All @@ -881,7 +908,14 @@ def setup_template_data(self):
for p in self.options.android_permissions:
i = p.find("=")
if i > -1:
android_permissions[p[:i]] = p[i + 1 :] == "True"
try:
android_permissions[p[:i]] = parse_cli_bool_value(p[i + 1 :])
except ValueError:
self.cleanup(
1,
f"Invalid Android permission option value for {p[:i]}: "
f"{p[i + 1 :]}. Expected true or false.",
)
else:
self.cleanup(1, f"Invalid Android permission option: {p}")

Expand All @@ -894,7 +928,14 @@ def setup_template_data(self):
for p in self.options.android_features:
i = p.find("=")
if i > -1:
android_features[p[:i]] = p[i + 1 :] == "True"
try:
android_features[p[:i]] = parse_cli_bool_value(p[i + 1 :])
except ValueError:
self.cleanup(
1,
f"Invalid Android feature option value for {p[:i]}: "
f"{p[i + 1 :]}. Expected true or false.",
)
else:
self.cleanup(1, f"Invalid Android feature option: {p}")

Expand Down
8 changes: 8 additions & 0 deletions sdk/python/packages/flet-cli/src/flet_cli/utils/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def parse_cli_bool_value(value: str) -> bool:
"""Parse a CLI boolean value, accepting only true/false tokens."""
normalized = value.strip().lower()
if normalized == "true":
return True
if normalized == "false":
return False
raise ValueError("expected true or false")
42 changes: 42 additions & 0 deletions sdk/python/packages/flet-cli/src/flet_cli/utils/plist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib


def is_supported_plist_value(value) -> bool:
"""Checks if the value is a supported type for plist values."""
# string and boolean
if isinstance(value, (str, bool)):
return True
# integer and float/real
if isinstance(value, (int, float)):
return True
# array
if isinstance(value, list):
return all(is_supported_plist_value(item) for item in value)
# dictionary
if isinstance(value, dict):
return all(
isinstance(key, str) and is_supported_plist_value(item)
for key, item in value.items()
)
return False


def parse_cli_plist_value(value: str):
"""Parses a CLI-provided plist value, supporting TOML syntax for complex types."""
value = value.strip()
lowered = value.lower()
if lowered in {"true", "false"}:
return lowered == "true"

try:
parsed = tomllib.loads(f"value = {value}")["value"]
except Exception:
return value

if is_supported_plist_value(parsed):
return parsed

return value
52 changes: 52 additions & 0 deletions sdk/python/packages/flet-cli/tests/test_plist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from flet_cli.utils.plist import is_supported_plist_value, parse_cli_plist_value


def test_parse_cli_plist_value_supports_strings_and_booleans():
"""Strings and booleans should parse into their respective Python types."""
assert parse_cli_plist_value("true") is True
assert parse_cli_plist_value("False") is False
assert parse_cli_plist_value("TEAMID.example.app") == "TEAMID.example.app"


def test_parse_cli_plist_value_keeps_quoted_literals_as_strings():
"""Quoted literals should remain strings even if they look typed."""
assert parse_cli_plist_value('"true"') == "true"
assert parse_cli_plist_value('"false"') == "false"
assert parse_cli_plist_value('"42"') == "42"
assert parse_cli_plist_value('"3.14"') == "3.14"


def test_parse_cli_plist_value_supports_integers_and_floats():
"""Numeric TOML literals should parse into Python numbers."""
assert parse_cli_plist_value("42") == 42
assert parse_cli_plist_value("3.14") == 3.14


def test_parse_cli_plist_value_supports_toml_arrays():
"""TOML arrays should parse into Python lists."""
assert parse_cli_plist_value('["group.dev.example", "group.dev.shared"]') == [
"group.dev.example",
"group.dev.shared",
]


def test_parse_cli_plist_value_supports_toml_inline_tables():
"""TOML inline tables should parse into Python dictionaries."""
assert parse_cli_plist_value('{ "com.apple.mail" = ["compose"] }') == {
"com.apple.mail": ["compose"]
}


def test_supported_plist_value_accepts_numbers():
"""The plist validator should accept integer and real values."""
assert is_supported_plist_value(1)
assert is_supported_plist_value(1.5)
assert is_supported_plist_value(["group.dev.example", 1, 1.5])
assert is_supported_plist_value({"com.apple.mail": 1})


def test_unsupported_plist_types_are_rejected():
"""The plist validator should reject null values recursively."""
assert not is_supported_plist_value(None)
assert not is_supported_plist_value(["group.dev.example", None])
assert not is_supported_plist_value({"com.apple.mail": None})
10 changes: 5 additions & 5 deletions sdk/python/packages/flet/docs/audio_recorder/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ Configuration to be made to access the microphone:
/// tab | `flet build`
```bash
flet build apk \
--android-permissions android.permission.RECORD_AUDIO=True \
android.permission.WRITE_EXTERNAL_STORAGE=True \
android.permission.MODIFY_AUDIO_SETTINGS=True
--android-permissions android.permission.RECORD_AUDIO=true \
--android-permissions android.permission.WRITE_EXTERNAL_STORAGE=true \
--android-permissions android.permission.MODIFY_AUDIO_SETTINGS=true
```
///
/// tab | `pyproject.toml`
Expand Down Expand Up @@ -101,7 +101,7 @@ Configuration to be made to access the microphone:
```bash
flet build macos \
--info-plist NSMicrophoneUsageDescription="Some message to describe why you need this permission..." \
--entitlement com.apple.security.device.audio-input=True
--macos-entitlements com.apple.security.device.audio-input=true
```
///
/// tab | `pyproject.toml`
Expand Down Expand Up @@ -134,7 +134,7 @@ sudo apt install pulseaudio-utils ffmpeg

### Cross-platform

Additionally/Alternatively, you can make use of our predefined cross-platform `microphone`
Additionally/alternatively, you can make use of our predefined cross-platform `microphone`
[permission bundle](../publish/index.md#predefined-cross-platform-permission-bundles):

/// tab | `flet build`
Expand Down
6 changes: 3 additions & 3 deletions sdk/python/packages/flet/docs/camera/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ Configuration to be made to access the camera and optionally the microphone:
/// tab | `flet build`
```bash
flet build apk \
--android-permissions android.permission.CAMERA=True \
android.permission.RECORD_AUDIO=True
--android-permissions android.permission.CAMERA=true \
--android-permissions android.permission.RECORD_AUDIO=true
```
///
/// tab | `pyproject.toml`
Expand Down Expand Up @@ -97,7 +97,7 @@ See also:

### Cross-platform

Additionally/Alternatively, you can make use of our predefined cross-platform `camera` (and optionally `microphone`)
Additionally/alternatively, you can make use of our predefined cross-platform `camera` (and optionally `microphone`)
[permission bundles](../publish/index.md#predefined-cross-platform-permission-bundles):

/// tab | `flet build`
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/packages/flet/docs/cookbook/assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The same approach applies to other asset types like fonts, Lottie animations, Ri

## Accessing Asset Files in Production

For UI controls such as [`Image`](/docs/controls/image), you usually pass a path relative to `assets_dir`:
For UI controls such as [`Image`][flet.Image], you usually pass a path relative to `assets_dir`:

```python
ft.Image(src="images/sample.png")
Expand Down
Loading
Loading