Skip to content
104 changes: 104 additions & 0 deletions packages/flet/lib/src/controls/date_range_picker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';

import '../models/control.dart';
import '../utils/colors.dart';
import '../utils/form_field.dart';
import '../utils/icons.dart';
import '../utils/numbers.dart';
import '../utils/time.dart';

class DateRangePickerControl extends StatefulWidget {
final Control control;

DateRangePickerControl({Key? key, required this.control})
: super(key: key ?? ValueKey("control_${control.id}"));

@override
State<DateRangePickerControl> createState() => _DateRangePickerControlState();
}

class _DateRangePickerControlState extends State<DateRangePickerControl> {
@override
Widget build(BuildContext context) {
debugPrint("DateRangePicker build: ${widget.control.id}");

bool lastOpen = widget.control.getBool("_open", false)!;

var open = widget.control.getBool("open", false)!;
var currentDate = widget.control.getDateTime("current_date");
var startValue = widget.control.getDateTime("start_value");
var endValue = widget.control.getDateTime("end_value");
var value = DateTimeRange<DateTime>(
start: startValue ?? currentDate ?? DateTime.now(),
end: endValue ?? currentDate ?? DateTime.now());

var switchToCalendarEntryModeIcon =
widget.control.getIconData("switch_to_calendar_icon");
var switchToInputEntryModeIcon =
widget.control.getIconData("switch_to_input_icon");

void onClosed(DateTimeRange<DateTime>? dateRangeValue) {
widget.control.updateProperties({"_open": false}, python: false);
var props = {
"start_value": dateRangeValue?.start ?? startValue,
"end_value": dateRangeValue?.end ?? endValue,
"open": false
};
widget.control.updateProperties(props);
if (dateRangeValue != null) {
widget.control.triggerEvent("change", dateRangeValue);
}
widget.control.triggerEvent("dismiss", dateRangeValue == null);
}

Widget createSelectDateDialog() {
Widget dialog = DateRangePickerDialog(
initialDateRange: value,
firstDate: widget.control.getDateTime("first_date", DateTime(1900))!,
lastDate: widget.control.getDateTime("last_date", DateTime(2050))!,
currentDate: currentDate ?? DateTime.now(),
helpText: widget.control.getString("help_text"),
cancelText: widget.control.getString("cancel_text"),
confirmText: widget.control.getString("confirm_text"),
saveText: widget.control.getString("save_text"),
errorInvalidRangeText:
widget.control.getString("error_invalid_range_text"),
errorFormatText: widget.control.getString("error_format_text"),
errorInvalidText: widget.control.getString("error_invalid_text"),
fieldStartHintText: widget.control.getString("field_start_hint_text"),
fieldEndHintText: widget.control.getString("field_end_hint_text"),
fieldStartLabelText: widget.control.getString("field_start_label_text"),
fieldEndLabelText: widget.control.getString("field_end_label_text"),
keyboardType: parseTextInputType(
widget.control.getString("keyboard_type"), TextInputType.text)!,
initialEntryMode: widget.control.getDatePickerEntryMode(
"date_picker_entry_mode", DatePickerEntryMode.calendar)!,
switchToCalendarEntryModeIcon: switchToCalendarEntryModeIcon != null
? Icon(switchToCalendarEntryModeIcon)
: null,
switchToInputEntryModeIcon: switchToInputEntryModeIcon != null
? Icon(switchToInputEntryModeIcon)
: null,
);

return dialog;
}

if (open && (open != lastOpen)) {
widget.control.updateProperties({"_open": open}, python: false);

WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog<DateTimeRange<DateTime>>(
barrierDismissible: !widget.control.getBool("modal", false)!,
barrierColor: widget.control.getColor("barrier_color", context),
useRootNavigator: false,
context: context,
builder: (context) => createSelectDateDialog()).then((result) {
debugPrint("pickDate() completed");
onClosed(result);
});
});
}
return const SizedBox.shrink();
}
}
3 changes: 3 additions & 0 deletions packages/flet/lib/src/flet_core_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import 'controls/cupertino_textfield.dart';
import 'controls/cupertino_timer_picker.dart';
import 'controls/datatable.dart';
import 'controls/date_picker.dart';
import 'controls/date_range_picker.dart';
import 'controls/dismissible.dart';
import 'controls/divider.dart';
import 'controls/drag_target.dart';
Expand Down Expand Up @@ -223,6 +224,8 @@ class FletCoreExtension extends FletExtension {
return DataTableControl(key: key, control: control);
case "DatePicker":
return DatePickerControl(key: key, control: control);
case "DateRangePicker":
return DateRangePickerControl(key: key, control: control);
case "Dismissible":
return DismissibleControl(key: key, control: control);
case "Divider":
Expand Down
3 changes: 1 addition & 2 deletions packages/flet/lib/src/utils/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import 'dismissible.dart';
import 'edge_insets.dart';
import 'geometry.dart';
import 'icons.dart';
import 'locale.dart';
import 'material_state.dart';
import 'menu.dart';
import 'misc.dart';
Expand Down Expand Up @@ -773,7 +772,7 @@ DatePickerThemeData? parseDatePickerTheme(
yearOverlayColor: parseWidgetStateColor(value["year_overlay_color"], theme),
weekdayStyle: parseTextStyle(value["weekday_text_style"], theme),
dayShape: parseWidgetStateOutlinedBorder(value["day_shape"], theme),
locale: parseLocale(value["locale"]),
//locale: parseLocale(value["locale"]),
);
}

