From 0e3409440a0e92705a1824fdad4f34d0e5f85821 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Mon, 17 Jun 2024 15:18:05 -0300 Subject: [PATCH 01/15] chore: Bump version --- bluecherry_appcast.xml | 5 +++++ installer/windows-installer.iss | 2 +- linux/debian/DEBIAN/control | 2 +- linux/debian/usr/share/metainfo/bluecherry.appdata.xml | 1 + pubspec.yaml | 2 +- snap/snapcraft.yaml | 2 +- 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bluecherry_appcast.xml b/bluecherry_appcast.xml index 9a1f088e..80c463be 100644 --- a/bluecherry_appcast.xml +++ b/bluecherry_appcast.xml @@ -2,6 +2,11 @@ Bluecherry - Appcast + + Version 3.0.0-beta17 + Fixed Secondary Windows; Improved logging; and Addressed memory leaks and stream continuity. + Fri, 07 Jun 2024 + Version 3.0.0-beta16 Improved iOS support, improved events fetching logic and performance and implemented software zoom. diff --git a/installer/windows-installer.iss b/installer/windows-installer.iss index d0fea591..2e936a49 100644 --- a/installer/windows-installer.iss +++ b/installer/windows-installer.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Bluecherry DVR" -#define MyAppVersion "3.0.0-beta16" +#define MyAppVersion "3.0.0-beta17" #define MyAppPublisher "Bluecherry DVR" #define MyAppURL "https://www.bluecherrydvr.com/" #define MyAppExeName "bluecherry_client.exe" diff --git a/linux/debian/DEBIAN/control b/linux/debian/DEBIAN/control index 3f5ad2d6..4cd30b57 100644 --- a/linux/debian/DEBIAN/control +++ b/linux/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: bluecherrydvr -Version: 3.0.0-beta16 +Version: 3.0.0-beta17 Section: base Priority: optional Homepage: https://www.bluecherrydvr.com/ diff --git a/linux/debian/usr/share/metainfo/bluecherry.appdata.xml b/linux/debian/usr/share/metainfo/bluecherry.appdata.xml index 8e0e4bd7..3d57af78 100644 --- a/linux/debian/usr/share/metainfo/bluecherry.appdata.xml +++ b/linux/debian/usr/share/metainfo/bluecherry.appdata.xml @@ -36,6 +36,7 @@ bluecherrydvr.desktop + | | | | diff --git a/pubspec.yaml b/pubspec.yaml index 49b3913a..538303b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Bluecherry client written in Flutter. publish_to: "none" -version: 3.0.0-beta16 +version: 3.0.0-beta17 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 481ddff5..a55224f7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: bluecherry-client -version: 3.0.0-beta16 +version: 3.0.0-beta17 summary: Bluecherry DVR Client description: Bluecherry DVR client From bf82dff4622954efc2cad4fb85444f30fcadbd1f Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Mon, 17 Jun 2024 15:21:57 -0300 Subject: [PATCH 02/15] fix: Disable "switch to next layout" button when there is only one layout --- lib/screens/layouts/desktop/sidebar.dart | 2 +- lib/screens/settings/updates_and_help.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/layouts/desktop/sidebar.dart b/lib/screens/layouts/desktop/sidebar.dart index e10ea04f..879b3b61 100644 --- a/lib/screens/layouts/desktop/sidebar.dart +++ b/lib/screens/layouts/desktop/sidebar.dart @@ -530,7 +530,7 @@ class CollapsedSidebar extends StatelessWidget { size: 20.0, ), tooltip: loc.switchToNext, - onPressed: view.switchToNextLayout, + onPressed: view.layouts.length > 1 ? view.switchToNextLayout : null, ), SquaredIconButton( icon: Icon( diff --git a/lib/screens/settings/updates_and_help.dart b/lib/screens/settings/updates_and_help.dart index dc99d80c..19da5f61 100644 --- a/lib/screens/settings/updates_and_help.dart +++ b/lib/screens/settings/updates_and_help.dart @@ -124,7 +124,7 @@ class UpdatesSettings extends StatelessWidget { ), const Divider(), ], - // TODO(bdlukaa): Show option to downlaod the native client when running + // TODO(bdlukaa): Show option to download the native client when running // on the web. const About(), ]); From 619b5a5cccd6d1e1769932a55866ba2d68acdbec Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Mon, 17 Jun 2024 15:29:08 -0300 Subject: [PATCH 03/15] feat: Show downloaded event file size --- lib/screens/downloads/downloads_manager.dart | 15 +++++++++++++-- lib/utils/extensions.dart | 9 +++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/screens/downloads/downloads_manager.dart b/lib/screens/downloads/downloads_manager.dart index 9984f6fd..65c2353f 100644 --- a/lib/screens/downloads/downloads_manager.dart +++ b/lib/screens/downloads/downloads_manager.dart @@ -252,8 +252,19 @@ class _DownloadTileState extends State { Text(eventType), Text(widget.event.deviceName), Text(widget.event.server.name), - Text(widget.event.duration - .humanReadable(context)), + Text( + () { + var fileSize = ''; + if (isDownloaded) { + fileSize = + ' (${File(widget.downloadPath!).mbSize.toStringAsFixed(2)} MB)'; + } + + return widget.event.duration + .humanReadable(context) + + fileSize; + }(), + ), Text(at), ], ), diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index a981708b..7e330121 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -17,6 +17,8 @@ * along with this program. If not, see . */ +import 'dart:io' show File; + import 'package:bluecherry_client/models/device.dart'; import 'package:bluecherry_client/models/server.dart'; import 'package:duration/duration.dart'; @@ -166,3 +168,10 @@ extension DeviceListExtension on Iterable { return list; } } + +extension FileExtension on File { + double get mbSize { + int sizeInBytes = this.lengthSync(); + return sizeInBytes / (1024 * 1024); + } +} From 409733804c11a59206ad2c77179ddf5275078128 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Mon, 17 Jun 2024 16:08:22 -0300 Subject: [PATCH 04/15] fix: Show 0 bytes as file size if file does not exist --- lib/screens/downloads/downloads_manager.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/screens/downloads/downloads_manager.dart b/lib/screens/downloads/downloads_manager.dart index 65c2353f..7ac9371d 100644 --- a/lib/screens/downloads/downloads_manager.dart +++ b/lib/screens/downloads/downloads_manager.dart @@ -256,8 +256,10 @@ class _DownloadTileState extends State { () { var fileSize = ''; if (isDownloaded) { + final size = + File(widget.downloadPath!).mbSize; fileSize = - ' (${File(widget.downloadPath!).mbSize.toStringAsFixed(2)} MB)'; + ' (${size.toStringAsFixed(2)} MB)'; } return widget.event.duration From b480acce6f1c974e43dcb6a8aec9fc0177b45d89 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Mon, 17 Jun 2024 16:17:09 -0300 Subject: [PATCH 05/15] feat: Add time filter button --- lib/l10n/app_en.arb | 16 +- lib/l10n/app_fr.arb | 14 ++ lib/l10n/app_pl.arb | 14 ++ lib/l10n/app_pt.arb | 16 +- lib/providers/events_provider.dart | 8 +- .../events_browser/date_time_filter.dart | 198 ++++++++++++++++++ lib/screens/events_browser/events_screen.dart | 57 +---- .../events_browser/events_screen_mobile.dart | 9 +- lib/screens/events_browser/sidebar.dart | 10 +- .../events_timeline/events_playback.dart | 4 +- lib/utils/extensions.dart | 4 +- 11 files changed, 271 insertions(+), 79 deletions(-) create mode 100644 lib/screens/events_browser/date_time_filter.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 52b92f92..083ccbd4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -332,7 +332,9 @@ } } }, - "timeFilter": "Time filter", + "period": "Period", + "dateFilter": "Date Filter", + "timeFilter": "Time Filter", "fromDate": "From", "toDate": "To", "today": "Today", @@ -349,6 +351,18 @@ } } }, + "fromToTime": "{from} to {to}", + "@fromToTime": { + "placeholders": { + "from": { + "type": "String" + }, + "to": { + "type": "String" + } + } + }, + "mostRecent": "Most recent", "allowAlarms": "Allow alarms", "nextEvents": "Next events", "nEvents": "{n, plural, =0{No events} =1{1 event} other{{n} events}}", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1735211c..ea8ccbc1 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -310,6 +310,8 @@ } } }, + "period": "Period", + "dateFilter": "Date Filter", "timeFilter": "Filtre par période", "fromDate": "De", "toDate": "À", @@ -327,6 +329,18 @@ } } }, + "fromToTime": "{from} to {to}", + "@fromToTime": { + "placeholders": { + "from": { + "type": "String" + }, + "to": { + "type": "String" + } + } + }, + "mostRecent": "Most recent", "allowAlarms": "Permettre les alarmes", "nextEvents": "Prochains évènements", "nEvents": "{n, plural, =0{Aucun évènement} =1{1 évènement} other{{n} évènements}}", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index e97341bf..a53aa05b 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -332,6 +332,8 @@ } } }, + "period": "Period", + "dateFilter": "Date Filter", "timeFilter": "Filtr czasu", "fromDate": "Od", "toDate": "Do", @@ -349,6 +351,18 @@ } } }, + "fromToTime": "{from} to {to}", + "@fromToTime": { + "placeholders": { + "from": { + "type": "String" + }, + "to": { + "type": "String" + } + } + }, + "mostRecent": "Most recent", "allowAlarms": "Zezwól na alarmy", "nextEvents": "Następne zdarzenia", "nEvents": "{n, plural, =0{Brak zdarzeń} =1{1 zdarzenie} other{{n} zdarzeń}}", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 80c2b81e..49ce94e8 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -332,7 +332,9 @@ } } }, - "timeFilter": "Filtro de tempo", + "period": "Period", + "dateFilter": "Periodo", + "timeFilter": "Filtro de Tempo", "fromDate": "De", "toDate": "à", "today": "Hoje", @@ -349,6 +351,18 @@ } } }, + "fromToTime": "De {from} às {to}", + "@fromToTime": { + "placeholders": { + "from": { + "type": "String" + }, + "to": { + "type": "String" + } + } + }, + "mostRecent": "Mais recentes", "allowAlarms": "Permitir alarmes", "nextEvents": "Próximos eventos", "nEvents": "{n, plural, =0{Nenhum evento} =1{1 evento} other{{n} eventos}}", diff --git a/lib/providers/events_provider.dart b/lib/providers/events_provider.dart index 1b28ec14..3d001f93 100644 --- a/lib/providers/events_provider.dart +++ b/lib/providers/events_provider.dart @@ -79,7 +79,9 @@ class EventsProvider extends UnityProvider { save(); } - DateTime? startTime, endTime; + DateTime? startDate, endDate; + bool get isDateSet => startDate != null && endDate != null; + EventsMinLevelFilter _levelFilter = EventsMinLevelFilter.any; EventsMinLevelFilter get levelFilter => _levelFilter; set levelFilter(EventsMinLevelFilter value) { @@ -140,8 +142,8 @@ extension EventsScreenProvider on EventsProvider { await Future.wait(allowedDevices.map((device) async { final iterable = (await API.instance.getEvents( server, - startTime: startTime, - endTime: endTime, + startTime: startDate, + endTime: endDate, device: device, )) .toList() diff --git a/lib/screens/events_browser/date_time_filter.dart b/lib/screens/events_browser/date_time_filter.dart new file mode 100644 index 00000000..a5b8e486 --- /dev/null +++ b/lib/screens/events_browser/date_time_filter.dart @@ -0,0 +1,198 @@ +/* + * This file is a part of Bluecherry Client (https://github.com/bluecherrydvr/unity). + * + * Copyright 2022 Bluecherry, LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import 'dart:async'; + +import 'package:bluecherry_client/providers/events_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class EventsDateTimeFilter extends StatelessWidget { + final VoidCallback? onSelect; + + const EventsDateTimeFilter({super.key, this.onSelect}); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + final eventsProvider = context.watch(); + + return ListTile( + dense: true, + isThreeLine: true, + title: Text( + loc.period, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: IntrinsicHeight( + child: Row(children: [ + Expanded( + child: _FilterCard( + title: loc.dateFilter, + value: () { + final formatter = DateFormat.MEd(); + if (eventsProvider.startDate == null || + eventsProvider.endDate == null) { + return loc.mostRecent; + } else if (DateUtils.isSameDay( + eventsProvider.startDate, + eventsProvider.endDate, + )) { + return formatter.format(eventsProvider.startDate!); + } else { + return loc.fromToDate( + formatter.format(eventsProvider.startDate!), + formatter.format(eventsProvider.endDate!), + ); + } + }(), + onPressed: () => _openDatePicker(context), + ), + ), + const SizedBox(width: 8.0), + Expanded( + child: _FilterCard( + title: loc.timeFilter, + value: () { + final formatter = DateFormat.Hm(); + if (eventsProvider.startDate == null || + eventsProvider.endDate == null) { + return loc.mostRecent; + } else if (DateUtils.isSameDay( + eventsProvider.startDate, + eventsProvider.endDate, + )) { + return formatter.format(eventsProvider.startDate!); + } else { + return loc.fromToTime( + formatter.format(eventsProvider.startDate!), + formatter.format(eventsProvider.endDate!), + ); + } + }(), + onPressed: eventsProvider.isDateSet + ? () => _openTimePicker(context) + : null, + ), + ), + ]), + ), + ), + ); + } + + Future _openDatePicker(BuildContext context) async { + final eventsProvider = context.read(); + + final range = await showDateRangePicker( + context: context, + firstDate: DateTime(1970), + lastDate: DateTime.now(), + initialEntryMode: DatePickerEntryMode.calendarOnly, + initialDateRange: + eventsProvider.startDate == null || eventsProvider.endDate == null + ? null + : DateTimeRange( + start: eventsProvider.startDate!, + end: eventsProvider.endDate!, + ), + ); + if (range != null) { + eventsProvider + ..startDate = range.start + ..endDate = range.end; + onSelect?.call(); + } + } + + Future _openTimePicker(BuildContext context) async { + final eventsProvider = context.read(); + + final time = await showTimePicker( + context: context, + initialTime: + TimeOfDay.fromDateTime(eventsProvider.startDate ?? DateTime.now()), + ); + if (time != null) { + eventsProvider.startDate = DateTime( + eventsProvider.startDate!.year, + eventsProvider.startDate!.month, + eventsProvider.startDate!.day, + time.hour, + time.minute, + ); + onSelect?.call(); + } + } +} + +class _FilterCard extends StatelessWidget { + final String title; + final String value; + final VoidCallback? onPressed; + + const _FilterCard({ + super.key, + required this.title, + required this.value, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return InkWell( + borderRadius: BorderRadius.circular(4.0), + onTap: onPressed, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.0), + border: Border.all(color: theme.colorScheme.primary), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title, + style: theme.textTheme.titleSmall?.copyWith( + color: onPressed == null + ? theme.colorScheme.onSurface.withOpacity(0.5) + : null, + ), + ), + Text( + value, + style: TextStyle( + color: onPressed == null + ? theme.colorScheme.onSurface.withOpacity(0.5) + : theme.colorScheme.onSurface, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/events_browser/events_screen.dart b/lib/screens/events_browser/events_screen.dart index 5a2ead4c..731a3ebf 100644 --- a/lib/screens/events_browser/events_screen.dart +++ b/lib/screens/events_browser/events_screen.dart @@ -28,6 +28,7 @@ import 'package:bluecherry_client/providers/home_provider.dart'; import 'package:bluecherry_client/providers/server_provider.dart'; import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/screens/downloads/indicators.dart'; +import 'package:bluecherry_client/screens/events_browser/date_time_filter.dart'; import 'package:bluecherry_client/screens/events_browser/filter.dart'; import 'package:bluecherry_client/screens/events_browser/sidebar.dart'; import 'package:bluecherry_client/screens/players/event_player_desktop.dart'; @@ -43,7 +44,6 @@ import 'package:bluecherry_client/widgets/squared_icon_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:unity_video_player/unity_video_player.dart'; @@ -87,14 +87,12 @@ class EventsScreenState extends State { loadedServers: eventsProvider.loadedEvents?.events.keys ?? [], refresh: fetch, invalid: eventsProvider.loadedEvents?.invalidResponses ?? [], - buildTimeFilterTile: buildTimeFilterTile, ); } return Material( child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ EventsScreenSidebar( - buildTimeFilterTile: (context) => buildTimeFilterTile(), fetch: fetch, ), Expanded( @@ -115,57 +113,4 @@ class EventsScreenState extends State { ); }); } - - Widget buildTimeFilterTile({VoidCallback? onSelect}) { - return Builder(builder: (context) { - final loc = AppLocalizations.of(context); - final eventsProvider = context.watch(); - return ListTile( - dense: true, - title: Text( - loc.timeFilter, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Text(() { - final formatter = DateFormat.MEd(); - if (eventsProvider.startTime == null || - eventsProvider.endTime == null) { - return loc.today; - } else if (DateUtils.isSameDay( - eventsProvider.startTime, - eventsProvider.endTime, - )) { - return formatter.format(eventsProvider.startTime!); - } else { - return loc.fromToDate( - formatter.format(eventsProvider.startTime!), - formatter.format(eventsProvider.endTime!), - ); - } - }()), - onTap: () async { - final range = await showDateRangePicker( - context: context, - firstDate: DateTime(1970), - lastDate: DateTime.now(), - initialEntryMode: DatePickerEntryMode.calendarOnly, - initialDateRange: eventsProvider.startTime == null || - eventsProvider.endTime == null - ? null - : DateTimeRange( - start: eventsProvider.startTime!, - end: eventsProvider.endTime!), - ); - if (range != null) { - setState(() { - eventsProvider - ..startTime = range.start - ..endTime = range.end; - }); - onSelect?.call(); - } - }, - ); - }); - } } diff --git a/lib/screens/events_browser/events_screen_mobile.dart b/lib/screens/events_browser/events_screen_mobile.dart index 95d3563e..4d055ecb 100644 --- a/lib/screens/events_browser/events_screen_mobile.dart +++ b/lib/screens/events_browser/events_screen_mobile.dart @@ -26,15 +26,12 @@ class EventsScreenMobile extends StatefulWidget { final RefreshCallback refresh; final Iterable invalid; - final Widget Function({required VoidCallback onSelect}) buildTimeFilterTile; - const EventsScreenMobile({ super.key, required this.events, required this.loadedServers, required this.refresh, required this.invalid, - required this.buildTimeFilterTile, }); @override @@ -193,10 +190,8 @@ class _EventsScreenMobileState extends State { return PrimaryScrollController( controller: controller, child: MobileFilterSheet( - onChanged: () { - hasChanged = true; - }, - timeFilterTile: widget.buildTimeFilterTile(onSelect: () { + onChanged: () => hasChanged = true, + timeFilterTile: EventsDateTimeFilter(onSelect: () { hasChanged = true; }), ), diff --git a/lib/screens/events_browser/sidebar.dart b/lib/screens/events_browser/sidebar.dart index 89dc451e..7db87346 100644 --- a/lib/screens/events_browser/sidebar.dart +++ b/lib/screens/events_browser/sidebar.dart @@ -20,6 +20,7 @@ import 'package:bluecherry_client/providers/events_provider.dart'; import 'package:bluecherry_client/providers/home_provider.dart'; import 'package:bluecherry_client/providers/server_provider.dart'; +import 'package:bluecherry_client/screens/events_browser/date_time_filter.dart'; import 'package:bluecherry_client/screens/events_browser/filter.dart'; import 'package:bluecherry_client/screens/layouts/device_grid.dart'; import 'package:bluecherry_client/widgets/misc.dart'; @@ -29,14 +30,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; class EventsScreenSidebar extends StatefulWidget { - final WidgetBuilder buildTimeFilterTile; final VoidCallback fetch; - const EventsScreenSidebar({ - super.key, - required this.buildTimeFilterTile, - required this.fetch, - }); + const EventsScreenSidebar({super.key, required this.fetch}); @override State createState() => _EventsScreenSidebarState(); @@ -79,7 +75,7 @@ class _EventsScreenSidebarState extends State ), ), const Divider(), - Builder(builder: widget.buildTimeFilterTile), + const EventsDateTimeFilter(), // const SubHeader('Minimum level', height: 24.0), // DropdownButton( // isExpanded: true, diff --git a/lib/screens/events_timeline/events_playback.dart b/lib/screens/events_timeline/events_playback.dart index 3b1547fe..99993655 100644 --- a/lib/screens/events_timeline/events_playback.dart +++ b/lib/screens/events_timeline/events_playback.dart @@ -79,8 +79,8 @@ class _EventsPlaybackState extends EventsScreenState { hasEverFetched = true; date = date.toLocal(); eventsProvider - ..startTime = DateTime(date.year, date.month, date.day).toLocal() - ..endTime = + ..startDate = DateTime(date.year, date.month, date.day).toLocal() + ..endDate = DateTime(date.year, date.month, date.day, 23, 59, 59).toLocal(); timeline?.dispose(); timeline = null; diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index 7e330121..f6e2b911 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -171,7 +171,7 @@ extension DeviceListExtension on Iterable { extension FileExtension on File { double get mbSize { - int sizeInBytes = this.lengthSync(); - return sizeInBytes / (1024 * 1024); + if (!existsSync()) return 0.0; + return lengthSync() / (1024 * 1024); } } From 5cdcfcb28351c6195f743c364fa3bc01e37c45bd Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 08:46:25 -0300 Subject: [PATCH 06/15] feat: Add from and to filters --- lib/providers/events_provider.dart | 16 ++- .../events_browser/date_time_filter.dart | 136 +++++++++++------- .../events_browser/events_screen_desktop.dart | 3 +- lib/utils/date.dart | 55 +++++++ 4 files changed, 154 insertions(+), 56 deletions(-) diff --git a/lib/providers/events_provider.dart b/lib/providers/events_provider.dart index 3d001f93..349c8acd 100644 --- a/lib/providers/events_provider.dart +++ b/lib/providers/events_provider.dart @@ -79,8 +79,20 @@ class EventsProvider extends UnityProvider { save(); } - DateTime? startDate, endDate; - bool get isDateSet => startDate != null && endDate != null; + DateTime? _startDate; + DateTime get startDate => + _startDate ?? DateTime.now().subtract(const Duration(hours: 24)); + set startDate(DateTime? value) { + _startDate = value; + notifyListeners(); + } + + DateTime? _endDate; + DateTime get endDate => _endDate ?? DateTime.now(); + set endDate(DateTime? value) { + _endDate = value; + notifyListeners(); + } EventsMinLevelFilter _levelFilter = EventsMinLevelFilter.any; EventsMinLevelFilter get levelFilter => _levelFilter; diff --git a/lib/screens/events_browser/date_time_filter.dart b/lib/screens/events_browser/date_time_filter.dart index a5b8e486..8f90c99f 100644 --- a/lib/screens/events_browser/date_time_filter.dart +++ b/lib/screens/events_browser/date_time_filter.dart @@ -20,6 +20,8 @@ import 'dart:async'; import 'package:bluecherry_client/providers/events_provider.dart'; +import 'package:bluecherry_client/providers/settings_provider.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; @@ -35,11 +37,62 @@ class EventsDateTimeFilter extends StatelessWidget { final loc = AppLocalizations.of(context); final eventsProvider = context.watch(); + return ExpansionTile( + title: Text( + loc.filter, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text(loc.fromToDate( + eventsProvider.startDate.formatDecoratedDateTime(context), + eventsProvider.endDate.formatDecoratedDateTime(context), + )), + children: [ + _FilterTile( + title: loc.fromDate, + date: eventsProvider.startDate, + isFrom: true, + onDateChanged: (date) { + context.read().startDate = date; + onSelect?.call(); + }, + ), + _FilterTile( + title: loc.toDate, + date: eventsProvider.endDate, + onDateChanged: (date) { + context.read().endDate = date; + onSelect?.call(); + }, + ), + ], + ); + } +} + +class _FilterTile extends StatelessWidget { + final String title; + + final DateTime? date; + final bool isFrom; + final ValueChanged onDateChanged; + + const _FilterTile({ + required this.onDateChanged, + required this.date, + this.isFrom = false, + required this.title, + }); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + final settings = context.watch(); + return ListTile( dense: true, isThreeLine: true, title: Text( - loc.period, + title, style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Padding( @@ -50,21 +103,19 @@ class EventsDateTimeFilter extends StatelessWidget { child: _FilterCard( title: loc.dateFilter, value: () { - final formatter = DateFormat.MEd(); - if (eventsProvider.startDate == null || - eventsProvider.endDate == null) { + if (date == null) { return loc.mostRecent; + } else if (DateUtils.isSameDay(date, DateTime.now())) { + return loc.today; } else if (DateUtils.isSameDay( - eventsProvider.startDate, - eventsProvider.endDate, + date, + DateTime.now().subtract(const Duration(days: 1)), )) { - return formatter.format(eventsProvider.startDate!); - } else { - return loc.fromToDate( - formatter.format(eventsProvider.startDate!), - formatter.format(eventsProvider.endDate!), - ); + return loc.yesterday; } + + final formatter = DateFormat.MEd(); + return formatter.format(date!); }(), onPressed: () => _openDatePicker(context), ), @@ -74,25 +125,12 @@ class EventsDateTimeFilter extends StatelessWidget { child: _FilterCard( title: loc.timeFilter, value: () { - final formatter = DateFormat.Hm(); - if (eventsProvider.startDate == null || - eventsProvider.endDate == null) { + if (date == null) { return loc.mostRecent; - } else if (DateUtils.isSameDay( - eventsProvider.startDate, - eventsProvider.endDate, - )) { - return formatter.format(eventsProvider.startDate!); - } else { - return loc.fromToTime( - formatter.format(eventsProvider.startDate!), - formatter.format(eventsProvider.endDate!), - ); } + return settings.kTimeFormat.value.format(date!); }(), - onPressed: eventsProvider.isDateSet - ? () => _openTimePicker(context) - : null, + onPressed: date != null ? () => _openTimePicker(context) : null, ), ), ]), @@ -102,46 +140,39 @@ class EventsDateTimeFilter extends StatelessWidget { } Future _openDatePicker(BuildContext context) async { - final eventsProvider = context.read(); - - final range = await showDateRangePicker( + final date = this.date ?? DateTime.now(); + final selectedDate = await showDatePicker( context: context, firstDate: DateTime(1970), lastDate: DateTime.now(), + initialDate: date, initialEntryMode: DatePickerEntryMode.calendarOnly, - initialDateRange: - eventsProvider.startDate == null || eventsProvider.endDate == null - ? null - : DateTimeRange( - start: eventsProvider.startDate!, - end: eventsProvider.endDate!, - ), ); - if (range != null) { - eventsProvider - ..startDate = range.start - ..endDate = range.end; - onSelect?.call(); + if (selectedDate != null) { + onDateChanged(date.copyWith( + year: selectedDate.year, + month: selectedDate.month, + day: selectedDate.day, + )); } } Future _openTimePicker(BuildContext context) async { - final eventsProvider = context.read(); + assert(date != null); final time = await showTimePicker( context: context, - initialTime: - TimeOfDay.fromDateTime(eventsProvider.startDate ?? DateTime.now()), + initialTime: TimeOfDay.fromDateTime(date!), + initialEntryMode: TimePickerEntryMode.dialOnly, ); if (time != null) { - eventsProvider.startDate = DateTime( - eventsProvider.startDate!.year, - eventsProvider.startDate!.month, - eventsProvider.startDate!.day, + onDateChanged(DateTime( + date!.year, + date!.month, + date!.day, time.hour, time.minute, - ); - onSelect?.call(); + )); } } } @@ -152,7 +183,6 @@ class _FilterCard extends StatelessWidget { final VoidCallback? onPressed; const _FilterCard({ - super.key, required this.title, required this.value, required this.onPressed, diff --git a/lib/screens/events_browser/events_screen_desktop.dart b/lib/screens/events_browser/events_screen_desktop.dart index 8e5eba0b..bb6e09ad 100644 --- a/lib/screens/events_browser/events_screen_desktop.dart +++ b/lib/screens/events_browser/events_screen_desktop.dart @@ -107,7 +107,8 @@ class EventsScreenDesktop extends StatelessWidget { ), _buildTilePart( child: Text( - settings.formatRawDateAndTime(event.publishedRaw), + // settings.formatRawDateAndTime(event.publishedRaw), + event.published.formatDecoratedDateTime(context), ), flex: 2, ), diff --git a/lib/utils/date.dart b/lib/utils/date.dart index da736481..8b09e7db 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -1,6 +1,28 @@ +/* + * This file is a part of Bluecherry Client (https://github.com/bluecherrydvr/unity). + * + * Copyright 2022 Bluecherry, LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/utils/logging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; Set _loggedErrorredDates = {}; @@ -101,3 +123,36 @@ extension DateSettingsExtension on SettingsProvider { return '$date $time'; } } + +extension DateTimeExtension on DateTime? { + /// Formats the date and time string. + String formatDecoratedDateTime(BuildContext context) { + final loc = AppLocalizations.of(context); + final settings = context.read(); + + var date = this; + if (settings.kConvertTimeToLocalTimezone.value) date = date?.toLocal(); + var dateString = () { + if (date == null) { + return loc.mostRecent; + } else if (DateUtils.isSameDay(date, DateTime.now())) { + return loc.today; + } else if (DateUtils.isSameDay( + date, + DateTime.now().subtract(const Duration(days: 1)), + )) { + return loc.yesterday; + } else { + return settings.kDateFormat.value.format(date!); + } + }(); + + if (this == null) { + return dateString; + } + + final timeFormatter = settings.kTimeFormat.value; + + return '$dateString ${timeFormatter.format(this!)}'; + } +} From 07c4f8fcfb0a9ddf043e0b99671cee2534d3b9d7 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 09:13:27 -0300 Subject: [PATCH 07/15] feat: Date and Time filter touches --- lib/api/events.dart | 4 +-- lib/l10n/app_en.arb | 4 +-- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_pl.arb | 2 +- lib/l10n/app_pt.arb | 6 ++-- .../events_browser/date_time_filter.dart | 35 +++++++++++++++---- .../events_browser/events_screen_desktop.dart | 2 -- lib/utils/date.dart | 2 +- 8 files changed, 39 insertions(+), 18 deletions(-) diff --git a/lib/api/events.dart b/lib/api/events.dart index 7121c27a..b8d32e38 100644 --- a/lib/api/events.dart +++ b/lib/api/events.dart @@ -45,8 +45,8 @@ extension EventsExtension on API { return []; } - var startTime = data['startTime'] as DateTime?; - final endTime = data['endTime'] as DateTime?; + var startTime = (data['startTime'] as DateTime?)?.toLocal(); + final endTime = (data['endTime'] as DateTime?)?.toLocal(); final deviceId = data['device_id'] as int?; final limit = (data['limit'] as int?) ?? -1; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 083ccbd4..22167b07 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -333,6 +333,7 @@ } }, "period": "Period", + "dateTimeFilter": "Date and Time Filter", "dateFilter": "Date Filter", "timeFilter": "Time Filter", "fromDate": "From", @@ -340,7 +341,7 @@ "today": "Today", "yesterday": "Yesterday", "never": "never", - "fromToDate": "{from} to {to}", + "fromToDate": "From {from} to {to}", "@fromToDate": { "placeholders": { "from": { @@ -351,7 +352,6 @@ } } }, - "fromToTime": "{from} to {to}", "@fromToTime": { "placeholders": { "from": { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ea8ccbc1..82622016 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -311,6 +311,7 @@ } }, "period": "Period", + "dateTimeFilter": "Date Time Filter", "dateFilter": "Date Filter", "timeFilter": "Filtre par période", "fromDate": "De", @@ -329,7 +330,6 @@ } } }, - "fromToTime": "{from} to {to}", "@fromToTime": { "placeholders": { "from": { diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index a53aa05b..2556abb9 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -333,6 +333,7 @@ } }, "period": "Period", + "dateTimeFilter": "Date Time Filter", "dateFilter": "Date Filter", "timeFilter": "Filtr czasu", "fromDate": "Od", @@ -351,7 +352,6 @@ } } }, - "fromToTime": "{from} to {to}", "@fromToTime": { "placeholders": { "from": { diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 49ce94e8..6076a8e1 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -332,7 +332,8 @@ } } }, - "period": "Period", + "period": "Período", + "dateTimeFilter": "Filtro de Data e Hora", "dateFilter": "Periodo", "timeFilter": "Filtro de Tempo", "fromDate": "De", @@ -340,7 +341,7 @@ "today": "Hoje", "yesterday": "Ontem", "never": "Nunca", - "fromToDate": "{from} à {to}", + "fromToDate": "De {from} à {to}", "@fromToDate": { "placeholders": { "from": { @@ -351,7 +352,6 @@ } } }, - "fromToTime": "De {from} às {to}", "@fromToTime": { "placeholders": { "from": { diff --git a/lib/screens/events_browser/date_time_filter.dart b/lib/screens/events_browser/date_time_filter.dart index 8f90c99f..53ffcfb5 100644 --- a/lib/screens/events_browser/date_time_filter.dart +++ b/lib/screens/events_browser/date_time_filter.dart @@ -27,11 +27,34 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -class EventsDateTimeFilter extends StatelessWidget { +class EventsDateTimeFilter extends StatefulWidget { final VoidCallback? onSelect; const EventsDateTimeFilter({super.key, this.onSelect}); + @override + State createState() => _EventsDateTimeFilterState(); +} + +class _EventsDateTimeFilterState extends State { + late final Timer _timer; + + @override + void initState() { + super.initState(); + _timer = Timer.periodic(const Duration(seconds: 1), (_) { + if (mounted && DateTime.now().second % 5 == 0) { + setState(() {}); + } + }); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); @@ -39,7 +62,7 @@ class EventsDateTimeFilter extends StatelessWidget { return ExpansionTile( title: Text( - loc.filter, + loc.dateTimeFilter, style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text(loc.fromToDate( @@ -52,16 +75,16 @@ class EventsDateTimeFilter extends StatelessWidget { date: eventsProvider.startDate, isFrom: true, onDateChanged: (date) { - context.read().startDate = date; - onSelect?.call(); + eventsProvider.startDate = date; + widget.onSelect?.call(); }, ), _FilterTile( title: loc.toDate, date: eventsProvider.endDate, onDateChanged: (date) { - context.read().endDate = date; - onSelect?.call(); + eventsProvider.endDate = date; + widget.onSelect?.call(); }, ), ], diff --git a/lib/screens/events_browser/events_screen_desktop.dart b/lib/screens/events_browser/events_screen_desktop.dart index bb6e09ad..3e753e2c 100644 --- a/lib/screens/events_browser/events_screen_desktop.dart +++ b/lib/screens/events_browser/events_screen_desktop.dart @@ -44,8 +44,6 @@ class EventsScreenDesktop extends StatelessWidget { @override Widget build(BuildContext context) { - final settings = context.watch(); - if (events.isEmpty) { return NoEventsLoaded( isLoading: context diff --git a/lib/utils/date.dart b/lib/utils/date.dart index 8b09e7db..118142af 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -143,7 +143,7 @@ extension DateTimeExtension on DateTime? { )) { return loc.yesterday; } else { - return settings.kDateFormat.value.format(date!); + return settings.kDateFormat.value.format(date); } }(); From dbd07c56d4724076ff01a73279ecbc28cea8ead2 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 09:23:58 -0300 Subject: [PATCH 08/15] feat: Local/server timezones are properly taken into consideration for events --- lib/api/events.dart | 5 ++-- lib/models/event.dart | 10 ++++--- lib/providers/events_provider.dart | 4 +-- lib/providers/update_provider.dart | 3 +- .../events_browser/date_time_filter.dart | 10 +++---- .../events_timeline/desktop/timeline.dart | 1 + .../desktop/timeline_sidebar.dart | 3 +- .../events_timeline/events_playback.dart | 10 +++---- .../mobile/timeline_device_view.dart | 3 +- lib/utils/date.dart | 29 ++++++++++++++++++- lib/utils/extensions.dart | 18 ------------ 11 files changed, 56 insertions(+), 40 deletions(-) diff --git a/lib/api/events.dart b/lib/api/events.dart index b8d32e38..913ee8e9 100644 --- a/lib/api/events.dart +++ b/lib/api/events.dart @@ -5,6 +5,7 @@ import 'package:bluecherry_client/api/api_helpers.dart'; import 'package:bluecherry_client/models/device.dart'; import 'package:bluecherry_client/models/event.dart'; import 'package:bluecherry_client/models/server.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/methods.dart'; import 'package:flutter/foundation.dart'; import 'package:xml2json/xml2json.dart'; @@ -151,11 +152,11 @@ extension EventsExtension on API { title: e['title']['\$t'], publishedRaw: e['published']['\$t'], published: e['published'] == null || e['published']['\$t'] == null - ? DateTime.now() + ? DateTimeExtension.now() : DateTime.parse(e['published']['\$t']), updatedRaw: e['updated']['\$t'] ?? e['published']['\$t'], updated: e['updated'] == null || e['updated']['\$t'] == null - ? DateTime.now() + ? DateTimeExtension.now() : DateTime.parse(e['updated']['\$t']), category: e['category']['term'], mediaID: !e.containsKey('content') diff --git a/lib/models/event.dart b/lib/models/event.dart index 1760fbd6..7eb6d898 100644 --- a/lib/models/event.dart +++ b/lib/models/event.dart @@ -19,6 +19,7 @@ import 'package:bluecherry_client/models/server.dart'; import 'package:bluecherry_client/providers/server_provider.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/extensions.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -64,10 +65,11 @@ class Event { this.mediaID, this.mediaURL, }) : server = server ?? ServersProvider.instance.servers.first, - publishedRaw = publishedRaw ?? DateTime.now().toIso8601String(), - published = published ?? DateTime.now(), - updatedRaw = updatedRaw ?? DateTime.now().toIso8601String(), - updated = updated ?? DateTime.now(); + publishedRaw = + publishedRaw ?? DateTimeExtension.now().toIso8601String(), + published = published ?? DateTimeExtension.now(), + updatedRaw = updatedRaw ?? DateTimeExtension.now().toIso8601String(), + updated = updated ?? DateTimeExtension.now(); String get deviceName { return title diff --git a/lib/providers/events_provider.dart b/lib/providers/events_provider.dart index 349c8acd..c5344ea3 100644 --- a/lib/providers/events_provider.dart +++ b/lib/providers/events_provider.dart @@ -81,14 +81,14 @@ class EventsProvider extends UnityProvider { DateTime? _startDate; DateTime get startDate => - _startDate ?? DateTime.now().subtract(const Duration(hours: 24)); + _startDate ?? DateTime.timestamp().subtract(const Duration(hours: 24)); set startDate(DateTime? value) { _startDate = value; notifyListeners(); } DateTime? _endDate; - DateTime get endDate => _endDate ?? DateTime.now(); + DateTime get endDate => _endDate ?? DateTime.timestamp(); set endDate(DateTime? value) { _endDate = value; notifyListeners(); diff --git a/lib/providers/update_provider.dart b/lib/providers/update_provider.dart index b8c6768b..8eccaf20 100644 --- a/lib/providers/update_provider.dart +++ b/lib/providers/update_provider.dart @@ -470,7 +470,8 @@ class UpdateManager extends UnityProvider { if (versions != this.versions) this.versions = versions; loading = false; - lastCheck = DateTime.now(); // this updates the screen already + // this updates the screen already because "lastCheck" is a setter. No need to trigger the update again + lastCheck = DateTime.now(); } catch (error, stackTrace) { debugPrint(error.toString()); debugPrint(stackTrace.toString()); diff --git a/lib/screens/events_browser/date_time_filter.dart b/lib/screens/events_browser/date_time_filter.dart index 53ffcfb5..78f2a380 100644 --- a/lib/screens/events_browser/date_time_filter.dart +++ b/lib/screens/events_browser/date_time_filter.dart @@ -43,7 +43,7 @@ class _EventsDateTimeFilterState extends State { void initState() { super.initState(); _timer = Timer.periodic(const Duration(seconds: 1), (_) { - if (mounted && DateTime.now().second % 5 == 0) { + if (mounted && DateTime.timestamp().second % 5 == 0) { setState(() {}); } }); @@ -128,11 +128,11 @@ class _FilterTile extends StatelessWidget { value: () { if (date == null) { return loc.mostRecent; - } else if (DateUtils.isSameDay(date, DateTime.now())) { + } else if (DateUtils.isSameDay(date, DateTime.timestamp())) { return loc.today; } else if (DateUtils.isSameDay( date, - DateTime.now().subtract(const Duration(days: 1)), + DateTime.timestamp().subtract(const Duration(days: 1)), )) { return loc.yesterday; } @@ -163,11 +163,11 @@ class _FilterTile extends StatelessWidget { } Future _openDatePicker(BuildContext context) async { - final date = this.date ?? DateTime.now(); + final date = this.date ?? DateTime.timestamp(); final selectedDate = await showDatePicker( context: context, firstDate: DateTime(1970), - lastDate: DateTime.now(), + lastDate: DateTime.timestamp(), initialDate: date, initialEntryMode: DatePickerEntryMode.calendarOnly, ); diff --git a/lib/screens/events_timeline/desktop/timeline.dart b/lib/screens/events_timeline/desktop/timeline.dart index 9cdd57ce..bbbed837 100644 --- a/lib/screens/events_timeline/desktop/timeline.dart +++ b/lib/screens/events_timeline/desktop/timeline.dart @@ -29,6 +29,7 @@ import 'package:bluecherry_client/screens/events_timeline/desktop/timeline_card. import 'package:bluecherry_client/screens/layouts/device_grid.dart' show calculateCrossAxisCount; import 'package:bluecherry_client/utils/constants.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/extensions.dart'; import 'package:bluecherry_client/utils/methods.dart'; import 'package:bluecherry_client/widgets/collapsable_sidebar.dart'; diff --git a/lib/screens/events_timeline/desktop/timeline_sidebar.dart b/lib/screens/events_timeline/desktop/timeline_sidebar.dart index aa9d6c46..cbcc35a5 100644 --- a/lib/screens/events_timeline/desktop/timeline_sidebar.dart +++ b/lib/screens/events_timeline/desktop/timeline_sidebar.dart @@ -20,6 +20,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:bluecherry_client/providers/events_provider.dart'; import 'package:bluecherry_client/screens/events_browser/filter.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/methods.dart'; import 'package:bluecherry_client/widgets/collapsable_sidebar.dart'; import 'package:bluecherry_client/widgets/misc.dart'; @@ -113,7 +114,7 @@ class _TimelineSidebarState extends State with Searchable { context: context, initialDate: widget.date, firstDate: DateTime.utc(1970), - lastDate: DateTime.now(), + lastDate: DateTimeExtension.now(), initialEntryMode: DatePickerEntryMode.calendarOnly, currentDate: widget.date, ); diff --git a/lib/screens/events_timeline/events_playback.dart b/lib/screens/events_timeline/events_playback.dart index 99993655..52275779 100644 --- a/lib/screens/events_timeline/events_playback.dart +++ b/lib/screens/events_timeline/events_playback.dart @@ -28,7 +28,7 @@ import 'package:bluecherry_client/screens/events_browser/events_screen.dart'; import 'package:bluecherry_client/screens/events_timeline/desktop/timeline.dart'; import 'package:bluecherry_client/screens/events_timeline/desktop/timeline_sidebar.dart'; import 'package:bluecherry_client/screens/events_timeline/mobile/timeline_device_view.dart'; -import 'package:bluecherry_client/utils/extensions.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/methods.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -63,9 +63,9 @@ class _EventsPlaybackState extends EventsScreenState { } DateTime date = DateTime( - DateTime.now().year, - DateTime.now().month, - DateTime.now().day, + DateTimeExtension.now().year, + DateTimeExtension.now().month, + DateTimeExtension.now().day, ).toLocal(); bool hasEverFetched = false; @@ -146,7 +146,7 @@ class _EventsPlaybackState extends EventsScreenState { ); }(), TimelineInitialPoint.hourAgo => Duration( - hours: DateTime.now().hour - 1, + hours: DateTimeExtension.now().hour - 1, ), }, ); diff --git a/lib/screens/events_timeline/mobile/timeline_device_view.dart b/lib/screens/events_timeline/mobile/timeline_device_view.dart index de3c908b..9b34845b 100644 --- a/lib/screens/events_timeline/mobile/timeline_device_view.dart +++ b/lib/screens/events_timeline/mobile/timeline_device_view.dart @@ -28,6 +28,7 @@ import 'package:bluecherry_client/screens/downloads/indicators.dart'; import 'package:bluecherry_client/screens/events_timeline/desktop/timeline.dart'; import 'package:bluecherry_client/screens/events_timeline/events_playback.dart'; import 'package:bluecherry_client/utils/constants.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/extensions.dart'; import 'package:bluecherry_client/utils/theme.dart'; import 'package:bluecherry_client/widgets/device_selector.dart'; @@ -540,7 +541,7 @@ class _TimelineDeviceViewState extends State { context: context, initialDate: widget.timeline.currentDate, firstDate: DateTime.utc(1970), - lastDate: DateTime.now(), + lastDate: DateTimeExtension.now(), initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (result != null) { diff --git a/lib/utils/date.dart b/lib/utils/date.dart index 118142af..9c00498b 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -125,6 +125,33 @@ extension DateSettingsExtension on SettingsProvider { } extension DateTimeExtension on DateTime? { + /// Returns true if this date is between [first] and [second] + /// + /// If [allowSameMoment] is true, then the date can be equal to [first] or + /// [second]. + bool isInBetween( + DateTime first, + DateTime second, { + bool allowSameMoment = false, + }) { + assert(this != null); + final isBetween = this!.toLocal().isAfter(first.toLocal()) && + this!.toLocal().isBefore(second.toLocal()); + + if (allowSameMoment) return isBetween; + return isBetween || + this!.isAtSameMomentAs(first) || + this!.isAtSameMomentAs(second); + } + + static DateTime now() { + if (SettingsProvider.instance.kConvertTimeToLocalTimezone.value) { + return DateTime.timestamp().toLocal(); + } + + return DateTime.timestamp(); + } + /// Formats the date and time string. String formatDecoratedDateTime(BuildContext context) { final loc = AppLocalizations.of(context); @@ -153,6 +180,6 @@ extension DateTimeExtension on DateTime? { final timeFormatter = settings.kTimeFormat.value; - return '$dateString ${timeFormatter.format(this!)}'; + return '$dateString ${timeFormatter.format(date!)}'; } } diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index f6e2b911..8977de5e 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -131,24 +131,6 @@ extension ServerExtension on List { } } -extension DateTimeExtension on DateTime { - /// Returns true if this date is between [first] and [second] - /// - /// If [allowSameMoment] is true, then the date can be equal to [first] or - /// [second]. - bool isInBetween( - DateTime first, - DateTime second, { - bool allowSameMoment = false, - }) { - final isBetween = toLocal().isAfter(first.toLocal()) && - toLocal().isBefore(second.toLocal()); - - if (allowSameMoment) return isBetween; - return isBetween || isAtSameMomentAs(first) || isAtSameMomentAs(second); - } -} - extension DeviceListExtension on Iterable { /// Returns this device list sorted properly List sorted({ From d5c490157528d93615f48b243cdbd3699770f521 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 09:37:17 -0300 Subject: [PATCH 09/15] feat: Decorated date string to TimelineView --- lib/l10n/app_en.arb | 1 + lib/l10n/app_fr.arb | 1 + lib/l10n/app_pl.arb | 1 + lib/l10n/app_pt.arb | 1 + .../desktop/timeline_sidebar.dart | 21 +++---------------- .../events_timeline/events_playback.dart | 1 - lib/screens/layouts/video_status_label.dart | 5 +++++ lib/utils/date.dart | 18 +++++++++++----- 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 22167b07..2768e2b6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -135,6 +135,7 @@ "lastImageUpdate": "Last Image Update", "fps": "FPS", "date": "Date", + "time": "Time", "lastUpdate": "Last Update", "screens": "Screens", "directCamera": "Direct Camera", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 82622016..515555e3 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -131,6 +131,7 @@ "lastImageUpdate": "Dernier rafraîchissemente", "fps": "FPS", "date": "Date", + "time": "Time", "lastUpdate": "Dernière mise à jour", "screens": "Écrans", "directCamera": "Caméra direct", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 2556abb9..441ffee8 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -135,6 +135,7 @@ "lastImageUpdate": "Ostatnia aktualizacja obrazu", "fps": "FPS", "date": "Data", + "time": "Time", "lastUpdate": "Ostatnia aktualizacja", "screens": "Ekrany", "directCamera": "Kamera bezpośrednia", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 6076a8e1..efd73b04 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -135,6 +135,7 @@ "lastImageUpdate": "Última atualização da imagem", "fps": "FPS", "date": "Data", + "time": "Hora", "lastUpdate": "Última atualização", "screens": "Câmeras", "directCamera": "Câmera específica", diff --git a/lib/screens/events_timeline/desktop/timeline_sidebar.dart b/lib/screens/events_timeline/desktop/timeline_sidebar.dart index cbcc35a5..0e069da8 100644 --- a/lib/screens/events_timeline/desktop/timeline_sidebar.dart +++ b/lib/screens/events_timeline/desktop/timeline_sidebar.dart @@ -27,7 +27,6 @@ import 'package:bluecherry_client/widgets/misc.dart'; import 'package:bluecherry_client/widgets/search.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; class TimelineSidebar extends StatefulWidget { @@ -35,14 +34,11 @@ class TimelineSidebar extends StatefulWidget { super.key, required this.date, required this.onDateChanged, - required this.onFetch, }); final DateTime date; final ValueChanged onDateChanged; - final VoidCallback onFetch; - @override State createState() => _TimelineSidebarState(); } @@ -86,27 +82,16 @@ class _TimelineSidebarState extends State with Searchable { padding: const EdgeInsetsDirectional.only(start: 16.0, end: 4.0), ), ToggleSearchBar(searchable: this), - Expanded( - child: EventsDevicesPicker( - searchQuery: searchQuery, - ), - ), + Expanded(child: EventsDevicesPicker(searchQuery: searchQuery)), const Divider(), ListTile( dense: true, title: Text( - loc.timeFilter, + loc.dateFilter, style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: AutoSizeText( - () { - final formatter = DateFormat.MEd(); - if (DateUtils.isSameDay(widget.date, DateTime.now())) { - return loc.today; - } else { - return formatter.format(widget.date); - } - }(), + widget.date.formatDecoratedDate(context), maxLines: 1, ), onTap: () async { diff --git a/lib/screens/events_timeline/events_playback.dart b/lib/screens/events_timeline/events_playback.dart index 52275779..a7b73577 100644 --- a/lib/screens/events_timeline/events_playback.dart +++ b/lib/screens/events_timeline/events_playback.dart @@ -230,7 +230,6 @@ class _EventsPlaybackState extends EventsScreenState { sidebar: TimelineSidebar( date: date, onDateChanged: (date) => setState(() => this.date = date), - onFetch: fetch, ), onFetch: fetch, ), diff --git a/lib/screens/layouts/video_status_label.dart b/lib/screens/layouts/video_status_label.dart index 5597031b..c5ab6cd5 100644 --- a/lib/screens/layouts/video_status_label.dart +++ b/lib/screens/layouts/video_status_label.dart @@ -315,6 +315,11 @@ class _DeviceVideoInfo extends StatelessWidget { title: loc.date, data: SettingsProvider.instance.formatDate(event!.published), ), + _buildTextSpan( + context, + title: loc.time, + data: SettingsProvider.instance.formatTimeRaw(event!.publishedRaw), + ), _buildTextSpan( context, title: loc.eventType, diff --git a/lib/utils/date.dart b/lib/utils/date.dart index 9c00498b..b37162de 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -152,8 +152,8 @@ extension DateTimeExtension on DateTime? { return DateTime.timestamp(); } - /// Formats the date and time string. - String formatDecoratedDateTime(BuildContext context) { + /// Formats the date string. + String formatDecoratedDate(BuildContext context) { final loc = AppLocalizations.of(context); final settings = context.read(); @@ -174,9 +174,17 @@ extension DateTimeExtension on DateTime? { } }(); - if (this == null) { - return dateString; - } + return dateString; + } + + /// Formats the date and time string. + String formatDecoratedDateTime(BuildContext context) { + var dateString = formatDecoratedDate(context); + if (this == null) return dateString; + + final settings = context.read(); + var date = this; + if (settings.kConvertTimeToLocalTimezone.value) date = date?.toLocal(); final timeFormatter = settings.kTimeFormat.value; From 84e4b2ad797ca84d95b234c83ac764051c6b6e17 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 09:49:21 -0300 Subject: [PATCH 10/15] fix: Correctly check if event is playing on different timezones --- .../events_timeline/desktop/timeline.dart | 4 ++-- lib/screens/players/event_player_desktop.dart | 2 +- lib/utils/date.dart | 24 +++++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/screens/events_timeline/desktop/timeline.dart b/lib/screens/events_timeline/desktop/timeline.dart index bbbed837..953a185d 100644 --- a/lib/screens/events_timeline/desktop/timeline.dart +++ b/lib/screens/events_timeline/desktop/timeline.dart @@ -161,12 +161,12 @@ class TimelineEvent { DateTime get endTime => startTime.add(duration); bool isPlaying(DateTime currentDate) { - return currentDate.isInBetween(startTime, endTime); + return currentDate.toUtc().isInBetween(startTime.toUtc(), endTime.toUtc()); } /// The position of the video at the [currentDate] Duration position(DateTime currentDate) { - return currentDate.difference(startTime); + return currentDate.toUtc().difference(startTime.toUtc()); } } diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index 2b8e5899..2c64ae9f 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -119,7 +119,7 @@ class _EventPlayerDesktopState extends State { setState(() {}); }); - setEvent(currentEvent); + if (widget.player == null) setEvent(currentEvent); } @override diff --git a/lib/utils/date.dart b/lib/utils/date.dart index b37162de..3b0f893e 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -26,20 +26,14 @@ import 'package:provider/provider.dart'; Set _loggedErrorredDates = {}; -/// Convert a date string to a DateTime object, considering the timezone offset. -DateTime timezoneAwareDate(String originalDateString) { - final originalDateTime = DateTime.parse(originalDateString); - +/// Returns the timezone offset in hours from a date string. +Duration dateTimezoneOffset(String originalDateString) { try { - // Get the offset sign and factors from the date string. - // - // If the timezone is positive (e.g. +02:00), the offset sign is positive. - // Otherwise (e.g. -02:00), the offset sign is negative. var offsetSign = 1.0; var offsetFactors = originalDateString.split('+'); if (offsetFactors.isEmpty) { offsetFactors = originalDateString.split('-'); - if (offsetFactors.length <= 2) return originalDateTime; + if (offsetFactors.length <= 2) return Duration.zero; offsetSign = -1.0; } final offsetString = offsetFactors.last; @@ -55,7 +49,17 @@ DateTime timezoneAwareDate(String originalDateString) { minutes: (minutes * offsetSign).toInt(), ); - return originalDateTime.add(offset); + return offset; + } catch (_) { + return Duration.zero; + } +} + +/// Convert a date string to a DateTime object, considering the timezone offset. +DateTime timezoneAwareDate(String originalDateString) { + final originalDateTime = DateTime.parse(originalDateString); + try { + return originalDateTime.add(dateTimezoneOffset(originalDateString)); } catch (e) { if (!_loggedErrorredDates.contains(originalDateString)) { writeLogToFile( From 0d41f37473022ae1c893731f38132e97813a8ff2 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 09:53:25 -0300 Subject: [PATCH 11/15] feat: Automatically start event player screen on push --- lib/screens/players/event_player_desktop.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index 2c64ae9f..a2384829 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -119,7 +119,11 @@ class _EventPlayerDesktopState extends State { setState(() {}); }); - if (widget.player == null) setEvent(currentEvent); + if (widget.player == null) { + setEvent(currentEvent); + } else { + videoController.start(); + } } @override @@ -157,7 +161,8 @@ class _EventPlayerDesktopState extends State { } videoController ..setVolume(volume) - ..setSpeed(speed); + ..setSpeed(speed) + ..start(); setState(() {}); } From 8b95dd954c545a77a6e3c1a562898e5c9ead260a Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 09:55:28 -0300 Subject: [PATCH 12/15] fix: Correctly apply timezone offset conversion --- lib/providers/events_provider.dart | 5 +++-- lib/screens/events_browser/date_time_filter.dart | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/providers/events_provider.dart b/lib/providers/events_provider.dart index c5344ea3..1f210e1e 100644 --- a/lib/providers/events_provider.dart +++ b/lib/providers/events_provider.dart @@ -24,6 +24,7 @@ import 'package:bluecherry_client/providers/app_provider_interface.dart'; import 'package:bluecherry_client/providers/server_provider.dart'; import 'package:bluecherry_client/screens/events_browser/filter.dart'; import 'package:bluecherry_client/utils/constants.dart'; +import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/storage.dart'; import 'package:flutter/foundation.dart'; @@ -81,14 +82,14 @@ class EventsProvider extends UnityProvider { DateTime? _startDate; DateTime get startDate => - _startDate ?? DateTime.timestamp().subtract(const Duration(hours: 24)); + _startDate ?? DateTimeExtension.now().subtract(const Duration(hours: 24)); set startDate(DateTime? value) { _startDate = value; notifyListeners(); } DateTime? _endDate; - DateTime get endDate => _endDate ?? DateTime.timestamp(); + DateTime get endDate => _endDate ?? DateTimeExtension.now(); set endDate(DateTime? value) { _endDate = value; notifyListeners(); diff --git a/lib/screens/events_browser/date_time_filter.dart b/lib/screens/events_browser/date_time_filter.dart index 78f2a380..8e24df24 100644 --- a/lib/screens/events_browser/date_time_filter.dart +++ b/lib/screens/events_browser/date_time_filter.dart @@ -128,11 +128,14 @@ class _FilterTile extends StatelessWidget { value: () { if (date == null) { return loc.mostRecent; - } else if (DateUtils.isSameDay(date, DateTime.timestamp())) { + } else if (DateUtils.isSameDay( + date, + DateTimeExtension.now(), + )) { return loc.today; } else if (DateUtils.isSameDay( date, - DateTime.timestamp().subtract(const Duration(days: 1)), + DateTimeExtension.now().subtract(const Duration(days: 1)), )) { return loc.yesterday; } @@ -163,11 +166,11 @@ class _FilterTile extends StatelessWidget { } Future _openDatePicker(BuildContext context) async { - final date = this.date ?? DateTime.timestamp(); + final date = this.date ?? DateTimeExtension.now(); final selectedDate = await showDatePicker( context: context, firstDate: DateTime(1970), - lastDate: DateTime.timestamp(), + lastDate: DateTimeExtension.now(), initialDate: date, initialEntryMode: DatePickerEntryMode.calendarOnly, ); From 0ff162c1a3ab2f33a2d247f42f5d2cb29d8a07e2 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 09:59:25 -0300 Subject: [PATCH 13/15] feat: Remove events that aren't in the specified time stamp --- lib/providers/events_provider.dart | 7 ++++++- lib/utils/date.dart | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/providers/events_provider.dart b/lib/providers/events_provider.dart index 1f210e1e..4bbf18ad 100644 --- a/lib/providers/events_provider.dart +++ b/lib/providers/events_provider.dart @@ -172,7 +172,12 @@ extension EventsScreenProvider on EventsProvider { break; } return false; - }); + }) + ..removeWhere((event) { + return event.published.toUtc().isBefore(startDate.toUtc()) || + event.updated.toUtc().isAfter(endDate.toUtc()); + }) + ..sort((a, b) => a.published.toUtc().compareTo(b.updated.toUtc())); loadedEvents!.events[server] ??= []; loadedEvents!.events[server]!.addAll(iterable); diff --git a/lib/utils/date.dart b/lib/utils/date.dart index 3b0f893e..e5f05183 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -150,7 +150,7 @@ extension DateTimeExtension on DateTime? { static DateTime now() { if (SettingsProvider.instance.kConvertTimeToLocalTimezone.value) { - return DateTime.timestamp().toLocal(); + return DateTime.now(); } return DateTime.timestamp(); From 8f05e66cb6881504fad68dd48ce4cc7ec65b315d Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 10:05:13 -0300 Subject: [PATCH 14/15] feat: Empty date and time as initial state --- lib/providers/events_provider.dart | 17 ++++++++++------- .../events_browser/date_time_filter.dart | 6 ++++-- lib/utils/date.dart | 4 ++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/providers/events_provider.dart b/lib/providers/events_provider.dart index 4bbf18ad..1314f2a1 100644 --- a/lib/providers/events_provider.dart +++ b/lib/providers/events_provider.dart @@ -24,7 +24,6 @@ import 'package:bluecherry_client/providers/app_provider_interface.dart'; import 'package:bluecherry_client/providers/server_provider.dart'; import 'package:bluecherry_client/screens/events_browser/filter.dart'; import 'package:bluecherry_client/utils/constants.dart'; -import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/storage.dart'; import 'package:flutter/foundation.dart'; @@ -81,20 +80,21 @@ class EventsProvider extends UnityProvider { } DateTime? _startDate; - DateTime get startDate => - _startDate ?? DateTimeExtension.now().subtract(const Duration(hours: 24)); + DateTime? get startDate => _startDate; set startDate(DateTime? value) { _startDate = value; notifyListeners(); } DateTime? _endDate; - DateTime get endDate => _endDate ?? DateTimeExtension.now(); + DateTime? get endDate => _endDate; set endDate(DateTime? value) { _endDate = value; notifyListeners(); } + bool get isDateSet => _startDate != null && _endDate != null; + EventsMinLevelFilter _levelFilter = EventsMinLevelFilter.any; EventsMinLevelFilter get levelFilter => _levelFilter; set levelFilter(EventsMinLevelFilter value) { @@ -174,10 +174,13 @@ extension EventsScreenProvider on EventsProvider { return false; }) ..removeWhere((event) { - return event.published.toUtc().isBefore(startDate.toUtc()) || - event.updated.toUtc().isAfter(endDate.toUtc()); + if (!isDateSet) return false; + + return event.published.toUtc().isBefore(startDate!.toUtc()) || + event.updated.toUtc().isAfter(endDate!.toUtc()); }) - ..sort((a, b) => a.published.toUtc().compareTo(b.updated.toUtc())); + ..sort( + (a, b) => b.published.toUtc().compareTo(a.published.toUtc())); loadedEvents!.events[server] ??= []; loadedEvents!.events[server]!.addAll(iterable); diff --git a/lib/screens/events_browser/date_time_filter.dart b/lib/screens/events_browser/date_time_filter.dart index 8e24df24..7684c459 100644 --- a/lib/screens/events_browser/date_time_filter.dart +++ b/lib/screens/events_browser/date_time_filter.dart @@ -166,11 +166,13 @@ class _FilterTile extends StatelessWidget { } Future _openDatePicker(BuildContext context) async { - final date = this.date ?? DateTimeExtension.now(); + final defaultDate = + isFrom ? DateTimeExtension.today() : DateTimeExtension.now(); + final date = this.date ?? defaultDate; final selectedDate = await showDatePicker( context: context, firstDate: DateTime(1970), - lastDate: DateTimeExtension.now(), + lastDate: defaultDate, initialDate: date, initialEntryMode: DatePickerEntryMode.calendarOnly, ); diff --git a/lib/utils/date.dart b/lib/utils/date.dart index e5f05183..682e7873 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -156,6 +156,10 @@ extension DateTimeExtension on DateTime? { return DateTime.timestamp(); } + static DateTime today() { + return now().copyWith(hour: 0, minute: 0, second: 0, millisecond: 0); + } + /// Formats the date string. String formatDecoratedDate(BuildContext context) { final loc = AppLocalizations.of(context); From ada083fb764aba897bfd9471f5609de5aed6d027 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 19 Jun 2024 10:09:41 -0300 Subject: [PATCH 15/15] fix: Events Timeline date/time filter does not interfere in Events History's --- lib/providers/events_provider.dart | 9 ++++++--- lib/screens/events_browser/events_screen.dart | 10 ++++++++-- lib/screens/events_timeline/events_playback.dart | 13 +++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/providers/events_provider.dart b/lib/providers/events_provider.dart index 1314f2a1..1f54083f 100644 --- a/lib/providers/events_provider.dart +++ b/lib/providers/events_provider.dart @@ -137,7 +137,10 @@ class EventsProvider extends UnityProvider { } extension EventsScreenProvider on EventsProvider { - Future loadEvents() async { + Future loadEvents({ + DateTime? startDate, + DateTime? endDate, + }) async { loadedEvents = LoadedEvents(); _notify(); @@ -155,8 +158,8 @@ extension EventsScreenProvider on EventsProvider { await Future.wait(allowedDevices.map((device) async { final iterable = (await API.instance.getEvents( server, - startTime: startDate, - endTime: endDate, + startTime: startDate ?? this.startDate, + endTime: endDate ?? this.startDate, device: device, )) .toList() diff --git a/lib/screens/events_browser/events_screen.dart b/lib/screens/events_browser/events_screen.dart index 731a3ebf..fddd7662 100644 --- a/lib/screens/events_browser/events_screen.dart +++ b/lib/screens/events_browser/events_screen.dart @@ -62,11 +62,17 @@ class EventsScreen extends StatefulWidget { class EventsScreenState extends State { /// Fetches the events from the servers. - Future fetch() async { + Future fetch({ + DateTime? startDate, + DateTime? endDate, + }) async { final home = context.read() ..loading(UnityLoadingReason.fetchingEventsHistory); - await context.read().loadEvents(); + await context.read().loadEvents( + startDate: startDate, + endDate: endDate, + ); home.notLoading(UnityLoadingReason.fetchingEventsHistory); } diff --git a/lib/screens/events_timeline/events_playback.dart b/lib/screens/events_timeline/events_playback.dart index a7b73577..dff4cbb6 100644 --- a/lib/screens/events_timeline/events_playback.dart +++ b/lib/screens/events_timeline/events_playback.dart @@ -71,21 +71,22 @@ class _EventsPlaybackState extends EventsScreenState { bool hasEverFetched = false; @override - Future fetch() async { + Future fetch({DateTime? startDate, DateTime? endDate}) async { if (!context.mounted) return; final eventsProvider = context.read(); final settings = context.read(); setState(() { hasEverFetched = true; date = date.toLocal(); - eventsProvider - ..startDate = DateTime(date.year, date.month, date.day).toLocal() - ..endDate = - DateTime(date.year, date.month, date.day, 23, 59, 59).toLocal(); + startDate = DateTime(date.year, date.month, date.day).toLocal(); + endDate = DateTime(date.year, date.month, date.day, 23, 59, 59).toLocal(); timeline?.dispose(); timeline = null; }); - await super.fetch(); + await super.fetch( + startDate: startDate, + endDate: endDate, + ); final devices = >{};