Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Changelog

## 7.5.16

- Native support for `TZDateTime` on Date and DateTime pickers.

## 7.5.15

- Adjusted `ThemedDateTimeSteppedPicker` to call the onChanged method only once after the time is selected.

## 7.5.14

- Created `ThemedDateTimeSteppedPicker` widget for a stepped date and time picking experience, allowing users to select date first and then time

## 7.5.13
Expand Down
4 changes: 3 additions & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import 'package:layrz_theme_example/router.dart';
import 'package:layrz_theme_example/store/store.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:layrz_state/layrz_state.dart';
import 'package:layrz_theme_example/timezone/native.dart'
if (dart.library.js_interop) 'package:layrz_theme_example/timezone/web.dart';

const font = AppFont(source: .google, name: 'Ubuntu Mono');

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();

await ThemedFontHandler.preloadFont(font);
await initializeTimeZone();

final prefs = await SharedPreferences.getInstance();
final rawThemeMode = prefs.getString('layrz.theme.mode');
Expand Down
2 changes: 1 addition & 1 deletion example/lib/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ final goRoutes = [
];

final router = GoRouter(
initialLocation: kDebugMode ? '/map/layer' : '/',
initialLocation: kDebugMode ? '/inputs/selectors/datetime' : '/',
errorPageBuilder: (context, state) => customTransitionBuilder(context, state, const NotFoundView()),
routes: goRoutes,
);
51 changes: 51 additions & 0 deletions example/lib/timezone/native.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:flutter/foundation.dart';
import 'package:timezone/standalone.dart' as tz;
import 'package:timezone/data/latest_all.dart' as tzl;
import 'package:flutter_timezone/flutter_timezone.dart';

export 'package:timezone/standalone.dart' show Location;

/// [initializeTimeZone] is a stub that should be implemented in platform-specific code to initialize the timezone data.
Future<void> initializeTimeZone() async {
debugPrint('layrz_utils/timezone: Initializing timezone data');
try {
await tz.initializeTimeZone('https://cdn.layrz.com/resources/utils/timezones-2025.tzf');
} catch (err) {
debugPrint(
'layrz_utils/timezone: Error initializing timezone data from Layrz CDN, falling back to built-in data',
);
tzl.initializeTimeZones();
}
return Future.value();
}

/// [getTimezone] is a stub that should be implemented in platform-specific code to get the current timezone.
Future<tz.Location> getTimezone() async {
debugPrint('layrz_utils/timezone: Getting current timezone');
final tzinfo = await FlutterTimezone.getLocalTimezone();
return tz.getLocation(tzinfo.identifier);
}

/// [setTimezone] is a stub that should be implemented in platform-specific code to set the timezone.
Future<void> setTimezone(tz.Location timezone) {
final pre = DateTime.now().toIso8601String();
tz.setLocalLocation(timezone);
final post = tz.TZDateTime.now(timezone).toIso8601String();
debugPrint('layrz_utils/timezone: Timezone changed from $pre to $post');
return Future.value();
}

/// [castTimezone] converts a timezone string to a [Location] object. If the input is null or invalid,
/// it defaults to the device's timezone.
Future<tz.Location> castTimezone(String? timezone) async {
if (timezone == null) return await getTimezone();
try {
return tz.getLocation(timezone);
} catch (e) {
debugPrint('layrz_utils/timezone: Invalid timezone "$timezone", defaulting to browser timezone');
return await getTimezone();
}
}

/// [getTimezones] gets the list of available timezones
List<tz.Location> getTimezones() => tz.timeZoneDatabase.locations.values.toList();
51 changes: 51 additions & 0 deletions example/lib/timezone/web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:flutter/foundation.dart';
import 'package:timezone/browser.dart' as tz;
import 'package:timezone/data/latest_all.dart' as tzl;
import 'package:flutter_timezone/flutter_timezone.dart';

export 'package:timezone/browser.dart' show Location;

/// [initializeTimeZone] is a stub that should be implemented in platform-specific code to initialize the timezone data.
Future<void> initializeTimeZone() async {
debugPrint('layrz_utils/timezone: Initializing timezone data');
try {
await tz.initializeTimeZone('https://cdn.layrz.com/resources/utils/timezones-2025.tzf');
} catch (err) {
debugPrint(
'layrz_utils/timezone: Error initializing timezone data from Layrz CDN, falling back to built-in data',
);
tzl.initializeTimeZones();
}
return Future.value();
}

