Skip to content

Commit

Permalink
TimelineView (bluecherrydvr#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdlukaa authored Jul 26, 2023
2 parents 6d886d4 + 55e92f0 commit 0b01a64
Show file tree
Hide file tree
Showing 38 changed files with 2,494 additions and 2,931 deletions.
57 changes: 30 additions & 27 deletions lib/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,33 +176,36 @@ class API {
// debugPrint(response.body);

final parser = Xml2Json()..parse(response.body);
final events = jsonDecode(parser.toGData())['feed']['entry'].map((e) {
if (!e.containsKey('content')) debugPrint(e.toString());
return Event(
server,
int.parse(e['id']['raw']),
int.parse((e['category']['term'] as String).split('/').first),
e['title']['\$t'],
e['published'] == null || e['published']['\$t'] == null
? DateTime.now()
: DateTime.parse(e['published']['\$t']),
e['updated'] == null || e['updated']['\$t'] == null
? DateTime.now()
: DateTime.parse(e['updated']['\$t']),
e['category']['term'],
!e.containsKey('content')
? null
: int.parse(e['content']['media_id']),
!e.containsKey('content')
? null
: Uri.parse(
e['content'][r'$t'].replaceAll(
'https://',
'https://${Uri.encodeComponent(server.login)}:${Uri.encodeComponent(server.password)}@',
),
),
);
}).cast<Event>();
final events = (jsonDecode(parser.toGData())['feed']['entry'] as List)
.map((e) {
if (!e.containsKey('content')) debugPrint(e.toString());
return Event(
server,
int.parse(e['id']['raw']),
int.parse((e['category']['term'] as String).split('/').first),
e['title']['\$t'],
e['published'] == null || e['published']['\$t'] == null
? DateTime.now()
: DateTime.parse(e['published']['\$t']).toLocal(),
e['updated'] == null || e['updated']['\$t'] == null
? DateTime.now()
: DateTime.parse(e['updated']['\$t']).toLocal(),
e['category']['term'],
!e.containsKey('content')
? null
: int.parse(e['content']['media_id']),
!e.containsKey('content')
? null
: Uri.parse(
e['content'][r'$t'].replaceAll(
'https://',
'https://${Uri.encodeComponent(server.login)}:${Uri.encodeComponent(server.password)}@',
),
),
);
})
.where((e) => e.duration > const Duration(minutes: 1))
.cast<Event>();

debugPrint('Loaded ${events.length} events for server ${server.name}');
return events;
Expand Down
10 changes: 10 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"replaceCamera": "Replace Camera",
"reloadCamera": "Reload Camera",
"selectACamera": "Select a camera",
"switchCamera": "Switch camera",
"online": "Online",
"offline": "Offline",
"removeFromView": "Remove from view",
Expand All @@ -56,6 +57,8 @@
"event": "Event",
"duration": "Duration",
"priority": "Priority",
"next": "Next",
"previous": "Previous",
"date": "Date",
"lastUpdate": "Last Update",
"theme": "Theme",
Expand Down Expand Up @@ -146,6 +149,7 @@
"downloads": "Downloads",
"download": "Download",
"downloaded": "Downloaded",
"downloading": "Downloading",
"seeInDownloads": "See in Downloads",
"delete": "Delete",
"showInFiles": "Show in Files",
Expand Down Expand Up @@ -181,6 +185,12 @@
"toDate": "To",
"allowAlarms": "Allow alarms",
"nextEvents": "Next events",
"nEvents": "{n} events",
"@nEvents": {
"placeholders": {
"n": {}
}
},
"@Event Priorities": {},
"info": "Info",
"warn": "Warning",
Expand Down
5 changes: 4 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ class UnityApp extends StatelessWidget {
if (settings.name == '/events') {
final data = settings.arguments! as Map;
final event = data['event'] as Event;
final upcomingEvents = data['upcoming'] as Iterable<Event>;
final upcomingEvents =
(data['upcoming'] as Iterable<Event>?) ?? [];
final videoPlayer = data['videoPlayer'] as UnityVideoPlayer?;

return MaterialPageRoute(
settings: RouteSettings(
Expand All @@ -234,6 +236,7 @@ class UnityApp extends StatelessWidget {
return EventPlayerScreen(
event: event,
upcomingEvents: upcomingEvents,
player: videoPlayer,
);
},
);
Expand Down
9 changes: 9 additions & 0 deletions lib/models/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ class Device {
this.hasPTZ = false,
});

Device.dump({
this.name = 'device',
this.id = 0,
this.status = true,
this.resolutionX = 640,
this.resolutionY = 480,
this.hasPTZ = false,
}) : server = Server.dump();

String get uri => 'live/$id';

factory Device.fromServerJson(Map map, Server server) {
Expand Down
7 changes: 3 additions & 4 deletions lib/models/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import 'package:bluecherry_client/models/server.dart';
import 'package:bluecherry_client/providers/server_provider.dart';
import 'package:bluecherry_client/utils/extensions.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

Expand Down Expand Up @@ -66,14 +67,12 @@ class Event {
.last
.trim()
.split(' ')
.map((e) => e.isEmpty ? '' : e[0].toUpperCase() + e.substring(1))
.map((e) => e.uppercaseFirst())
.join(' ');
}

Duration get duration {
// return mediaDuration ?? updated.difference(published);
// TODO(bdlukaa): for some reason, the diff is off by a few seconds. use this to counterpart the issue
final dur = updated.difference(published) - const Duration(seconds: 3);
final dur = updated.difference(published);
if (dur < Duration.zero) return updated.difference(published);
return dur;
}
Expand Down
16 changes: 16 additions & 0 deletions lib/models/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ class Server {
this.passedCertificates = true,
});

Server.dump({
this.name = 'server',
this.ip = 'server:ip',
this.port = 7001,
this.login = 'admin',
this.password = 'admin',
this.devices = const [],
this.rtspPort = 7002,
this.serverUUID,
this.cookie,
this.savePassword = false,
this.connectAutomaticallyAtStartup = true,
this.online = true,
this.passedCertificates = true,
});

String get id {
return '$name;$ip;$port';
}
Expand Down
15 changes: 8 additions & 7 deletions lib/providers/home_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,20 @@ class HomeProvider extends ChangeNotifier {
final home = context.read<HomeProvider>();
final tab = home.tab;

/// On device grid or in eventsPlayback, use landscape
if ([
UnityTab.deviceGrid.index,
UnityTab.eventsPlayback.index,
].contains(tab)) {
/// On device grid, use landscape
if ([UnityTab.deviceGrid.index].contains(tab)) {
setDefaultStatusBarStyle();
DeviceOrientations.instance.set([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
} else if ([UnityTab.directCameraScreen.index].contains(tab)) {
setDefaultStatusBarStyle();
DeviceOrientations.instance.set([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
} else if ([UnityTab.addServer.index].contains(tab)) {
// Use portrait orientation in "Add Server" tab.
// See #14.
setDefaultStatusBarStyle(isLight: true);
DeviceOrientations.instance.set([
DeviceOrientation.portraitUp,
Expand Down
4 changes: 2 additions & 2 deletions lib/providers/settings_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ class SettingsProvider extends ChangeNotifier {
/// Formats the date according to the current [dateFormat].
///
/// [toLocal] defines if the date will be converted to local time. Defaults to `true`
String formatDate(DateTime date, {bool toLocal = true}) {
String formatDate(DateTime date, {bool toLocal = false}) {
if (toLocal) date = date.toLocal();

return dateFormat.format(date);
Expand All @@ -237,7 +237,7 @@ class SettingsProvider extends ChangeNotifier {
/// Formats the date according to the current [dateFormat].
///
/// [toLocal] defines if the date will be converted to local time. Defaults to `true`
String formatTime(DateTime time, {bool toLocal = true}) {
String formatTime(DateTime time, {bool toLocal = false}) {
if (toLocal) time = time.toLocal();

return timeFormat.format(time);
Expand Down
20 changes: 17 additions & 3 deletions lib/utils/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ extension DurationExtension on Duration {

return this;
}

double get inDoubleSeconds {
return inMilliseconds / 1000;
}
}

extension IterableExtension<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T element) test) {
try {
return firstWhere(test);
} catch (_) {
return null;
}
}
}

extension NotificationExtensions on NotificationClickAction {
Expand Down Expand Up @@ -152,9 +166,9 @@ extension DateTimeExtension on DateTime {
}

bool isInBetween(DateTime first, DateTime second) {
return isAfter(first) && isBefore(second) ||
this == first ||
this == second;
return (isAfter(first) && isBefore(second)) ||
isAtSameMomentAs(first) ||
isAtSameMomentAs(second);
}
}

Expand Down
23 changes: 23 additions & 0 deletions lib/utils/tree_view/tree_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ import 'package:flutter_simple_treeview/flutter_simple_treeview.dart'
export 'package:flutter_simple_treeview/flutter_simple_treeview.dart'
show TreeNode, TreeController;

Widget buildCheckbox({
required bool? value,
required ValueChanged<bool?> onChanged,
required bool isError,
double checkboxScale = 0.8,
}) {
return Transform.scale(
scale: checkboxScale,
child: Checkbox(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -4,
),
splashRadius: 0.0,
tristate: true,
value: value,
isError: isError,
onChanged: onChanged,
),
);
}

/// Tree view with collapsible and expandable nodes.
class TreeView extends StatefulWidget {
/// List of root level tree nodes.
Expand Down
5 changes: 4 additions & 1 deletion lib/widgets/desktop_buttons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'package:bluecherry_client/models/device.dart';
import 'package:bluecherry_client/models/event.dart';
import 'package:bluecherry_client/providers/home_provider.dart';
import 'package:bluecherry_client/widgets/events/events_screen.dart';
import 'package:bluecherry_client/widgets/events_timeline/events_playback.dart';
import 'package:bluecherry_client/widgets/home.dart';
import 'package:bluecherry_client/widgets/misc.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -200,10 +201,12 @@ class _WindowButtonsState extends State<WindowButtons> with WindowListener {
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: UnityLoadingIndicator(),
)
else if (home.tab == UnityTab.eventsScreen.index && !canPop)
else if (home.tab == UnityTab.eventsScreen.index ||
home.tab == UnityTab.eventsPlayback.index && !canPop)
IconButton(
onPressed: () {
eventsScreenKey.currentState?.fetch();
eventsPlaybackScreenKey.currentState?.fetch();
},
icon: const Icon(Icons.refresh),
iconSize: 20.0,
Expand Down
21 changes: 20 additions & 1 deletion lib/widgets/device_grid/desktop/desktop_device_grid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ class DesktopDeviceTile extends StatelessWidget {

return UnityVideoView(
key: ValueKey(device.fullName),
heroTag: device.streamURL,
player: videoPlayer,
paneBuilder: (context, controller) {
return DesktopTileViewport(controller: controller, device: device);
Expand Down Expand Up @@ -322,6 +323,25 @@ class DesktopTileViewport extends StatefulWidget {
State<DesktopTileViewport> createState() => _DesktopTileViewportState();
}

const shadows = [
Shadow(
blurRadius: 10,
offset: Offset(-4, -4),
),
Shadow(
blurRadius: 10,
offset: Offset(4, 4),
),
Shadow(
blurRadius: 10,
offset: Offset(-4, 4),
),
Shadow(
blurRadius: 10,
offset: Offset(4, -4),
),
];

class _DesktopTileViewportState extends State<DesktopTileViewport> {
bool ptzEnabled = false;

Expand Down Expand Up @@ -359,7 +379,6 @@ class _DesktopTileViewportState extends State<DesktopTileViewport> {

final theme = Theme.of(context);
final view = context.watch<DesktopViewProvider>();

final isSubView = AlternativeWindow.maybeOf(context) != null;

Widget foreground = PTZController(
Expand Down
3 changes: 2 additions & 1 deletion lib/widgets/device_grid/desktop/layout_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class _LayoutManagerState extends State<LayoutManager> {
final settings = context.watch<SettingsProvider>();
timer?.cancel();
timer = Timer.periodic(settings.layoutCyclingTogglePeriod, (timer) {
final settings = SettingsProvider.instance;
if (!mounted) return;

final view = DesktopViewProvider.instance;

if (settings.layoutCyclingEnabled) {
Expand Down
10 changes: 3 additions & 7 deletions lib/widgets/device_grid/mobile/device_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,8 @@ class _MobileDeviceViewState extends State<MobileDeviceView> {
break;
case 1:
if (mounted) {
final result = await Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const DeviceSelectorScreen(),
),
);

if (result is Device) {
final result = await showDeviceSelectorScreen(context);
if (result != null) {
view.replace(widget.tab, widget.index, result);
if (mounted) setState(() {});
}
Expand Down Expand Up @@ -251,6 +246,7 @@ class DeviceTileState extends State<DeviceTile> {
if (videoPlayer == null) return const SizedBox.shrink();

return UnityVideoView(
heroTag: widget.device.streamURL,
player: videoPlayer!,
paneBuilder: (context, controller) {
final error = UnityVideoView.of(context).error;
Expand Down
Loading

0 comments on commit 0b01a64

Please sign in to comment.