Expand Down
36 changes: 36 additions & 0 deletions sdk/python/examples/controls/date_range_picker/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import datetime

import flet as ft


def main(page: ft.Page):
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER

def handle_change(e):
page.add(
ft.Text(
f"Start Date changed: {e.control.start_value.strftime('%m/%d/%Y')}"
),
ft.Text(f"End Date changed: {e.control.end_value.strftime('%m/%d/%Y')}"),
)

def handle_dismissal(e):
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please type annotate e in both functions?

page.add(ft.Text("DatePicker dismissed"))

page.add(
ft.Button(
content=ft.Text("Pick date"),
icon=ft.Icons.PHONE,
on_click=lambda e: page.show_dialog(
ft.DateRangePicker(
start_value=datetime.datetime(year=2000, month=10, day=1),
end_value=datetime.datetime(year=2000, month=10, day=15),
on_change=handle_change,
on_dismiss=handle_dismissal,
)
),
)
)


ft.run(main)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions sdk/python/packages/flet/docs/controls/daterangepicker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Examples

[Live example](https://flet-controls-gallery.fly.dev/dialogs/daterangepicker)

### Basic Example

```python
--8<-- "../../examples/controls/date_range_picker/basic.py"
```

![basic](../test-images/controls/golden/macos/date_range_picker/basic.png){width="60%"}
Copy link
Contributor

Choose a reason for hiding this comment

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

I won't suggest we use references to golden images folder. The image for this example should be in examples/controls/daterangepicker/media. You could basically copy the golden image in there.

/// caption
///

::: flet.DateRangePicker
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.
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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import datetime

import pytest
import pytest_asyncio

import flet as ft
import flet.testing as ftt


# 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_date_picker_theme(flet_app: ftt.FletTestApp, request):
flet_app.page.theme = ft.Theme(
date_picker_theme=ft.DatePickerTheme(
bgcolor=ft.Colors.GREEN_200,
)
)

dp = ft.DatePicker(
current_date=datetime.datetime(year=2025, month=8, day=15),
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2025, month=10, day=1),
)
flet_app.page.enable_screenshots = True
flet_app.page.window.width = 400
flet_app.page.window.height = 600
flet_app.page.show_dialog(dp)
flet_app.page.update()
await flet_app.tester.pump_and_settle()