/// [getTimezone] is a stub that should be implemented in platform-specific code to get the current timezone.
Future<tz.Location> getTimezone() async {
debugPrint('layrz_utils/timezone: Getting current timezone');
final tzinfo = await FlutterTimezone.getLocalTimezone();
return tz.getLocation(tzinfo.identifier);
}

/// [setTimezone] is a stub that should be implemented in platform-specific code to set the timezone.
Future<void> setTimezone(tz.Location timezone) {
final pre = DateTime.now().toIso8601String();
tz.setLocalLocation(timezone);
final post = tz.TZDateTime.now(timezone).toIso8601String();
debugPrint('layrz_utils/timezone: Timezone changed from $pre to $post');
return Future.value();
}

/// [castTimezone] converts a timezone string to a [Location] object. If the input is null or invalid,
/// it defaults to the device's timezone.
Future<tz.Location> castTimezone(String? timezone) async {
if (timezone == null) return await getTimezone();
try {
return tz.getLocation(timezone);
} catch (e) {
debugPrint('layrz_utils/timezone: Invalid timezone "$timezone", defaulting to browser timezone');
return await getTimezone();
}
}

/// [getTimezones] gets the list of available timezones
List<tz.Location> getTimezones() => tz.timeZoneDatabase.locations.values.toList();
6 changes: 6 additions & 0 deletions example/lib/views/inputs/inputs.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
library;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:layrz_icons/layrz_icons.dart';
import 'package:layrz_theme/layrz_theme.dart';
import 'package:layrz_theme_example/store/store.dart';
import 'package:timezone/standalone.dart';
import 'package:timezone/timezone.dart';

import 'package:layrz_theme_example/timezone/native.dart'
if (dart.library.js_interop) 'package:layrz_theme_example/timezone/web.dart';

part 'src/buttons.dart';
part 'src/calendar.dart';
Expand Down
2 changes: 1 addition & 1 deletion example/lib/views/inputs/src/buttons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ class _ButtonsViewState extends State<ButtonsView> {
}

