Add flutter 3+ support. Update dependencies. Fix code style and format issues.
This commit is contained in:
@@ -6,8 +6,8 @@ import 'package:thingsboard_app/modules/alarm/alarms_page.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
class AlarmRoutes extends TbRoutes {
|
||||
|
||||
late var alarmsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var alarmsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
if (searchMode) {
|
||||
return AlarmsPage(tbContext, searchMode: true);
|
||||
@@ -22,5 +22,4 @@ class AlarmRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/alarms", handler: alarmsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -8,7 +7,6 @@ import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
|
||||
const Map<AlarmSeverity, Color> alarmSeverityColors = {
|
||||
AlarmSeverity.CRITICAL: Color(0xFFFF0000),
|
||||
AlarmSeverity.MAJOR: Color(0xFFFFA500),
|
||||
@@ -33,7 +31,6 @@ const Map<AlarmStatus, String> alarmStatusTranslations = {
|
||||
};
|
||||
|
||||
mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
|
||||
|
||||
@override
|
||||
String get title => 'Alarms';
|
||||
|
||||
@@ -49,11 +46,14 @@ mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
|
||||
void onEntityTap(AlarmInfo alarm) {
|
||||
String? dashboardId = alarm.details?['dashboardId'];
|
||||
if (dashboardId != null) {
|
||||
var state = Utils.createDashboardEntityState(alarm.originator, entityName: alarm.originatorName);
|
||||
navigateToDashboard(dashboardId, dashboardTitle: alarm.originatorName, state: state);
|
||||
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!');
|
||||
showWarnNotification(
|
||||
'Mobile dashboard should be configured in device profile alarm rules!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,11 @@ mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
|
||||
}
|
||||
|
||||
class AlarmQueryController extends PageKeyController<AlarmQuery> {
|
||||
|
||||
AlarmQueryController({int pageSize = 20, String? searchText}) : super(AlarmQuery(TimePageLink(pageSize, 0, searchText, SortOrder('createdTime', Direction.DESC)), fetchOriginator: true));
|
||||
AlarmQueryController({int pageSize = 20, String? searchText})
|
||||
: super(AlarmQuery(
|
||||
TimePageLink(pageSize, 0, searchText,
|
||||
SortOrder('createdTime', Direction.DESC)),
|
||||
fetchOriginator: true));
|
||||
|
||||
@override
|
||||
AlarmQuery nextPageKey(AlarmQuery pageKey) {
|
||||
@@ -83,28 +86,24 @@ class AlarmQueryController extends PageKeyController<AlarmQuery> {
|
||||
query.pageLink.textSearch = searchText;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AlarmCard extends TbContextWidget {
|
||||
|
||||
final AlarmInfo alarm;
|
||||
|
||||
AlarmCard(TbContext tbContext, {required this.alarm}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AlarmCardState createState() => _AlarmCardState(alarm);
|
||||
|
||||
}
|
||||
|
||||
class _AlarmCardState extends TbContextState<AlarmCard> {
|
||||
|
||||
bool loading = false;
|
||||
AlarmInfo alarm;
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
_AlarmCardState(this.alarm): super();
|
||||
_AlarmCardState(this.alarm) : super();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -121,161 +120,176 @@ class _AlarmCardState extends TbContextState<AlarmCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (this.loading) {
|
||||
return Container( height: 134, alignment: Alignment.center, child: RefreshProgressIndicator());
|
||||
return Container(
|
||||
height: 134,
|
||||
alignment: Alignment.center,
|
||||
child: RefreshProgressIndicator());
|
||||
} else {
|
||||
bool hasDashboard = alarm.details?['dashboardId'] != null;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
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(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!)),
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
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(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,
|
||||
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),
|
||||
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(
|
||||
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)
|
||||
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(height: 8)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
SizedBox(width: 8)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8)
|
||||
]))
|
||||
])
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_clearAlarm(AlarmInfo alarm) async {
|
||||
var res = await confirm(title: 'Clear Alarm', message: 'Are you sure you want to clear Alarm?', cancel: 'No', ok: 'Yes');
|
||||
var res = await confirm(
|
||||
title: 'Clear Alarm',
|
||||
message: 'Are you sure you want to clear Alarm?',
|
||||
cancel: 'No',
|
||||
ok: 'Yes');
|
||||
if (res != null && res) {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
await tbClient.getAlarmService().clearAlarm(alarm.id!.id!);
|
||||
var newAlarm = await tbClient.getAlarmService().getAlarmInfo(
|
||||
alarm.id!.id!);
|
||||
var newAlarm =
|
||||
await tbClient.getAlarmService().getAlarmInfo(alarm.id!.id!);
|
||||
setState(() {
|
||||
loading = false;
|
||||
this.alarm = newAlarm!;
|
||||
@@ -284,19 +298,22 @@ class _AlarmCardState extends TbContextState<AlarmCard> {
|
||||
}
|
||||
|
||||
_ackAlarm(AlarmInfo alarm) async {
|
||||
var res = await confirm(title: 'Acknowledge Alarm', message: 'Are you sure you want to acknowledge Alarm?', cancel: 'No', ok: 'Yes');
|
||||
var res = await confirm(
|
||||
title: 'Acknowledge Alarm',
|
||||
message: 'Are you sure you want to acknowledge Alarm?',
|
||||
cancel: 'No',
|
||||
ok: 'Yes');
|
||||
if (res != null && res) {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
await tbClient.getAlarmService().ackAlarm(alarm.id!.id!);
|
||||
var newAlarm = await tbClient.getAlarmService().getAlarmInfo(
|
||||
alarm.id!.id!);
|
||||
var newAlarm =
|
||||
await tbClient.getAlarmService().getAlarmInfo(alarm.id!.id!);
|
||||
setState(() {
|
||||
loading = false;
|
||||
this.alarm = newAlarm!;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
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';
|
||||
@@ -6,9 +5,10 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'alarms_base.dart';
|
||||
|
||||
class AlarmsList extends BaseEntitiesWidget<AlarmInfo, AlarmQuery> with AlarmsBase, EntitiesListStateBase {
|
||||
|
||||
AlarmsList(TbContext tbContext, PageKeyController<AlarmQuery> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class AlarmsList extends BaseEntitiesWidget<AlarmInfo, AlarmQuery>
|
||||
with AlarmsBase, EntitiesListStateBase {
|
||||
AlarmsList(
|
||||
TbContext tbContext, PageKeyController<AlarmQuery> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,18 +7,16 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'alarms_list.dart';
|
||||
|
||||
class AlarmsPage extends TbContextWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
AlarmsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AlarmsPageState createState() => _AlarmsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AlarmsPageState extends TbContextState<AlarmsPage> with AutomaticKeepAliveClientMixin<AlarmsPage> {
|
||||
|
||||
class _AlarmsPageState extends TbContextState<AlarmsPage>
|
||||
with AutomaticKeepAliveClientMixin<AlarmsPage> {
|
||||
final AlarmQueryController _alarmQueryController = AlarmQueryController();
|
||||
|
||||
@override
|
||||
@@ -29,32 +27,26 @@ class _AlarmsPageState extends TbContextState<AlarmsPage> with AutomaticKeepAliv
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
var alarmsList = AlarmsList(tbContext, _alarmQueryController, searchMode: widget.searchMode);
|
||||
var alarmsList = AlarmsList(tbContext, _alarmQueryController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _alarmQueryController.onSearchText(searchText),
|
||||
onSearch: (searchText) =>
|
||||
_alarmQueryController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(alarmsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/alarms?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(alarmsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/alarms?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: alarmsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: alarmsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -62,5 +54,4 @@ class _AlarmsPageState extends TbContextState<AlarmsPage> with AutomaticKeepAliv
|
||||
_alarmQueryController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AssetDetailsPage extends EntityDetailsPage<AssetInfo> {
|
||||
|
||||
AssetDetailsPage(TbContext tbContext, String assetId):
|
||||
super(tbContext,
|
||||
entityId: assetId,
|
||||
defaultTitle: 'Asset', subTitle: 'Asset details');
|
||||
AssetDetailsPage(TbContext tbContext, String assetId)
|
||||
: super(tbContext,
|
||||
entityId: assetId,
|
||||
defaultTitle: 'Asset',
|
||||
subTitle: 'Asset details');
|
||||
|
||||
@override
|
||||
Future<AssetInfo?> fetchEntity(String assetId) {
|
||||
@@ -35,9 +35,6 @@ class AssetDetailsPage extends EntityDetailsPage<AssetInfo> {
|
||||
SizedBox(height: 16),
|
||||
Text('Assigned to customer', style: labelTextStyle),
|
||||
Text(asset.customerTitle ?? '', style: valueTextStyle),
|
||||
]
|
||||
)
|
||||
);
|
||||
]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import 'package:thingsboard_app/modules/asset/assets_page.dart';
|
||||
import 'asset_details_page.dart';
|
||||
|
||||
class AssetRoutes extends TbRoutes {
|
||||
|
||||
late var assetsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var assetsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return AssetsPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
|
||||
late var assetDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var assetDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return AssetDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -24,5 +25,4 @@ class AssetRoutes extends TbRoutes {
|
||||
router.define("/assets", handler: assetsHandler);
|
||||
router.define("/asset/:id", handler: assetDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
|
||||
|
||||
@override
|
||||
String get title => 'Assets';
|
||||
|
||||
@@ -16,7 +14,9 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
return tbClient.getAssetService().getTenantAssetInfos(pageLink);
|
||||
} else {
|
||||
return tbClient.getAssetService().getCustomerAssetInfos(tbClient.getAuthUser()!.customerId, pageLink);
|
||||
return tbClient
|
||||
.getAssetService()
|
||||
.getCustomerAssetInfos(tbClient.getAuthUser()!.customerId!, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,115 +41,91 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
|
||||
}
|
||||
|
||||
Widget _buildCard(context, AssetInfo asset) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(asset.createdTime!)),
|
||||
return Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text('${asset.type}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
SizedBox(width: 16)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
)
|
||||
]
|
||||
);
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14))),
|
||||
Text(
|
||||
entityDateFormat.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
asset.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12))
|
||||
]),
|
||||
SizedBox(height: 4),
|
||||
Text('${asset.type}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33))
|
||||
],
|
||||
)),
|
||||
SizedBox(width: 16),
|
||||
Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
SizedBox(width: 16)
|
||||
],
|
||||
),
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildListWidgetCard(BuildContext context, AssetInfo asset) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7
|
||||
))
|
||||
),
|
||||
Text('${asset.type}',
|
||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7))),
|
||||
Text('${asset.type}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33))
|
||||
],
|
||||
))
|
||||
])))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'assets_base.dart';
|
||||
|
||||
class AssetsList extends BaseEntitiesWidget<AssetInfo, PageLink> with AssetsBase, EntitiesListStateBase {
|
||||
|
||||
AssetsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class AssetsList extends BaseEntitiesWidget<AssetInfo, PageLink>
|
||||
with AssetsBase, EntitiesListStateBase {
|
||||
AssetsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ import 'package:thingsboard_app/core/entity/entities_list_widget.dart';
|
||||
import 'package:thingsboard_app/modules/asset/assets_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AssetsListWidget extends EntitiesListPageLinkWidget<AssetInfo> with AssetsBase {
|
||||
|
||||
AssetsListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller);
|
||||
class AssetsListWidget extends EntitiesListPageLinkWidget<AssetInfo>
|
||||
with AssetsBase {
|
||||
AssetsListWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
navigateTo('/assets');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,23 +7,21 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'assets_list.dart';
|
||||
|
||||
class AssetsPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
AssetsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AssetsPageState createState() => _AssetsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AssetsPageState extends TbPageState<AssetsPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var assetsList = AssetsList(tbContext, _pageLinkController, searchMode: widget.searchMode);
|
||||
var assetsList = AssetsList(tbContext, _pageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
@@ -31,24 +29,16 @@ class _AssetsPageState extends TbPageState<AssetsPage> {
|
||||
onSearch: (searchText) => _pageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(assetsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/assets?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(assetsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/assets?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: assetsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: assetsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -56,5 +46,4 @@ class _AssetsPageState extends TbPageState<AssetsPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,29 +9,20 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AuditLogDetailsPage extends TbContextWidget {
|
||||
|
||||
final AuditLog auditLog;
|
||||
|
||||
AuditLogDetailsPage(TbContext tbContext, this.auditLog) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogDetailsPageState createState() => _AuditLogDetailsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
final labelTextStyle =
|
||||
TextStyle(color: Color(0xFF757575), fontSize: 14, height: 20 / 14);
|
||||
|
||||
final labelTextStyle = TextStyle(
|
||||
color: Color(0xFF757575),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
|
||||
final valueTextStyle = TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
final valueTextStyle =
|
||||
TextStyle(color: Color(0xFF282828), fontSize: 14, height: 20 / 14);
|
||||
|
||||
final JsonEncoder encoder = new JsonEncoder.withIndent(' ');
|
||||
|
||||
@@ -39,51 +30,52 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.auditLog.entityName != null)
|
||||
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
|
||||
))
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: TbAppBar(tbContext,
|
||||
title:
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
if (widget.auditLog.entityName != null)
|
||||
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)
|
||||
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),
|
||||
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: buildBorderedText('Failure details', widget.auditLog.actionFailureDetails!)
|
||||
)
|
||||
]
|
||||
),
|
||||
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!))
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -96,8 +88,7 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
padding: EdgeInsets.fromLTRB(16, 18, 48, 18),
|
||||
margin: EdgeInsets.only(top: 6),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Color(0xFFDEDEDE), width: 1),
|
||||
border: Border.all(color: Color(0xFFDEDEDE), width: 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
shape: BoxShape.rectangle,
|
||||
),
|
||||
@@ -105,10 +96,7 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
child: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
color: Color(0xFF282828), fontSize: 14, height: 20 / 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -120,11 +108,11 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
color: Colors.white,
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(color: Color(0xFF757575), fontSize: 12, height: 14 / 12),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF757575), fontSize: 12, height: 14 / 12),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -46,7 +45,6 @@ const Map<ActionStatus, String> actionStatusTranslations = {
|
||||
};
|
||||
|
||||
mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
|
||||
|
||||
@override
|
||||
String get title => 'Audit Logs';
|
||||
|
||||
@@ -59,8 +57,7 @@ mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
|
||||
}
|
||||
|
||||
@override
|
||||
void onEntityTap(AuditLog auditLog) {
|
||||
}
|
||||
void onEntityTap(AuditLog auditLog) {}
|
||||
|
||||
@override
|
||||
Widget buildEntityListCard(BuildContext context, AuditLog auditLog) {
|
||||
@@ -73,21 +70,18 @@ mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
|
||||
}
|
||||
|
||||
class AuditLogCard extends TbContextWidget {
|
||||
|
||||
final AuditLog auditLog;
|
||||
|
||||
AuditLogCard(TbContext tbContext, {required this.auditLog}) : super(tbContext);
|
||||
AuditLogCard(TbContext tbContext, {required this.auditLog})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogCardState createState() => _AuditLogCardState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogCardState extends TbContextState<AuditLogCard> {
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -100,131 +94,141 @@ class _AuditLogCardState extends TbContextState<AuditLogCard> {
|
||||
|
||||
@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,
|
||||
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: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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(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)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
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) {
|
||||
tbContext.showFullScreenDialog(new AuditLogDetailsPage(tbContext, auditLog));
|
||||
tbContext
|
||||
.showFullScreenDialog(new AuditLogDetailsPage(tbContext, auditLog));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ 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);
|
||||
|
||||
class AuditLogsList extends BaseEntitiesWidget<AuditLog, TimePageLink>
|
||||
with AuditLogsBase, EntitiesListStateBase {
|
||||
AuditLogsList(
|
||||
TbContext tbContext, PageKeyController<TimePageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
@@ -6,48 +6,41 @@ import 'package:thingsboard_app/modules/audit_log/audit_logs_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class AuditLogsPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
AuditLogsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
AuditLogsPage(TbContext tbContext, {this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogsPageState createState() => _AuditLogsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogsPageState extends TbPageState<AuditLogsPage> {
|
||||
|
||||
final TimePageLinkController _timePageLinkController = TimePageLinkController();
|
||||
final TimePageLinkController _timePageLinkController =
|
||||
TimePageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var auditLogsList = AuditLogsList(tbContext, _timePageLinkController, searchMode: widget.searchMode);
|
||||
var auditLogsList = AuditLogsList(tbContext, _timePageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _timePageLinkController.onSearchText(searchText),
|
||||
onSearch: (searchText) =>
|
||||
_timePageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(auditLogsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/auditLogs?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(auditLogsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/auditLogs?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: auditLogsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: auditLogsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -55,5 +48,4 @@ class _AuditLogsPageState extends TbPageState<AuditLogsPage> {
|
||||
_timePageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ 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) {
|
||||
late var auditLogsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return AuditLogsPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
@@ -17,5 +17,4 @@ class AuditLogsRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/auditLogs", handler: auditLogsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class CustomerDetailsPage extends ContactBasedDetailsPage<Customer> {
|
||||
|
||||
CustomerDetailsPage(TbContext tbContext, String customerId):
|
||||
super(tbContext, entityId: customerId, defaultTitle: 'Customer', subTitle: 'Customer details');
|
||||
CustomerDetailsPage(TbContext tbContext, String customerId)
|
||||
: super(tbContext,
|
||||
entityId: customerId,
|
||||
defaultTitle: 'Customer',
|
||||
subTitle: 'Customer details');
|
||||
|
||||
@override
|
||||
Future<Customer?> fetchEntity(String customerId) {
|
||||
return tbClient.getCustomerService().getCustomer(customerId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import 'customer_details_page.dart';
|
||||
import 'customers_page.dart';
|
||||
|
||||
class CustomerRoutes extends TbRoutes {
|
||||
|
||||
late var customersHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var customersHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return CustomersPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
|
||||
late var customerDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var customerDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return CustomerDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -23,5 +24,4 @@ class CustomerRoutes extends TbRoutes {
|
||||
router.define("/customers", handler: customersHandler);
|
||||
router.define("/customer/:id", handler: customerDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin CustomersBase on EntitiesBase<Customer, PageLink> {
|
||||
|
||||
mixin CustomersBase on EntitiesBase<Customer, PageLink> {
|
||||
@override
|
||||
String get title => 'Customers';
|
||||
|
||||
@@ -18,5 +17,4 @@ mixin CustomersBase on EntitiesBase<Customer, PageLink> {
|
||||
void onEntityTap(Customer customer) {
|
||||
navigateTo('/customer/${customer.id!.id}');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'customers_base.dart';
|
||||
|
||||
class CustomersList extends BaseEntitiesWidget<Customer, PageLink> with CustomersBase, ContactBasedBase, EntitiesListStateBase {
|
||||
|
||||
CustomersList(TbContext tbContext, PageKeyController<PageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class CustomersList extends BaseEntitiesWidget<Customer, PageLink>
|
||||
with CustomersBase, ContactBasedBase, EntitiesListStateBase {
|
||||
CustomersList(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
@@ -6,23 +6,22 @@ import 'package:thingsboard_app/modules/customer/customers_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class CustomersPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
CustomersPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
CustomersPage(TbContext tbContext, {this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_CustomersPageState createState() => _CustomersPageState();
|
||||
|
||||
}
|
||||
|
||||
class _CustomersPageState extends TbPageState<CustomersPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var customersList = CustomersList(tbContext, _pageLinkController, searchMode: widget.searchMode);
|
||||
var customersList = CustomersList(tbContext, _pageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
@@ -30,24 +29,16 @@ class _CustomersPageState extends TbPageState<CustomersPage> {
|
||||
onSearch: (searchText) => _pageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(customersList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/customers?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(customersList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/customers?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: customersList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: customersList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -55,5 +46,4 @@ class _CustomersPageState extends TbPageState<CustomersPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:thingsboard_app/constants/app_constants.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
@@ -11,10 +10,9 @@ import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_app/widgets/two_value_listenable_builder.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class DashboardController {
|
||||
|
||||
final ValueNotifier<bool> canGoBack = ValueNotifier(false);
|
||||
final ValueNotifier<bool> hasRightLayout = ValueNotifier(false);
|
||||
final ValueNotifier<bool> rightLayoutOpened = ValueNotifier(false);
|
||||
@@ -22,8 +20,10 @@ class DashboardController {
|
||||
final _DashboardState dashboardState;
|
||||
DashboardController(this.dashboardState);
|
||||
|
||||
Future<void> openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
return await dashboardState._openDashboard(dashboardId, state: state, hideToolbar: hideToolbar, fullscreen: fullscreen);
|
||||
Future<void> openDashboard(String dashboardId,
|
||||
{String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
return await dashboardState._openDashboard(dashboardId,
|
||||
state: state, hideToolbar: hideToolbar, fullscreen: fullscreen);
|
||||
}
|
||||
|
||||
Future<bool> goBack() async {
|
||||
@@ -59,22 +59,26 @@ class DashboardController {
|
||||
hasRightLayout.dispose();
|
||||
rightLayoutOpened.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
typedef DashboardTitleCallback = void Function(String title);
|
||||
|
||||
typedef DashboardControllerCallback = void Function(DashboardController controller);
|
||||
typedef DashboardControllerCallback = void Function(
|
||||
DashboardController controller);
|
||||
|
||||
class Dashboard extends TbContextWidget {
|
||||
|
||||
final bool? _home;
|
||||
final bool _activeByDefault;
|
||||
final DashboardTitleCallback? _titleCallback;
|
||||
final DashboardControllerCallback? _controllerCallback;
|
||||
|
||||
Dashboard(TbContext tbContext, {Key? key, bool? home, bool activeByDefault = true, DashboardTitleCallback? titleCallback, DashboardControllerCallback? controllerCallback}):
|
||||
this._home = home,
|
||||
Dashboard(TbContext tbContext,
|
||||
{Key? key,
|
||||
bool? home,
|
||||
bool activeByDefault = true,
|
||||
DashboardTitleCallback? titleCallback,
|
||||
DashboardControllerCallback? controllerCallback})
|
||||
: this._home = home,
|
||||
this._activeByDefault = activeByDefault,
|
||||
this._titleCallback = titleCallback,
|
||||
this._controllerCallback = controllerCallback,
|
||||
@@ -82,12 +86,11 @@ class Dashboard extends TbContextWidget {
|
||||
|
||||
@override
|
||||
_DashboardState createState() => _DashboardState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardState extends TbContextState<Dashboard> {
|
||||
|
||||
final Completer<InAppWebViewController> _controller = Completer<InAppWebViewController>();
|
||||
final Completer<InAppWebViewController> _controller =
|
||||
Completer<InAppWebViewController>();
|
||||
|
||||
bool webViewLoading = true;
|
||||
final ValueNotifier<bool> dashboardLoading = ValueNotifier(true);
|
||||
@@ -98,8 +101,6 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
|
||||
late final DashboardController _dashboardController;
|
||||
|
||||
bool _fullscreen = false;
|
||||
|
||||
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(
|
||||
useShouldOverrideUrlLoading: true,
|
||||
@@ -110,13 +111,10 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
// useOnDownloadStart: true
|
||||
),
|
||||
android: AndroidInAppWebViewOptions(
|
||||
useHybridComposition: true,
|
||||
thirdPartyCookiesEnabled: true
|
||||
),
|
||||
useHybridComposition: true, thirdPartyCookiesEnabled: true),
|
||||
ios: IOSInAppWebViewOptions(
|
||||
allowsInlineMediaPlayback: true,
|
||||
allowsBackForwardNavigationGestures: false
|
||||
));
|
||||
allowsInlineMediaPlayback: true,
|
||||
allowsBackForwardNavigationGestures: false));
|
||||
|
||||
late Uri _initialUrl;
|
||||
|
||||
@@ -137,7 +135,8 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
void _onAuthenticated() async {
|
||||
if (tbContext.isAuthenticated) {
|
||||
if (!readyState.value) {
|
||||
_initialUrl = Uri.parse(ThingsboardAppConstants.thingsBoardApiEndpoint + '?accessToken=${tbClient.getJwtToken()!}&refreshToken=${tbClient.getRefreshToken()!}');
|
||||
_initialUrl = Uri.parse(ThingsboardAppConstants.thingsBoardApiEndpoint +
|
||||
'?accessToken=${tbClient.getJwtToken()!}&refreshToken=${tbClient.getRefreshToken()!}');
|
||||
readyState.value = true;
|
||||
} else {
|
||||
var windowMessage = <String, dynamic>{
|
||||
@@ -193,8 +192,8 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
_fullscreen = fullscreen;
|
||||
Future<void> _openDashboard(String dashboardId,
|
||||
{String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
dashboardLoading.value = true;
|
||||
InAppWebViewController? controller;
|
||||
if (!UniversalPlatform.isWeb) {
|
||||
@@ -202,9 +201,7 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
}
|
||||
var windowMessage = <String, dynamic>{
|
||||
'type': 'openDashboardMessage',
|
||||
'data': <String, dynamic>{
|
||||
'dashboardId': dashboardId
|
||||
}
|
||||
'data': <String, dynamic>{'dashboardId': dashboardId}
|
||||
};
|
||||
if (state != null) {
|
||||
windowMessage['data']['state'] = state;
|
||||
@@ -217,18 +214,17 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
}
|
||||
var webMessage = WebMessage(data: jsonEncode(windowMessage));
|
||||
if (!UniversalPlatform.isWeb) {
|
||||
await controller!.postWebMessage(
|
||||
message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
await controller!
|
||||
.postWebMessage(message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleRightLayout() async {
|
||||
var controller = await _controller.future;
|
||||
var windowMessage = <String, dynamic>{
|
||||
'type': 'toggleDashboardLayout'
|
||||
};
|
||||
var windowMessage = <String, dynamic>{'type': 'toggleDashboardLayout'};
|
||||
var webMessage = WebMessage(data: jsonEncode(windowMessage));
|
||||
await controller.postWebMessage(message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
await controller.postWebMessage(
|
||||
message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
}
|
||||
|
||||
Future<void> tryLocalNavigation(String? path) async {
|
||||
@@ -244,7 +240,8 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
'customers',
|
||||
'auditLogs'
|
||||
].contains(parts[0])) {
|
||||
if ((parts[0] == 'dashboard' || parts[0] == 'dashboards') && parts.length > 1) {
|
||||
if ((parts[0] == 'dashboard' || parts[0] == 'dashboards') &&
|
||||
parts.length > 1) {
|
||||
var dashboardId = parts[1];
|
||||
await navigateToDashboard(dashboardId);
|
||||
} else if (parts[0] != 'dashboard') {
|
||||
@@ -261,147 +258,176 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (widget._home == true && !tbContext.isHomePage()) {
|
||||
return true;
|
||||
}
|
||||
if (readyState.value) {
|
||||
return await _goBack();
|
||||
onWillPop: () async {
|
||||
if (widget._home == true && !tbContext.isHomePage()) {
|
||||
return true;
|
||||
}
|
||||
if (readyState.value) {
|
||||
return await _goBack();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: readyState,
|
||||
builder: (BuildContext context, bool ready, child) {
|
||||
if (!ready) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child:
|
||||
ValueListenableBuilder(
|
||||
valueListenable: readyState,
|
||||
builder: (BuildContext context, bool ready, child) {
|
||||
if (!ready) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
return Stack(
|
||||
children: [
|
||||
UniversalPlatform.isWeb ? Center(child: Text('Not implemented!')) :
|
||||
InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: _initialUrl),
|
||||
initialOptions: options,
|
||||
onWebViewCreated: (webViewController) {
|
||||
log.debug("onWebViewCreated");
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLoadedHandler", callback: (args) async {
|
||||
bool hasRightLayout = args[0];
|
||||
bool rightLayoutOpened = args[1];
|
||||
log.debug("Invoked tbMobileDashboardLoadedHandler: hasRightLayout: $hasRightLayout, rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController.onHasRightLayout(hasRightLayout);
|
||||
_dashboardController.onRightLayoutOpened(rightLayoutOpened);
|
||||
dashboardLoading.value = false;
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLayoutHandler", callback: (args) async {
|
||||
bool rightLayoutOpened = args[0];
|
||||
log.debug("Invoked tbMobileDashboardLayoutHandler: rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController.onRightLayoutOpened(rightLayoutOpened);
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardStateNameHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileDashboardStateNameHandler: $args");
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
if (widget._titleCallback != null) {
|
||||
widget._titleCallback!(args[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileNavigationHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileNavigationHandler: $args");
|
||||
if (args.length > 0) {
|
||||
String? path = args[0];
|
||||
Map<String, dynamic>? params;
|
||||
if (args.length > 1) {
|
||||
params = args[1];
|
||||
}
|
||||
log.debug("path: $path");
|
||||
log.debug("params: $params");
|
||||
tryLocalNavigation(path);
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileHandler: $args");
|
||||
return await widgetActionHandler.handleWidgetMobileAction(args, webViewController);
|
||||
});
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
var uri = navigationAction.request.url!;
|
||||
var uriString = uri.toString();
|
||||
log.debug('shouldOverrideUrlLoading $uriString');
|
||||
if (Platform.isAndroid || Platform.isIOS && navigationAction.iosWKNavigationType == IOSWKNavigationType.LINK_ACTIVATED) {
|
||||
if (uriString.startsWith(ThingsboardAppConstants.thingsBoardApiEndpoint)) {
|
||||
var target = uriString.substring(ThingsboardAppConstants.thingsBoardApiEndpoint.length);
|
||||
if (!target.startsWith("?accessToken")) {
|
||||
if (target.startsWith("/")) {
|
||||
target = target.substring(1);
|
||||
return Stack(children: [
|
||||
UniversalPlatform.isWeb
|
||||
? Center(child: Text('Not implemented!'))
|
||||
: InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: _initialUrl),
|
||||
initialOptions: options,
|
||||
onWebViewCreated: (webViewController) {
|
||||
log.debug("onWebViewCreated");
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileDashboardLoadedHandler",
|
||||
callback: (args) async {
|
||||
bool hasRightLayout = args[0];
|
||||
bool rightLayoutOpened = args[1];
|
||||
log.debug(
|
||||
"Invoked tbMobileDashboardLoadedHandler: hasRightLayout: $hasRightLayout, rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController
|
||||
.onHasRightLayout(hasRightLayout);
|
||||
_dashboardController
|
||||
.onRightLayoutOpened(rightLayoutOpened);
|
||||
dashboardLoading.value = false;
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileDashboardLayoutHandler",
|
||||
callback: (args) async {
|
||||
bool rightLayoutOpened = args[0];
|
||||
log.debug(
|
||||
"Invoked tbMobileDashboardLayoutHandler: rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController
|
||||
.onRightLayoutOpened(rightLayoutOpened);
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName:
|
||||
"tbMobileDashboardStateNameHandler",
|
||||
callback: (args) async {
|
||||
log.debug(
|
||||
"Invoked tbMobileDashboardStateNameHandler: $args");
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
if (widget._titleCallback != null) {
|
||||
widget._titleCallback!(args[0]);
|
||||
}
|
||||
await tryLocalNavigation(target);
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
} else if (await canLaunch(uriString)) {
|
||||
await launch(
|
||||
uriString,
|
||||
);
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileNavigationHandler",
|
||||
callback: (args) async {
|
||||
log.debug(
|
||||
"Invoked tbMobileNavigationHandler: $args");
|
||||
if (args.length > 0) {
|
||||
String? path = args[0];
|
||||
Map<String, dynamic>? params;
|
||||
if (args.length > 1) {
|
||||
params = args[1];
|
||||
}
|
||||
log.debug("path: $path");
|
||||
log.debug("params: $params");
|
||||
tryLocalNavigation(path);
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileHandler",
|
||||
callback: (args) async {
|
||||
log.debug("Invoked tbMobileHandler: $args");
|
||||
return await widgetActionHandler
|
||||
.handleWidgetMobileAction(
|
||||
args, webViewController);
|
||||
});
|
||||
},
|
||||
shouldOverrideUrlLoading:
|
||||
(controller, navigationAction) async {
|
||||
var uri = navigationAction.request.url!;
|
||||
var uriString = uri.toString();
|
||||
log.debug('shouldOverrideUrlLoading $uriString');
|
||||
if (Platform.isAndroid ||
|
||||
Platform.isIOS &&
|
||||
navigationAction.iosWKNavigationType ==
|
||||
IOSWKNavigationType.LINK_ACTIVATED) {
|
||||
if (uriString.startsWith(ThingsboardAppConstants
|
||||
.thingsBoardApiEndpoint)) {
|
||||
var target = uriString.substring(
|
||||
ThingsboardAppConstants
|
||||
.thingsBoardApiEndpoint.length);
|
||||
if (!target.startsWith("?accessToken")) {
|
||||
if (target.startsWith("/")) {
|
||||
target = target.substring(1);
|
||||
}
|
||||
await tryLocalNavigation(target);
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
} else if (await canLaunchUrlString(uriString)) {
|
||||
await launchUrlString(
|
||||
uriString,
|
||||
);
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
return Platform.isIOS ? NavigationActionPolicy.ALLOW : NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
onUpdateVisitedHistory: (controller, url, androidIsReload) async {
|
||||
log.debug('onUpdateVisitedHistory: $url');
|
||||
_dashboardController.onHistoryUpdated(controller.canGoBack());
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
log.debug('[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
|
||||
},
|
||||
onLoadStart: (controller, url) async {
|
||||
log.debug('onLoadStart: $url');
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
log.debug('onLoadStop: $url');
|
||||
if (webViewLoading) {
|
||||
webViewLoading = false;
|
||||
_controller.complete(controller);
|
||||
}
|
||||
},
|
||||
androidOnPermissionRequest: (controller, origin, resources) async {
|
||||
log.debug('androidOnPermissionRequest origin: $origin, resources: $resources');
|
||||
return PermissionRequestResponse(
|
||||
resources: resources,
|
||||
action: PermissionRequestResponseAction.GRANT);
|
||||
},
|
||||
),
|
||||
if (!UniversalPlatform.isWeb)
|
||||
TwoValueListenableBuilder(
|
||||
firstValueListenable: dashboardLoading,
|
||||
secondValueListenable: dashboardActive,
|
||||
builder: (BuildContext context, bool loading, bool active, child) {
|
||||
if (!loading && active) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
var bottomPadding = data.padding.top;
|
||||
if (widget._home != true) {
|
||||
bottomPadding += kToolbarHeight;
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
alignment: Alignment.center,
|
||||
color: Colors.white,
|
||||
child: TbProgressIndicator(
|
||||
size: 50.0
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return Platform.isIOS
|
||||
? NavigationActionPolicy.ALLOW
|
||||
: NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
onUpdateVisitedHistory:
|
||||
(controller, url, androidIsReload) async {
|
||||
log.debug('onUpdateVisitedHistory: $url');
|
||||
_dashboardController
|
||||
.onHistoryUpdated(controller.canGoBack());
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
log.debug(
|
||||
'[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
|
||||
},
|
||||
onLoadStart: (controller, url) async {
|
||||
log.debug('onLoadStart: $url');
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
log.debug('onLoadStop: $url');
|
||||
if (webViewLoading) {
|
||||
webViewLoading = false;
|
||||
_controller.complete(controller);
|
||||
}
|
||||
},
|
||||
androidOnPermissionRequest:
|
||||
(controller, origin, resources) async {
|
||||
log.debug(
|
||||
'androidOnPermissionRequest origin: $origin, resources: $resources');
|
||||
return PermissionRequestResponse(
|
||||
resources: resources,
|
||||
action: PermissionRequestResponseAction.GRANT);
|
||||
},
|
||||
),
|
||||
if (!UniversalPlatform.isWeb)
|
||||
TwoValueListenableBuilder(
|
||||
firstValueListenable: dashboardLoading,
|
||||
secondValueListenable: dashboardActive,
|
||||
builder: (BuildContext context, bool loading,
|
||||
bool active, child) {
|
||||
if (!loading && active) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
var data = MediaQueryData.fromWindow(
|
||||
WidgetsBinding.instance.window);
|
||||
var bottomPadding = data.padding.top;
|
||||
if (widget._home != true) {
|
||||
bottomPadding += kToolbarHeight;
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
alignment: Alignment.center,
|
||||
color: Colors.white,
|
||||
child: TbProgressIndicator(size: 50.0),
|
||||
);
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DashboardPage extends TbPageWidget {
|
||||
|
||||
final String? _dashboardTitle;
|
||||
final String? _dashboardId;
|
||||
final String? _state;
|
||||
final bool? _fullscreen;
|
||||
// final String? _dashboardId;
|
||||
// final String? _state;
|
||||
// final bool? _fullscreen;
|
||||
|
||||
DashboardPage(TbContext tbContext, {String? dashboardId, bool? fullscreen, String? dashboardTitle, String? state}):
|
||||
_dashboardId = dashboardId,
|
||||
_fullscreen = fullscreen,
|
||||
DashboardPage(TbContext tbContext,
|
||||
{String? dashboardId,
|
||||
bool? fullscreen,
|
||||
String? dashboardTitle,
|
||||
String? state})
|
||||
:
|
||||
// _dashboardId = dashboardId,
|
||||
// _fullscreen = fullscreen,
|
||||
_dashboardTitle = dashboardTitle,
|
||||
_state = state,
|
||||
// _state = state,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardPageState createState() => _DashboardPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardPageState extends TbPageState<DashboardPage> {
|
||||
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
|
||||
@override
|
||||
@@ -37,27 +37,26 @@ class _DashboardPageState extends TbPageState<DashboardPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
showLoadingIndicator: false,
|
||||
elevation: 0,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
},
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
showLoadingIndicator: false,
|
||||
elevation: 0,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Text('Deprecated') //Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
|
||||
//fullscreen: widget._fullscreen, titleCallback: (title) {
|
||||
body: Text(
|
||||
'Deprecated') //Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
|
||||
//fullscreen: widget._fullscreen, titleCallback: (title) {
|
||||
//dashboardTitleValue.value = title;
|
||||
//}
|
||||
//),
|
||||
);
|
||||
//}
|
||||
//),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,20 +8,25 @@ import 'package:thingsboard_app/modules/dashboard/fullscreen_dashboard_page.dart
|
||||
import 'dashboard_page.dart';
|
||||
|
||||
class DashboardRoutes extends TbRoutes {
|
||||
|
||||
late var dashboardsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var dashboardsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DashboardsPage(tbContext);
|
||||
});
|
||||
|
||||
late var dashboardDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||
late var dashboardDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||
var fullscreen = params['fullscreen']?.first == 'true';
|
||||
var dashboardTitle = params['title']?.first;
|
||||
var state = params['state']?.first;
|
||||
return DashboardPage(tbContext, dashboardId: params["id"]![0], fullscreen: fullscreen,
|
||||
dashboardTitle: dashboardTitle, state: state);
|
||||
return DashboardPage(tbContext,
|
||||
dashboardId: params["id"]![0],
|
||||
fullscreen: fullscreen,
|
||||
dashboardTitle: dashboardTitle,
|
||||
state: state);
|
||||
});
|
||||
|
||||
late var fullscreenDashboardHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var fullscreenDashboardHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return FullscreenDashboardPage(tbContext, params["id"]![0]);
|
||||
});
|
||||
|
||||
@@ -31,7 +36,7 @@ class DashboardRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/dashboards", handler: dashboardsHandler);
|
||||
router.define("/dashboard/:id", handler: dashboardDetailsHandler);
|
||||
router.define("/fullscreenDashboard/:id", handler: fullscreenDashboardHandler);
|
||||
router.define("/fullscreenDashboard/:id",
|
||||
handler: fullscreenDashboardHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
@@ -10,7 +9,6 @@ import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
|
||||
@override
|
||||
String get title => 'Dashboards';
|
||||
|
||||
@@ -20,9 +18,13 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
@override
|
||||
Future<PageData<DashboardInfo>> fetchEntities(PageLink pageLink) {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
return tbClient.getDashboardService().getTenantDashboards(pageLink, mobile: true);
|
||||
return tbClient
|
||||
.getDashboardService()
|
||||
.getTenantDashboards(pageLink, mobile: true);
|
||||
} else {
|
||||
return tbClient.getDashboardService().getCustomerDashboards(tbClient.getAuthUser()!.customerId, pageLink, mobile: true);
|
||||
return tbClient.getDashboardService().getCustomerDashboards(
|
||||
tbClient.getAuthUser()!.customerId!, pageLink,
|
||||
mobile: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,34 +41,37 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityListWidgetCard(BuildContext context, DashboardInfo dashboard) {
|
||||
Widget buildEntityListWidgetCard(
|
||||
BuildContext context, DashboardInfo dashboard) {
|
||||
return _buildEntityListCard(context, dashboard, true);
|
||||
}
|
||||
|
||||
@override
|
||||
EntityCardSettings entityGridCardSettings(DashboardInfo dashboard) => EntityCardSettings(dropShadow: true); //dashboard.image != null);
|
||||
EntityCardSettings entityGridCardSettings(DashboardInfo dashboard) =>
|
||||
EntityCardSettings(dropShadow: true); //dashboard.image != null);
|
||||
|
||||
@override
|
||||
Widget buildEntityGridCard(BuildContext context, DashboardInfo dashboard) {
|
||||
return DashboardGridCard(tbContext, dashboard: dashboard);
|
||||
}
|
||||
|
||||
Widget _buildEntityListCard(BuildContext context, DashboardInfo dashboard, bool listWidgetCard) {
|
||||
Widget _buildEntityListCard(
|
||||
BuildContext context, DashboardInfo dashboard, bool listWidgetCard) {
|
||||
return Row(
|
||||
mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: listWidgetCard ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: listWidgetCard ? 9 : 10, horizontal: 16),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: listWidgetCard ? 9 : 10, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
mainAxisSize:
|
||||
listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: listWidgetCard ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Column(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
@@ -77,37 +82,35 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7
|
||||
))
|
||||
),
|
||||
height: 1.7))),
|
||||
Text('${_dashboardDetailsText(dashboard)}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
height: 1.33))
|
||||
],
|
||||
)
|
||||
),
|
||||
(!listWidgetCard ? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(dashboard.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
) : Container())
|
||||
)),
|
||||
(!listWidgetCard
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
entityDateFormat.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
dashboard.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33))
|
||||
],
|
||||
)
|
||||
: Container())
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
String _dashboardDetailsText(DashboardInfo dashboard) {
|
||||
@@ -124,23 +127,20 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
bool _isPublicDashboard(DashboardInfo dashboard) {
|
||||
return dashboard.assignedCustomers.any((element) => element.isPublic);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DashboardGridCard extends TbContextWidget {
|
||||
|
||||
final DashboardInfo dashboard;
|
||||
|
||||
DashboardGridCard(TbContext tbContext, {required this.dashboard}) : super(tbContext);
|
||||
DashboardGridCard(TbContext tbContext, {required this.dashboard})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardGridCardState createState() => _DashboardGridCardState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardGridCardState extends TbContextState<DashboardGridCard> {
|
||||
|
||||
_DashboardGridCardState(): super();
|
||||
_DashboardGridCardState() : super();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -164,48 +164,37 @@ class _DashboardGridCardState extends TbContextState<DashboardGridCard> {
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Dashboard');
|
||||
}
|
||||
return
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack (
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: BoxFit.cover,
|
||||
child: image
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
Divider(height: 1),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child:
|
||||
Center(
|
||||
child: AutoSizeText(widget.dashboard.title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack(children: [
|
||||
SizedBox.expand(
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: BoxFit.cover,
|
||||
child: image))
|
||||
])),
|
||||
Divider(height: 1),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Center(
|
||||
child: AutoSizeText(
|
||||
widget.dashboard.title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14),
|
||||
))),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,16 +8,13 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'dashboards_base.dart';
|
||||
|
||||
class DashboardsGridWidget extends TbContextWidget {
|
||||
|
||||
DashboardsGridWidget(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardsGridWidgetState createState() => _DashboardsGridWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardsGridWidgetState extends TbContextState<DashboardsGridWidget> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
@@ -30,13 +27,11 @@ class _DashboardsGridWidgetState extends TbContextState<DashboardsGridWidget> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class DashboardsGrid extends BaseEntitiesWidget<DashboardInfo, PageLink> with DashboardsBase, EntitiesGridStateBase {
|
||||
|
||||
DashboardsGrid(TbContext tbContext, PageKeyController<PageLink> pageKeyController) : super(tbContext, pageKeyController);
|
||||
|
||||
class DashboardsGrid extends BaseEntitiesWidget<DashboardInfo, PageLink>
|
||||
with DashboardsBase, EntitiesGridStateBase {
|
||||
DashboardsGrid(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController)
|
||||
: super(tbContext, pageKeyController);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'dashboards_base.dart';
|
||||
|
||||
class DashboardsList extends BaseEntitiesWidget<DashboardInfo, PageLink> with DashboardsBase, EntitiesListStateBase {
|
||||
|
||||
DashboardsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController) : super(tbContext, pageKeyController);
|
||||
|
||||
class DashboardsList extends BaseEntitiesWidget<DashboardInfo, PageLink>
|
||||
with DashboardsBase, EntitiesListStateBase {
|
||||
DashboardsList(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController)
|
||||
: super(tbContext, pageKeyController);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import 'package:thingsboard_app/core/entity/entities_list_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboards_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DashboardsListWidget extends EntitiesListPageLinkWidget<DashboardInfo> with DashboardsBase {
|
||||
|
||||
DashboardsListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller);
|
||||
class DashboardsListWidget extends EntitiesListPageLinkWidget<DashboardInfo>
|
||||
with DashboardsBase {
|
||||
DashboardsListWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
navigateTo('/dashboards');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,30 +6,22 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'dashboards_grid.dart';
|
||||
|
||||
class DashboardsPage extends TbPageWidget {
|
||||
|
||||
DashboardsPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardsPageState createState() => _DashboardsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardsPageState extends TbPageState<DashboardsPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text('Dashboards')
|
||||
),
|
||||
body: DashboardsGridWidget(tbContext)
|
||||
);
|
||||
appBar: TbAppBar(tbContext, title: Text('Dashboards')),
|
||||
body: DashboardsGridWidget(tbContext));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class FullscreenDashboardPage extends TbPageWidget {
|
||||
|
||||
final String fullscreenDashboardId;
|
||||
final String? _dashboardTitle;
|
||||
|
||||
FullscreenDashboardPage(TbContext tbContext, this.fullscreenDashboardId, {String? dashboardTitle}):
|
||||
_dashboardTitle = dashboardTitle,
|
||||
FullscreenDashboardPage(TbContext tbContext, this.fullscreenDashboardId,
|
||||
{String? dashboardTitle})
|
||||
: _dashboardTitle = dashboardTitle,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_FullscreenDashboardPageState createState() => _FullscreenDashboardPageState();
|
||||
|
||||
_FullscreenDashboardPageState createState() =>
|
||||
_FullscreenDashboardPageState();
|
||||
}
|
||||
|
||||
class _FullscreenDashboardPageState extends TbPageState<FullscreenDashboardPage> {
|
||||
|
||||
class _FullscreenDashboardPageState
|
||||
extends TbPageState<FullscreenDashboardPage> {
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
final ValueNotifier<bool> showBackValue = ValueNotifier(false);
|
||||
|
||||
@@ -47,13 +46,12 @@ class _FullscreenDashboardPageState extends TbPageState<FullscreenDashboardPage>
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: showBackValue,
|
||||
builder: (context, canGoBack, widget) {
|
||||
return TbAppBar(
|
||||
tbContext,
|
||||
leading: canGoBack ? BackButton(
|
||||
onPressed: () {
|
||||
maybePop();
|
||||
}
|
||||
) : null,
|
||||
return TbAppBar(tbContext,
|
||||
leading: canGoBack
|
||||
? BackButton(onPressed: () {
|
||||
maybePop();
|
||||
})
|
||||
: null,
|
||||
showLoadingIndicator: false,
|
||||
elevation: 1,
|
||||
shadowColor: Colors.transparent,
|
||||
@@ -63,29 +61,25 @@ class _FullscreenDashboardPageState extends TbPageState<FullscreenDashboardPage>
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
child: Text(title));
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(icon: Icon(Icons.settings), onPressed: () => navigateTo('/profile?fullscreen=true'))
|
||||
]
|
||||
);
|
||||
}
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings),
|
||||
onPressed: () =>
|
||||
navigateTo('/profile?fullscreen=true'))
|
||||
]);
|
||||
}),
|
||||
),
|
||||
body: Dashboard(
|
||||
tbContext,
|
||||
titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
},
|
||||
controllerCallback: (controller) {
|
||||
controller.canGoBack.addListener(() {
|
||||
_onCanGoBack(controller.canGoBack.value);
|
||||
});
|
||||
controller.openDashboard(widget.fullscreenDashboardId, fullscreen: true);
|
||||
}
|
||||
)
|
||||
);
|
||||
body: Dashboard(tbContext, titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
}, controllerCallback: (controller) {
|
||||
controller.canGoBack.addListener(() {
|
||||
_onCanGoBack(controller.canGoBack.value);
|
||||
});
|
||||
controller.openDashboard(widget.fullscreenDashboardId,
|
||||
fullscreen: true);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class MainDashboardPageController {
|
||||
|
||||
DashboardController? _dashboardController;
|
||||
_MainDashboardPageState? _mainDashboardPageState;
|
||||
|
||||
@@ -26,11 +24,13 @@ class MainDashboardPageController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar}) async {
|
||||
Future<void> openDashboard(String dashboardId,
|
||||
{String? dashboardTitle, String? state, bool? hideToolbar}) async {
|
||||
if (dashboardTitle != null) {
|
||||
_mainDashboardPageState?._updateTitle(dashboardTitle);
|
||||
}
|
||||
await _dashboardController?.openDashboard(dashboardId, state: state, hideToolbar: hideToolbar);
|
||||
await _dashboardController?.openDashboard(dashboardId,
|
||||
state: state, hideToolbar: hideToolbar);
|
||||
}
|
||||
|
||||
Future<void> activateDashboard() async {
|
||||
@@ -40,28 +40,24 @@ class MainDashboardPageController {
|
||||
Future<void> deactivateDashboard() async {
|
||||
await _dashboardController?.deactivateDashboard();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MainDashboardPage extends TbContextWidget {
|
||||
|
||||
final String? _dashboardTitle;
|
||||
final MainDashboardPageController? _controller;
|
||||
|
||||
MainDashboardPage(TbContext tbContext,
|
||||
{MainDashboardPageController? controller,
|
||||
String? dashboardTitle}):
|
||||
_controller = controller,
|
||||
{MainDashboardPageController? controller, String? dashboardTitle})
|
||||
: _controller = controller,
|
||||
_dashboardTitle = dashboardTitle,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_MainDashboardPageState createState() => _MainDashboardPageState();
|
||||
|
||||
}
|
||||
|
||||
class _MainDashboardPageState extends TbContextState<MainDashboardPage> with TickerProviderStateMixin {
|
||||
|
||||
class _MainDashboardPageState extends TbContextState<MainDashboardPage>
|
||||
with TickerProviderStateMixin {
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
final ValueNotifier<bool> hasRightLayout = ValueNotifier(false);
|
||||
DashboardController? _dashboardController;
|
||||
@@ -76,9 +72,7 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
|
||||
duration: Duration(milliseconds: 200),
|
||||
);
|
||||
rightLayoutMenuAnimation = CurvedAnimation(
|
||||
curve: Curves.linear,
|
||||
parent: rightLayoutMenuController
|
||||
);
|
||||
curve: Curves.linear, parent: rightLayoutMenuController);
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setMainDashboardPageState(this);
|
||||
}
|
||||
@@ -99,68 +93,57 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
maybePop();
|
||||
}
|
||||
),
|
||||
showLoadingIndicator: false,
|
||||
elevation: 1,
|
||||
shadowColor: Colors.transparent,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
ValueListenableBuilder<bool>(
|
||||
tbContext,
|
||||
leading: BackButton(onPressed: () {
|
||||
maybePop();
|
||||
}),
|
||||
showLoadingIndicator: false,
|
||||
elevation: 1,
|
||||
shadowColor: Colors.transparent,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title));
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: hasRightLayout,
|
||||
builder: (context, _hasRightLayout, widget) {
|
||||
if (_hasRightLayout) {
|
||||
return IconButton(
|
||||
onPressed: () => _dashboardController?.toggleRightLayout(),
|
||||
onPressed: () =>
|
||||
_dashboardController?.toggleRightLayout(),
|
||||
icon: AnimatedIcon(
|
||||
progress: rightLayoutMenuAnimation,
|
||||
icon: AnimatedIcons.menu_close
|
||||
)
|
||||
);
|
||||
progress: rightLayoutMenuAnimation,
|
||||
icon: AnimatedIcons.menu_close));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
)
|
||||
],
|
||||
})
|
||||
],
|
||||
),
|
||||
body: Dashboard(
|
||||
tbContext,
|
||||
activeByDefault: false,
|
||||
body: Dashboard(tbContext, activeByDefault: false,
|
||||
titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
},
|
||||
controllerCallback: (controller) {
|
||||
_dashboardController = controller;
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setDashboardController(controller);
|
||||
controller.hasRightLayout.addListener(() {
|
||||
hasRightLayout.value = controller.hasRightLayout.value;
|
||||
});
|
||||
controller.rightLayoutOpened.addListener(() {
|
||||
if(controller.rightLayoutOpened.value) {
|
||||
rightLayoutMenuController.forward();
|
||||
} else {
|
||||
rightLayoutMenuController.reverse();
|
||||
}
|
||||
});
|
||||
dashboardTitleValue.value = title;
|
||||
}, controllerCallback: (controller) {
|
||||
_dashboardController = controller;
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setDashboardController(controller);
|
||||
controller.hasRightLayout.addListener(() {
|
||||
hasRightLayout.value = controller.hasRightLayout.value;
|
||||
});
|
||||
controller.rightLayoutOpened.addListener(() {
|
||||
if (controller.rightLayoutOpened.value) {
|
||||
rightLayoutMenuController.forward();
|
||||
} else {
|
||||
rightLayoutMenuController.reverse();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DeviceDetailsPage extends EntityDetailsPage<DeviceInfo> {
|
||||
|
||||
DeviceDetailsPage(TbContext tbContext, String deviceId):
|
||||
super(tbContext,
|
||||
entityId: deviceId,
|
||||
defaultTitle: 'Device');
|
||||
DeviceDetailsPage(TbContext tbContext, String deviceId)
|
||||
: super(tbContext, entityId: deviceId, defaultTitle: 'Device');
|
||||
|
||||
@override
|
||||
Future<DeviceInfo?> fetchEntity(String deviceId) {
|
||||
@@ -23,5 +19,4 @@ class DeviceDetailsPage extends EntityDetailsPage<DeviceInfo> {
|
||||
subtitle: Text('${device.type}'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
|
||||
final RefreshDeviceCounts refreshDeviceCounts = RefreshDeviceCounts();
|
||||
|
||||
@override
|
||||
@@ -48,7 +46,8 @@ mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityGridCard(BuildContext context, DeviceProfileInfo deviceProfile) {
|
||||
Widget buildEntityGridCard(
|
||||
BuildContext context, DeviceProfileInfo deviceProfile) {
|
||||
return DeviceProfileCard(tbContext, deviceProfile);
|
||||
}
|
||||
|
||||
@@ -56,7 +55,6 @@ mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
double? gridChildAspectRatio() {
|
||||
return 156 / 200;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RefreshDeviceCounts {
|
||||
@@ -64,20 +62,20 @@ class RefreshDeviceCounts {
|
||||
}
|
||||
|
||||
class AllDevicesCard extends TbContextWidget {
|
||||
|
||||
final RefreshDeviceCounts refreshDeviceCounts;
|
||||
|
||||
AllDevicesCard(TbContext tbContext, this.refreshDeviceCounts) : super(tbContext);
|
||||
AllDevicesCard(TbContext tbContext, this.refreshDeviceCounts)
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_AllDevicesCardState createState() => _AllDevicesCardState();
|
||||
|
||||
}
|
||||
|
||||
class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
|
||||
final StreamController<int?> _activeDevicesCount = StreamController.broadcast();
|
||||
final StreamController<int?> _inactiveDevicesCount = StreamController.broadcast();
|
||||
final StreamController<int?> _activeDevicesCount =
|
||||
StreamController.broadcast();
|
||||
final StreamController<int?> _inactiveDevicesCount =
|
||||
StreamController.broadcast();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -103,9 +101,12 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
Future<void> _countDevices() {
|
||||
_activeDevicesCount.add(null);
|
||||
_inactiveDevicesCount.add(null);
|
||||
Future<int> activeDevicesCount = EntityQueryApi.countDevices(tbClient, active: true);
|
||||
Future<int> inactiveDevicesCount = EntityQueryApi.countDevices(tbClient, active: false);
|
||||
Future<List<int>> countsFuture = Future.wait([activeDevicesCount, inactiveDevicesCount]);
|
||||
Future<int> activeDevicesCount =
|
||||
EntityQueryApi.countDevices(tbClient, active: true);
|
||||
Future<int> inactiveDevicesCount =
|
||||
EntityQueryApi.countDevices(tbClient, active: false);
|
||||
Future<List<int>> countsFuture =
|
||||
Future.wait([activeDevicesCount, inactiveDevicesCount]);
|
||||
countsFuture.then((counts) {
|
||||
if (this.mounted) {
|
||||
_activeDevicesCount.add(counts[0]);
|
||||
@@ -117,20 +118,19 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child:
|
||||
Container(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.fromLTRB(16, 12, 16, 15),
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 12, 16, 15),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -139,120 +139,126 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
)
|
||||
),
|
||||
height: 20 / 14)),
|
||||
Icon(Icons.arrow_forward, size: 18)
|
||||
],
|
||||
)
|
||||
),
|
||||
Divider(height: 1),
|
||||
Padding(padding: EdgeInsets.all(0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _activeDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true');
|
||||
}
|
||||
),
|
||||
),
|
||||
// SizedBox(width: 4),
|
||||
Container(width: 1,
|
||||
height: 40,
|
||||
child: VerticalDivider(width: 1)
|
||||
),
|
||||
Flexible(fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _inactiveDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false');
|
||||
}
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
Divider(height: 1),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _activeDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(
|
||||
context, true, deviceCount);
|
||||
} else {
|
||||
return Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext
|
||||
.currentState!
|
||||
.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true');
|
||||
}),
|
||||
),
|
||||
// SizedBox(width: 4),
|
||||
Container(
|
||||
width: 1,
|
||||
height: 40,
|
||||
child: VerticalDivider(width: 1)),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _inactiveDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(
|
||||
context, false, deviceCount);
|
||||
} else {
|
||||
return Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext
|
||||
.currentState!
|
||||
.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false');
|
||||
}),
|
||||
)
|
||||
],
|
||||
))
|
||||
],
|
||||
)),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4))
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList');
|
||||
}
|
||||
);
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeviceProfileCard extends TbContextWidget {
|
||||
|
||||
final DeviceProfileInfo deviceProfile;
|
||||
|
||||
DeviceProfileCard(TbContext tbContext, this.deviceProfile) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DeviceProfileCardState createState() => _DeviceProfileCardState();
|
||||
|
||||
}
|
||||
|
||||
class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
|
||||
|
||||
late Future<int> activeDevicesCount;
|
||||
late Future<int> inactiveDevicesCount;
|
||||
|
||||
@@ -269,8 +275,10 @@ class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
|
||||
}
|
||||
|
||||
_countDevices() {
|
||||
activeDevicesCount = EntityQueryApi.countDevices(tbClient, deviceType: widget.deviceProfile.name, active: true);
|
||||
inactiveDevicesCount = EntityQueryApi.countDevices(tbClient, deviceType: widget.deviceProfile.name, active: false);
|
||||
activeDevicesCount = EntityQueryApi.countDevices(tbClient,
|
||||
deviceType: widget.deviceProfile.name, active: true);
|
||||
inactiveDevicesCount = EntityQueryApi.countDevices(tbClient,
|
||||
deviceType: widget.deviceProfile.name, active: false);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -292,99 +300,95 @@ class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
|
||||
imageFit = BoxFit.cover;
|
||||
padding = 0;
|
||||
}
|
||||
return
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack (
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: imageFit,
|
||||
child: image
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Center(
|
||||
child: AutoSizeText(entity.name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: activeDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Container(height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20, width: 20,
|
||||
child:
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true&deviceType=${entity.name}');
|
||||
}
|
||||
),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: inactiveDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Container(height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20, width: 20,
|
||||
child:
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false&deviceType=${entity.name}');
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(children: [
|
||||
Expanded(
|
||||
child: Stack(children: [
|
||||
SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: imageFit,
|
||||
child: image)))
|
||||
])),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Center(
|
||||
child: AutoSizeText(
|
||||
entity.name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14),
|
||||
)))),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: activeDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Container(
|
||||
height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(
|
||||
tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true&deviceType=${entity.name}');
|
||||
}),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: inactiveDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Container(
|
||||
height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(
|
||||
tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo(
|
||||
'/deviceList?active=false&deviceType=${entity.name}');
|
||||
})
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,26 +406,27 @@ Widget _buildDeviceCount(BuildContext context, bool active, int count) {
|
||||
Stack(
|
||||
children: [
|
||||
Icon(Icons.devices_other, size: 16, color: color),
|
||||
if (!active) CustomPaint(
|
||||
size: Size.square(16),
|
||||
painter: StrikeThroughPainter(color: color, offset: 2),
|
||||
)
|
||||
if (!active)
|
||||
CustomPaint(
|
||||
size: Size.square(16),
|
||||
painter: StrikeThroughPainter(color: color, offset: 2),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(width: 8.67),
|
||||
Text(active ? 'Active' : 'Inactive', style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color
|
||||
)),
|
||||
SizedBox(width: 8.67),
|
||||
Text(count.toString(), style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color
|
||||
))
|
||||
SizedBox(width: 8.67),
|
||||
Text(active ? 'Active' : 'Inactive',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color)),
|
||||
SizedBox(width: 8.67),
|
||||
Text(count.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color))
|
||||
],
|
||||
),
|
||||
Icon(Icons.chevron_right, size: 16, color: Color(0xFFACACAC))
|
||||
@@ -431,7 +436,6 @@ Widget _buildDeviceCount(BuildContext context, bool active, int count) {
|
||||
}
|
||||
|
||||
class StrikeThroughPainter extends CustomPainter {
|
||||
|
||||
final Color color;
|
||||
final double offset;
|
||||
|
||||
@@ -441,7 +445,8 @@ class StrikeThroughPainter extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
paint.strokeWidth = 1.5;
|
||||
canvas.drawLine(Offset(offset, offset), Offset(size.width - offset, size.height - offset), paint);
|
||||
canvas.drawLine(Offset(offset, offset),
|
||||
Offset(size.width - offset, size.height - offset), paint);
|
||||
paint.color = Colors.white;
|
||||
canvas.drawLine(Offset(2, 0), Offset(size.width + 2, size.height), paint);
|
||||
}
|
||||
@@ -450,5 +455,4 @@ class StrikeThroughPainter extends CustomPainter {
|
||||
bool shouldRepaint(covariant StrikeThroughPainter oldDelegate) {
|
||||
return color != oldDelegate.color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'device_profiles_base.dart';
|
||||
|
||||
class DeviceProfilesGrid extends BaseEntitiesWidget<DeviceProfileInfo, PageLink> with DeviceProfilesBase, EntitiesGridStateBase {
|
||||
|
||||
DeviceProfilesGrid(TbContext tbContext, PageKeyController<PageLink> pageKeyController) : super(tbContext, pageKeyController);
|
||||
|
||||
class DeviceProfilesGrid extends BaseEntitiesWidget<DeviceProfileInfo, PageLink>
|
||||
with DeviceProfilesBase, EntitiesGridStateBase {
|
||||
DeviceProfilesGrid(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController)
|
||||
: super(tbContext, pageKeyController);
|
||||
}
|
||||
|
||||
@@ -9,24 +9,28 @@ import 'device_details_page.dart';
|
||||
import 'devices_list_page.dart';
|
||||
|
||||
class DeviceRoutes extends TbRoutes {
|
||||
|
||||
late var devicesHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var devicesHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return MainPage(tbContext, path: '/devices');
|
||||
});
|
||||
|
||||
late var devicesPageHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var devicesPageHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DevicesPage(tbContext);
|
||||
});
|
||||
|
||||
late var deviceListHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var deviceListHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
var deviceType = params['deviceType']?.first;
|
||||
String? activeStr = params['active']?.first;
|
||||
bool? active = activeStr != null ? activeStr == 'true' : null;
|
||||
return DevicesListPage(tbContext, searchMode: searchMode, deviceType: deviceType, active: active);
|
||||
return DevicesListPage(tbContext,
|
||||
searchMode: searchMode, deviceType: deviceType, active: active);
|
||||
});
|
||||
|
||||
late var deviceDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var deviceDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DeviceDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -39,5 +43,4 @@ class DeviceRoutes extends TbRoutes {
|
||||
router.define("/deviceList", handler: deviceListHandler);
|
||||
router.define("/device/:id", handler: deviceDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:core';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
|
||||
@override
|
||||
String get title => 'Devices';
|
||||
|
||||
@@ -28,14 +26,19 @@ mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
|
||||
@override
|
||||
void onEntityTap(EntityData device) async {
|
||||
var profile = await DeviceProfileCache.getDeviceProfileInfo(tbClient, device.field('type')!, device.entityId.id!);
|
||||
var profile = await DeviceProfileCache.getDeviceProfileInfo(
|
||||
tbClient, device.field('type')!, device.entityId.id!);
|
||||
if (profile.defaultDashboardId != null) {
|
||||
var dashboardId = profile.defaultDashboardId!.id!;
|
||||
var state = Utils.createDashboardEntityState(device.entityId, entityName: device.field('name')!, entityLabel: device.field('label')!);
|
||||
navigateToDashboard(dashboardId, dashboardTitle: device.field('name'), state: state);
|
||||
var state = Utils.createDashboardEntityState(device.entityId,
|
||||
entityName: device.field('name')!,
|
||||
entityLabel: device.field('label')!);
|
||||
navigateToDashboard(dashboardId,
|
||||
dashboardTitle: device.field('name'), state: state);
|
||||
} else {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
showWarnNotification('Mobile dashboard should be configured in device profile!');
|
||||
showWarnNotification(
|
||||
'Mobile dashboard should be configured in device profile!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,42 +60,51 @@ mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
|
||||
bool displayCardImage(bool listWidgetCard) => listWidgetCard;
|
||||
|
||||
Widget _buildEntityListCard(BuildContext context, EntityData device, bool listWidgetCard) {
|
||||
return DeviceCard(tbContext, device: device, listWidgetCard: listWidgetCard, displayImage: displayCardImage(listWidgetCard));
|
||||
Widget _buildEntityListCard(
|
||||
BuildContext context, EntityData device, bool listWidgetCard) {
|
||||
return DeviceCard(tbContext,
|
||||
device: device,
|
||||
listWidgetCard: listWidgetCard,
|
||||
displayImage: displayCardImage(listWidgetCard));
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceQueryController extends PageKeyController<EntityDataQuery> {
|
||||
|
||||
DeviceQueryController({int pageSize = 20, String? searchText, String? deviceType, bool? active}):
|
||||
super(EntityQueryApi.createDefaultDeviceQuery(pageSize: pageSize, searchText: searchText, deviceType: deviceType, active: active));
|
||||
DeviceQueryController(
|
||||
{int pageSize = 20, String? searchText, String? deviceType, bool? active})
|
||||
: super(EntityQueryApi.createDefaultDeviceQuery(
|
||||
pageSize: pageSize,
|
||||
searchText: searchText,
|
||||
deviceType: deviceType,
|
||||
active: active));
|
||||
|
||||
@override
|
||||
EntityDataQuery nextPageKey(EntityDataQuery deviceQuery) => deviceQuery.next();
|
||||
EntityDataQuery nextPageKey(EntityDataQuery deviceQuery) =>
|
||||
deviceQuery.next();
|
||||
|
||||
onSearchText(String searchText) {
|
||||
value.pageKey.pageLink.page = 0;
|
||||
value.pageKey.pageLink.textSearch = searchText;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeviceCard extends TbContextWidget {
|
||||
|
||||
final EntityData device;
|
||||
final bool listWidgetCard;
|
||||
final bool displayImage;
|
||||
|
||||
DeviceCard(TbContext tbContext, {required this.device, this.listWidgetCard = false, this.displayImage = false}) : super(tbContext);
|
||||
DeviceCard(TbContext tbContext,
|
||||
{required this.device,
|
||||
this.listWidgetCard = false,
|
||||
this.displayImage = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_DeviceCardState createState() => _DeviceCardState();
|
||||
|
||||
}
|
||||
|
||||
class _DeviceCardState extends TbContextState<DeviceCard> {
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
late Future<DeviceProfileInfo> deviceProfileFuture;
|
||||
@@ -129,250 +141,256 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
|
||||
}
|
||||
|
||||
Widget buildCard(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
return Stack(children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
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>(
|
||||
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) {
|
||||
image = Utils.imageFromBase64(profile.image!);
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = SvgPicture.asset(
|
||||
ThingsboardImage.deviceProfilePlaceholder,
|
||||
color: Theme.of(context).primaryColor,
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Device');
|
||||
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: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment:
|
||||
Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${widget.device.field('name')!}',
|
||||
style: TextStyle(
|
||||
color: Color(
|
||||
0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.w500,
|
||||
height:
|
||||
20 / 14)))),
|
||||
SizedBox(width: 12),
|
||||
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: MainAxisSize.min, children: [
|
||||
if (widget.displayImage)
|
||||
Container(
|
||||
width: 58,
|
||||
height: 58,
|
||||
decoration: BoxDecoration(
|
||||
// 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) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
bool hasDashboard = profile.defaultDashboardId != null;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
image = Utils.imageFromBase64(profile.image!);
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = SvgPicture.asset(ThingsboardImage.deviceProfilePlaceholder,
|
||||
image = SvgPicture.asset(
|
||||
ThingsboardImage.deviceProfilePlaceholder,
|
||||
color: Theme.of(context).primaryColor,
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Device');
|
||||
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: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${widget.device.field('name')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
)
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
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)
|
||||
],
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
))
|
||||
],
|
||||
));
|
||||
} else {
|
||||
return Container(
|
||||
height: 64,
|
||||
child: Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
|
||||
)
|
||||
)
|
||||
);
|
||||
return Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary)));
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildListWidgetCard(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.displayImage) Container(
|
||||
width: 58,
|
||||
height: 58,
|
||||
decoration: BoxDecoration(
|
||||
// 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) {
|
||||
image = Utils.imageFromBase64(profile.image!);
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = SvgPicture.asset(ThingsboardImage.deviceProfilePlaceholder,
|
||||
color: Theme.of(context).primaryColor,
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Device');
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return Center(child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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
|
||||
))
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${widget.device.field('type')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
]
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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)))
|
||||
]),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${widget.device.field('type')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12)),
|
||||
])
|
||||
],
|
||||
)))
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import 'package:thingsboard_app/core/entity/entities_list.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DevicesList extends BaseEntitiesWidget<EntityData, EntityDataQuery> with DevicesBase, EntitiesListStateBase {
|
||||
|
||||
class DevicesList extends BaseEntitiesWidget<EntityData, EntityDataQuery>
|
||||
with DevicesBase, EntitiesListStateBase {
|
||||
final bool displayDeviceImage;
|
||||
|
||||
DevicesList(TbContext tbContext, PageKeyController<EntityDataQuery> pageKeyController, {searchMode = false, this.displayDeviceImage = false}):
|
||||
super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
DevicesList(
|
||||
TbContext tbContext, PageKeyController<EntityDataQuery> pageKeyController,
|
||||
{searchMode = false, this.displayDeviceImage = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
@override
|
||||
bool displayCardImage(bool listWidgetCard) => displayDeviceImage;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,87 +6,85 @@ import 'package:thingsboard_app/modules/device/devices_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesListPage extends TbPageWidget {
|
||||
|
||||
final String? deviceType;
|
||||
final bool? active;
|
||||
final bool searchMode;
|
||||
|
||||
DevicesListPage(TbContext tbContext, {this.deviceType, this.active, this.searchMode = false}) : super(tbContext);
|
||||
DevicesListPage(TbContext tbContext,
|
||||
{this.deviceType, this.active, this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesListPageState createState() => _DevicesListPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DevicesListPageState extends TbPageState<DevicesListPage> {
|
||||
|
||||
late final DeviceQueryController _deviceQueryController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_deviceQueryController = DeviceQueryController(deviceType: widget.deviceType, active: widget.active);
|
||||
_deviceQueryController = DeviceQueryController(
|
||||
deviceType: widget.deviceType, active: widget.active);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var devicesList = DevicesList(tbContext, _deviceQueryController, searchMode: widget.searchMode, displayDeviceImage: widget.deviceType == null);
|
||||
var devicesList = DevicesList(tbContext, _deviceQueryController,
|
||||
searchMode: widget.searchMode,
|
||||
displayDeviceImage: widget.deviceType == null);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _deviceQueryController.onSearchText(searchText),
|
||||
onSearch: (searchText) =>
|
||||
_deviceQueryController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
String titleText = widget.deviceType != null ? widget.deviceType! : 'All devices';
|
||||
String titleText =
|
||||
widget.deviceType != null ? widget.deviceType! : 'All devices';
|
||||
String? subTitleText;
|
||||
if (widget.active != null) {
|
||||
subTitleText = widget.active == true ? 'Active' : 'Inactive';
|
||||
}
|
||||
Column title = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(titleText, style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: subTitleText != null ? 16 : 20,
|
||||
height: subTitleText != null ? 20 / 16 : 24 / 20
|
||||
)),
|
||||
if (subTitleText != null)
|
||||
Text(subTitleText, style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline6!.color!.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
);
|
||||
Column title =
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(titleText,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: subTitleText != null ? 16 : 20,
|
||||
height: subTitleText != null ? 20 / 16 : 24 / 20)),
|
||||
if (subTitleText != null)
|
||||
Text(subTitleText,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline6!
|
||||
.color!
|
||||
.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12))
|
||||
]);
|
||||
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: title,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
List<String> params = [];
|
||||
params.add('search=true');
|
||||
if (widget.deviceType != null) {
|
||||
params.add('deviceType=${widget.deviceType}');
|
||||
}
|
||||
if (widget.active != null) {
|
||||
params.add('active=${widget.active}');
|
||||
}
|
||||
navigateTo('/deviceList?${params.join('&')}');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: title, actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
List<String> params = [];
|
||||
params.add('search=true');
|
||||
if (widget.deviceType != null) {
|
||||
params.add('deviceType=${widget.deviceType}');
|
||||
}
|
||||
if (widget.active != null) {
|
||||
params.add('active=${widget.active}');
|
||||
}
|
||||
navigateTo('/deviceList?${params.join('&')}');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: devicesList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: devicesList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,5 +92,4 @@ class _DevicesListPageState extends TbPageState<DevicesListPage> {
|
||||
_deviceQueryController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import 'package:thingsboard_app/core/entity/entities_list_widget.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DevicesListWidget extends EntitiesListWidget<EntityData, EntityDataQuery> with DevicesBase {
|
||||
|
||||
DevicesListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller);
|
||||
class DevicesListWidget extends EntitiesListWidget<EntityData, EntityDataQuery>
|
||||
with DevicesBase {
|
||||
DevicesListWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
@@ -14,6 +16,6 @@ class DevicesListWidget extends EntitiesListWidget<EntityData, EntityDataQuery>
|
||||
}
|
||||
|
||||
@override
|
||||
PageKeyController<EntityDataQuery> createPageKeyController() => DeviceQueryController(pageSize: 5);
|
||||
|
||||
PageKeyController<EntityDataQuery> createPageKeyController() =>
|
||||
DeviceQueryController(pageSize: 5);
|
||||
}
|
||||
|
||||
@@ -6,16 +6,14 @@ import 'package:thingsboard_app/modules/device/device_profiles_grid.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesMainPage extends TbContextWidget {
|
||||
|
||||
DevicesMainPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesMainPageState createState() => _DevicesMainPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DevicesMainPageState extends TbContextState<DevicesMainPage> with AutomaticKeepAliveClientMixin<DevicesMainPage> {
|
||||
|
||||
class _DevicesMainPageState extends TbContextState<DevicesMainPage>
|
||||
with AutomaticKeepAliveClientMixin<DevicesMainPage> {
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
@@ -28,12 +26,8 @@ class _DevicesMainPageState extends TbContextState<DevicesMainPage> with Automat
|
||||
super.build(context);
|
||||
var deviceProfilesList = DeviceProfilesGrid(tbContext, _pageLinkController);
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text(deviceProfilesList.title)
|
||||
),
|
||||
body: deviceProfilesList
|
||||
);
|
||||
appBar: TbAppBar(tbContext, title: Text(deviceProfilesList.title)),
|
||||
body: deviceProfilesList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -41,5 +35,4 @@ class _DevicesMainPageState extends TbContextState<DevicesMainPage> with Automat
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,28 +6,21 @@ import 'package:thingsboard_app/modules/device/device_profiles_grid.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesPage extends TbPageWidget {
|
||||
|
||||
DevicesPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesPageState createState() => _DevicesPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DevicesPageState extends TbPageState<DevicesPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var deviceProfilesList = DeviceProfilesGrid(tbContext, _pageLinkController);
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text(deviceProfilesList.title)
|
||||
),
|
||||
body: deviceProfilesList
|
||||
);
|
||||
appBar: TbAppBar(tbContext, title: Text(deviceProfilesList.title)),
|
||||
body: deviceProfilesList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -35,5 +28,4 @@ class _DevicesPageState extends TbPageState<DevicesPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart' as dashboardUi;
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart'
|
||||
as dashboardUi;
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboards_grid.dart';
|
||||
import 'package:thingsboard_app/modules/tenant/tenants_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class HomePage extends TbContextWidget {
|
||||
|
||||
HomePage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
|
||||
}
|
||||
|
||||
class _HomePageState extends TbContextState<HomePage> with AutomaticKeepAliveClientMixin<HomePage> {
|
||||
|
||||
class _HomePageState extends TbContextState<HomePage>
|
||||
with AutomaticKeepAliveClientMixin<HomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -50,33 +48,29 @@ class _HomePageState extends TbContextState<HomePage> with AutomaticKeepAliveCli
|
||||
height: 24,
|
||||
child: SvgPicture.asset(ThingsboardImage.thingsBoardWithTitle,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticsLabel: 'ThingsBoard Logo')
|
||||
)
|
||||
),
|
||||
semanticsLabel: 'ThingsBoard Logo'))),
|
||||
actions: [
|
||||
if (tbClient.isSystemAdmin()) IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
if (tbClient.isSystemAdmin())
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
if (dashboardState) {
|
||||
return _buildDashboardHome(context, homeDashboard!);
|
||||
} else {
|
||||
return _buildDefaultHome(context);
|
||||
}
|
||||
}
|
||||
),
|
||||
body: Builder(builder: (context) {
|
||||
if (dashboardState) {
|
||||
return _buildDashboardHome(context, homeDashboard!);
|
||||
} else {
|
||||
return _buildDefaultHome(context);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDashboardHome(BuildContext context, HomeDashboardInfo dashboard) {
|
||||
Widget _buildDashboardHome(
|
||||
BuildContext context, HomeDashboardInfo dashboard) {
|
||||
return HomeDashboard(tbContext, dashboard);
|
||||
}
|
||||
|
||||
@@ -91,31 +85,24 @@ class _HomePageState extends TbContextState<HomePage> with AutomaticKeepAliveCli
|
||||
Widget _buildSysAdminHome(BuildContext context) {
|
||||
return TenantsWidget(tbContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class HomeDashboard extends TbContextWidget {
|
||||
|
||||
final HomeDashboardInfo dashboard;
|
||||
|
||||
HomeDashboard(TbContext tbContext, this.dashboard) : super(tbContext);
|
||||
|
||||
@override
|
||||
_HomeDashboardState createState() => _HomeDashboardState();
|
||||
|
||||
}
|
||||
|
||||
class _HomeDashboardState extends TbContextState<HomeDashboard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return dashboardUi.Dashboard(tbContext,
|
||||
home: true,
|
||||
controllerCallback: (controller) {
|
||||
controller.openDashboard(widget.dashboard.dashboardId!.id!,
|
||||
hideToolbar: widget.dashboard.hideDashboardToolbar);
|
||||
}
|
||||
);
|
||||
return dashboardUi.Dashboard(tbContext, home: true,
|
||||
controllerCallback: (controller) {
|
||||
controller.openDashboard(widget.dashboard.dashboardId!.id!,
|
||||
hideToolbar: widget.dashboard.hideDashboardToolbar);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
class HomeRoutes extends TbRoutes {
|
||||
|
||||
late var homeHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var homeHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return MainPage(tbContext, path: '/home');
|
||||
});
|
||||
|
||||
@@ -16,5 +16,4 @@ class HomeRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/home", handler: homeHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/alarm/alarms_page.dart';
|
||||
@@ -15,17 +13,18 @@ class TbMainNavigationItem {
|
||||
final Icon icon;
|
||||
final String path;
|
||||
|
||||
TbMainNavigationItem({
|
||||
required this.page,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.path
|
||||
});
|
||||
TbMainNavigationItem(
|
||||
{required this.page,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.path});
|
||||
|
||||
static Map<Authority, Set<String>> mainPageStateMap = {
|
||||
Authority.SYS_ADMIN: Set.unmodifiable(['/home', '/more']),
|
||||
Authority.TENANT_ADMIN: Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
Authority.CUSTOMER_USER: Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
Authority.TENANT_ADMIN:
|
||||
Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
Authority.CUSTOMER_USER:
|
||||
Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
};
|
||||
|
||||
static bool isMainPageState(TbContext tbContext, String path) {
|
||||
@@ -44,40 +43,38 @@ class TbMainNavigationItem {
|
||||
page: HomePage(tbContext),
|
||||
title: 'Home',
|
||||
icon: Icon(Icons.home),
|
||||
path: '/home'
|
||||
)
|
||||
path: '/home')
|
||||
];
|
||||
switch(tbContext.tbClient.getAuthUser()!.authority) {
|
||||
switch (tbContext.tbClient.getAuthUser()!.authority) {
|
||||
case Authority.SYS_ADMIN:
|
||||
break;
|
||||
case Authority.TENANT_ADMIN:
|
||||
case Authority.CUSTOMER_USER:
|
||||
items.addAll([
|
||||
TbMainNavigationItem(
|
||||
page: AlarmsPage(tbContext),
|
||||
title: 'Alarms',
|
||||
icon: Icon(Icons.notifications),
|
||||
path: '/alarms'
|
||||
),
|
||||
TbMainNavigationItem(
|
||||
page: DevicesMainPage(tbContext),
|
||||
title: 'Devices',
|
||||
icon: Icon(Icons.devices_other),
|
||||
path: '/devices'
|
||||
)
|
||||
]);
|
||||
break;
|
||||
items.addAll([
|
||||
TbMainNavigationItem(
|
||||
page: AlarmsPage(tbContext),
|
||||
title: 'Alarms',
|
||||
icon: Icon(Icons.notifications),
|
||||
path: '/alarms'),
|
||||
TbMainNavigationItem(
|
||||
page: DevicesMainPage(tbContext),
|
||||
title: 'Devices',
|
||||
icon: Icon(Icons.devices_other),
|
||||
path: '/devices')
|
||||
]);
|
||||
break;
|
||||
case Authority.REFRESH_TOKEN:
|
||||
break;
|
||||
case Authority.ANONYMOUS:
|
||||
break;
|
||||
case Authority.PRE_VERIFICATION_TOKEN:
|
||||
break;
|
||||
}
|
||||
items.add(TbMainNavigationItem(
|
||||
page: MorePage(tbContext),
|
||||
title: 'More',
|
||||
icon: Icon(Icons.menu),
|
||||
path: '/more'
|
||||
));
|
||||
path: '/more'));
|
||||
return items;
|
||||
} else {
|
||||
return [];
|
||||
@@ -86,19 +83,18 @@ class TbMainNavigationItem {
|
||||
}
|
||||
|
||||
class MainPage extends TbPageWidget {
|
||||
|
||||
final String _path;
|
||||
|
||||
MainPage(TbContext tbContext, {required String path}):
|
||||
_path = path, super(tbContext);
|
||||
MainPage(TbContext tbContext, {required String path})
|
||||
: _path = path,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_MainPageState createState() => _MainPageState();
|
||||
|
||||
}
|
||||
|
||||
class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProviderStateMixin {
|
||||
|
||||
class _MainPageState extends TbPageState<MainPage>
|
||||
with TbMainState, TickerProviderStateMixin {
|
||||
late ValueNotifier<int> _currentIndexNotifier;
|
||||
late final List<TbMainNavigationItem> _tabItems;
|
||||
late TabController _tabController;
|
||||
@@ -108,7 +104,8 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
super.initState();
|
||||
_tabItems = TbMainNavigationItem.getItems(tbContext);
|
||||
int currentIndex = _indexFromPath(widget._path);
|
||||
_tabController = TabController(initialIndex: currentIndex, length: _tabItems.length, vsync: this);
|
||||
_tabController = TabController(
|
||||
initialIndex: currentIndex, length: _tabItems.length, vsync: this);
|
||||
_currentIndexNotifier = ValueNotifier(currentIndex);
|
||||
_tabController.animation!.addListener(_onTabAnimation);
|
||||
}
|
||||
@@ -119,7 +116,7 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onTabAnimation () {
|
||||
_onTabAnimation() {
|
||||
var value = _tabController.animation!.value;
|
||||
var targetIndex;
|
||||
if (value >= _tabController.previousIndex) {
|
||||
@@ -142,24 +139,24 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
},
|
||||
child: Scaffold(
|
||||
body: TabBarView(
|
||||
physics: tbContext.homeDashboard != null ? NeverScrollableScrollPhysics() : null,
|
||||
physics: tbContext.homeDashboard != null
|
||||
? NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
controller: _tabController,
|
||||
children: _tabItems.map((item) => item.page).toList(),
|
||||
),
|
||||
bottomNavigationBar: ValueListenableBuilder<int>(
|
||||
valueListenable: _currentIndexNotifier,
|
||||
builder: (context, index, child) => BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: index,
|
||||
onTap: (int index) => _setIndex(index) /*_currentIndex = index*/,
|
||||
items: _tabItems.map((item) => BottomNavigationBarItem(
|
||||
icon: item.icon,
|
||||
label: item.title
|
||||
)).toList()
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
valueListenable: _currentIndexNotifier,
|
||||
builder: (context, index, child) => BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: index,
|
||||
onTap: (int index) =>
|
||||
_setIndex(index) /*_currentIndex = index*/,
|
||||
items: _tabItems
|
||||
.map((item) => BottomNavigationBarItem(
|
||||
icon: item.icon, label: item.title))
|
||||
.toList()),
|
||||
)));
|
||||
}
|
||||
|
||||
int _indexFromPath(String path) {
|
||||
@@ -175,7 +172,7 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
navigateToPath(String path) {
|
||||
int targetIndex = _indexFromPath(path);
|
||||
_setIndex(targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool isHomePage() {
|
||||
@@ -185,5 +182,4 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
_setIndex(int index) {
|
||||
_tabController.index = index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,129 +4,108 @@ import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class MorePage extends TbContextWidget {
|
||||
|
||||
MorePage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_MorePageState createState() => _MorePageState();
|
||||
|
||||
}
|
||||
|
||||
class _MorePageState extends TbContextState<MorePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Container(
|
||||
padding: EdgeInsets.fromLTRB(16, 40, 16, 20),
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.account_circle, size: 48, color: Color(0xFFAFAFAF)),
|
||||
Spacer(),
|
||||
IconButton(icon: Icon(Icons.settings, color: Color(0xFFAFAFAF)), onPressed: () async {
|
||||
await navigateTo('/profile');
|
||||
setState(() {});
|
||||
})
|
||||
],
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Text(_getUserDisplayName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 20,
|
||||
height: 23 / 20
|
||||
)
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(_getAuthorityName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 16 / 14
|
||||
)
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
buildMoreMenuItems(context),
|
||||
SizedBox(height: 8),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.account_circle,
|
||||
size: 48, color: Color(0xFFAFAFAF)),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings, color: Color(0xFFAFAFAF)),
|
||||
onPressed: () async {
|
||||
await navigateTo('/profile');
|
||||
setState(() {});
|
||||
})
|
||||
],
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Text(_getUserDisplayName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 20,
|
||||
height: 23 / 20)),
|
||||
SizedBox(height: 2),
|
||||
Text(_getAuthorityName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 16 / 14)),
|
||||
SizedBox(height: 24),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
buildMoreMenuItems(context),
|
||||
SizedBox(height: 8),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 48,
|
||||
height: 48,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Icon(Icons.logout, color: Color(0xFFE04B2F)),
|
||||
SizedBox(width: 34),
|
||||
Text('Log out',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFE04B2F),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
))
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
padding:
|
||||
EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
Icon(Icons.logout, color: Color(0xFFE04B2F)),
|
||||
SizedBox(width: 34),
|
||||
Text('Log out',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFE04B2F),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14))
|
||||
]))),
|
||||
onTap: () {
|
||||
tbClient.logout(
|
||||
requestConfig: RequestConfig(ignoreErrors: true));
|
||||
}
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget buildMoreMenuItems(BuildContext context) {
|
||||
List<Widget> items = MoreMenuItem.getItems(tbContext).map((menuItem) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 48,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Icon(menuItem.icon, color: Color(0xFF282828)),
|
||||
SizedBox(width: 34),
|
||||
Text(menuItem.title,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
))
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo(menuItem.path);
|
||||
}
|
||||
);
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 48,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
Icon(menuItem.icon, color: Color(0xFF282828)),
|
||||
SizedBox(width: 34),
|
||||
Text(menuItem.title,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14))
|
||||
]))),
|
||||
onTap: () {
|
||||
navigateTo(menuItem.path);
|
||||
});
|
||||
}).toList();
|
||||
return Column(
|
||||
children: items
|
||||
);
|
||||
return Column(children: items);
|
||||
}
|
||||
|
||||
String _getUserDisplayName() {
|
||||
@@ -156,7 +135,7 @@ class _MorePageState extends TbContextState<MorePage> {
|
||||
var name = '';
|
||||
if (user != null) {
|
||||
var authority = user.authority;
|
||||
switch(authority) {
|
||||
switch (authority) {
|
||||
case Authority.SYS_ADMIN:
|
||||
name = 'System Administrator';
|
||||
break;
|
||||
@@ -166,11 +145,12 @@ class _MorePageState extends TbContextState<MorePage> {
|
||||
case Authority.CUSTOMER_USER:
|
||||
name = 'Customer';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MoreMenuItem {
|
||||
@@ -178,11 +158,7 @@ class MoreMenuItem {
|
||||
final IconData icon;
|
||||
final String path;
|
||||
|
||||
MoreMenuItem({
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.path
|
||||
});
|
||||
MoreMenuItem({required this.title, required this.icon, required this.path});
|
||||
|
||||
static List<MoreMenuItem> getItems(TbContext tbContext) {
|
||||
if (tbContext.isAuthenticated) {
|
||||
@@ -195,33 +171,25 @@ class MoreMenuItem {
|
||||
MoreMenuItem(
|
||||
title: 'Customers',
|
||||
icon: Icons.supervisor_account,
|
||||
path: '/customers'
|
||||
),
|
||||
MoreMenuItem(
|
||||
title: 'Assets',
|
||||
icon: Icons.domain,
|
||||
path: '/assets'
|
||||
),
|
||||
path: '/customers'),
|
||||
MoreMenuItem(title: 'Assets', icon: Icons.domain, path: '/assets'),
|
||||
MoreMenuItem(
|
||||
title: 'Audit Logs',
|
||||
icon: Icons.track_changes,
|
||||
path: '/auditLogs'
|
||||
)
|
||||
path: '/auditLogs')
|
||||
]);
|
||||
break;
|
||||
case Authority.CUSTOMER_USER:
|
||||
items.addAll([
|
||||
MoreMenuItem(
|
||||
title: 'Assets',
|
||||
icon: Icons.domain,
|
||||
path: '/assets'
|
||||
)
|
||||
MoreMenuItem(title: 'Assets', icon: Icons.domain, path: '/assets')
|
||||
]);
|
||||
break;
|
||||
case Authority.REFRESH_TOKEN:
|
||||
break;
|
||||
case Authority.ANONYMOUS:
|
||||
break;
|
||||
case Authority.PRE_VERIFICATION_TOKEN:
|
||||
break;
|
||||
}
|
||||
return items;
|
||||
} else {
|
||||
|
||||
@@ -7,16 +7,13 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
|
||||
class ChangePasswordPage extends TbContextWidget {
|
||||
|
||||
ChangePasswordPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_ChangePasswordPageState createState() => _ChangePasswordPageState();
|
||||
|
||||
}
|
||||
|
||||
class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
|
||||
final _isLoadingNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
final _showCurrentPasswordNotifier = ValueNotifier<bool>(false);
|
||||
@@ -39,96 +36,105 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SingleChildScrollView(
|
||||
child: FormBuilder(
|
||||
key: _changePasswordFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
ValueListenableBuilder(
|
||||
child: FormBuilder(
|
||||
key: _changePasswordFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showCurrentPasswordNotifier,
|
||||
builder: (BuildContext context, bool showPassword, child) {
|
||||
builder: (BuildContext context, bool showPassword,
|
||||
child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'currentPassword',
|
||||
obscureText: !showPassword,
|
||||
autofocus: true,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'Current password is required.')
|
||||
FormBuilderValidators.required(
|
||||
errorText:
|
||||
'Current password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
|
||||
icon: Icon(showPassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showCurrentPasswordNotifier.value = !_showCurrentPasswordNotifier.value;
|
||||
_showCurrentPasswordNotifier.value =
|
||||
!_showCurrentPasswordNotifier
|
||||
.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Current password *'
|
||||
),
|
||||
labelText: 'Current password *'),
|
||||
);
|
||||
}
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPasswordNotifier,
|
||||
builder: (BuildContext context, bool showPassword, child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'New password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPasswordNotifier.value = !_showNewPasswordNotifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password *'
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPassword2Notifier,
|
||||
builder: (BuildContext context, bool showPassword, child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword2',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'New password again is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPassword2Notifier.value = !_showNewPassword2Notifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password again *'
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password'))
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPasswordNotifier,
|
||||
builder: (BuildContext context, bool showPassword,
|
||||
child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText: 'New password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPasswordNotifier.value =
|
||||
!_showNewPasswordNotifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password *'),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPassword2Notifier,
|
||||
builder: (BuildContext context, bool showPassword,
|
||||
child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword2',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText:
|
||||
'New password again is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPassword2Notifier.value =
|
||||
!_showNewPassword2Notifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password again *'),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password')))
|
||||
]),
|
||||
))),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isLoadingNotifier,
|
||||
@@ -136,18 +142,15 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
child: Container(
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
)
|
||||
);
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _changePassword() async {
|
||||
@@ -165,11 +168,10 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
await tbClient.changePassword(currentPassword, newPassword);
|
||||
pop(true);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
_isLoadingNotifier.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:thingsboard_app/modules/profile/change_password_page.dart';
|
||||
@@ -11,18 +10,17 @@ import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class ProfilePage extends TbPageWidget {
|
||||
|
||||
final bool _fullscreen;
|
||||
|
||||
ProfilePage(TbContext tbContext, {bool fullscreen = false}) : _fullscreen = fullscreen, super(tbContext);
|
||||
ProfilePage(TbContext tbContext, {bool fullscreen = false})
|
||||
: _fullscreen = fullscreen,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_ProfilePageState createState() => _ProfilePageState();
|
||||
|
||||
}
|
||||
|
||||
class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
|
||||
final _isLoadingNotifier = ValueNotifier<bool>(true);
|
||||
|
||||
final _profileFormKey = GlobalKey<FormBuilderState>();
|
||||
@@ -49,21 +47,16 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
title: const Text('Profile'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.check
|
||||
),
|
||||
icon: Icon(Icons.check),
|
||||
onPressed: () {
|
||||
_saveProfile();
|
||||
}
|
||||
),
|
||||
if (widget._fullscreen) IconButton(
|
||||
icon: Icon(
|
||||
Icons.logout
|
||||
),
|
||||
onPressed: () {
|
||||
tbClient.logout();
|
||||
}
|
||||
)
|
||||
}),
|
||||
if (widget._fullscreen)
|
||||
IconButton(
|
||||
icon: Icon(Icons.logout),
|
||||
onPressed: () {
|
||||
tbClient.logout();
|
||||
})
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
@@ -72,73 +65,66 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SingleChildScrollView(
|
||||
child: FormBuilder(
|
||||
key: _profileFormKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
FormBuilderTextField(
|
||||
name: 'email',
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(context, errorText: 'Invalid email format.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email *'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'firstName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'First Name'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'lastName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Last Name'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password'))
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _profileFormKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
FormBuilderTextField(
|
||||
name: 'email',
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(
|
||||
errorText: 'Invalid email format.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email *'),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'firstName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'First Name'),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'lastName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Last Name'),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password')))
|
||||
]),
|
||||
))),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isLoadingNotifier,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
child: Container(
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
)
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _loadUser() async {
|
||||
@@ -170,15 +156,18 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
_setUser();
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
_isLoadingNotifier.value = false;
|
||||
showSuccessNotification('Profile successfully updated', duration: Duration(milliseconds: 1500));
|
||||
showSuccessNotification('Profile successfully updated',
|
||||
duration: Duration(milliseconds: 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_changePassword() async {
|
||||
var res = await tbContext.showFullScreenDialog<bool>(new ChangePasswordPage(tbContext));
|
||||
var res = await tbContext
|
||||
.showFullScreenDialog<bool>(new ChangePasswordPage(tbContext));
|
||||
if (res == true) {
|
||||
showSuccessNotification('Password successfully changed', duration: Duration(milliseconds: 1500));
|
||||
showSuccessNotification('Password successfully changed',
|
||||
duration: Duration(milliseconds: 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'profile_page.dart';
|
||||
|
||||
class ProfileRoutes extends TbRoutes {
|
||||
|
||||
late var profileHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var profileHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var fullscreen = params['fullscreen']?.first == 'true';
|
||||
return ProfilePage(tbContext, fullscreen: fullscreen);
|
||||
});
|
||||
@@ -18,5 +18,4 @@ class ProfileRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/profile", handler: profileHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class TenantDetailsPage extends ContactBasedDetailsPage<Tenant> {
|
||||
|
||||
TenantDetailsPage(TbContext tbContext, String tenantId):
|
||||
super(tbContext, entityId: tenantId, defaultTitle: 'Tenant', subTitle: 'Tenant details');
|
||||
TenantDetailsPage(TbContext tbContext, String tenantId)
|
||||
: super(tbContext,
|
||||
entityId: tenantId,
|
||||
defaultTitle: 'Tenant',
|
||||
subTitle: 'Tenant details');
|
||||
|
||||
@override
|
||||
Future<Tenant?> fetchEntity(String tenantId) {
|
||||
return tbClient.getTenantService().getTenant(tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import 'tenant_details_page.dart';
|
||||
import 'tenants_page.dart';
|
||||
|
||||
class TenantRoutes extends TbRoutes {
|
||||
|
||||
late var tenantsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var tenantsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return TenantsPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
|
||||
late var tenantDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var tenantDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return TenantDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -23,5 +24,4 @@ class TenantRoutes extends TbRoutes {
|
||||
router.define("/tenants", handler: tenantsHandler);
|
||||
router.define("/tenant/:id", handler: tenantDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin TenantsBase on EntitiesBase<Tenant, PageLink> {
|
||||
|
||||
mixin TenantsBase on EntitiesBase<Tenant, PageLink> {
|
||||
@override
|
||||
String get title => 'Tenants';
|
||||
|
||||
@@ -18,5 +17,4 @@ mixin TenantsBase on EntitiesBase<Tenant, PageLink> {
|
||||
void onEntityTap(Tenant tenant) {
|
||||
navigateTo('/tenant/${tenant.id!.id}');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'tenants_base.dart';
|
||||
|
||||
class TenantsList extends BaseEntitiesWidget<Tenant, PageLink> with TenantsBase, ContactBasedBase, EntitiesListStateBase {
|
||||
|
||||
TenantsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class TenantsList extends BaseEntitiesWidget<Tenant, PageLink>
|
||||
with TenantsBase, ContactBasedBase, EntitiesListStateBase {
|
||||
TenantsList(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
@@ -7,23 +7,22 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'tenants_list.dart';
|
||||
|
||||
class TenantsPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
TenantsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
TenantsPage(TbContext tbContext, {this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_TenantsPageState createState() => _TenantsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _TenantsPageState extends TbPageState<TenantsPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var tenantsList = TenantsList(tbContext, _pageLinkController, searchMode: widget.searchMode);
|
||||
var tenantsList = TenantsList(tbContext, _pageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
@@ -31,24 +30,16 @@ class _TenantsPageState extends TbPageState<TenantsPage> {
|
||||
onSearch: (searchText) => _pageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(tenantsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(tenantsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: tenantsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: tenantsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -56,5 +47,4 @@ class _TenantsPageState extends TbPageState<TenantsPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,21 +6,18 @@ import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'tenants_list.dart';
|
||||
|
||||
class TenantsWidget extends TbContextWidget {
|
||||
|
||||
TenantsWidget(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_TenantsWidgetState createState() => _TenantsWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _TenantsWidgetState extends TbContextState<TenantsWidget> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TenantsList(tbContext, _pageLinkController);
|
||||
return TenantsList(tbContext, _pageLinkController);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -28,5 +25,4 @@ class _TenantsWidgetState extends TbContextState<TenantsWidget> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user