diff --git a/client/lib/main.dart b/client/lib/main.dart index dbb2ff65ad..37828804fe 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -87,8 +87,7 @@ void main([List? args]) async { assetsDir = args[2]; debugPrint("Args contain a path assets directory: $assetsDir}"); } - } else if (!kDebugMode && - (Platform.isWindows || Platform.isMacOS || Platform.isLinux)) { + } else if (!kDebugMode && isDesktopPlatform()) { throw Exception( 'In desktop mode Flet app URL must be provided as a first argument.'); } diff --git a/packages/flet/lib/src/controls/page.dart b/packages/flet/lib/src/controls/page.dart index 19a5126304..80e1a1b3bf 100644 --- a/packages/flet/lib/src/controls/page.dart +++ b/packages/flet/lib/src/controls/page.dart @@ -32,6 +32,8 @@ import '../utils/session_store_web.dart' import '../utils/theme.dart'; import '../utils/time.dart'; import '../utils/user_fonts.dart'; +import '../utils/web_interface.dart' + if (dart.library.io) "../utils/io_interface.dart"; import '../widgets/animated_transition_page.dart'; import '../widgets/loading_page.dart'; import '../widgets/page_context.dart'; @@ -59,6 +61,7 @@ class _PageControlState extends State with WidgetsBindingObserver { ServiceRegistry? _userServices; bool? _prevOnKeyboardEvent; bool _keyboardHandlerSubscribed = false; + bool? _prevFullScreen; String? _prevViewRoutes; final Map _multiViews = {}; @@ -95,6 +98,7 @@ class _PageControlState extends State with WidgetsBindingObserver { _attachKeyboardListenerIfNeeded(); widget.control.addInvokeMethodListener(_invokeMethod); + _applyFullScreenFromControl(widget.control); } @override @@ -131,6 +135,7 @@ class _PageControlState extends State with WidgetsBindingObserver { _attachKeyboardListenerIfNeeded(); _loadFontsIfNeeded(FletBackend.of(context)); + _applyFullScreenFromControl(widget.control); } @override @@ -151,6 +156,25 @@ class _PageControlState extends State with WidgetsBindingObserver { super.dispose(); } + /// Applies full screen mode to the app. + Future _applyFullScreenFromControl(Control control) async { + final fullScreen = control.getBool("full_screen", false)!; + if (_prevFullScreen != fullScreen) { + _prevFullScreen = fullScreen; + if (isDesktopPlatform() || isWebPlatform()) { + await setWindowFullScreen(fullScreen); + } else if (isMobilePlatform()) { + if (fullScreen) { + await SystemChrome.setEnabledSystemUIMode( + SystemUiMode.immersiveSticky); + } else { + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + } + } + } + } + Future _invokeMethod(String name, dynamic args) async { debugPrint("Page.$name($args)"); @@ -197,7 +221,6 @@ class _PageControlState extends State with WidgetsBindingObserver { await SystemChrome.setPreferredOrientations(orientations); } break; - default: throw Exception("Unknown Page method: $name"); } @@ -375,11 +398,10 @@ class _PageControlState extends State with WidgetsBindingObserver { } Widget _buildApp(Control control, Widget? home) { - var platform = TargetPlatform.values.firstWhere( - (a) => - a.name.toLowerCase() == - control.getString("platform", "")!.toLowerCase(), - orElse: () => defaultTargetPlatform); + _applyFullScreenFromControl(control); + + var platform = + control.getTargetPlatform("platform", defaultTargetPlatform)!; var widgetsDesign = control.adaptive == true && (platform == TargetPlatform.iOS || platform == TargetPlatform.macOS) diff --git a/packages/flet/lib/src/utils/form_field.dart b/packages/flet/lib/src/utils/form_field.dart index 4cbeda33f4..e5480c543d 100644 --- a/packages/flet/lib/src/utils/form_field.dart +++ b/packages/flet/lib/src/utils/form_field.dart @@ -24,32 +24,22 @@ FormFieldInputBorder? parseFormFieldInputBorder(String? value, TextInputType? parseTextInputType(String? value, [TextInputType? defaultValue]) { - switch (value?.toLowerCase()) { - case "datetime": - return TextInputType.datetime; - case "email": - return TextInputType.emailAddress; - case "multiline": - return TextInputType.multiline; - case "name": - return TextInputType.name; - case "none": - return TextInputType.none; - case "number": - return TextInputType.number; - case "phone": - return TextInputType.phone; - case "streetaddress": - return TextInputType.streetAddress; - case "text": - return TextInputType.text; - case "url": - return TextInputType.url; - case "visiblepassword": - return TextInputType.visiblePassword; - default: - return defaultValue; - } + const typeMap = { + "datetime": TextInputType.datetime, + "email": TextInputType.emailAddress, + "multiline": TextInputType.multiline, + "name": TextInputType.name, + "none": TextInputType.none, + "number": TextInputType.number, + "phone": TextInputType.phone, + "streetaddress": TextInputType.streetAddress, + "text": TextInputType.text, + "url": TextInputType.url, + "visiblepassword": TextInputType.visiblePassword, + "websearch": TextInputType.webSearch, + "twitter": TextInputType.twitter, + }; + return typeMap[value?.toLowerCase()] ?? defaultValue; } InputDecoration buildInputDecoration( @@ -86,7 +76,7 @@ InputDecoration buildInputDecoration( ?.replaceAll("{value_length}", valueLength.toString()) .replaceAll("{max_length}", maxLength?.toString() ?? "None") .replaceAll("{symbols_left}", - "${maxLength == null ? 'None' : (maxLength - (valueLength ?? 0))}"); + "${maxLength == null ? 'None' : (maxLength - (valueLength ?? 0))}"); } // error diff --git a/packages/flet/lib/src/utils/user_fonts_io.dart b/packages/flet/lib/src/utils/io_interface.dart similarity index 56% rename from packages/flet/lib/src/utils/user_fonts_io.dart rename to packages/flet/lib/src/utils/io_interface.dart index fd592a2cf8..e9667eaaaa 100644 --- a/packages/flet/lib/src/utils/user_fonts_io.dart +++ b/packages/flet/lib/src/utils/io_interface.dart @@ -1,8 +1,14 @@ -import 'dart:typed_data'; import 'dart:io'; +import 'dart:typed_data'; + +import 'desktop.dart' as desktop show setWindowFullScreen; Future fetchFontFromFile(String path) async { File file = File(path); Uint8List bytes = await file.readAsBytes(); return ByteData.view(bytes.buffer); } + +Future setWindowFullScreen(bool fullScreen) async { + await desktop.setWindowFullScreen(fullScreen); +} diff --git a/packages/flet/lib/src/utils/user_fonts.dart b/packages/flet/lib/src/utils/user_fonts.dart index ec4c5bb3f4..51746d4fc0 100644 --- a/packages/flet/lib/src/utils/user_fonts.dart +++ b/packages/flet/lib/src/utils/user_fonts.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import '../models/control.dart'; -import 'user_fonts_web.dart' if (dart.library.io) "user_fonts_io.dart"; +import 'web_interface.dart' if (dart.library.io) "io_interface.dart"; class UserFonts { static Map fontLoaders = {}; diff --git a/packages/flet/lib/src/utils/user_fonts_web.dart b/packages/flet/lib/src/utils/user_fonts_web.dart deleted file mode 100644 index 2ff3ba68b7..0000000000 --- a/packages/flet/lib/src/utils/user_fonts_web.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'dart:typed_data'; - -Future fetchFontFromFile(String path) async { - throw UnimplementedError(); -} diff --git a/packages/flet/lib/src/utils/web_interface.dart b/packages/flet/lib/src/utils/web_interface.dart new file mode 100644 index 0000000000..ea2f3d6740 --- /dev/null +++ b/packages/flet/lib/src/utils/web_interface.dart @@ -0,0 +1,15 @@ +import 'dart:typed_data'; + +import 'package:web/web.dart' as web show window; + +Future fetchFontFromFile(String path) async { + throw UnimplementedError(); +} + +Future setWindowFullScreen(bool fullScreen) async { + if (fullScreen) { + web.window.document.documentElement?.requestFullscreen(); + } else { + web.window.document.exitFullscreen(); + } +} diff --git a/sdk/python/examples/controls/page/fullscreen.py b/sdk/python/examples/controls/page/fullscreen.py new file mode 100644 index 0000000000..dbba36565a --- /dev/null +++ b/sdk/python/examples/controls/page/fullscreen.py @@ -0,0 +1,22 @@ +import flet as ft + + +def main(page: ft.Page) -> None: + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + + def handle_fullscreen_change(e: ft.Event[ft.Switch]): + page.full_screen = e.control.value + + page.add( + ft.SafeArea( + ft.Switch( + value=page.full_screen, + label="Toggle Fullscreen", + on_change=handle_fullscreen_change, + ), + ) + ) + + +ft.run(main) diff --git a/sdk/python/packages/flet/docs/controls/page.md b/sdk/python/packages/flet/docs/controls/page.md index 7b7f628a59..68f7d699c1 100644 --- a/sdk/python/packages/flet/docs/controls/page.md +++ b/sdk/python/packages/flet/docs/controls/page.md @@ -29,6 +29,21 @@ Shows how to lock your app to specific device orientations --8<-- "{{ examples }}/app_exit_confirm_dialog.py" ``` +### Toggle Fullscreen + +This example demonstrates how to toggle fullscreen mode in a Flet application. + +/// admonition + type: note +[`Page.full_screen`][flet.] is cross-platform (mobile, desktop and web), whereas +[`Page.window.full_screen`][flet.Window.full_screen] is desktop only. +/// + + +```python +--8<-- "{{ examples }}/fullscreen.py" +``` + ### Hidden app window on startup ```python diff --git a/sdk/python/packages/flet/src/flet/controls/material/textfield.py b/sdk/python/packages/flet/src/flet/controls/material/textfield.py index 31326af67f..ed951e2f8d 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/textfield.py +++ b/sdk/python/packages/flet/src/flet/controls/material/textfield.py @@ -30,29 +30,168 @@ class KeyboardType(Enum): + """ + The type of information for which to optimize the text input control. + + On Android, behavior may vary across device and keyboard provider. + """ + NONE = "none" + """ + Prevents the OS from showing the on-screen virtual keyboard. + """ + TEXT = "text" + """ + Optimized for textual information. + + Requests the default platform keyboard. + """ + MULTILINE = "multiline" + """ + Optimized for multiline textual information. + + Requests the default platform keyboard, but accepts newlines when the + enter key is pressed. This is the input type used for all multiline text + fields. + """ + NUMBER = "number" + """ + Optimized for unsigned numerical information without a decimal point. + + Requests a default keyboard with ready access to the number keys. + """ + PHONE = "phone" + """ + Optimized for telephone numbers. + + Requests a keyboard with ready access to the number keys, `"*"`, and `"#"`. + """ + DATETIME = "datetime" + """ + Optimized for date and time information. + + - On iOS, requests the default keyboard. + - On Android, requests a keyboard with ready + access to the number keys, `":"`, and `"-"`. + """ + EMAIL = "email" + """ + Optimized for email addresses. + + Requests a keyboard with ready access to the `"@"` and `"."` keys. + """ + URL = "url" + """ + Optimized for URLs. + + Requests a keyboard with ready access to the `"/"` and `"."` keys. + """ + VISIBLE_PASSWORD = "visiblePassword" + """ + Optimized for passwords that are visible to the user. + + Requests a keyboard with ready access to both letters and numbers. + """ + NAME = "name" + """ + Optimized for a person's name. + + - On iOS, requests the [UIKeyboardType.namePhonePad](https://developer.apple.com/documentation/uikit/uikeyboardtype/namephonepad) + keyboard, a keyboard optimized for entering a person’s name or phone number. + Does not support auto-capitalization. + - On Android, requests a keyboard optimized for + [TYPE_TEXT_VARIATION_PERSON_NAME](https://developer.android.com/reference/android/text/InputType#TYPE_TEXT_VARIATION_PERSON_NAME). + """ # noqa: E501 + STREET_ADDRESS = "streetAddress" + """ + Optimized for postal mailing addresses. + + - On iOS, requests the default keyboard. + - On Android, requests a keyboard optimized for + [TYPE_TEXT_VARIATION_POSTAL_ADDRESS](https://developer.android.com/reference/android/text/InputType#TYPE_TEXT_VARIATION_POSTAL_ADDRESS). + """ # noqa: E501 + + WEB_SEARCH = "webSearch" + """ + Optimized for web searches. + + Requests a keyboard that includes keys useful for web searches as well as URLs. + + - On iOS, requests a default keyboard with ready access to the `"."` key. + In contrast to [`URL`][(c).], a space bar is available. + - On Android this is remapped to the [`URL`][(c).] keyboard type as it always + shows a space bar. + """ + + TWITTER = "twitter" + """ + Optimized for social media. + + Requests a keyboard that includes keys useful for handles and tags. + + - On iOS, requests a default keyboard with ready access to the `"@"` and `"#"` keys. + - On Android this is remapped to the [`EMAIL`][(c).] keyboard type as it + always shows the `"@"` key. + """ class TextCapitalization(Enum): + """ + Configures how the platform keyboard will select an uppercase or + lowercase keyboard. + + Only supports text keyboards, other keyboard types will ignore this + configuration. Capitalization is locale-aware. + """ + CHARACTERS = "characters" + """ + Uppercase keyboard for each character. + + Info: + Corresponds to `InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS` on Android, and + `UITextAutocapitalizationTypeAllCharacters` on iOS. + """ + WORDS = "words" + """ + Uppercase keyboard for the first letter of each word. + + Info: + Corresponds to `InputType.TYPE_TEXT_FLAG_CAP_WORDS` on Android, and + `UITextAutocapitalizationTypeWords` on iOS. + """ + SENTENCES = "sentences" + """ + Uppercase keyboard for the first letter of each sentence. + + Info: + Corresponds to `InputType.TYPE_TEXT_FLAG_CAP_SENTENCES` on Android, and + `UITextAutocapitalizationTypeSentences` on iOS. + """ + + NONE = "none" + """ + Lowercase keyboard. + """ @dataclass class InputFilter: """ - `InputFilter` class. + An input filter that uses a regular expression to allow or deny/block certain + patterns in the input. """ regex_string: str @@ -79,7 +218,7 @@ class InputFilter: """ Whether this regular expression matches multiple lines. - If the regexp does match multiple lines, the "^" and "$" characters match the + If the regexp does match multiple lines, the `"^"` and `"$"` characters match the beginning and end of lines. If not, the characters match the beginning and end of the input. """ @@ -100,10 +239,10 @@ class InputFilter: dot_all: bool = False """ - Whether "." in this regular expression matches line terminators. + Whether `"."` in this regular expression matches line terminators. - When false, the "." character matches a single character, unless that character - terminates a line. When true, then the "." character will match any single + When false, the `"."` character matches a single character, unless that character + terminates a line. When true, then the `"."` character will match any single character including line terminators. This feature is distinct from `multiline`. They affect the behavior of different diff --git a/sdk/python/packages/flet/src/flet/controls/page.py b/sdk/python/packages/flet/src/flet/controls/page.py index 6c358a1974..7edbf99dd7 100644 --- a/sdk/python/packages/flet/src/flet/controls/page.py +++ b/sdk/python/packages/flet/src/flet/controls/page.py @@ -344,6 +344,16 @@ class Page(BasePage): Usage example [here](https://flet.dev/docs/cookbook/fonts#importing-fonts). """ + full_screen: bool = False + """ + Whether the app should run in full screen mode. + + Difference to `Window.full_screen`: + This property is cross-platform, in contrast to [`full_screen`][flet.Window.] + property of [`Page.window`][flet.], which is only + available on desktop platforms (Windows, macOS and Linux). + """ + on_platform_brightness_change: Optional[ EventHandler[PlatformBrightnessChangeEvent] ] = None