diff --git a/client/integration_test/flutter_tester.dart b/client/integration_test/flutter_tester.dart index 47b368b4f7..6affc6bd79 100644 --- a/client/integration_test/flutter_tester.dart +++ b/client/integration_test/flutter_tester.dart @@ -71,20 +71,21 @@ class FlutterWidgetTester implements Tester { } @override - Future tap(TestFinder finder) => - _tester.tap((finder as FlutterTestFinder).raw); + Future tap(TestFinder finder, int finderIndex) => + _tester.tap((finder as FlutterTestFinder).raw.at(finderIndex)); @override - Future longPress(TestFinder finder) => - _tester.longPress((finder as FlutterTestFinder).raw); - + Future longPress(TestFinder finder, int finderIndex) => + _tester.longPress((finder as FlutterTestFinder).raw.at(finderIndex)); @override - Future enterText(TestFinder finder, String text) => - _tester.enterText((finder as FlutterTestFinder).raw, text); + Future enterText(TestFinder finder, int finderIndex, String text) => + _tester.enterText( + (finder as FlutterTestFinder).raw.at(finderIndex), text); @override - Future mouseHover(TestFinder finder) async { - final center = _tester.getCenter((finder as FlutterTestFinder).raw); + Future mouseHover(TestFinder finder, int finderIndex) async { + final center = + _tester.getCenter((finder as FlutterTestFinder).raw.at(finderIndex)); final gesture = await _tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); await gesture.moveTo(center); diff --git a/packages/flet/lib/src/controls/time_picker.dart b/packages/flet/lib/src/controls/time_picker.dart index 3aaea38596..115c4c3033 100644 --- a/packages/flet/lib/src/controls/time_picker.dart +++ b/packages/flet/lib/src/controls/time_picker.dart @@ -25,6 +25,7 @@ class _TimePickerControlState extends State { var open = widget.control.getBool("open", false)!; var value = widget.control.getTimeOfDay("value", TimeOfDay.now())!; + var hourFormat = widget.control.getString("hour_format"); void onClosed(TimeOfDay? timeValue) { widget.control.updateProperties({"_open": false}, python: false); @@ -44,15 +45,22 @@ class _TimePickerControlState extends State { hourLabelText: widget.control.getString("hour_label_text"), minuteLabelText: widget.control.getString("minute_label_text"), errorInvalidText: widget.control.getString("error_invalid_text"), - initialEntryMode: widget.control.getTimePickerEntryMode( - "time_picker_entry_mode", TimePickerEntryMode.dial)!, + initialEntryMode: widget.control + .getTimePickerEntryMode("entry_mode", TimePickerEntryMode.dial)!, orientation: widget.control.getOrientation("orientation"), onEntryModeChanged: (TimePickerEntryMode mode) { - widget.control.triggerEvent("entry_mode_change", mode.name); + widget.control.updateProperties({"entry_mode": mode.name}); + widget.control + .triggerEvent("entry_mode_change", {"entry_mode": mode.name}); }, ); - return dialog; + final hourFormatMap = {"h12": false, "h24": true, "system": null}; + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(alwaysUse24HourFormat: hourFormatMap[hourFormat]), + child: dialog, + ); } if (open && (open != lastOpen)) { diff --git a/packages/flet/lib/src/flet_backend.dart b/packages/flet/lib/src/flet_backend.dart index 86a21a1dbd..a8c74faf27 100644 --- a/packages/flet/lib/src/flet_backend.dart +++ b/packages/flet/lib/src/flet_backend.dart @@ -77,11 +77,13 @@ class FletBackend extends ChangeNotifier { }; Brightness platformBrightness = Brightness.light; PageMediaData media = PageMediaData( - padding: PaddingData(EdgeInsets.zero), - viewPadding: PaddingData(EdgeInsets.zero), - viewInsets: PaddingData(EdgeInsets.zero), - devicePixelRatio: 0, - orientation: Orientation.portrait); + padding: PaddingData(EdgeInsets.zero), + viewPadding: PaddingData(EdgeInsets.zero), + viewInsets: PaddingData(EdgeInsets.zero), + devicePixelRatio: 0, + orientation: Orientation.portrait, + alwaysUse24HourFormat: false, + ); TargetPlatform platform = defaultTargetPlatform; late Control _page; diff --git a/packages/flet/lib/src/protocol/page_media_data.dart b/packages/flet/lib/src/protocol/page_media_data.dart index 7cb5c561ca..52b63f8fce 100644 --- a/packages/flet/lib/src/protocol/page_media_data.dart +++ b/packages/flet/lib/src/protocol/page_media_data.dart @@ -7,6 +7,7 @@ class PageMediaData extends Equatable { final PaddingData viewInsets; final double devicePixelRatio; final Orientation orientation; + final bool alwaysUse24HourFormat; const PageMediaData({ required this.padding, @@ -14,6 +15,7 @@ class PageMediaData extends Equatable { required this.viewInsets, required this.devicePixelRatio, required this.orientation, + required this.alwaysUse24HourFormat, }); Map toMap() => { @@ -22,11 +24,18 @@ class PageMediaData extends Equatable { 'view_insets': viewInsets.toMap(), 'device_pixel_ratio': devicePixelRatio, 'orientation': orientation.name, + 'always_use_24_hour_format': alwaysUse24HourFormat, }; @override - List get props => - [padding, viewPadding, viewInsets, devicePixelRatio, orientation]; + List get props => [ + padding, + viewPadding, + viewInsets, + devicePixelRatio, + orientation, + alwaysUse24HourFormat, + ]; } class PaddingData extends Equatable { diff --git a/packages/flet/lib/src/services/tester.dart b/packages/flet/lib/src/services/tester.dart index cc5d011ae4..3a066ed03b 100644 --- a/packages/flet/lib/src/services/tester.dart +++ b/packages/flet/lib/src/services/tester.dart @@ -49,7 +49,7 @@ class TesterService extends FletService { ? control.backend.globalKeys[controlKey.toString()] : ValueKey(controlKey.value); if (key == null) { - throw Exception("Scroll key not found: $key"); + throw Exception("Key not found: $key"); } var finder = control.backend.tester!.findByKey(key); _finders[finder.id] = finder; @@ -74,27 +74,29 @@ class TesterService extends FletService { return await control.backend.tester!.takeScreenshot(args["name"]); case "tap": - var finder = _finders[args["id"]]; + var finder = _finders[args["finder_id"]]; if (finder != null) { - await control.backend.tester!.tap(finder); + await control.backend.tester!.tap(finder, args["finder_index"]); } case "long_press": - var finder = _finders[args["id"]]; + var finder = _finders[args["finder_id"]]; if (finder != null) { - await control.backend.tester!.longPress(finder); + await control.backend.tester!.longPress(finder, args["finder_index"]); } case "enter_text": - var finder = _finders[args["id"]]; + var finder = _finders[args["finder_id"]]; if (finder != null) { - await control.backend.tester!.enterText(finder, args["text"]); + await control.backend.tester! + .enterText(finder, args["finder_index"], args["text"]); } case "mouse_hover": - var finder = _finders[args["id"]]; + var finder = _finders[args["finder_id"]]; if (finder != null) { - await control.backend.tester!.mouseHover(finder); + await control.backend.tester! + .mouseHover(finder, args["finder_index"]); } case "teardown": diff --git a/packages/flet/lib/src/testing/tester.dart b/packages/flet/lib/src/testing/tester.dart index 93267e7dc4..eaabbba0dc 100644 --- a/packages/flet/lib/src/testing/tester.dart +++ b/packages/flet/lib/src/testing/tester.dart @@ -13,10 +13,10 @@ abstract class Tester { TestFinder findByTooltip(String value); TestFinder findByIcon(IconData icon); Future takeScreenshot(String name); - Future tap(TestFinder finder); - Future longPress(TestFinder finder); - Future enterText(TestFinder finder, String text); - Future mouseHover(TestFinder finder); + Future tap(TestFinder finder, int finderIndex); + Future longPress(TestFinder finder, int finderIndex); + Future enterText(TestFinder finder, int finderIndex, String text); + Future mouseHover(TestFinder finder, int finderIndex); void teardown(); Future waitForTeardown(); } diff --git a/packages/flet/lib/src/widgets/page_media.dart b/packages/flet/lib/src/widgets/page_media.dart index d814bdc648..16429cb1c7 100644 --- a/packages/flet/lib/src/widgets/page_media.dart +++ b/packages/flet/lib/src/widgets/page_media.dart @@ -60,6 +60,7 @@ class _PageMediaState extends State { viewInsets: PaddingData(MediaQuery.viewInsetsOf(context)), devicePixelRatio: MediaQuery.devicePixelRatioOf(context), orientation: MediaQuery.orientationOf(context), + alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context), ); if (newMedia != backend.media || !pageSizeUpdated) { diff --git a/sdk/python/examples/controls/time_picker/basic.py b/sdk/python/examples/controls/time_picker/basic.py index 99c9743108..4fe2da01e9 100644 --- a/sdk/python/examples/controls/time_picker/basic.py +++ b/sdk/python/examples/controls/time_picker/basic.py @@ -1,24 +1,27 @@ -import flet as ft from datetime import time +import flet as ft + def main(page: ft.Page): page.horizontal_alignment = ft.CrossAxisAlignment.CENTER def handle_change(e: ft.Event[ft.TimePicker]): - page.add(ft.Text(f"TimePicker change: {time_picker.value}")) + selection.value = f"Selection: {time_picker.value}" + page.show_dialog(ft.SnackBar(f"TimePicker change: {time_picker.value}")) def handle_dismissal(e: ft.Event[ft.TimePicker]): - page.add(ft.Text(f"TimePicker dismissed: {time_picker.value}")) + page.show_dialog(ft.SnackBar("TimePicker dismissed!")) def handle_entry_mode_change(e: ft.TimePickerEntryModeChangeEvent): - page.add(ft.Text(f"TimePicker Entry mode changed to {e.entry_mode}")) + page.show_dialog(ft.SnackBar(f"Entry mode changed: {time_picker.entry_mode}")) time_picker = ft.TimePicker( - value=time(1, 2), + value=time(hour=19, minute=30), confirm_text="Confirm", error_invalid_text="Time out of range", help_text="Pick your time slot", + entry_mode=ft.TimePickerEntryMode.DIAL, on_change=handle_change, on_dismiss=handle_dismissal, on_entry_mode_change=handle_entry_mode_change, @@ -28,8 +31,9 @@ def handle_entry_mode_change(e: ft.TimePickerEntryModeChangeEvent): ft.Button( content="Pick time", icon=ft.Icons.TIME_TO_LEAVE, - on_click=lambda _: page.show_dialog(time_picker), - ) + on_click=lambda: page.show_dialog(time_picker), + ), + selection := ft.Text(weight=ft.FontWeight.BOLD), ) diff --git a/sdk/python/examples/controls/time_picker/hour_formats.py b/sdk/python/examples/controls/time_picker/hour_formats.py new file mode 100644 index 0000000000..8013f3ca0f --- /dev/null +++ b/sdk/python/examples/controls/time_picker/hour_formats.py @@ -0,0 +1,70 @@ +from datetime import time + +import flet as ft + + +def main(page: ft.Page): + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + + def get_system_hour_format(): + """Returns the current system's hour format.""" + return "24h" if page.media.always_use_24_hour_format else "12h" + + def format_time(value: time) -> str: + """Returns a formatted time string based on TimePicker and system settings.""" + use_24h = time_picker.hour_format == ft.TimePickerHourFormat.H24 or ( + time_picker.hour_format == ft.TimePickerHourFormat.SYSTEM + and page.media.always_use_24_hour_format + ) + return value.strftime("%H:%M" if use_24h else "%I:%M %p") + + def handle_change(e: ft.Event[ft.TimePicker]): + selection.value = f"Selection: {format_time(time_picker.value)}" + + time_picker = ft.TimePicker( + value=time(hour=19, minute=30), + help_text="Choose your meeting time", + on_change=handle_change, + ) + + def open_picker(e: ft.Event[ft.Button]): + choice = format_dropdown.value + hour_format_map = { + "system": ft.TimePickerHourFormat.SYSTEM, + "12h": ft.TimePickerHourFormat.H12, + "24h": ft.TimePickerHourFormat.H24, + } + time_picker.hour_format = hour_format_map[choice] + page.show_dialog(time_picker) + + page.add( + ft.Row( + alignment=ft.MainAxisAlignment.CENTER, + controls=[ + format_dropdown := ft.Dropdown( + label="Hour format", + value="system", + width=260, + key="dd", + options=[ + ft.DropdownOption( + key="system", + text=f"System default ({get_system_hour_format()})", + ), + ft.DropdownOption(key="12h", text="12-hour clock"), + ft.DropdownOption(key="24h", text="24-hour clock"), + ], + ), + ft.Button( + "Open TimePicker", + icon=ft.Icons.SCHEDULE, + on_click=open_picker, + ), + ], + ), + selection := ft.Text(weight=ft.FontWeight.BOLD), + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/packages/flet/docs/controls/timepicker.md b/sdk/python/packages/flet/docs/controls/timepicker.md index 60d512cfde..e1b1c28887 100644 --- a/sdk/python/packages/flet/docs/controls/timepicker.md +++ b/sdk/python/packages/flet/docs/controls/timepicker.md @@ -16,7 +16,15 @@ example_images: ../test-images/examples/material/golden/macos/time_picker --8<-- "{{ examples }}/basic.py" ``` -{{ image(example_images + "/basic.png", alt="basic", width="80%") }} +{{ image(example_images + "/basic.png", width="80%") }} + +### Hour Formats + +```python +--8<-- "{{ examples }}/hour_formats.py" +``` + +{{ image(example_images + "/hour_formats.gif", width="80%") }} {{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/timepickerhourformat.md b/sdk/python/packages/flet/docs/types/timepickerhourformat.md new file mode 100644 index 0000000000..860e231e99 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/timepickerhourformat.md @@ -0,0 +1 @@ +{{ class_all_options("flet.TimePickerHourFormat", separate_signature=False) }} diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/dropdown/basic_2.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/dropdown/basic_2.png new file mode 100644 index 0000000000..d79b18764a Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/dropdown/basic_2.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/basic.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/basic.png new file mode 100644 index 0000000000..49180bfb76 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/basic.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/hour_format_12.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/hour_format_12.png new file mode 100644 index 0000000000..d21b6630ea Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/hour_format_12.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/hour_format_24.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/hour_format_24.png new file mode 100644 index 0000000000..dab4e6d3c8 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/hour_format_24.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/time_picker_basic.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/time_picker_basic.png deleted file mode 100644 index 643f85f788..0000000000 Binary files a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/time_picker/time_picker_basic.png and /dev/null differ diff --git a/sdk/python/packages/flet/integration_tests/controls/material/test_dropdown.py b/sdk/python/packages/flet/integration_tests/controls/material/test_dropdown.py index 1a23beb0fb..29864437df 100644 --- a/sdk/python/packages/flet/integration_tests/controls/material/test_dropdown.py +++ b/sdk/python/packages/flet/integration_tests/controls/material/test_dropdown.py @@ -48,6 +48,18 @@ async def test_basic(flet_app: ftt.FletTestApp, request): ), ) + blue_option = await flet_app.tester.find_by_text("blue") + assert blue_option.count == 2 + + await flet_app.tester.tap(blue_option.last) + await flet_app.tester.pump_and_settle() + flet_app.assert_screenshot( + "basic_2", + await flet_app.page.take_screenshot( + pixel_ratio=flet_app.screenshots_pixel_ratio + ), + ) + @pytest.mark.asyncio(loop_scope="function") async def test_theme(flet_app: ftt.FletTestApp, request): diff --git a/sdk/python/packages/flet/integration_tests/controls/material/test_time_picker.py b/sdk/python/packages/flet/integration_tests/controls/material/test_time_picker.py index 9b63c5da5e..efacbda781 100644 --- a/sdk/python/packages/flet/integration_tests/controls/material/test_time_picker.py +++ b/sdk/python/packages/flet/integration_tests/controls/material/test_time_picker.py @@ -1,27 +1,76 @@ import datetime import pytest +import pytest_asyncio import flet as ft import flet.testing as ftt -@pytest.mark.asyncio(loop_scope="module") -async def test_time_picker_basic(flet_app: ftt.FletTestApp, request): +# Create a new flet_app instance for each test method +@pytest_asyncio.fixture(scope="function", autouse=True) +def flet_app(flet_app_function): + return flet_app_function + + +@pytest.mark.asyncio(loop_scope="function") +async def test_basic(flet_app: ftt.FletTestApp, request): + flet_app.page.enable_screenshots = True + flet_app.resize_page(600, 450) + time_picker = ft.TimePicker( confirm_text="Confirm", error_invalid_text="Time out of range", help_text="Pick your time slot", - value=datetime.time(hour=1, minute=30, second=30), + value=datetime.time(hour=19, minute=30), + hour_format=ft.TimePickerHourFormat.H24, + ) + flet_app.page.show_dialog(time_picker) + flet_app.page.update() + await flet_app.tester.pump_and_settle() + + flet_app.assert_screenshot( + request.node.name, + await flet_app.page.take_screenshot( + pixel_ratio=flet_app.screenshots_pixel_ratio + ), ) + + +@pytest.mark.asyncio(loop_scope="function") +async def test_hour_format_12(flet_app: ftt.FletTestApp, request): flet_app.page.enable_screenshots = True - flet_app.resize_page(400, 600) + flet_app.resize_page(600, 450) + + time_picker = ft.TimePicker( + value=datetime.time(hour=19, minute=30), + hour_format=ft.TimePickerHourFormat.H12, + ) flet_app.page.show_dialog(time_picker) flet_app.page.update() await flet_app.tester.pump_and_settle() + flet_app.assert_screenshot( + request.node.name, + await flet_app.page.take_screenshot( + pixel_ratio=flet_app.screenshots_pixel_ratio + ), + ) + +@pytest.mark.asyncio(loop_scope="function") +async def test_hour_format_24(flet_app: ftt.FletTestApp, request): + flet_app.page.enable_screenshots = True + flet_app.resize_page(600, 450) + + time_picker = ft.TimePicker( + value=datetime.time(hour=19, minute=30), + hour_format=ft.TimePickerHourFormat.H24, + ) + flet_app.page.show_dialog(time_picker) + flet_app.page.update() + await flet_app.tester.pump_and_settle() flet_app.assert_screenshot( - "time_picker_basic", + request.node.name, await flet_app.page.take_screenshot( pixel_ratio=flet_app.screenshots_pixel_ratio ), diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/basic.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/basic.png index d6140f866d..602ff9a1da 100644 Binary files a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/basic.png and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/basic.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats.gif b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats.gif new file mode 100644 index 0000000000..e5b76f00af Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats.gif differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_1.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_1.png new file mode 100644 index 0000000000..96ed0eabd3 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_1.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_10.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_10.png new file mode 100644 index 0000000000..ac5e67bd39 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_10.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_11.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_11.png new file mode 100644 index 0000000000..b45a0baec1 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_11.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_2.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_2.png new file mode 100644 index 0000000000..e7882b74f1 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_2.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_3.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_3.png new file mode 100644 index 0000000000..85f0ee73e9 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_3.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_4.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_4.png new file mode 100644 index 0000000000..3ce0e134b3 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_4.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_5.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_5.png new file mode 100644 index 0000000000..ab37911926 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_5.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_6.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_6.png new file mode 100644 index 0000000000..fa5bc37bdc Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_6.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_7.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_7.png new file mode 100644 index 0000000000..ab37911926 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_7.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_8.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_8.png new file mode 100644 index 0000000000..a5c40b5a4a Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_8.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_9.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_9.png new file mode 100644 index 0000000000..f72778ad23 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/hour_formats_9.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/image_for_docs.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/image_for_docs.png index 9c91c90865..ad6744fdbc 100644 Binary files a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/image_for_docs.png and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/time_picker/image_for_docs.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/test_time_picker.py b/sdk/python/packages/flet/integration_tests/examples/material/test_time_picker.py index 35b367398e..e95a846388 100644 --- a/sdk/python/packages/flet/integration_tests/examples/material/test_time_picker.py +++ b/sdk/python/packages/flet/integration_tests/examples/material/test_time_picker.py @@ -4,20 +4,23 @@ import flet as ft import flet.testing as ftt -from examples.controls.time_picker import basic +from examples.controls.time_picker import basic, hour_formats + +# Note: CI macOS runner uses a 12-hour (AM / PM) time format by default. @pytest.mark.asyncio(loop_scope="function") async def test_image_for_docs(flet_app_function: ftt.FletTestApp, request): flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT - tp = ft.TimePicker( - value=time(1, 2), - time_picker_entry_mode=ft.TimePickerEntryMode.INPUT_ONLY, - open=True, - ) flet_app_function.page.enable_screenshots = True - flet_app_function.resize_page(400, 300) - flet_app_function.page.add(tp) + flet_app_function.resize_page(600, 400) + + time_picker = ft.TimePicker( + value=time(hour=19, minute=30), + hour_format=ft.TimePickerHourFormat.H12, + ) + flet_app_function.page.show_dialog(time_picker) + flet_app_function.page.update() await flet_app_function.tester.pump_and_settle() flet_app_function.assert_screenshot( @@ -36,14 +39,97 @@ async def test_image_for_docs(flet_app_function: ftt.FletTestApp, request): @pytest.mark.asyncio(loop_scope="function") async def test_basic(flet_app_function: ftt.FletTestApp): flet_app_function.page.enable_screenshots = True - flet_app_function.resize_page(350, 300) - button = await flet_app_function.tester.find_by_icon(ft.Icons.TIME_TO_LEAVE) - await flet_app_function.tester.tap(button) + flet_app_function.resize_page(600, 400) + + # open picker + await flet_app_function.tester.tap( + await flet_app_function.tester.find_by_icon(ft.Icons.TIME_TO_LEAVE) + ) flet_app_function.page.update() await flet_app_function.tester.pump_and_settle() + flet_app_function.assert_screenshot( "basic", await flet_app_function.page.take_screenshot( pixel_ratio=flet_app_function.screenshots_pixel_ratio ), ) + + +@pytest.mark.parametrize( + "flet_app_function", [{"flet_app_main": hour_formats.main}], indirect=True +) +@pytest.mark.asyncio(loop_scope="function") +async def test_hour_formats(flet_app_function: ftt.FletTestApp): + counter = 0 + images = [] + + async def _snap(): + nonlocal counter + counter += 1 + name = f"hour_formats_{counter}" + images.append(name) + flet_app_function.assert_screenshot( + name, + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), + ) + + async def _settle(): + await flet_app_function.tester.pump_and_settle() + + async def _open_picker(): + await flet_app_function.tester.tap( + await flet_app_function.tester.find_by_icon(ft.Icons.SCHEDULE) + ) + await _settle() + await _snap() + + async def _close_picker(): + await flet_app_function.tester.tap( + await flet_app_function.tester.find_by_text("OK") + ) + await _settle() + await _snap() + + async def _select_clock(label: str): + dd = await flet_app_function.tester.find_by_key("dd") + await flet_app_function.tester.tap(dd) + await _settle() + await flet_app_function.tester.tap( + (await flet_app_function.tester.find_by_text(label)).last + ) + await _settle() + await flet_app_function.tester.tap(dd) + await _settle() + await _snap() + await flet_app_function.tester.tap(dd) + await _settle() + await _snap() + + flet_app_function.page.enable_screenshots = True + flet_app_function.resize_page(600, 450) + flet_app_function.page.update() + await _settle() + + # initial state + await _snap() + + # picker open/close on default + await _open_picker() + await _close_picker() + + # switch to 12h, then open/close + await _select_clock("12-hour clock") + await _open_picker() + await _close_picker() + + # switch to 24h, then open/close + await _select_clock("24-hour clock") + await _open_picker() + await _close_picker() + + flet_app_function.create_gif( + image_names=images, output_name="hour_formats", duration=2000 + ) diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index e6acfdbdf8..2077f885e5 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -820,6 +820,7 @@ nav: - ThemeMode: types/thememode.md - TileAffinity: types/tileaffinity.md - TimePickerEntryMode: types/timepickerentrymode.md + - TimePickerHourFormat: types/timepickerhourformat.md - TooltipTriggerMode: types/tooltiptriggermode.md - UrlTarget: types/urltarget.md - VerticalAlignment: types/verticalalignment.md diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index c83ab4f6bc..788f6ada3f 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -380,6 +380,7 @@ TimePicker, TimePickerEntryMode, TimePickerEntryModeChangeEvent, + TimePickerHourFormat, ) from flet.controls.material.tooltip import Tooltip, TooltipTriggerMode, TooltipValue from flet.controls.material.vertical_divider import VerticalDivider @@ -950,6 +951,7 @@ "TimePicker", "TimePickerEntryMode", "TimePickerEntryModeChangeEvent", + "TimePickerHourFormat", "TimePickerTheme", "Tooltip", "TooltipTheme", diff --git a/sdk/python/packages/flet/src/flet/controls/base_page.py b/sdk/python/packages/flet/src/flet/controls/base_page.py index e0dff8dced..2136fd9be8 100644 --- a/sdk/python/packages/flet/src/flet/controls/base_page.py +++ b/sdk/python/packages/flet/src/flet/controls/base_page.py @@ -61,7 +61,7 @@ class PageMediaData: view_padding: Padding """ - Similar to `padding`, but includes padding that is always reserved + Similar to [`padding`][(c).], but includes padding that is always reserved (even when the system UI is hidden). """ @@ -81,6 +81,22 @@ class PageMediaData: The orientation of the page. """ + always_use_24_hour_format: bool = False + """ + Whether to use 24-hour format when formatting time. + + Note: + The behavior of this flag is different across platforms: + + - On Android this flag is reported directly from the user settings called + "Use 24-hour format". It applies to any locale used by the application, + whether it is the system-wide locale, or the custom locale set by the + application. + - On iOS this flag is set to true when the user setting called "24-Hour Time" + is set or the system-wide locale's default uses 24-hour + formatting. + """ + @dataclass class PageResizeEvent(Event["BasePage"]): @@ -201,6 +217,7 @@ def handle_page_size(e): view_insets=Padding.zero(), device_pixel_ratio=0, orientation=Orientation.PORTRAIT, + always_use_24_hour_format=False, ) ) """ @@ -317,17 +334,17 @@ def show_dialog(self, dialog: DialogControl) -> None: Displays a dialog and manages its dismissal lifecycle. This method adds the specified `dialog` to the active dialog stack - and renders it on the page. If the dialog is already open, an exception + and renders it on the page. If the dialog is already open, a `RuntimeError` is raised. The [`on_dismiss`][flet.DialogControl.] handler of the dialog is temporarily wrapped to ensure the dialog is removed from the stack and its dismissal event is triggered appropriately. Args: - dialog: The dialog instance to display. Must not already be open. + dialog: The dialog instance to display. Must not yet be open. Raises: - Exception: If the specified dialog is already open. + RuntimeError: If the specified dialog is already open. """ if dialog in self._dialogs.controls: raise RuntimeError("Dialog is already opened") diff --git a/sdk/python/packages/flet/src/flet/controls/material/time_picker.py b/sdk/python/packages/flet/src/flet/controls/material/time_picker.py index 88eff6bd31..c990baaec6 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/time_picker.py +++ b/sdk/python/packages/flet/src/flet/controls/material/time_picker.py @@ -1,4 +1,4 @@ -from dataclasses import field +from dataclasses import dataclass, field from datetime import datetime, time from enum import Enum from typing import Optional @@ -15,18 +15,76 @@ Orientation, ) -__all__ = ["TimePicker", "TimePickerEntryMode", "TimePickerEntryModeChangeEvent"] +__all__ = [ + "TimePicker", + "TimePickerEntryMode", + "TimePickerEntryModeChangeEvent", + "TimePickerHourFormat", +] + + +class TimePickerHourFormat(Enum): + """ + Defines the hour format for the [`TimePicker`][flet.] control. + """ + + SYSTEM = "system" + """Respect the host platform setting.""" + + H12 = "h12" + """A 12-hour clock with an AM/PM selector.""" + + H24 = "h24" + """A 24-hour clock without an AM/PM selector.""" class TimePickerEntryMode(Enum): + """ + Interactive input mode of the [`TimePicker`][flet.] dialog. + + In [`DIAL`][(c).] mode, a clock dial is displayed, and the user taps or drags + the time they wish to select. In [`INPUT`][(c).] mode, [`TextField`][flet.]s are + displayed and the user types in the time they wish to select. + """ + DIAL = "dial" + """ + User picks time from a clock dial. + + Can switch to [`INPUT`][(c).] by activating a mode button in the time picker dialog. + """ + INPUT = "input" + """ + User can input the time by typing it into text fields. + + Can switch to [`DIAL`][(c).] by activating a mode button in the time picker dialog. + """ + DIAL_ONLY = "dialOnly" + """ + User can only pick time from a clock dial. + + There is no user interface to switch to another mode. + """ + INPUT_ONLY = "inputOnly" + """ + User can only input the time by typing it into text fields. + + There is no user interface to switch to another mode. + """ +@dataclass class TimePickerEntryModeChangeEvent(Event["TimePicker"]): - entry_mode: Optional[TimePickerEntryMode] + """ + Represents the event triggered when the + entry mode of a [`TimePicker`][flet.] changes. + """ + + entry_mode: TimePickerEntryMode + """The new entry mode.""" @control("TimePicker") @@ -34,24 +92,27 @@ class TimePicker(DialogControl): """ A Material-style time picker dialog. - Can be opened by calling `page.show_dialog()` method. + Can be opened by calling the + [`Page.show_dialog()`][flet.Page.show_dialog] method. - Depending on the `time_picker_entry_mode`, it will show either a Dial or + Depending on the [`entry_mode`][(c).], it will show either a Dial or an Input (hour and minute text fields) for picking a time. + Example: ```python ft.TimePicker( - value=time(1, 2), - time_picker_entry_mode=ft.TimePickerEntryMode.INPUT_ONLY, open=True, + value=time(1, 2), + entry_mode=ft.TimePickerEntryMode.INPUT_ONLY, ) ``` """ value: Optional[time] = field(default_factory=lambda: datetime.now().time()) """ - The selected time that the picker should display. The default value is equal - to the current time. + The selected time that this picker should display. + + The default value is equal to the current time. """ modal: bool = False @@ -59,25 +120,25 @@ class TimePicker(DialogControl): TBD """ - time_picker_entry_mode: Optional[TimePickerEntryMode] = None + entry_mode: Optional[TimePickerEntryMode] = None """ - The initial mode of time entry method for the time picker dialog. + The initial mode of time entry method for this picker. - Defaults to `TimePickerEntryMode.DIAL`. + Defaults to [`TimePickerEntryMode.DIAL`][flet.]. """ hour_label_text: Optional[str] = None """ The text that is displayed below the hour input text field. - The default value is "Hour". + The default value is `"Hour"`. """ minute_label_text: Optional[str] = None """ The text that is displayed below the minute input text field. - The default value is "Minute". + The default value is `"Minute"`. """ help_text: Optional[str] = None @@ -85,23 +146,29 @@ class TimePicker(DialogControl): The text that is displayed at the top of the header. This is used to indicate to the user what they are selecting a time for. - The default value is "Enter time". + The default value is `"Enter time"`. """ cancel_text: Optional[str] = None """ - The text that is displayed on the cancel button. The default value is "Cancel". + The text that is displayed on the cancel button. + + The default value is `"Cancel"`. """ confirm_text: Optional[str] = None """ - The text that is displayed on the confirm button. The default value is "OK". + The text that is displayed on the confirm button. + + The default value is `"OK"`. """ error_invalid_text: Optional[str] = None """ The error message displayed below the input text field if the input is not a - valid hour/minute. The default value is "Enter a valid time". + valid hour/minute. + + The default value is `"Enter a valid time"`. """ orientation: Optional[Orientation] = None @@ -116,11 +183,19 @@ class TimePicker(DialogControl): on_change: Optional[ControlEventHandler["TimePicker"]] = None """ - Called when user clicks confirm button. `value` property is updated with selected - time. `e.data` also contains the selected time. + Called when user clicks confirm button. + + [`value`][(c).] property is updated with selected time. + Additionally, the [`data`][flet.Event.] property of the event handler argument + also contains the selected time. """ on_entry_mode_change: Optional[EventHandler[TimePickerEntryModeChangeEvent]] = None """ - Called when the `time_picker_entry_mode` is changed. + Called when the [`entry_mode`][(c).] is changed through the time picker dialog. + """ + + hour_format: TimePickerHourFormat = TimePickerHourFormat.SYSTEM + """ + Defines the hour format of this time picker. """ diff --git a/sdk/python/packages/flet/src/flet/controls/theme.py b/sdk/python/packages/flet/src/flet/controls/theme.py index 2f91b59f4b..b931c9a988 100644 --- a/sdk/python/packages/flet/src/flet/controls/theme.py +++ b/sdk/python/packages/flet/src/flet/controls/theme.py @@ -80,104 +80,106 @@ class ColorScheme: on_primary: Optional[ColorValue] = field(default=None, metadata={"event": False}) """ - A color that's clearly legible when drawn on `primary`. + A color that's clearly legible when drawn on [`primary`][(c).]. """ primary_container: Optional[ColorValue] = None """ - A color used for elements needing less emphasis than `primary`. + A color used for elements needing less emphasis than [`primary`][(c).]. """ on_primary_container: Optional[ColorValue] = field( default=None, metadata={"event": False} ) """ - A color that's clearly legible when drawn on `primary_container`. + A color that's clearly legible when drawn on [`primary_container`][(c).]. """ secondary: Optional[ColorValue] = None """ - An accent color used for less prominent components in the UI, such as filter chips, - while expanding the opportunity for color expression. + An accent color used for less prominent components in the UI, such as filter + [`Chip`][flet.]s, while expanding the opportunity for color expression. """ on_secondary: Optional[ColorValue] = field(default=None, metadata={"event": False}) """ - A color that's clearly legible when drawn on `secondary`. + A color that's clearly legible when drawn on [`secondary`][(c).]. """ secondary_container: Optional[ColorValue] = None """ - A color used for elements needing less emphasis than `secondary`. + A color used for elements needing less emphasis than [`secondary`][(c).]. """ on_secondary_container: Optional[ColorValue] = field( default=None, metadata={"event": False} ) """ - A color that's clearly legible when drawn on `secondary_container`. + A color that's clearly legible when drawn on [`secondary_container`][(c).]. """ tertiary: Optional[ColorValue] = None """ - A color used as a contrasting accent that can balance `primary` and `secondary` - colors or bring heightened attention to an element, such as an input field. + A color used as a contrasting accent that can balance [`primary`][(c).] and + [`secondary`][(c).] colors or bring heightened attention to an element, such as + an input field. """ on_tertiary: Optional[ColorValue] = field(default=None, metadata={"event": False}) """ - A color that's clearly legible when drawn on `tertiary`. + A color that's clearly legible when drawn on [`tertiary`][(c).]. """ tertiary_container: Optional[ColorValue] = None """ - A color used for elements needing less emphasis than `tertiary`. + A color used for elements needing less emphasis than [`tertiary`][(c).]. """ on_tertiary_container: Optional[ColorValue] = field( default=None, metadata={"event": False} ) """ - A color that's clearly legible when drawn on `tertiary_container`. + A color that's clearly legible when drawn on [`tertiary_container`][(c).]. """ error: Optional[ColorValue] = None """ - The color to use for input validation errors, e.g. for `TextField.error_text`. + The color to use for input validation errors, + e.g. for [`TextField.error_text`][flet.]. """ on_error: Optional[ColorValue] = field(default=None, metadata={"event": False}) """ - A color that's clearly legible when drawn on `error`. + A color that's clearly legible when drawn on [`error`][(c).]. """ error_container: Optional[ColorValue] = None """ - A color used for error elements needing less emphasis than `error`. + A color used for error elements needing less emphasis than [`error`][(c).]. """ on_error_container: Optional[ColorValue] = field( default=None, metadata={"event": False} ) """ - A color that's clearly legible when drawn on `error_container`. + A color that's clearly legible when drawn on [`error_container`][(c).]. """ surface: Optional[ColorValue] = None """ - The background color for widgets like `Card`. + The background color for widgets like [`Card`][flet.]. """ on_surface: Optional[ColorValue] = field(default=None, metadata={"event": False}) """ - A color that's clearly legible when drawn on `surface`. + A color that's clearly legible when drawn on [`surface`][(c).]. """ on_surface_variant: Optional[ColorValue] = field( default=None, metadata={"event": False} ) """ - A color that's clearly legible when drawn on `surface_variant`. + A color that's clearly legible when drawn on [`surface_variant`][(c).]. """ outline: Optional[ColorValue] = None @@ -204,20 +206,20 @@ class ColorScheme: inverse_surface: Optional[ColorValue] = None """ A surface color used for displaying the reverse of what’s seen in the surrounding - UI, for example in a `SnackBar` to bring attention to an alert. + UI, for example in a [`SnackBar`][flet.] to bring attention to an alert. """ on_inverse_surface: Optional[ColorValue] = field( default=None, metadata={"event": False} ) """ - A color that's clearly legible when drawn on `inverse_surface`. + A color that's clearly legible when drawn on [`inverse_surface`][(c).]. """ inverse_primary: Optional[ColorValue] = None """ - An accent color used for displaying a highlight color on `inverse_surface` - backgrounds, like button text in a `SnackBar`. + An accent color used for displaying a highlight color on [`inverse_surface`][(c).] + backgrounds, like button text in a [`SnackBar`][flet.]. """ surface_tint: Optional[ColorValue] = None @@ -230,7 +232,7 @@ class ColorScheme: ) """ A color that is used for text and icons that exist on top of elements having - `primary_fixed` color. + [`primary_fixed`][(c).] color. """ on_secondary_fixed: Optional[ColorValue] = field( @@ -238,7 +240,7 @@ class ColorScheme: ) """ A color that is used for text and icons that exist on top of elements having - `secondary_fixed` color. + [`secondary_fixed`][(c).] color. """ on_tertiary_fixed: Optional[ColorValue] = field( @@ -246,7 +248,7 @@ class ColorScheme: ) """ A color that is used for text and icons that exist on top of elements having - `tertiary_fixed` color. + [`tertiary_fixed`][(c).] color. """ on_primary_fixed_variant: Optional[ColorValue] = field( @@ -254,7 +256,7 @@ class ColorScheme: ) """ A color that provides a lower-emphasis option for text and icons than - `on_primary_fixed`. + [`on_primary_fixed`][(c).]. """ on_secondary_fixed_variant: Optional[ColorValue] = field( @@ -262,7 +264,7 @@ class ColorScheme: ) """ A color that provides a lower-emphasis option for text and icons than - `on_secondary_fixed`. + [`on_secondary_fixed`][(c).]. """ on_tertiary_fixed_variant: Optional[ColorValue] = field( @@ -270,35 +272,35 @@ class ColorScheme: ) """ A color that provides a lower-emphasis option for text and icons than - `on_tertiary_fixed`. + [`on_tertiary_fixed`][(c).]. """ primary_fixed: Optional[ColorValue] = None """ - A substitute for `primary_container` that's the same color for the dark and light - themes. + A substitute for [`primary_container`][(c).] that's the + same color for the dark and light themes. """ secondary_fixed: Optional[ColorValue] = None """ - A substitute for `secondary_container` that's the same color for the dark and light - themes. + A substitute for [`secondary_container`][(c).] that's the + same color for the dark and light themes. """ tertiary_fixed: Optional[ColorValue] = None """ - A substitute for `tertiary_container` that's the same color for dark and light - themes. + A substitute for [`tertiary_container`][(c).] that's the + same color for dark and light themes. """ primary_fixed_dim: Optional[ColorValue] = None """ - A color used for elements needing more emphasis than `primary_fixed`. + A color used for elements needing more emphasis than [`primary_fixed`][(c).]. """ secondary_fixed_dim: Optional[ColorValue] = None """ - A color used for elements needing more emphasis than `secondary_fixed`. + A color used for elements needing more emphasis than [`secondary_fixed`][(c).]. """ surface_bright: Optional[ColorValue] = None @@ -325,7 +327,7 @@ class ColorScheme: surface_container_low: Optional[ColorValue] = None """ A surface container color with a lighter tone that creates less emphasis than - `surface_container` but more emphasis than `surface_container_lowest`. + `surface_container` but more emphasis than [`surface_container_lowest`][(c).]. """ surface_container_lowest: Optional[ColorValue] = None @@ -341,7 +343,7 @@ class ColorScheme: tertiary_fixed_dim: Optional[ColorValue] = None """ - A color used for elements needing more emphasis than `tertiary_fixed`. + A color used for elements needing more emphasis than [`tertiary_fixed`][(c).]. """ @@ -1849,11 +1851,8 @@ class DatePickerTheme: Overrides the default text style used for each individual day label in the grid of the [`DatePicker`][flet.]. - The color in - [`DatePickerTheme.day_text_style`][flet.] is not - used, - [`DatePickerTheme.day_foreground_color`][flet.] - is used instead. + The color in [`DatePickerTheme.day_text_style`][flet.] is not + used, [`DatePickerTheme.day_foreground_color`][flet.] is used instead. """ weekday_text_style: Optional[TextStyle] = None @@ -1867,11 +1866,8 @@ class DatePickerTheme: Overrides the default text style used to paint each of the year entries in the year selector of the [`DatePicker`][flet.]. - The color of the - [`DatePickerTheme.year_text_style`][flet.] is not - used, - [`DatePickerTheme.year_foreground_color`][flet.] - is used instead. + The color of the [`DatePickerTheme.year_text_style`][flet.] is not used, + [`DatePickerTheme.year_foreground_color`][flet.] is used instead. """ shape: Optional[OutlinedBorder] = None @@ -1884,14 +1880,12 @@ class DatePickerTheme: cancel_button_style: Optional[ButtonStyle] = None """ - Overrides the default style of the cancel button of a - [`DatePicker`][flet.]. + Overrides the default style of the cancel button of a [`DatePicker`][flet.]. """ confirm_button_style: Optional[ButtonStyle] = None """ - Overrides the default style of the confirm (OK) button of a - [`DatePicker`][flet.]. + Overrides the default style of the confirm (OK) button of a [`DatePicker`][flet.]. """ header_foreground_color: Optional[ColorValue] = None @@ -1902,8 +1896,7 @@ class DatePickerTheme: This is used instead of the color property of [`DatePickerTheme.header_headline_text_style`][flet.] - and - [`DatePickerTheme.header_help_text_style`][flet.]. + and [`DatePickerTheme.header_help_text_style`][flet.]. """ header_headline_text_style: Optional[TextStyle] = None @@ -1912,11 +1905,8 @@ class DatePickerTheme: The dialog's header displays the currently selected date. - The color of the - [`DatePickerTheme.header_headline_text_style`][flet.] - is not used, - [`DatePickerTheme.header_foreground_color`][flet.] - is used instead. + The color of the [`DatePickerTheme.header_headline_text_style`][flet.] + is not used, [`DatePickerTheme.header_foreground_color`][flet.] is used instead. """ header_help_text_style: Optional[TextStyle] = None @@ -1927,18 +1917,17 @@ class DatePickerTheme: usually a prompt to the user at the top of the header (i.e. 'Select date'). The color of the `header_help_style` is not used, - [`DatePickerTheme.header_foreground_color`][flet.] - is used instead. + [`DatePickerTheme.header_foreground_color`][flet.] is used instead. """ range_picker_bgcolor: Optional[ColorValue] = None """ - Overrides the default background color for DateRangePicker (TBD). + Overrides the default background color for [`DateRangePicker`][flet.]. """ range_picker_header_bgcolor: Optional[ColorValue] = None """ - Overrides the default background fill color for DateRangePicker (TBD). + Overrides the default background fill color for [`DateRangePicker`][flet.]. The dialog's header displays the currently selected date range. """ @@ -1946,7 +1935,7 @@ class DatePickerTheme: range_picker_header_foreground_color: Optional[ColorValue] = None """ Overrides the default color used for text labels and icons in the header of a full - screen DateRangePicker (TBD) + screen [`DateRangePicker`][flet.]. The dialog's header displays the currently selected date range. @@ -1988,7 +1977,7 @@ class DatePickerTheme: range_picker_header_headline_text_style: Optional[TextStyle] = None """ Overrides the default text style used for the headline text in the header of a - full screen DateRangePicker (TBD). + full screen [`DateRangePicker`][flet.]. The dialog's header displays the currently selected date range. @@ -1999,25 +1988,23 @@ class DatePickerTheme: range_selection_bgcolor: Optional[ColorValue] = None """ Overrides the default background color used to paint days selected between the - start and end dates in a DateRangePicker (TBD). + start and end dates in a [`DateRangePicker`][flet.]. """ range_selection_overlay_color: Optional[ControlStateValue[ColorValue]] = None """ Overrides the default highlight color that's typically used to indicate that a - date in the selected range of a DateRangePicker (TBD) is focused, hovered, or + date in the selected range of a [`DateRangePicker`][flet.] is focused, hovered, or pressed. """ today_border_side: Optional[BorderSide] = None """ - Overrides the border used to paint the - [`DatePicker.current_date`][flet.] label in the - grid of the [`DatePicker`][flet.]. + Overrides the border used to paint the [`DatePicker.current_date`][flet.] label + in the grid of the [`DatePicker`][flet.]. The border side's [`BorderSide.color`] is not used, - [`DatePickerTheme.today_foreground_color`][flet.] - is used instead. + [`DatePickerTheme.today_foreground_color`][flet.] is used instead. """ year_bgcolor: Optional[ControlStateValue[ColorValue]] = None @@ -2135,7 +2122,7 @@ class TimePickerTheme: day_period_text_style: Optional[TextStyle] = None """ - Used to configure the [TextStyle][flet.TextStyle] for the AM/PM toggle control. + Used to configure the [`TextStyle`][flet.TextStyle] for the AM/PM toggle control. If this is null, the time picker defaults to the overall theme's [`TextTheme.title_medium`][flet.]. @@ -2143,18 +2130,18 @@ class TimePickerTheme: dial_text_style: Optional[TextStyle] = None """ - The [TextStyle][flet.TextStyle] for the numbers on the time selection dial. + The [`TextStyle`][flet.TextStyle] for the numbers on the time selection dial. """ help_text_style: Optional[TextStyle] = None """ - Used to configure the [TextStyle][flet.TextStyle] for the helper text in the header. - + Used to configure the [`TextStyle`][flet.TextStyle] + for the helper text in the header. """ hour_minute_text_style: Optional[TextStyle] = None """ - Used to configure the [TextStyle][flet.TextStyle] for the hour/minute controls. + Used to configure the [`TextStyle`][flet.] for the hour/minute controls. """ elevation: Optional[Number] = None @@ -2547,7 +2534,7 @@ class SliderTheme: value_indicator_text_style: Optional[TextStyle] = None """ - The [TextStyle][flet.TextStyle] for the text on the value indicator. + The [`TextStyle`][flet.TextStyle] for the text on the value indicator. """ mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None diff --git a/sdk/python/packages/flet/src/flet/testing/finder.py b/sdk/python/packages/flet/src/flet/testing/finder.py index 1e4a0874a3..69f58df4c2 100644 --- a/sdk/python/packages/flet/src/flet/testing/finder.py +++ b/sdk/python/packages/flet/src/flet/testing/finder.py @@ -18,3 +18,37 @@ class Finder: """ The number of controls found by this finder. """ + + index: int = 0 + """ + The index of the control to interact with when multiple controls are found. + """ + + @property + def first(self) -> "Finder": + """ + Returns a Finder that finds the first control found by this finder. + """ + if self.count == 0: + raise ValueError("No controls found by this finder.") + return Finder(id=self.id, count=1, index=0) + + @property + def last(self) -> "Finder": + """ + Returns a Finder that finds the last control found by this finder. + """ + if self.count == 0: + raise ValueError("No controls found by this finder.") + return Finder(id=self.id, count=1, index=self.count - 1) + + def at(self, index: int) -> "Finder": + """ + Returns a Finder that finds the control at the given index. + + Args: + index: The index of the control to find. + """ + if index < 0 or index >= self.count: + raise IndexError("Index out of range.") + return Finder(id=self.id, count=1, index=index) diff --git a/sdk/python/packages/flet/src/flet/testing/tester.py b/sdk/python/packages/flet/src/flet/testing/tester.py index 80aed66d2f..f9c5883472 100644 --- a/sdk/python/packages/flet/src/flet/testing/tester.py +++ b/sdk/python/packages/flet/src/flet/testing/tester.py @@ -112,7 +112,9 @@ async def tap(self, finder: Finder): Args: finder: Finder to search for a control. """ - await self._invoke_method("tap", {"id": finder.id}) + await self._invoke_method( + "tap", {"finder_id": finder.id, "finder_index": finder.index} + ) async def long_press(self, finder: Finder): """ @@ -123,7 +125,9 @@ async def long_press(self, finder: Finder): Args: finder: Finder to search for a control. """ - await self._invoke_method("long_press", {"id": finder.id}) + await self._invoke_method( + "long_press", {"finder_id": finder.id, "finder_index": finder.index} + ) async def enter_text(self, finder: Finder, text: str): """ @@ -135,7 +139,10 @@ async def enter_text(self, finder: Finder, text: str): finder: Finder to search for a control. text: The text to enter. """ - await self._invoke_method("enter_text", {"id": finder.id, "text": text}) + await self._invoke_method( + "enter_text", + {"finder_id": finder.id, "finder_index": finder.index, "text": text}, + ) async def mouse_hover(self, finder: Finder): """ @@ -144,7 +151,9 @@ async def mouse_hover(self, finder: Finder): Args: finder: Finder to search for a control. """ - await self._invoke_method("mouse_hover", {"id": finder.id}) + await self._invoke_method( + "mouse_hover", {"finder_id": finder.id, "finder_index": finder.index} + ) async def teardown(self): """