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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions packages/flet/lib/src/controls/time_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class _TimePickerControlState extends State<TimePickerControl> {

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);
Expand All @@ -44,15 +45,22 @@ class _TimePickerControlState extends State<TimePickerControl> {
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)) {
Expand Down
12 changes: 7 additions & 5 deletions packages/flet/lib/src/flet_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,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;
Expand Down
13 changes: 11 additions & 2 deletions packages/flet/lib/src/protocol/page_media_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ class PageMediaData extends Equatable {
final PaddingData viewInsets;
final double devicePixelRatio;
final Orientation orientation;
final bool alwaysUse24HourFormat;

const PageMediaData({
required this.padding,
required this.viewPadding,
required this.viewInsets,
required this.devicePixelRatio,
required this.orientation,
required this.alwaysUse24HourFormat,
});

Map<String, dynamic> toMap() => <String, dynamic>{
Expand All @@ -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<Object?> get props =>
[padding, viewPadding, viewInsets, devicePixelRatio, orientation];
List<Object?> get props => [
padding,
viewPadding,
viewInsets,
devicePixelRatio,
orientation,
alwaysUse24HourFormat,
];
}

class PaddingData extends Equatable {
Expand Down
2 changes: 1 addition & 1 deletion packages/flet/lib/src/services/tester.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/flet/lib/src/widgets/page_media.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class _PageMediaState extends State<PageMedia> {
viewInsets: PaddingData(MediaQuery.viewInsetsOf(context)),
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
orientation: MediaQuery.orientationOf(context),
alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context),
);

if (newMedia != backend.media || !pageSizeUpdated) {
Expand Down
19 changes: 12 additions & 7 deletions sdk/python/examples/controls/time_picker/basic.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
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}"))
print(e, e.entry_mode)
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The print statement appears to be a debugging artifact and should be removed before merging to production code.

Suggested change
print(e, e.entry_mode)

Copilot uses AI. Check for mistakes.

time_picker = ft.TimePicker(
value=time(1, 2),
value=time(hour=1, minute=2),
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,
Expand All @@ -28,8 +32,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),
)


Expand Down
70 changes: 70 additions & 0 deletions sdk/python/examples/controls/time_picker/hour_formats.py
Original file line number Diff line number Diff line change
@@ -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=9, 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)
8 changes: 7 additions & 1 deletion sdk/python/packages/flet/docs/controls/timepicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ 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"
```


{{ class_members(class_name) }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ class_all_options("flet.TimePickerHourFormat", separate_signature=False) }}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,27 +1,75 @@
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
await 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=1, minute=30),
)
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
await flet_app.resize_page(400, 600)
await flet_app.resize_page(600, 450)

time_picker = ft.TimePicker(
value=datetime.time(hour=1, 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
await flet_app.resize_page(600, 450)

time_picker = ft.TimePicker(
value=datetime.time(hour=1, 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
),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading