Improve device and alarm cards. Implement audit logs page.

This commit is contained in:
Igor Kulikov
2021-06-14 19:04:31 +03:00
parent 648942de68
commit 21e42820fd
11 changed files with 793 additions and 142 deletions

View File

@@ -6,6 +6,7 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/init/init_routes.dart';
import 'package:thingsboard_app/modules/alarm/alarm_routes.dart';
import 'package:thingsboard_app/modules/asset/asset_routes.dart';
import 'package:thingsboard_app/modules/audit_log/audit_logs_routes.dart';
import 'package:thingsboard_app/modules/dashboard/dashboard_routes.dart';
import 'package:thingsboard_app/modules/device/device_routes.dart';
import 'package:thingsboard_app/modules/home/home_routes.dart';
@@ -37,6 +38,7 @@ class ThingsboardAppRouter {
DeviceRoutes(_tbContext).registerRoutes();
AlarmRoutes(_tbContext).registerRoutes();
DashboardRoutes(_tbContext).registerRoutes();
AuditLogsRoutes(_tbContext).registerRoutes();
}
TbContext get tbContext => _tbContext;

View File

@@ -6,9 +6,29 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:intl/intl.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
const Map<EntityType, String> entityTypeTranslations = {
EntityType.TENANT: 'Tenant',
EntityType.TENANT_PROFILE: 'Tenant profile',
EntityType.CUSTOMER: 'Customer',
EntityType.USER: 'User',
EntityType.DASHBOARD: 'Dashboard',
EntityType.ASSET: 'Asset',
EntityType.DEVICE: 'Device',
EntityType.DEVICE_PROFILE: 'Device profile',
EntityType.ALARM: 'Alarm',
EntityType.RULE_CHAIN: 'Rule chain',
EntityType.RULE_NODE: 'Rule node',
EntityType.EDGE: 'Edge',
EntityType.ENTITY_VIEW: 'Entity view',
EntityType.WIDGETS_BUNDLE: 'Widgets bundle',
EntityType.WIDGET_TYPE: 'Widget type',
EntityType.API_USAGE_STATE: 'Api Usage State',
EntityType.TB_RESOURCE: 'Resource',
EntityType.OTA_PACKAGE: 'OTA package'
};
typedef EntityTapFunction<T> = Function(T entity);
typedef EntityCardWidgetBuilder<T> = Widget Function(BuildContext context, T entity);

View File

