Skip to content

Commit 48e162e

Browse files
committed
feat: Event Type Filter for Events History
1 parent 3e415ad commit 48e162e

File tree

5 files changed

+154
-121
lines changed

5 files changed

+154
-121
lines changed

lib/main.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,8 @@ class _UnityAppState extends State<UnityApp>
369369
if (settings.name == '/events') {
370370
final data = settings.arguments! as Map;
371371
final event = data['event'] as Event;
372-
final upcomingEvents =
373-
(data['upcoming'] as Iterable<Event>?) ?? [];
372+
final upcomingEvents = (data['upcoming'] as Iterable<Event>?) ??
373+
List.empty(growable: true);
374374
final videoPlayer = data['videoPlayer'] as UnityVideoPlayer?;
375375

376376
return MaterialPageRoute(
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* This file is a part of Bluecherry Client (https://github.com/bluecherrydvr/unity).
3+
*
4+
* Copyright 2022 Bluecherry, LLC
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU General Public License as
8+
* published by the Free Software Foundation; either version 3 of
9+
* the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
import 'package:auto_size_text/auto_size_text.dart';
21+
import 'package:bluecherry_client/models/event.dart';
22+
import 'package:bluecherry_client/providers/events_provider.dart';
23+
import 'package:bluecherry_client/widgets/misc.dart';
24+
import 'package:flutter/material.dart';
25+
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
26+
import 'package:provider/provider.dart';
27+
28+
class EventTypeFilterTile extends StatefulWidget {
29+
const EventTypeFilterTile({super.key});
30+
31+
@override
32+
State<EventTypeFilterTile> createState() => _EventTypeFilterTileState();
33+
}
34+
35+
class _EventTypeFilterTileState extends State<EventTypeFilterTile> {
36+
final _eventTypeFilterTileKey = GlobalKey();
37+
38+
@override
39+
Widget build(BuildContext context) {
40+
final loc = AppLocalizations.of(context);
41+
final eventsProvider = context.watch<EventsProvider>();
42+
final theme = Theme.of(context);
43+
44+
return ListTile(
45+
key: _eventTypeFilterTileKey,
46+
dense: true,
47+
title: Text(
48+
loc.eventType,
49+
style: const TextStyle(fontWeight: FontWeight.bold),
50+
),
51+
trailing: AutoSizeText(
52+
() {
53+
final type = eventsProvider.eventTypeFilter;
54+
// For some reason I can not use a switch here
55+
if (type == EventType.motion.index) {
56+
return loc.motion;
57+
} else if (type == EventType.continuous.index) {
58+
return loc.continuous;
59+
} else {
60+
return 'All';
61+
}
62+
}(),
63+
maxLines: 1,
64+
),
65+
onTap: () async {
66+
final box = _eventTypeFilterTileKey.currentContext!.findRenderObject()
67+
as RenderBox;
68+
69+
showMenu(
70+
context: context,
71+
position: RelativeRect.fromRect(
72+
box.localToGlobal(
73+
Offset.zero,
74+
ancestor: Navigator.of(context).context.findRenderObject(),
75+
) &
76+
box.size,
77+
Offset.zero & MediaQuery.of(context).size,
78+
),
79+
constraints: BoxConstraints(
80+
minWidth: box.size.width - 8,
81+
maxWidth: box.size.width - 8,
82+
),
83+
items: <PopupMenuEntry>[
84+
PopupMenuLabel(
85+
label: Padding(
86+
padding: const EdgeInsets.symmetric(
87+
horizontal: 16.0,
88+
vertical: 6.0,
89+
),
90+
child: Text(
91+
loc.eventType,
92+
maxLines: 1,
93+
style: theme.textTheme.labelSmall,
94+
),
95+
),
96+
),
97+
const PopupMenuDivider(),
98+
_buildMenuItem(
99+
value: -1,
100+
child: const Text('All'),
101+
),
102+
_buildMenuItem(
103+
value: EventType.motion.index,
104+
child: Text(loc.motion),
105+
),
106+
_buildMenuItem(
107+
value: EventType.continuous.index,
108+
child: Text(loc.continuous),
109+
),
110+
],
111+
);
112+
},
113+
);
114+
}
115+
116+
PopupMenuItem _buildMenuItem({required Widget child, required int value}) {
117+
final eventsProvider = context.read<EventsProvider>();
118+
final selected = eventsProvider.eventTypeFilter == value;
119+
120+
return CheckedPopupMenuItem(
121+
value: value,
122+
padding: const EdgeInsets.symmetric(horizontal: 20.0),
123+
checked: selected,
124+
// enabled: !selected,
125+
onTap: () {
126+
eventsProvider.eventTypeFilter = value;
127+
},
128+
child: Align(
129+
alignment: AlignmentDirectional.centerEnd,
130+
child: child,
131+
),
132+
);
133+
}
134+
}

lib/screens/events_browser/events_screen.dart

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,27 @@ class EventsScreenState<T extends StatefulWidget> extends State<T> {
8787
final hasDrawer = Scaffold.hasDrawer(context);
8888

8989
return LayoutBuilder(builder: (context, consts) {
90+
final events =
91+
(eventsProvider.loadedEvents?.filteredEvents ?? List.empty())
92+
.where((event) {
93+
final typeFilter = eventsProvider.eventTypeFilter;
94+
if (typeFilter == -1) return true;
95+
return event.type.index == typeFilter;
96+
});
9097
if (hasDrawer || consts.maxWidth < kMobileBreakpoint.width) {
9198
return EventsScreenMobile(
92-
events: eventsProvider.loadedEvents?.filteredEvents ?? [],
93-
loadedServers: eventsProvider.loadedEvents?.events.keys ?? [],
99+
events: events,
100+
loadedServers:
101+
eventsProvider.loadedEvents?.events.keys ?? List.empty(),
94102
refresh: fetch,
95-
invalid: eventsProvider.loadedEvents?.invalidResponses ?? [],
103+
invalid:
104+
eventsProvider.loadedEvents?.invalidResponses ?? List.empty(),
96105
);
97106
}
98107

99108
return Material(
100109
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
101-
EventsScreenSidebar(
102-
fetch: fetch,
103-
),
110+
EventsScreenSidebar(fetch: fetch),
104111
Expanded(
105112
child: Card(
106113
margin: EdgeInsets.zero,
@@ -109,10 +116,7 @@ class EventsScreenState<T extends StatefulWidget> extends State<T> {
109116
topStart: Radius.circular(12.0),
110117
),
111118
),
112-
child: EventsScreenDesktop(
113-
events:
114-
eventsProvider.loadedEvents?.filteredEvents ?? List.empty(),
115-
),
119+
child: EventsScreenDesktop(events: events),
116120
),
117121
),
118122
]),

lib/screens/events_browser/sidebar.dart

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'package:bluecherry_client/providers/events_provider.dart';
2121
import 'package:bluecherry_client/providers/home_provider.dart';
2222
import 'package:bluecherry_client/providers/server_provider.dart';
2323
import 'package:bluecherry_client/screens/events_browser/date_time_filter.dart';
24+
import 'package:bluecherry_client/screens/events_browser/event_type_filter.dart';
2425
import 'package:bluecherry_client/screens/events_browser/filter.dart';
2526
import 'package:bluecherry_client/screens/layouts/device_grid.dart';
2627
import 'package:bluecherry_client/widgets/misc.dart';
@@ -76,20 +77,7 @@ class _EventsScreenSidebarState extends State<EventsScreenSidebar>
7677
),
7778
const Divider(),
7879
const EventsDateTimeFilter(),
79-
// const SubHeader('Minimum level', height: 24.0),
80-
// DropdownButton<EventsMinLevelFilter>(
81-
// isExpanded: true,
82-
// value: levelFilter,
83-
// items: EventsMinLevelFilter.values.map((level) {
84-
// return DropdownMenuItem(
85-
// value: level,
86-
// child: Text(level.name.uppercaseFirst),
87-
// );
88-
// }).toList(),
89-
// onChanged: (v) => setState(
90-
// () => levelFilter = v ?? levelFilter,
91-
// ),
92-
// ),
80+
const EventTypeFilterTile(),
9381
const SizedBox(height: 8.0),
9482
FilledButton(
9583
onPressed: isLoading ? null : widget.fetch,

lib/screens/events_timeline/desktop/timeline_sidebar.dart

Lines changed: 2 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
*/
1919

2020
import 'package:auto_size_text/auto_size_text.dart';
21-
import 'package:bluecherry_client/models/event.dart';
2221
import 'package:bluecherry_client/providers/events_provider.dart';
22+
import 'package:bluecherry_client/screens/events_browser/event_type_filter.dart';
2323
import 'package:bluecherry_client/screens/events_browser/filter.dart';
2424
import 'package:bluecherry_client/utils/date.dart';
2525
import 'package:bluecherry_client/utils/methods.dart';
@@ -46,14 +46,10 @@ class TimelineSidebar extends StatefulWidget {
4646
}
4747

4848
class _TimelineSidebarState extends State<TimelineSidebar> with Searchable {
49-
final _eventTypeFilterTileKey = GlobalKey();
50-
5149
@override
5250
Widget build(BuildContext context) {
5351
final loc = AppLocalizations.of(context);
5452
final eventsProvider = context.watch<EventsProvider>();
55-
final theme = Theme.of(context);
56-
5753
return Card(
5854
shape: const RoundedRectangleBorder(
5955
borderRadius: BorderRadiusDirectional.vertical(
@@ -119,99 +115,10 @@ class _TimelineSidebarState extends State<TimelineSidebar> with Searchable {
119115
}
120116
},
121117
),
122-
ListTile(
123-
key: _eventTypeFilterTileKey,
124-
dense: true,
125-
title: Text(
126-
loc.eventType,
127-
style: const TextStyle(fontWeight: FontWeight.bold),
128-
),
129-
trailing: AutoSizeText(
130-
() {
131-
final type = eventsProvider.eventTypeFilter;
132-
// For some reason I can not use a switch here
133-
if (type == EventType.motion.index) {
134-
return loc.motion;
135-
} else if (type == EventType.continuous.index) {
136-
return loc.continuous;
137-
} else {
138-
return 'All';
139-
}
140-
}(),
141-
maxLines: 1,
142-
),
143-
onTap: () async {
144-
final box = _eventTypeFilterTileKey.currentContext!
145-
.findRenderObject() as RenderBox;
146-
147-
showMenu(
148-
context: context,
149-
position: RelativeRect.fromRect(
150-
box.localToGlobal(
151-
Offset.zero,
152-
ancestor:
153-
Navigator.of(context).context.findRenderObject(),
154-
) &
155-
box.size,
156-
Offset.zero & MediaQuery.of(context).size,
157-
),
158-
constraints: BoxConstraints(
159-
minWidth: box.size.width - 8,
160-
maxWidth: box.size.width - 8,
161-
),
162-
items: <PopupMenuEntry>[
163-
PopupMenuLabel(
164-
label: Padding(
165-
padding: const EdgeInsets.symmetric(
166-
horizontal: 16.0,
167-
vertical: 6.0,
168-
),
169-
child: Text(
170-
loc.eventType,
171-
maxLines: 1,
172-
style: theme.textTheme.labelSmall,
173-
),
174-
),
175-
),
176-
const PopupMenuDivider(),
177-
_buildMenuItem(
178-
value: -1,
179-
child: const Text('All'),
180-
),
181-
_buildMenuItem(
182-
value: EventType.motion.index,
183-
child: Text(loc.motion),
184-
),
185-
_buildMenuItem(
186-
value: EventType.continuous.index,
187-
child: Text(loc.continuous),
188-
),
189-
],
190-
);
191-
},
192-
),
118+
const EventTypeFilterTile(),
193119
]);
194120
},
195121
),
196122
);
197123
}
198-
199-
PopupMenuItem _buildMenuItem({required Widget child, required int value}) {
200-
final eventsProvider = context.read<EventsProvider>();
201-
final selected = eventsProvider.eventTypeFilter == value;
202-
203-
return CheckedPopupMenuItem(
204-
value: value,
205-
padding: const EdgeInsets.symmetric(horizontal: 20.0),
206-
checked: selected,
207-
// enabled: !selected,
208-
onTap: () {
209-
eventsProvider.eventTypeFilter = value;
210-
},
211-
child: Align(
212-
alignment: AlignmentDirectional.centerEnd,
213-
child: child,
214-
),
215-
);
216-
}
217124
}

0 commit comments

Comments
 (0)