diff --git a/lib/main.dart b/lib/main.dart index a2541fe0..a1695014 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -369,8 +369,8 @@ class _UnityAppState extends State if (settings.name == '/events') { final data = settings.arguments! as Map; final event = data['event'] as Event; - final upcomingEvents = - (data['upcoming'] as Iterable?) ?? []; + final upcomingEvents = (data['upcoming'] as Iterable?) ?? + List.empty(growable: true); final videoPlayer = data['videoPlayer'] as UnityVideoPlayer?; return MaterialPageRoute( diff --git a/lib/screens/events_browser/event_type_filter.dart b/lib/screens/events_browser/event_type_filter.dart new file mode 100644 index 00000000..73d3030a --- /dev/null +++ b/lib/screens/events_browser/event_type_filter.dart @@ -0,0 +1,134 @@ +/* + * 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:auto_size_text/auto_size_text.dart'; +import 'package:bluecherry_client/models/event.dart'; +import 'package:bluecherry_client/providers/events_provider.dart'; +import 'package:bluecherry_client/widgets/misc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; + +class EventTypeFilterTile extends StatefulWidget { + const EventTypeFilterTile({super.key}); + + @override + State createState() => _EventTypeFilterTileState(); +} + +class _EventTypeFilterTileState extends State { + final _eventTypeFilterTileKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + final eventsProvider = context.watch(); + final theme = Theme.of(context); + + return ListTile( + key: _eventTypeFilterTileKey, + dense: true, + title: Text( + loc.eventType, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + trailing: AutoSizeText( + () { + final type = eventsProvider.eventTypeFilter; + // For some reason I can not use a switch here + if (type == EventType.motion.index) { + return loc.motion; + } else if (type == EventType.continuous.index) { + return loc.continuous; + } else { + return 'All'; + } + }(), + maxLines: 1, + ), + onTap: () async { + final box = _eventTypeFilterTileKey.currentContext!.findRenderObject() + as RenderBox; + + showMenu( + context: context, + position: RelativeRect.fromRect( + box.localToGlobal( + Offset.zero, + ancestor: Navigator.of(context).context.findRenderObject(), + ) & + box.size, + Offset.zero & MediaQuery.of(context).size, + ), + constraints: BoxConstraints( + minWidth: box.size.width - 8, + maxWidth: box.size.width - 8, + ), + items: [ + PopupMenuLabel( + label: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 6.0, + ), + child: Text( + loc.eventType, + maxLines: 1, + style: theme.textTheme.labelSmall, + ), + ), + ), + const PopupMenuDivider(), + _buildMenuItem( + value: -1, + child: const Text('All'), + ), + _buildMenuItem( + value: EventType.motion.index, + child: Text(loc.motion), + ), + _buildMenuItem( + value: EventType.continuous.index, + child: Text(loc.continuous), + ), + ], + ); + }, + ); + } + + PopupMenuItem _buildMenuItem({required Widget child, required int value}) { + final eventsProvider = context.read(); + final selected = eventsProvider.eventTypeFilter == value; + + return CheckedPopupMenuItem( + value: value, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + checked: selected, + // enabled: !selected, + onTap: () { + eventsProvider.eventTypeFilter = value; + }, + child: Align( + alignment: AlignmentDirectional.centerEnd, + child: child, + ), + ); + } +} diff --git a/lib/screens/events_browser/events_screen.dart b/lib/screens/events_browser/events_screen.dart index fddd7662..67add2c5 100644 --- a/lib/screens/events_browser/events_screen.dart +++ b/lib/screens/events_browser/events_screen.dart @@ -87,20 +87,27 @@ class EventsScreenState extends State { final hasDrawer = Scaffold.hasDrawer(context); return LayoutBuilder(builder: (context, consts) { + final events = + (eventsProvider.loadedEvents?.filteredEvents ?? List.empty()) + .where((event) { + final typeFilter = eventsProvider.eventTypeFilter; + if (typeFilter == -1) return true; + return event.type.index == typeFilter; + }); if (hasDrawer || consts.maxWidth < kMobileBreakpoint.width) { return EventsScreenMobile( - events: eventsProvider.loadedEvents?.filteredEvents ?? [], - loadedServers: eventsProvider.loadedEvents?.events.keys ?? [], + events: events, + loadedServers: + eventsProvider.loadedEvents?.events.keys ?? List.empty(), refresh: fetch, - invalid: eventsProvider.loadedEvents?.invalidResponses ?? [], + invalid: + eventsProvider.loadedEvents?.invalidResponses ?? List.empty(), ); } return Material( child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - EventsScreenSidebar( - fetch: fetch, - ), + EventsScreenSidebar(fetch: fetch), Expanded( child: Card( margin: EdgeInsets.zero, @@ -109,10 +116,7 @@ class EventsScreenState extends State { topStart: Radius.circular(12.0), ), ), - child: EventsScreenDesktop( - events: - eventsProvider.loadedEvents?.filteredEvents ?? List.empty(), - ), + child: EventsScreenDesktop(events: events), ), ), ]), diff --git a/lib/screens/events_browser/sidebar.dart b/lib/screens/events_browser/sidebar.dart index 7db87346..26a6c35d 100644 --- a/lib/screens/events_browser/sidebar.dart +++ b/lib/screens/events_browser/sidebar.dart @@ -21,6 +21,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/event_type_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'; @@ -76,20 +77,7 @@ class _EventsScreenSidebarState extends State ), const Divider(), const EventsDateTimeFilter(), - // const SubHeader('Minimum level', height: 24.0), - // DropdownButton( - // isExpanded: true, - // value: levelFilter, - // items: EventsMinLevelFilter.values.map((level) { - // return DropdownMenuItem( - // value: level, - // child: Text(level.name.uppercaseFirst), - // ); - // }).toList(), - // onChanged: (v) => setState( - // () => levelFilter = v ?? levelFilter, - // ), - // ), + const EventTypeFilterTile(), const SizedBox(height: 8.0), FilledButton( onPressed: isLoading ? null : widget.fetch, diff --git a/lib/screens/events_timeline/desktop/timeline_sidebar.dart b/lib/screens/events_timeline/desktop/timeline_sidebar.dart index 62b9e43b..cb4dc441 100644 --- a/lib/screens/events_timeline/desktop/timeline_sidebar.dart +++ b/lib/screens/events_timeline/desktop/timeline_sidebar.dart @@ -18,8 +18,8 @@ */ import 'package:auto_size_text/auto_size_text.dart'; -import 'package:bluecherry_client/models/event.dart'; import 'package:bluecherry_client/providers/events_provider.dart'; +import 'package:bluecherry_client/screens/events_browser/event_type_filter.dart'; import 'package:bluecherry_client/screens/events_browser/filter.dart'; import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/methods.dart'; @@ -46,14 +46,10 @@ class TimelineSidebar extends StatefulWidget { } class _TimelineSidebarState extends State with Searchable { - final _eventTypeFilterTileKey = GlobalKey(); - @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); final eventsProvider = context.watch(); - final theme = Theme.of(context); - return Card( shape: const RoundedRectangleBorder( borderRadius: BorderRadiusDirectional.vertical( @@ -119,99 +115,10 @@ class _TimelineSidebarState extends State with Searchable { } }, ), - ListTile( - key: _eventTypeFilterTileKey, - dense: true, - title: Text( - loc.eventType, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - trailing: AutoSizeText( - () { - final type = eventsProvider.eventTypeFilter; - // For some reason I can not use a switch here - if (type == EventType.motion.index) { - return loc.motion; - } else if (type == EventType.continuous.index) { - return loc.continuous; - } else { - return 'All'; - } - }(), - maxLines: 1, - ), - onTap: () async { - final box = _eventTypeFilterTileKey.currentContext! - .findRenderObject() as RenderBox; - - showMenu( - context: context, - position: RelativeRect.fromRect( - box.localToGlobal( - Offset.zero, - ancestor: - Navigator.of(context).context.findRenderObject(), - ) & - box.size, - Offset.zero & MediaQuery.of(context).size, - ), - constraints: BoxConstraints( - minWidth: box.size.width - 8, - maxWidth: box.size.width - 8, - ), - items: [ - PopupMenuLabel( - label: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 6.0, - ), - child: Text( - loc.eventType, - maxLines: 1, - style: theme.textTheme.labelSmall, - ), - ), - ), - const PopupMenuDivider(), - _buildMenuItem( - value: -1, - child: const Text('All'), - ), - _buildMenuItem( - value: EventType.motion.index, - child: Text(loc.motion), - ), - _buildMenuItem( - value: EventType.continuous.index, - child: Text(loc.continuous), - ), - ], - ); - }, - ), + const EventTypeFilterTile(), ]); }, ), ); } - - PopupMenuItem _buildMenuItem({required Widget child, required int value}) { - final eventsProvider = context.read(); - final selected = eventsProvider.eventTypeFilter == value; - - return CheckedPopupMenuItem( - value: value, - padding: const EdgeInsets.symmetric(horizontal: 20.0), - checked: selected, - // enabled: !selected, - onTap: () { - eventsProvider.eventTypeFilter = value; - }, - child: Align( - alignment: AlignmentDirectional.centerEnd, - child: child, - ), - ); - } }