Skip to content

Commit

Permalink
Merge pull request #72 from bdlukaa/quality
Browse files Browse the repository at this point in the history
  • Loading branch information
curtishall authored Mar 2, 2023
2 parents 7483869 + 00eb41a commit 444c542
Show file tree
Hide file tree
Showing 16 changed files with 443 additions and 111 deletions.
53 changes: 28 additions & 25 deletions lib/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ class API {
});
final response = await request.send();
final body = await response.stream.bytesToString();
debugPrint(body.toString());
debugPrint(response.headers.toString());

if (response.statusCode == 200) {
debugPrint(body.toString());
debugPrint(response.headers.toString());
final json = await compute(jsonDecode, body);
return server.copyWith(
serverUUID: json['server_uuid'],
Expand All @@ -65,7 +65,7 @@ class API {
/// Gets [Device] devices present on the [server] after login.
/// Returns `true` if it is a success or `false` if it failed.
/// The found [Device] devices are saved in [Server.devices].
Future<bool> getDevices(Server server) async {
Future<List<Device>?> getDevices(Server server) async {
try {
assert(server.serverUUID != null && server.cookie != null);
final response = await get(
Expand All @@ -77,34 +77,37 @@ class API {
},
),
headers: {
'Cookie': server.cookie!,
if (server.cookie != null) 'Cookie': server.cookie!,
},
);
debugPrint(response.body);
final parser = Xml2Json()..parse(response.body);
server.devices.clear();
server.devices.addAll(
(await compute(jsonDecode, parser.toParker()))['devices']['device']
.map(
(e) => Device(
e['device_name'],
'live/${e['id']}',
e['status'] == 'OK',
e['resolutionX'] == null ? null : int.parse(e['resolutionX']),
e['resolutionX'] == null ? null : int.parse(e['resolutionY']),
server,
),
)
.toList()
.cast<Device>()
// cause `online` devies to show on top.
..sort((a, b) => a.status ? 0 : 1),
);
return true;
final devices =
(await compute(jsonDecode, parser.toParker()))['devices']['device']
.map(
(e) => Device(
e['device_name'],
'live/${e['id']}',
e['status'] == 'OK',
e['resolutionX'] == null ? null : int.parse(e['resolutionX']),
e['resolutionX'] == null ? null : int.parse(e['resolutionY']),
server,
),
)
.toList()
.cast<Device>()
// cause `online` devies to show on top.
..sort((Device a, Device b) => a.status ? 0 : 1);

server.devices
..clear()
..addAll(devices);
return devices;
} catch (exception, stacktrace) {
debugPrint(exception.toString());
debugPrint(stacktrace.toString());
}
return false;
return null;
}

/// Gets [Event]s present on the [server] after login.
Expand Down Expand Up @@ -136,7 +139,7 @@ class API {
return Event(
server,
int.parse(e['id']['raw']),
int.parse(e['category']['term'].split('/').first),
int.parse((e['category']['term'] as String).split('/').first),
e['title']['\$t'],
e['published'] == null || e['published']['\$t'] == null
? DateTime.now()
Expand Down
14 changes: 14 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,25 @@
"fromDate": "From",
"toDate": "To",
"allowAlarms": "Allow alarms",
"@Event Priorities": {},
"info": "Info",
"warn": "Warning",
"alarm": "Alarm",
"critical": "Critical",
"@Event Types": {},
"motion": "Motion",
"continuous": "Continouous",
"notFound": "Not found",
"cameraVideoLost": "Video Lost",
"cameraAudioLost": "Audio Lost",
"systemDiskSpace": "Disk Space",
"systemCrash": "Crash",
"systemBoot": "Startup",
"systemShutdown": "Shutdown",
"systemReboot": "Reboot",
"systemPowerOutage": "Power Lost",
"unknown": "Unknown",
"@": {},
"close": "Close",
"open": "Open"
}
23 changes: 20 additions & 3 deletions lib/models/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ class Event {
}
}

// TODO(bdlukaa): locale for these
enum EventPriority {
info,
warning,
Expand All @@ -218,12 +217,13 @@ enum EventPriority {
final localizations = AppLocalizations.of(context);
switch (this) {
case EventPriority.info:
return localizations.info;
case EventPriority.warning:
return localizations.warn;
case EventPriority.alarm:
return localizations.alarm;
case EventPriority.critical:
return localizations.notFound;
return localizations.critical;
}
}
}
Expand All @@ -250,8 +250,25 @@ enum EventType {
case EventType.continuous:
return localizations.continuous;
case EventType.notFound:
default:
return localizations.notFound;
case EventType.cameraVideoLost:
return localizations.cameraVideoLost;
case EventType.cameraAudioLost:
return localizations.cameraAudioLost;
case EventType.systemDiskSpace:
return localizations.systemDiskSpace;
case EventType.systemCrash:
return localizations.systemCrash;
case EventType.systemBoot:
return localizations.systemBoot;
case EventType.systemShutdown:
return localizations.systemShutdown;
case EventType.systemReboot:
return localizations.systemReboot;
case EventType.systemPowerOutage:
return localizations.systemPowerOutage;
case EventType.unknown:
return localizations.unknown;
}
}
}
11 changes: 11 additions & 0 deletions lib/providers/server_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ class ServersProvider extends ChangeNotifier {
} else {
await _restore();
}

/// Fetch for any new device at startup
// for (final server in servers) {
// final devices = await API.instance.getDevices(server);
// if (devices != null) {
// server.devices
// ..clear()
// ..addAll(devices);
// }
// }
// await _save();
}