@@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
import 'package:thingsboard_app/core/entity/entities_base.dart';
import 'package:thingsboard_app/utils/utils.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
@@ -46,7 +47,15 @@ mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
@override
void onEntityTap(AlarmInfo alarm) {
showErrorNotification('Balalai: alarm tap not implemented!');
String? dashboardId = alarm.details?['dashboardId'];
if (dashboardId != null) {
var state = Utils.createDashboardEntityState(alarm.originator, entityName: alarm.originatorName);
navigateToDashboard(dashboardId, dashboardTitle: alarm.originatorName, state: state);
} else {
if (tbClient.isTenantAdmin()) {
showWarnNotification('Mobile dashboard should be configured in device profile alarm rules!');
}
}
}
@override
@@ -114,6 +123,7 @@ class _AlarmCardState extends TbContextState<AlarmCard, _AlarmCardState> {
if (this.loading) {
return Container( height: 134, alignment: Alignment.center, child: RefreshProgressIndicator());
} else {
bool hasDashboard = alarm.details?['dashboardId'] != null;
return Stack(
children: [
Positioned.fill(
@@ -134,102 +144,121 @@ class _AlarmCardState extends TbContextState<AlarmCard, _AlarmCardState> {
SizedBox(width: 4),
Flexible(
fit: FlexFit.tight,
child:
Padding(
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: AutoSizeText(alarm.type,
maxLines: 2,
minFontSize: 8,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 14)
)
),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
]
),
SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: Text(alarm.originatorName != null ? alarm.originatorName! : '',
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
),
Text(alarmSeverityTranslations[alarm.severity]!,
style: TextStyle(
color: alarmSeverityColors[alarm.severity]!,
fontWeight: FontWeight.w500,
fontSize: 12,
height: 16 / 12)
)
]
),
SizedBox(height: 22),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
fit: FlexFit.tight,
child: Text(alarmStatusTranslations[alarm.status]!,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.normal,
fontSize: 14,
height: 20 / 14)
)
),
Row(
children: [
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
CircleAvatar(
radius: 16,
backgroundColor: Color(0xffF0F4F9),
child: IconButton(icon: Icon(Icons.done, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _ackAlarm(alarm))
),
if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status))
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 16),
Flexible(
fit: FlexFit.tight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12),
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 4),
CircleAvatar(
radius: 16,
backgroundColor: Color(0xffF0F4F9),
child: IconButton(icon: Icon(Icons.clear, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _clearAlarm(alarm))
Flexible(
fit: FlexFit.tight,
child: AutoSizeText(alarm.type,
maxLines: 2,
minFontSize: 8,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 14)
)
),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
]
)
],
),
SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: Text(alarm.originatorName != null ? alarm.originatorName! : '',
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
),
Text(alarmSeverityTranslations[alarm.severity]!,
style: TextStyle(
color: alarmSeverityColors[alarm.severity]!,
fontWeight: FontWeight.w500,
fontSize: 12,
height: 16 / 12)
)
]
),
SizedBox(height: 12)],
)
),
SizedBox(width: 16),
if (hasDashboard) Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
if (hasDashboard) SizedBox(width: 16),
]
),
Divider(height: 1),
SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 16),
Flexible(
fit: FlexFit.tight,
child: Text(alarmStatusTranslations[alarm.status]!,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.normal,
fontSize: 14,
height: 20 / 14)
)
),
SizedBox(height: 32),
Row(
children: [
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
CircleAvatar(
radius: 16,
backgroundColor: Color(0xffF0F4F9),
child: IconButton(icon: Icon(Icons.done, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _ackAlarm(alarm))
),
if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status))
Row(
children: [
SizedBox(width: 4),
CircleAvatar(
radius: 16,
backgroundColor: Color(0xffF0F4F9),
child: IconButton(icon: Icon(Icons.clear, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _clearAlarm(alarm))
)
]
)
],
)
]
)
),
SizedBox(width: 8)
],
),
SizedBox(height: 8)
]
)
)
]
)

View File

@@ -0,0 +1,129 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
import 'package:thingsboard_app/core/entity/entities_base.dart';
import 'package:thingsboard_app/modules/audit_log/audit_logs_base.dart';
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
class AuditLogDetailsPage extends TbContextWidget<AuditLogDetailsPage, _AuditLogDetailsPageState> {
final AuditLog auditLog;
AuditLogDetailsPage(TbContext tbContext, this.auditLog) : super(tbContext);
@override
_AuditLogDetailsPageState createState() => _AuditLogDetailsPageState();
}
class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage, _AuditLogDetailsPageState> {
final labelTextStyle = TextStyle(
color: Color(0xFF757575),
fontSize: 14,
height: 20 / 14
);
final valueTextStyle = TextStyle(
color: Color(0xFF282828),
fontSize: 14,
height: 20 / 14
);
final JsonEncoder encoder = new JsonEncoder.withIndent(' ');
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: TbAppBar(
tbContext,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.auditLog.entityName, style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16,
height: 20 / 16
)),
Text('Audit log details', style: TextStyle(
color: Theme.of(context).primaryTextTheme.headline6!.color!.withAlpha((0.38 * 255).ceil()),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
))
]
)
),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Text('Entity Type', style: labelTextStyle),
Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!, style: valueTextStyle),
SizedBox(height: 16),
Text('Type', style: labelTextStyle),
Text(actionTypeTranslations[widget.auditLog.actionType]!, style: valueTextStyle),
SizedBox(height: 16),
Flexible(
fit: FlexFit.loose,
child: buildBorderedText('Action data', encoder.convert(widget.auditLog.actionData))
),
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
SizedBox(height: 16),
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
Flexible(
fit: FlexFit.loose,
child: buildBorderedText('Failure details', widget.auditLog.actionFailureDetails!)
)
]
),
),
);
}
Widget buildBorderedText(String title, String content) {
return Stack(
children: <Widget>[
Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(16, 18, 48, 18),
margin: EdgeInsets.only(top: 6),
decoration: BoxDecoration(
border: Border.all(
color: Color(0xFFDEDEDE), width: 1),
borderRadius: BorderRadius.circular(4),
shape: BoxShape.rectangle,
),
child: SingleChildScrollView(
child: Text(
content,
style: TextStyle(
color: Color(0xFF282828),
fontSize: 14,
height: 20 / 14
),
),
),
),
Positioned(
left: 16,
top: 0,
child: Container(
padding: EdgeInsets.only(left: 4, right: 4),
color: Colors.white,
child: Text(
title,
style: TextStyle(color: Color(0xFF757575), fontSize: 12, height: 14 / 12),
),
)),
],
);
}
}