Widget _factorButton({
required style,
required ThemedButtonStyle style,
ThemedTooltipPosition tooltipPosition = ThemedTooltipPosition.right,
}) {
return ThemedButton(
Expand Down
62 changes: 59 additions & 3 deletions example/lib/views/inputs/src/selectors/datetime.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ class _DateTimePickersViewState extends State<DateTimePickersView> {
ThemedMonth? _selectedMonth;
List<ThemedMonth> _selectedMonthRange = [];

late Location tz;
@override
void initState() {
super.initState();
tz = getLocation('Asia/Tokyo');
setTimezone(tz);
}

@override
Widget build(BuildContext context) {
return Layout(
Expand All @@ -43,10 +51,20 @@ class _DateTimePickersViewState extends State<DateTimePickersView> {
),
const SizedBox(height: 10),
const Text("Classic picker"),

ThemedDatePicker(
labelText: "Example label",
value: _selectedDate,
onChanged: (val) => setState(() => _selectedDate = val),
onChanged: (val) {
setState(() => _selectedDate = val);
if (val is TZDateTime) {
debugPrint('Selected date: $val (TZDateTime) in timezone ${tz.name}');
_selectedDate = val;
} else {
_selectedDate = TZDateTime.from(val, tz);
debugPrint('Selected date: $_selectedDate (converted to TZDateTime) in timezone ${tz.name}');
}
},
),
const Text("And the range variant"),
ThemedDateRangePicker(
Expand Down Expand Up @@ -99,16 +117,54 @@ class _DateTimePickersViewState extends State<DateTimePickersView> {
),
const SizedBox(height: 10),
const Text("Classic picker"),
if (kDebugMode) ...[
if (_selectedDateTime is TZDateTime)
Text('Selected date and time: $_selectedDateTime (TZDateTime) in timezone ${tz.name}')
else if (_selectedDateTime != null)
Text('Selected date and time: $_selectedDateTime (DateTime, not converted to TZDateTime)'),
],
ThemedDateTimePicker(
labelText: "Example label",
value: _selectedDateTime,
onChanged: (val) => setState(() => _selectedDateTime = val),
onChanged: (val) {
debugPrint("Raw selected date and time: $val (${val.runtimeType})");
if (val is TZDateTime) {
debugPrint('Selected date and time: $val (TZDateTime) in timezone ${tz.name}');
setState(() => _selectedDateTime = val);
} else {
final converted = TZDateTime.from(val, tz);
debugPrint('Selected date and time: $converted (converted to TZDateTime) in timezone ${tz.name}');
setState(() => _selectedDateTime = converted);
}
},
),
const Text("And the range variant"),
if (kDebugMode) ...[
for (final entry in _selectedDateTimeRange.asMap().entries) ...[
if (entry.value is TZDateTime) ...[
Text('[${entry.key}] Selected date and time: ${entry.value} (TZDateTime) in timezone ${tz.name}'),
] else ...[
Text('[${entry.key}] Selected date and time: ${entry.value} (DateTime, not converted to TZDateTime)'),
],
],
],
ThemedDateTimeRangePicker(
labelText: "Example label",
value: _selectedDateTimeRange,
onChanged: (val) => setState(() => _selectedDateTimeRange = val),
onChanged: (val) {
debugPrint("Raw selected date and time range: $val");
final convertedRange = val.map((dateTime) {
if (dateTime is TZDateTime) {
debugPrint('Selected date and time: $dateTime (TZDateTime) in timezone ${tz.name}');
return dateTime;
} else {
final converted = TZDateTime.from(dateTime, tz);
debugPrint('Selected date and time: $converted (converted to TZDateTime) in timezone ${tz.name}');
return converted;
}
}).toList();
setState(() => _selectedDateTimeRange = convertedRange);
},
),
const Text("Stepped variant, after selecting the date, you will select the time"),
ThemedDateTimeSteppedPicker(
Expand Down
2 changes: 1 addition & 1 deletion example/lib/views/map/src/layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class _MapLayerViewState extends State<MapLayerView> with TickerProviderStateMix
super.dispose();
}

void _listener(event) {
void _listener(ThemedMapEvent event) {
debugPrint('layrz_theme_example/ThemedMapController() event: $event');
}

Expand Down
34 changes: 29 additions & 5 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.9"
equatable:
dependency: transitive
description:
name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
fake_async:
dependency: transitive
description:
Expand Down Expand Up @@ -202,10 +210,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "6.0.0"
flutter_map:
dependency: "direct main"
description:
Expand Down Expand Up @@ -243,6 +251,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_timezone:
dependency: "direct main"
description:
name: flutter_timezone
sha256: "978192f2f9ea6d019a4de4f0211d76a9af955ca24865828fa98ca4e20cf0cb3c"
url: "https://pub.dev"
source: hosted
version: "5.0.1"
flutter_web_plugins:
dependency: transitive
description: flutter
Expand Down Expand Up @@ -374,7 +390,7 @@ packages:
path: ".."
relative: true
source: path
version: "7.5.14"
version: "7.5.16"
leak_tracker:
dependency: transitive
description:
Expand Down Expand Up @@ -411,10 +427,10 @@ packages:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
version: "6.1.0"
lists:
dependency: transitive
description:
Expand Down Expand Up @@ -804,6 +820,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.9"
timezone:
dependency: "direct main"
description:
name: timezone
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
url: "https://pub.dev"
source: hosted
version: "0.10.1"
two_dimensional_scrollables:
dependency: transitive
description:
Expand Down
4 changes: 3 additions & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ dependencies:
layrz_state: any
layrz_icons: any
layrz_models: any
timezone: any
flutter_timezone: any
layrz_theme:
path: ../


dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter_lints: ^6.0.0

flutter:
uses-material-design: true
Expand Down
2 changes: 1 addition & 1 deletion lib/src/inputs/inputs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import 'package:layrz_theme/src/languages/lml/lml.dart' as lml;
import 'package:layrz_theme/src/languages/python/python.dart' as python;
import 'package:layrz_theme/src/languages/mjml/mjml.dart' as mjml;
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:timezone/standalone.dart';
import 'package:url_launcher/url_launcher_string.dart';

export 'package:emojis/emoji.dart' show Emoji, EmojiGroup;
Expand Down Expand Up @@ -90,7 +91,6 @@ part 'src/pickers/datetime/single.dart';
part 'src/pickers/datetime/range.dart';
part 'src/pickers/datetime/single_stepped.dart';


// Blocks
part 'src/dynamic_configurable/block.dart';
part 'src/dynamic_configurable/dialog.dart';
1 change: 1 addition & 0 deletions lib/src/inputs/src/general/multiselect_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class _ThemedMultiSelectInputState<T> extends State<ThemedMultiSelectInput<T>> w
}

void _handleUpdate({bool force = false, List<T> previousValues = const [], List<T> newValues = const []}) {
debugPrint("_handleUpdate: ${previousValues.length} vs ${newValues.length}");
if (newValues.isEmpty && force) {
if (widget.autoselectFirst && widget.items.isNotEmpty) {
selected = [widget.items.first];
Expand Down
Loading