Add flutter 3+ support. Update dependencies. Fix code style and format issues.

This commit is contained in:
Igor Kulikov
2022-08-12 13:55:27 +03:00
parent 1a07bcd7a0
commit 944c36ce7b
94 changed files with 3167 additions and 3173 deletions

View File

@@ -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);
}
}

View File

@@ -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!;
});
}
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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),
]
)
);
]));
}
}

View File

@@ -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);
}
}

View File

@@ -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))
],
))
])))
]);
}
}

View File

@@ -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);
}

View File

@@ -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');
}
}

View File

@@ -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();
}
}

View File

@@ -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),
),
)),
],
);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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}');
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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),
);
}
})
]);
}
}));
}
}

View File

@@ -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;
//}
//),
);
//}
//),
);
}
}

View File

@@ -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);
}
}

View File

@@ -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),
))),
)
],
));
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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');
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}));
}
}

View File

@@ -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();
}
}
)
);
});
}
}));
}
}

View File

@@ -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}'),
);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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)),
])
],
)))
]);
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
});
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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;
}
}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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}');
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}