/// Adds a new [Server] to the cache.
Expand Down
9 changes: 9 additions & 0 deletions lib/utils/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,12 @@ extension EventsExtension on Iterable<Event> {
return where((e) => e.published.isInBetween(d1, d2));
}
}

extension DeviceListExtension on List<Device> {
/// Returns this device list sorted properly
List<Device> sorted() {
return [...this]
..sort((a, b) => a.name.compareTo(b.name))
..sort((a, b) => a.status ? 0 : 1);
}
}
6 changes: 4 additions & 2 deletions lib/utils/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,10 @@ ThemeData createTheme({
},
),
),
buttonTheme:
ButtonThemeData(disabledColor: light ? Colors.black12 : Colors.white24),
buttonTheme: ButtonThemeData(
disabledColor: light ? Colors.black12 : Colors.white24,
alignedDropdown: true,
),
splashFactory: InkSparkle.splashFactory,
highlightColor: defaultTargetPlatform == TargetPlatform.android
? Colors.transparent
Expand Down
185 changes: 185 additions & 0 deletions lib/utils/tree_view/tree_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// ORIGINAL PACKAGE: https://pub.dev/packages/flutter_simple_treeview

import 'package:flutter/material.dart';
import 'package:flutter_simple_treeview/flutter_simple_treeview.dart'
show TreeNode, TreeController;

export 'package:flutter_simple_treeview/flutter_simple_treeview.dart'
show TreeNode, TreeController;

/// Tree view with collapsible and expandable nodes.
class TreeView extends StatefulWidget {
/// List of root level tree nodes.
final List<TreeNode> nodes;

/// Horizontal indent between levels.
final double? indent;

/// Size of the expand/collapse icon.
final double? iconSize;

/// Tree controller to manage the tree state.
final TreeController? treeController;

TreeView(
{Key? key,
required List<TreeNode> nodes,
this.indent = 40,
this.iconSize,
this.treeController})
: nodes = copyTreeNodes(nodes),
super(key: key);

@override
State<TreeView> createState() => _TreeViewState();
}

class _TreeViewState extends State<TreeView> {
TreeController? _controller;

@override
void initState() {
_controller = widget.treeController ?? TreeController();
super.initState();
}

@override
Widget build(BuildContext context) {
return buildNodes(
widget.nodes, widget.indent, _controller!, widget.iconSize);
}
}

/// Builds set of [nodes] respecting [state], [indent] and [iconSize].
Widget buildNodes(Iterable<TreeNode> nodes, double? indent,
TreeController state, double? iconSize) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var node in nodes)
NodeWidget(
treeNode: node,
indent: indent,
state: state,
iconSize: iconSize,
)
],
);
}

/// Copies nodes to unmodifiable list, assigning missing keys and checking for duplicates.
List<TreeNode> copyTreeNodes(List<TreeNode>? nodes) {
return _copyNodesRecursively(nodes, KeyProvider())!;
}

List<TreeNode>? _copyNodesRecursively(
List<TreeNode>? nodes, KeyProvider keyProvider) {
if (nodes == null) {
return null;
}
return List.unmodifiable(nodes.map((n) {
return TreeNode(
key: keyProvider.key(n.key),
content: n.content,
children: _copyNodesRecursively(n.children, keyProvider),
);
}));
}

class _TreeNodeKey extends ValueKey {
const _TreeNodeKey(dynamic value) : super(value);
}

/// Provides unique keys and verifies duplicates.
class KeyProvider {
int _nextIndex = 0;
final Set<Key> _keys = <Key>{};

/// If [originalKey] is null, generates new key, otherwise verifies the key
/// was not met before.
Key key(Key? originalKey) {
if (originalKey == null) {
return _TreeNodeKey(_nextIndex++);
}
if (_keys.contains(originalKey)) {
throw ArgumentError('There should not be nodes with the same kays. '
'Duplicate value found: $originalKey.');
}
_keys.add(originalKey);
return originalKey;
}
}

/// Widget that displays one [TreeNode] and its children.
class NodeWidget extends StatefulWidget {
final TreeNode treeNode;
final double? indent;
final double? iconSize;
final TreeController state;

const NodeWidget(
{Key? key,
required this.treeNode,
this.indent,
required this.state,
this.iconSize})
: super(key: key);

@override
State<NodeWidget> createState() => _NodeWidgetState();
}

class _NodeWidgetState extends State<NodeWidget> {
bool get _isLeaf {
return widget.treeNode.children == null;
}

bool get _isEnabled {
return widget.treeNode.children?.isNotEmpty ?? false;
}

bool get _isExpanded {
return widget.state.isNodeExpanded(widget.treeNode.key!);
}

@override
Widget build(BuildContext context) {
var icon = _isLeaf
? null
: _isExpanded
? Icons.expand_more
: Icons.chevron_right;

var onIconPressed = _isLeaf || !_isEnabled
? null
: () => setState(
() => widget.state.toggleNodeExpanded(widget.treeNode.key!));

return IgnorePointer(
ignoring: _isLeaf ? false : !_isEnabled,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
if (!_isLeaf)
Padding(
padding: const EdgeInsetsDirectional.only(start: 8.0),
child: InkWell(
onTap: onIconPressed,
borderRadius: BorderRadius.circular(100),
child: Padding(
padding: const EdgeInsets.all(4.5),
child: Icon(icon, size: widget.iconSize),
),
),
),
Expanded(child: widget.treeNode.content),
]),
if (_isExpanded && !_isLeaf)
Padding(
padding: EdgeInsetsDirectional.only(start: widget.indent!),
child: buildNodes(widget.treeNode.children!, widget.indent,
widget.state, widget.iconSize),
)
]),
);
}
}
Loading

0 comments on commit 444c542

Please sign in to comment.