View File

@@ -0,0 +1,235 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
import 'package:thingsboard_app/core/entity/entities_base.dart';
import 'package:thingsboard_app/modules/audit_log/audit_log_details_page.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
const Map<ActionType, String> actionTypeTranslations = {
ActionType.ADDED: 'Added',
ActionType.DELETED: 'Deleted',
ActionType.UPDATED: 'Updated',
ActionType.ATTRIBUTES_UPDATED: 'Attributes Updated',
ActionType.ATTRIBUTES_DELETED: 'Attributes Deleted',
ActionType.RPC_CALL: 'RPC Call',
ActionType.CREDENTIALS_UPDATED: 'Credentials Updated',
ActionType.ASSIGNED_TO_CUSTOMER: 'Assigned to Customer',
ActionType.UNASSIGNED_FROM_CUSTOMER: 'Unassigned from Customer',
ActionType.ACTIVATED: 'Activated',
ActionType.SUSPENDED: 'Suspended',
ActionType.CREDENTIALS_READ: 'Credentials read',
ActionType.ATTRIBUTES_READ: 'Attributes read',
ActionType.RELATION_ADD_OR_UPDATE: 'Relation updated',
ActionType.RELATION_DELETED: 'Relation deleted',
ActionType.RELATIONS_DELETED: 'All relation deleted',
ActionType.ALARM_ACK: 'Acknowledged',
ActionType.ALARM_CLEAR: 'Cleared',
ActionType.LOGIN: 'Login',
ActionType.LOGOUT: 'Logout',
ActionType.LOCKOUT: 'Lockout',
ActionType.ASSIGNED_FROM_TENANT: 'Assigned from Tenant',
ActionType.ASSIGNED_TO_TENANT: 'Assigned to Tenant',
ActionType.PROVISION_SUCCESS: 'Device provisioned',
ActionType.PROVISION_FAILURE: 'Device provisioning was failed',
ActionType.TIMESERIES_UPDATED: 'Telemetry updated',
ActionType.TIMESERIES_DELETED: 'Telemetry deleted',
ActionType.ASSIGNED_TO_EDGE: 'Assigned to Edge',
ActionType.UNASSIGNED_FROM_EDGE: 'Unassigned from Edge'
};
const Map<ActionStatus, String> actionStatusTranslations = {
ActionStatus.SUCCESS: 'Success',
ActionStatus.FAILURE: 'Failure'
};
mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
@override
String get title => 'Audit Logs';
@override
String get noItemsFoundText => 'No audit logs found';
@override
Future<PageData<AuditLog>> fetchEntities(TimePageLink pageLink) {
return tbClient.getAuditLogService().getAuditLogs(pageLink);
}
@override
void onEntityTap(AuditLog auditLog) {
}
@override
Widget buildEntityListCard(BuildContext context, AuditLog auditLog) {
return _buildEntityListCard(context, auditLog);
}
Widget _buildEntityListCard(BuildContext context, AuditLog auditLog) {
return AuditLogCard(tbContext, auditLog: auditLog);
}
}
class AuditLogCard extends TbContextWidget<AuditLogCard, _AuditLogCardState> {
final AuditLog auditLog;
AuditLogCard(TbContext tbContext, {required this.auditLog}) : super(tbContext);
@override
_AuditLogCardState createState() => _AuditLogCardState();
}
class _AuditLogCardState extends TbContextState<AuditLogCard, _AuditLogCardState> {
final entityDateFormat = DateFormat('yyyy-MM-dd');
@override
void initState() {
super.initState();
}
@override
void didUpdateWidget(AuditLogCard oldWidget) {
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: Container(
alignment: Alignment.centerLeft,
child: Container(
width: 4,
decoration: BoxDecoration(
color: widget.auditLog.actionStatus == ActionStatus.SUCCESS ? Color(0xFF008A00) : Color(0xFFFF0000),
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
),
)
)
),
Row(
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(width: 4),
Flexible(
fit: FlexFit.tight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 16),
Flexible(
fit: FlexFit.tight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12),
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: AutoSizeText(widget.auditLog.entityName,
maxLines: 2,
minFontSize: 8,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 14)
)
),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.auditLog.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
]
),
SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!,
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
),
Text(actionStatusTranslations[widget.auditLog.actionStatus]!,
style: TextStyle(
color: widget.auditLog.actionStatus == ActionStatus.SUCCESS ? Color(0xFF008A00) : Color(0xFFFF0000),
fontWeight: FontWeight.w500,
fontSize: 12,
height: 16 / 12)
)
]
),
SizedBox(height: 12)],
)
),
SizedBox(width: 16)
]
),
SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 16),
Flexible(
fit: FlexFit.tight,
child: Text(actionTypeTranslations[widget.auditLog.actionType]!,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.normal,
fontSize: 14,
height: 20 / 14)
)
),
SizedBox(height: 32),
CircleAvatar(
radius: 16,
backgroundColor: Color(0xffF0F4F9),
child: IconButton(icon: Icon(Icons.code, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _auditLogDetails(widget.auditLog))
),
SizedBox(width: 8)
],
),
SizedBox(height: 8)
]
)
)
]
)
],
);
}
_auditLogDetails(AuditLog auditLog) {
Navigator.of(tbContext.currentState!.context).push(new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return new AuditLogDetailsPage(tbContext, auditLog);
},
fullscreenDialog: true
));
}
}