flet_app.assert_screenshot(
"theme1",
await flet_app.page.take_screenshot(
pixel_ratio=flet_app.screenshots_pixel_ratio
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import datetime

import pytest
import pytest_asyncio

import flet as ft
import flet.testing as ftt


# 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):
dp = ft.DateRangePicker(
start_value=datetime.datetime(year=2000, month=10, day=1),
end_value=datetime.datetime(year=2000, month=10, day=15),
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2000, month=11, day=15),
current_date=datetime.datetime(year=2000, month=10, day=16),
)
flet_app.page.enable_screenshots = True
flet_app.page.window.width = 400
flet_app.page.window.height = 600
flet_app.page.show_dialog(dp)
flet_app.page.update()
await flet_app.tester.pump_and_settle()

flet_app.assert_screenshot(
"basic",
await flet_app.page.take_screenshot(
pixel_ratio=flet_app.screenshots_pixel_ratio
),
)


@pytest.mark.asyncio(loop_scope="function")
async def test_properties1(flet_app: ftt.FletTestApp, request):
dp = ft.DateRangePicker(
start_value=datetime.datetime(year=2000, month=10, day=7),
end_value=datetime.datetime(year=2000, month=10, day=15),
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2000, month=11, day=15),
current_date=datetime.datetime(year=2000, month=10, day=16),
switch_to_calendar_icon=ft.Icons.BABY_CHANGING_STATION,
switch_to_input_icon=ft.Icons.ACCESS_ALARM,
save_text="Custom save text",
error_invalid_range_text="Invalid range custom text",
help_text="Custom help text",
cancel_text="Custom cancel text",
confirm_text="Custom confirm text",
error_format_text="Custom error format text",
error_invalid_text="Custom error invalid text",
field_end_hint_text="Custom end hint text",
field_start_hint_text="Custom start hint text",
field_end_label_text="Custom end label text",
field_start_label_text="Custom start label text",
modal=False,
barrier_color=ft.Colors.RED,
keyboard_type=ft.KeyboardType.EMAIL,
# date_picker_entry_mode=ft.DatePickerEntryMode.CALENDAR,
)
flet_app.page.enable_screenshots = True
flet_app.page.window.width = 400
flet_app.page.window.height = 600
flet_app.page.show_dialog(dp)
flet_app.page.update()
await flet_app.tester.pump_and_settle()

flet_app.assert_screenshot(
"properties_calendar",
await flet_app.page.take_screenshot(
pixel_ratio=flet_app.screenshots_pixel_ratio
),
)

# change to input mode
input_icon = await flet_app.tester.find_by_icon(ft.Icons.ACCESS_ALARM)
assert input_icon.count == 1
await flet_app.tester.tap(input_icon)
await flet_app.tester.pump_and_settle()
flet_app.assert_screenshot(
"properties_input",
await flet_app.page.take_screenshot(
pixel_ratio=flet_app.screenshots_pixel_ratio
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import datetime

import pytest
import pytest_asyncio

import flet as ft
import flet.testing as ftt


# 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_date_picker_theme(flet_app: ftt.FletTestApp, request):
flet_app.page.theme = ft.Theme(
date_picker_theme=ft.DatePickerTheme(
bgcolor=ft.Colors.GREEN_200,
range_picker_bgcolor=ft.Colors.GREEN_100,
range_picker_elevation=10,
range_picker_header_foreground_color=ft.Colors.GREEN_900,
range_picker_header_headline_text_style=ft.TextStyle(italic=True),
range_picker_shape=ft.BeveledRectangleBorder(
radius=20,
side=ft.BorderSide(5, color=ft.Colors.PURPLE),
),
range_picker_header_help_text_style=ft.TextStyle(color=ft.Colors.GREEN_800),
range_selection_bgcolor=ft.Colors.YELLOW_200,
range_selection_overlay_color=ft.Colors.YELLOW_400,
)
)

dp = ft.DateRangePicker(
current_date=datetime.datetime(year=2025, month=8, day=15),
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2025, month=10, day=1),
start_value=datetime.datetime(year=2000, month=10, day=7),
end_value=datetime.datetime(year=2000, month=10, day=15),
)
flet_app.page.enable_screenshots = True
flet_app.page.window.width = 400
flet_app.page.window.height = 600
flet_app.page.show_dialog(dp)
flet_app.page.update()
await flet_app.tester.pump_and_settle()

flet_app.assert_screenshot(
"theme1",
await flet_app.page.take_screenshot(
pixel_ratio=flet_app.screenshots_pixel_ratio
),
)
4 changes: 4 additions & 0 deletions sdk/python/packages/flet/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ plugins:
- source_dir: ../../examples
target_url_path: examples
include_exts: [".png", ".gif", ".svg"]
- source_dir: integration_tests
target_url_path: test-images
include_exts: [".png", ".gif", ".svg"]
Comment on lines +149 to +151
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be removed when addressing #5642 (comment).


# Markdown Extensions
markdown_extensions:
Expand Down Expand Up @@ -268,6 +271,7 @@ nav:
- CupertinoPicker: controls/cupertinopicker.md
- CupertinoTimerPicker: controls/cupertinotimerpicker.md
- DatePicker: controls/datepicker.md
- DateRangePicker: controls/daterangepicker.md
- SnackBar: controls/snackbar.md
- TimePicker: controls/timepicker.md
- Information Displays:
Expand Down
Loading
Loading