View File

@@ -0,0 +1,11 @@
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/entity/entities_base.dart';
import 'package:thingsboard_app/core/entity/entities_list.dart';
import 'package:thingsboard_app/modules/audit_log/audit_logs_base.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
class AuditLogsList extends BaseEntitiesWidget<AuditLog, TimePageLink> with AuditLogsBase, EntitiesListStateBase {
AuditLogsList(TbContext tbContext, PageKeyController<TimePageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
import 'package:thingsboard_app/core/entity/entities_base.dart';
import 'package:thingsboard_app/modules/audit_log/audit_logs_list.dart';
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
class AuditLogsPage extends TbPageWidget<AuditLogsPage, _AuditLogsPageState> {
final bool searchMode;
AuditLogsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
@override
_AuditLogsPageState createState() => _AuditLogsPageState();
}
class _AuditLogsPageState extends TbPageState<AuditLogsPage, _AuditLogsPageState> {
final TimePageLinkController _timePageLinkController = TimePageLinkController();
@override
Widget build(BuildContext context) {
var auditLogsList = AuditLogsList(tbContext, _timePageLinkController, searchMode: widget.searchMode);
PreferredSizeWidget appBar;
if (widget.searchMode) {
appBar = TbAppSearchBar(
tbContext,
onSearch: (searchText) => _timePageLinkController.onSearchText(searchText),
);
} else {
appBar = TbAppBar(
tbContext,
title: Text(auditLogsList.title),
actions: [
IconButton(
icon: Icon(
Icons.search
),
onPressed: () {
navigateTo('/auditLogs?search=true');
},
)
]);
}
return Scaffold(
appBar: appBar,
body: auditLogsList
);
}
@override
void dispose() {
_timePageLinkController.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,21 @@
import 'package:fluro/fluro.dart';
import 'package:flutter/widgets.dart';
import 'package:thingsboard_app/config/routes/router.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/modules/audit_log/audit_logs_page.dart';
class AuditLogsRoutes extends TbRoutes {
late var auditLogsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
var searchMode = params['search']?.first == 'true';
return AuditLogsPage(tbContext, searchMode: searchMode);
});
AuditLogsRoutes(TbContext tbContext) : super(tbContext);
@override
void doRegisterRoutes(router) {
router.define("/auditLogs", handler: auditLogsHandler);
}
}

View File

@@ -85,10 +85,17 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
_countDevices();
}
@override
void didUpdateWidget(AllDevicesCard oldWidget) {
super.didUpdateWidget(oldWidget);
widget.refreshDeviceCounts.onRefresh = _countDevices;
}
@override
void dispose() {
_activeDevicesCount.close();
_inactiveDevicesCount.close();
widget.refreshDeviceCounts.onRefresh = null;
super.dispose();
}

View File

@@ -31,12 +31,10 @@ mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
if (profile.defaultDashboardId != null) {
var dashboardId = profile.defaultDashboardId!.id!;
var state = Utils.createDashboardEntityState(device.entityId, entityName: device.field('name')!, entityLabel: device.field('label')!);
// navigateTo('/dashboard/$dashboardId?title=${device.field('name')!}&state=$state');
navigateToDashboard(dashboardId, dashboardTitle: device.field('name'), state: state);
} else {
// navigateTo('/device/${device.entityId.id}');
if (tbClient.isTenantAdmin()) {
showWarnNotification('BALALAI');
showWarnNotification('Mobile dashboard should be configured in device profile!');
}
}
}
@@ -101,7 +99,7 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
@override
void initState() {
super.initState();
if (widget.displayImage) {
if (widget.displayImage || !widget.listWidgetCard) {
deviceProfileFuture = DeviceProfileCache.getDeviceProfileInfo(
tbClient, widget.device.field('type')!, widget.device.entityId.id!);
}
@@ -110,7 +108,7 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
@override
void didUpdateWidget(DeviceCard oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.displayImage) {
if (widget.displayImage || !widget.listWidgetCard) {
var oldDevice = oldWidget.device;
var device = widget.device;
if (oldDevice.field('type')! != device.field('type')!) {
@@ -122,32 +120,186 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
@override
Widget build(BuildContext context) {
if (widget.listWidgetCard) {
return buildListWidgetCard(context);
} else {
return buildCard(context);
}
}
Widget buildCard(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: Container(
alignment: Alignment.centerLeft,
child: Container(
width: 4,
decoration: BoxDecoration(
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
),
)
)
),
FutureBuilder<DeviceProfileInfo>(
future: deviceProfileFuture,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
var profile = snapshot.data!;
bool hasDashboard = profile.defaultDashboardId != null;
Widget image;
BoxFit imageFit;
if (profile.image != null) {
var uriData = UriData.parse(profile.image!);
image = Image.memory(uriData.contentAsBytes());
imageFit = BoxFit.contain;
} else {
image = Image.asset(ThingsboardImage.deviceProfilePlaceholder);
imageFit = BoxFit.cover;
}
return Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 20),
Flexible(
fit: FlexFit.tight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12),
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (widget.displayImage) Container(
width: 40,
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4))
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(4)),
child: Stack(
children: [
Positioned.fill(
child: FittedBox(
fit: imageFit,
child: image,
)
)
],
)
)
),
SizedBox(width: 12),
Flexible(
fit: FlexFit.tight,
child: Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
FittedBox(
fit: BoxFit.fitWidth,
alignment: Alignment.centerLeft,
child: Text('${widget.device.field('name')!}',
style: TextStyle(
color: Color(0xFF282828),
fontSize: 14,
fontWeight: FontWeight.w500,
height: 20 / 14
))
),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
))
]
),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${widget.device.field('type')!}',
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
)),
Text(widget.device.attribute('active') == 'true' ? 'Active' : 'Inactive',
style: TextStyle(
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
fontSize: 12,
height: 16 / 12,
fontWeight: FontWeight.normal,
))
],
)
]
)
),
SizedBox(width: 16),
if (hasDashboard) Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
if (hasDashboard) SizedBox(width: 16),
]
),
SizedBox(height: 12)
],
)
)
]
);
} else {
return Container(
height: 64,
child: Center(
child: RefreshProgressIndicator(
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
)
)
);
}
}
)
]
);
}
Widget buildListWidgetCard(BuildContext context) {
return Row(
mainAxisSize: widget.listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
mainAxisSize: MainAxisSize.min,
children: [
if (widget.displayImage) Container(
width: widget.listWidgetCard ? 58 : 60,
height: widget.listWidgetCard ? 58 : 60,
width: 58,
height: 58,
decoration: BoxDecoration(
// color: Color(0xFFEEEEEE),
// color: Color(0xFFEEEEEE),
borderRadius: BorderRadius.horizontal(left: Radius.circular(4))
),
child: FutureBuilder<DeviceProfileInfo>(
future: deviceProfileFuture,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
var profile = snapshot.data!;
Widget image;
BoxFit imageFit;
if (profile.image != null) {
var uriData = UriData.parse(profile.image!);
image = Image.memory(uriData.contentAsBytes());
imageFit = BoxFit.contain;
} else {
image = Image.asset(ThingsboardImage.deviceProfilePlaceholder);
imageFit = BoxFit.cover;
}
return ClipRRect(
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
var profile = snapshot.data!;
Widget image;
BoxFit imageFit;
if (profile.image != null) {
var uriData = UriData.parse(profile.image!);
image = Image.memory(uriData.contentAsBytes());
imageFit = BoxFit.contain;
} else {
image = Image.asset(ThingsboardImage.deviceProfilePlaceholder);
imageFit = BoxFit.cover;
}
return ClipRRect(
borderRadius: BorderRadius.horizontal(left: Radius.circular(4)),
child: Stack(
children: [
@@ -159,24 +311,24 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
)
],
)
);
} else {
return Center(child: RefreshProgressIndicator(
);
} else {
return Center(child: RefreshProgressIndicator(
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
));
}
));
}
},
),
),
Flexible(
fit: widget.listWidgetCard ? FlexFit.loose : FlexFit.tight,
fit: FlexFit.loose,
child:
Container(
padding: EdgeInsets.symmetric(vertical: widget.listWidgetCard ? 9 : 10, horizontal: 16),
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
child: Column(
children: [
Row(
mainAxisSize: widget.listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
FittedBox(
@@ -189,19 +341,12 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
fontWeight: FontWeight.w500,
height: 20 / 14
))
),
if (!widget.listWidgetCard) Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
))
)
]
),
SizedBox(height: 4),
Row(
mainAxisSize: widget.listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${widget.device.field('type')!}',
@@ -211,14 +356,7 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
fontWeight: FontWeight.normal,
height: 16 / 12
)),
if (!widget.listWidgetCard) Text(widget.device.attribute('active') == 'true' ? 'Active' : 'Inactive',
style: TextStyle(
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
fontSize: 12,
height: 16 / 12,
fontWeight: FontWeight.normal,
))
],
]
)
],
)