Base pages implementation
This commit is contained in:
27
lib/modules/asset/asset_details_page.dart
Normal file
27
lib/modules/asset/asset_details_page.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
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 AssetDetailsPage extends EntityDetailsPage<AssetInfo> {
|
||||
|
||||
AssetDetailsPage(TbContext tbContext, String assetId):
|
||||
super(tbContext,
|
||||
entityId: assetId,
|
||||
defaultTitle: 'Asset');
|
||||
|
||||
@override
|
||||
Future<AssetInfo> fetchEntity(String assetId) {
|
||||
return tbClient.getAssetService().getAssetInfo(assetId);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityDetails(BuildContext context, AssetInfo asset) {
|
||||
return ListTile(
|
||||
title: Text('${asset.name}'),
|
||||
subtitle: Text('${asset.type}'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
27
lib/modules/asset/asset_routes.dart
Normal file
27
lib/modules/asset/asset_routes.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/asset/assets_page.dart';
|
||||
|
||||
import 'asset_details_page.dart';
|
||||
|
||||
class AssetRoutes extends TbRoutes {
|
||||
|
||||
late var assetsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return AssetsPage(tbContext);
|
||||
});
|
||||
|
||||
late var assetDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return AssetDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
AssetRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/assets", handler: assetsHandler);
|
||||
router.define("/asset/:id", handler: assetDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
89
lib/modules/asset/assets_base.dart
Normal file
89
lib/modules/asset/assets_base.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
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> {
|
||||
|
||||
@override
|
||||
String get title => 'Assets';
|
||||
|
||||
@override
|
||||
String get noItemsFoundText => 'No assets found';
|
||||
|
||||
@override
|
||||
Future<PageData<AssetInfo>> fetchEntities(PageLink pageLink) {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
return tbClient.getAssetService().getTenantAssetInfos(pageLink);
|
||||
} else {
|
||||
return tbClient.getAssetService().getCustomerAssetInfos(tbClient.getAuthUser()!.customerId, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityCard(BuildContext context, AssetInfo asset, bool briefView) {
|
||||
return Row(
|
||||
mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: briefView ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: briefView ? 9 : 10, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: briefView ? FlexFit.loose : FlexFit.tight,
|
||||
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}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
)
|
||||
),
|
||||
(!briefView ? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(asset.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
) : Container())
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onEntityDetails(AssetInfo asset) {
|
||||
navigateTo('/asset/${asset.id!.id}');
|
||||
}
|
||||
|
||||
}
|
||||
16
lib/modules/asset/assets_page.dart
Normal file
16
lib/modules/asset/assets_page.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_page.dart';
|
||||
import 'package:thingsboard_app/modules/asset/assets_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AssetsPage extends EntitiesPage<AssetInfo> with AssetsBase {
|
||||
|
||||
AssetsPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
String get noMoreItemsText => 'No more assets';
|
||||
|
||||
@override
|
||||
String get searchHint => 'Search assets';
|
||||
|
||||
}
|
||||
16
lib/modules/asset/assets_widget.dart
Normal file
16
lib/modules/asset/assets_widget.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_widget.dart';
|
||||
import 'package:thingsboard_app/modules/asset/assets_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AssetsWidget extends EntitiesWidget<AssetInfo> with AssetsBase {
|
||||
|
||||
AssetsWidget(TbContext tbContext, {EntitiesWidgetController? controller}): super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
navigateTo('/assets');
|
||||
}
|
||||
|
||||
}
|
||||
250
lib/modules/dashboard/dashboard.dart
Normal file
250
lib/modules/dashboard/dashboard.dart
Normal file
@@ -0,0 +1,250 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:thingsboard_app/constants/api_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
typedef DashboardTitleCallback = void Function(String title);
|
||||
|
||||
class Dashboard extends TbContextWidget<Dashboard, _DashboardState> {
|
||||
|
||||
final String _dashboardId;
|
||||
final String? _state;
|
||||
final bool? _home;
|
||||
final bool? _hideToolbar;
|
||||
final bool _fullscreen;
|
||||
final DashboardTitleCallback? _titleCallback;
|
||||
|
||||
Dashboard(TbContext tbContext, {required String dashboardId, required bool fullscreen,
|
||||
DashboardTitleCallback? titleCallback, String? state, bool? home,
|
||||
bool? hideToolbar}):
|
||||
this._dashboardId = dashboardId,
|
||||
this._fullscreen = fullscreen,
|
||||
this._titleCallback = titleCallback,
|
||||
this._state = state,
|
||||
this._home = home,
|
||||
this._hideToolbar = hideToolbar,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardState createState() => _DashboardState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardState extends TbContextState<Dashboard, _DashboardState> {
|
||||
|
||||
final Completer<InAppWebViewController> _controller = Completer<InAppWebViewController>();
|
||||
|
||||
final ValueNotifier<bool> webViewLoading = ValueNotifier(true);
|
||||
|
||||
final GlobalKey webViewKey = GlobalKey();
|
||||
|
||||
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(
|
||||
useShouldOverrideUrlLoading: true,
|
||||
mediaPlaybackRequiresUserGesture: false,
|
||||
javaScriptEnabled: true,
|
||||
),
|
||||
android: AndroidInAppWebViewOptions(
|
||||
useHybridComposition: false,
|
||||
thirdPartyCookiesEnabled: true
|
||||
),
|
||||
ios: IOSInAppWebViewOptions(
|
||||
allowsInlineMediaPlayback: true,
|
||||
));
|
||||
|
||||
late String _dashboardUrl;
|
||||
late String _currentDashboardId;
|
||||
late String? _currentDashboardState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dashboardUrl = thingsBoardApiEndpoint + '/dashboard/' + widget._dashboardId;
|
||||
List<String> params = [];
|
||||
if (widget._state != null) {
|
||||
params.add('state=${widget._state}');
|
||||
}
|
||||
if (widget._home == true) {
|
||||
params.add('embedded=true');
|
||||
}
|
||||
if (widget._hideToolbar == true) {
|
||||
params.add('hideToolbar=true');
|
||||
}
|
||||
if (params.isNotEmpty) {
|
||||
_dashboardUrl += '?${params.join('&')}';
|
||||
}
|
||||
_currentDashboardId = widget._dashboardId;
|
||||
_currentDashboardState = widget._state;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
var controller = await _controller.future;
|
||||
if (await controller.canGoBack()) {
|
||||
await controller.goBack();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: Uri.parse(_dashboardUrl)),
|
||||
initialOptions: options,
|
||||
onWebViewCreated: (webViewController) {
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardStateNameHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileDashboardStateNameHandler: $args");
|
||||
webViewLoading.value = false;
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
if (widget._titleCallback != null) {
|
||||
widget._titleCallback!(args[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileHandler: $args");
|
||||
return await widgetActionHandler.handleWidgetMobileAction(args, webViewController);
|
||||
});
|
||||
_controller.complete(webViewController);
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
var uri = navigationAction.request.url!;
|
||||
var uriString = uri.toString();
|
||||
log.debug('disallowing navigation to $uriString');
|
||||
if (await canLaunch(uriString)) {
|
||||
await launch(uriString);
|
||||
}
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
onUpdateVisitedHistory: (controller, url, androidIsReload) async {
|
||||
if (url != null) {
|
||||
String newStateId = url.pathSegments.last;
|
||||
log.debug('onUpdateVisitedHistory: $newStateId');
|
||||
if (newStateId == 'profile') {
|
||||
webViewLoading.value = true;
|
||||
await controller.goBack();
|
||||
await navigateTo('/profile');
|
||||
webViewLoading.value = false;
|
||||
return;
|
||||
} else if (newStateId == 'login') {
|
||||
webViewLoading.value = true;
|
||||
await controller.pauseTimers();
|
||||
await controller.stopLoading();
|
||||
await tbClient.logout();
|
||||
return;
|
||||
} else if (['devices', 'assets', 'dashboards'].contains(newStateId)) {
|
||||
var controller = await _controller.future;
|
||||
await controller.goBack();
|
||||
navigateTo('/$newStateId');
|
||||
return;
|
||||
} else {
|
||||
if (url.pathSegments.length > 1) {
|
||||
var segmentName = url.pathSegments[url.pathSegments.length-2];
|
||||
if (segmentName == 'dashboards' && widget._home != true) {
|
||||
webViewLoading.value = true;
|
||||
var targetPath = _createDashboardNavigationPath(newStateId, fullscreen: widget._fullscreen);
|
||||
await navigateTo(targetPath, replace: true);
|
||||
return;
|
||||
} else if (segmentName == 'dashboard') {
|
||||
_currentDashboardId = newStateId;
|
||||
_currentDashboardState = url.queryParameters['state'];
|
||||
return;
|
||||
}
|
||||
}
|
||||
webViewLoading.value = true;
|
||||
if (widget._home == true) {
|
||||
await navigateTo('/home', replace: true);
|
||||
} else {
|
||||
var targetPath = _createDashboardNavigationPath(_currentDashboardId, state: _currentDashboardState, fullscreen: widget._fullscreen);
|
||||
await navigateTo(targetPath, replace: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
log.debug('[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
|
||||
},
|
||||
onLoadStart: (controller, url) async {
|
||||
log.debug('onLoadStart: $url');
|
||||
await _setTokens(controller.webStorage.localStorage);
|
||||
},
|
||||
onLoadStop: (controller, url) {
|
||||
},
|
||||
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: webViewLoading,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (!loading) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: Colors.white),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
String _createDashboardNavigationPath(String dashboardId, {bool? fullscreen, String? state}) {
|
||||
var targetPath = '/dashboard/$dashboardId';
|
||||
List<String> params = [];
|
||||
if (state != null) {
|
||||
params.add('state=$state');
|
||||
}
|
||||
if (fullscreen != null) {
|
||||
params.add('fullscreen=$fullscreen');
|
||||
}
|
||||
if (params.isNotEmpty) {
|
||||
targetPath += '?${params.join('&')}';
|
||||
}
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
Future<void> _setTokens(Storage storage) async {
|
||||
String jwtToken = tbClient.getJwtToken()!;
|
||||
int jwtTokenExpiration = _getClientExpiration(jwtToken);
|
||||
String refreshToken = tbClient.getRefreshToken()!;
|
||||
int refreshTokenExpiration = _getClientExpiration(refreshToken);
|
||||
await storage.setItem(key: 'jwt_token', value: jwtToken);
|
||||
await storage.setItem(key: 'jwt_token_expiration', value: jwtTokenExpiration);
|
||||
await storage.setItem(key: 'refresh_token', value: refreshToken);
|
||||
await storage.setItem(key: 'refresh_token_expiration', value: refreshTokenExpiration);
|
||||
}
|
||||
|
||||
/* String _setTokensJavaScript() {
|
||||
String jwtToken = tbClient.getJwtToken()!;
|
||||
int jwtTokenExpiration = _getClientExpiration(jwtToken);
|
||||
String refreshToken = tbClient.getRefreshToken()!;
|
||||
int refreshTokenExpiration = _getClientExpiration(refreshToken);
|
||||
return "window.localStorage.setItem('jwt_token','$jwtToken');\n"+
|
||||
"window.localStorage.setItem('jwt_token_expiration','$jwtTokenExpiration');\n"+
|
||||
"window.localStorage.setItem('refresh_token','$refreshToken');\n"+
|
||||
"window.localStorage.setItem('refresh_token_expiration','$refreshTokenExpiration');";
|
||||
} */
|
||||
|
||||
int _getClientExpiration(String token) {
|
||||
var decodedToken = JwtDecoder.decode(tbClient.getJwtToken()!);
|
||||
int issuedAt = decodedToken['iat'];
|
||||
int expTime = decodedToken['exp'];
|
||||
int ttl = expTime - issuedAt;
|
||||
int clientExpiration = DateTime.now().millisecondsSinceEpoch + ttl * 1000;
|
||||
return clientExpiration;
|
||||
}
|
||||
|
||||
}
|
||||
62
lib/modules/dashboard/dashboard_page.dart
Normal file
62
lib/modules/dashboard/dashboard_page.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
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<DashboardPage, _DashboardPageState> {
|
||||
|
||||
final String? _dashboardTitle;
|
||||
final String _dashboardId;
|
||||
final String? _state;
|
||||
final bool _fullscreen;
|
||||
|
||||
DashboardPage(TbContext tbContext, {required String dashboardId, required bool fullscreen, String? dashboardTitle, String? state}):
|
||||
_dashboardId = dashboardId,
|
||||
_fullscreen = fullscreen,
|
||||
_dashboardTitle = dashboardTitle,
|
||||
_state = state,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardPageState createState() => _DashboardPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardPageState extends TbPageState<DashboardPage, _DashboardPageState> {
|
||||
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
dashboardTitleValue = ValueNotifier(widget._dashboardTitle ?? 'Dashboard');
|
||||
}
|
||||
|
||||
@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)
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
|
||||
fullscreen: widget._fullscreen, titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
31
lib/modules/dashboard/dashboard_routes.dart
Normal file
31
lib/modules/dashboard/dashboard_routes.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
import 'dashboard_page.dart';
|
||||
|
||||
class DashboardRoutes extends TbRoutes {
|
||||
|
||||
late var dashboardsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return MainPage(tbContext, path: '/dashboards');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
DashboardRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/dashboards", handler: dashboardsHandler);
|
||||
router.define("/dashboard/:id", handler: dashboardDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
104
lib/modules/dashboard/dashboards_base.dart
Normal file
104
lib/modules/dashboard/dashboards_base.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
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 DashboardsBase on EntitiesBase<DashboardInfo> {
|
||||
|
||||
@override
|
||||
String get title => 'Dashboards';
|
||||
|
||||
@override
|
||||
String get noItemsFoundText => 'No dashboards found';
|
||||
|
||||
@override
|
||||
Future<PageData<DashboardInfo>> fetchEntities(PageLink pageLink) {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
return tbClient.getDashboardService().getTenantDashboards(pageLink);
|
||||
} else {
|
||||
return tbClient.getDashboardService().getCustomerDashboards(tbClient.getAuthUser()!.customerId, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityCard(BuildContext context, DashboardInfo dashboard, bool briefView) {
|
||||
return Row(
|
||||
mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: briefView ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: briefView ? 9 : 10, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: briefView ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${dashboard.title}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7
|
||||
))
|
||||
),
|
||||
Text('${_dashboardDetailsText(dashboard)}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
)
|
||||
),
|
||||
(!briefView ? 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) {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
if (_isPublicDashboard(dashboard)) {
|
||||
return 'Public';
|
||||
} else {
|
||||
return dashboard.assignedCustomers.map((e) => e.title).join(', ');
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
bool _isPublicDashboard(DashboardInfo dashboard) {
|
||||
return dashboard.assignedCustomers.any((element) => element.isPublic);
|
||||
}
|
||||
|
||||
@override
|
||||
void onEntityDetails(DashboardInfo dashboard) {
|
||||
navigateTo('/dashboard/${dashboard.id!.id}?title=${dashboard.title}');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
lib/modules/dashboard/dashboards_page.dart
Normal file
18
lib/modules/dashboard/dashboards_page.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_page.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboards_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DashboardsPage extends EntitiesPage<DashboardInfo> with DashboardsBase {
|
||||
|
||||
DashboardsPage(TbContext tbContext) :
|
||||
super(tbContext);
|
||||
|
||||
|
||||
@override
|
||||
String get noMoreItemsText => 'No more dashboards';
|
||||
|
||||
@override
|
||||
String get searchHint => 'Search dashboards';
|
||||
|
||||
}
|
||||
15
lib/modules/dashboard/dashboards_widget.dart
Normal file
15
lib/modules/dashboard/dashboards_widget.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboards_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DashboardsWidget extends EntitiesWidget<DashboardInfo> with DashboardsBase {
|
||||
|
||||
DashboardsWidget(TbContext tbContext, {EntitiesWidgetController? controller}): super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
navigateTo('/dashboards');
|
||||
}
|
||||
|
||||
}
|
||||
27
lib/modules/device/device_details_page.dart
Normal file
27
lib/modules/device/device_details_page.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
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');
|
||||
|
||||
@override
|
||||
Future<DeviceInfo> fetchEntity(String deviceId) {
|
||||
return tbClient.getDeviceService().getDeviceInfo(deviceId);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityDetails(BuildContext context, DeviceInfo device) {
|
||||
return ListTile(
|
||||
title: Text('${device.name}'),
|
||||
subtitle: Text('${device.type}'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
28
lib/modules/device/device_routes.dart
Normal file
28
lib/modules/device/device_routes.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
import 'device_details_page.dart';
|
||||
import 'devices_page.dart';
|
||||
|
||||
class DeviceRoutes extends TbRoutes {
|
||||
|
||||
late var devicesHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return MainPage(tbContext, path: '/devices');
|
||||
});
|
||||
|
||||
late var deviceDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DeviceDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
DeviceRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/devices", handler: devicesHandler);
|
||||
router.define("/device/:id", handler: deviceDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
98
lib/modules/device/devices_base.dart
Normal file
98
lib/modules/device/devices_base.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
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 DevicesBase on EntitiesBase<DeviceInfo> {
|
||||
|
||||
@override
|
||||
String get title => 'Devices';
|
||||
|
||||
@override
|
||||
String get noItemsFoundText => 'No devices found';
|
||||
|
||||
@override
|
||||
Future<PageData<DeviceInfo>> fetchEntities(PageLink pageLink) {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
return tbClient.getDeviceService().getTenantDeviceInfos(pageLink);
|
||||
} else {
|
||||
return tbClient.getDeviceService().getCustomerDeviceInfos(tbClient.getAuthUser()!.customerId, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onEntityDetails(DeviceInfo device) {
|
||||
navigateTo('/device/${device.id!.id}');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityCard(BuildContext context, DeviceInfo device, bool briefView) {
|
||||
return Row(
|
||||
mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: briefView ? 58 : 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFEEEEEE),
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(briefView ? 4 : 6))
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(Icons.devices_other, color: Color(0xFFC2C2C2))
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
fit: briefView ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: briefView ? 9 : 10, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: briefView ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${device.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7
|
||||
))
|
||||
),
|
||||
Text('${device.type}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
)
|
||||
),
|
||||
(!briefView ? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(device.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
) : Container())
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,209 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_page.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
class DeviceInfoCard extends StatelessWidget {
|
||||
|
||||
final DeviceInfo device;
|
||||
final void Function(DeviceInfo device)? onDetails;
|
||||
|
||||
DeviceInfoCard(this.device, {this.onDetails});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: ListTile(
|
||||
title: Text('${device.name}'),
|
||||
subtitle: Text('${device.type}'),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.navigate_next),
|
||||
onPressed: () {
|
||||
if (onDetails != null) {
|
||||
onDetails!(device);
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FirstPageExceptionIndicator extends StatelessWidget {
|
||||
const FirstPageExceptionIndicator({
|
||||
required this.title,
|
||||
this.message,
|
||||
this.onTryAgain,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final String? message;
|
||||
final VoidCallback? onTryAgain;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final message = this.message;
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
if (message != null)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (message != null)
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (onTryAgain != null)
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
if (onTryAgain != null)
|
||||
SizedBox(
|
||||
height: 50,
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: onTryAgain,
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
color: Colors.white,
|
||||
),
|
||||
label: const Text(
|
||||
'Try Again',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DevicesPage extends TbPageWidget<DevicesPage, _DevicesPageState> {
|
||||
class DevicesPage extends EntitiesPage<DeviceInfo> with DevicesBase {
|
||||
|
||||
DevicesPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesPageState createState() => _DevicesPageState();
|
||||
String get noMoreItemsText => 'No more devices';
|
||||
|
||||
@override
|
||||
String get searchHint => 'Search devices';
|
||||
|
||||
}
|
||||
|
||||
class _DevicesPageState extends TbPageState<DevicesPage, _DevicesPageState> {
|
||||
|
||||
final _searchModeNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
final PagingController<PageLink, DeviceInfo> _pagingController = PagingController(firstPageKey: PageLink(10));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pagingController.addPageRequestListener((pageKey) {
|
||||
_fetchPage(pageKey);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pagingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool dataLoading = false;
|
||||
bool scheduleRefresh = false;
|
||||
|
||||
void refresh() {
|
||||
if (dataLoading) {
|
||||
scheduleRefresh = true;
|
||||
} else {
|
||||
_pagingController.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchPage(PageLink pageKey) async {
|
||||
dataLoading = true;
|
||||
try {
|
||||
hideNotification();
|
||||
final pageData = await tbContext.tbClient.getDeviceService().getTenantDeviceInfos(pageKey);
|
||||
final isLastPage = !pageData.hasNext;
|
||||
if (isLastPage) {
|
||||
_pagingController.appendLastPage(pageData.data);
|
||||
} else {
|
||||
final nextPageKey = pageKey.nextPageLink();
|
||||
_pagingController.appendPage(pageData.data, nextPageKey);
|
||||
}
|
||||
} catch (error) {
|
||||
_pagingController.error = error;
|
||||
} finally {
|
||||
dataLoading = false;
|
||||
if (scheduleRefresh) {
|
||||
scheduleRefresh = false;
|
||||
_pagingController.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: const Text('Devices'),
|
||||
searchModeNotifier: _searchModeNotifier,
|
||||
searchHint: 'Search devices',
|
||||
onSearch: (String searchText) {
|
||||
_pagingController.firstPageKey.textSearch = searchText;
|
||||
refresh();
|
||||
},
|
||||
),
|
||||
body: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return PagedListView(
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<DeviceInfo>(
|
||||
itemBuilder: (context, item, index) {
|
||||
return DeviceInfoCard(
|
||||
item,
|
||||
onDetails: (device) {
|
||||
print('open details: $device');
|
||||
},
|
||||
);
|
||||
},
|
||||
noMoreItemsIndicatorBuilder: (context) => FirstPageExceptionIndicator(
|
||||
title: 'No more devices'
|
||||
),
|
||||
noItemsFoundIndicatorBuilder: (context) => FirstPageExceptionIndicator(
|
||||
title: 'No devices found',
|
||||
message: 'The list is currently empty.',
|
||||
onTryAgain: () => refresh(),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
/* bottomNavigationBar: BottomAppBar(
|
||||
shape: CircularNotchedRectangle(),
|
||||
notchMargin: 4.0,
|
||||
child: new Row(
|
||||
@@ -222,48 +34,6 @@ class _DevicesPageState extends TbPageState<DevicesPage, _DevicesPageState> {
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: const Icon(Icons.add), onPressed: () {},),
|
||||
|
||||
/* SpeedDial(
|
||||
animatedIcon: AnimatedIcons.menu_close,
|
||||
animatedIconTheme: IconThemeData(size: 22),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.white,
|
||||
visible: true,
|
||||
curve: Curves.bounceIn,
|
||||
children: [
|
||||
// FAB 1
|
||||
SpeedDialChild(
|
||||
child: Icon(Icons.refresh),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.white,
|
||||
onTap: () {
|
||||
refresh();
|
||||
/* setState(() {
|
||||
var rng = Random();
|
||||
var pageSize = 1 + rng.nextInt(9);
|
||||
futureDevices = tbContext.tbClient.getDeviceService().getTenantDeviceInfos(PageLink(pageSize));
|
||||
}); */
|
||||
},
|
||||
label: 'Refresh',
|
||||
labelStyle: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16.0),
|
||||
),
|
||||
// FAB 2
|
||||
SpeedDialChild(
|
||||
child: Icon(Icons.logout),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.white,
|
||||
onTap: () {
|
||||
tbContext.tbClient.logout(requestConfig: RequestConfig(ignoreErrors: true));
|
||||
},
|
||||
label: 'Logout',
|
||||
labelStyle: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16.0),
|
||||
)
|
||||
],
|
||||
)*/
|
||||
);
|
||||
}
|
||||
|
||||
@@ -316,7 +86,7 @@ class _DevicesPageState extends TbPageState<DevicesPage, _DevicesPageState> {
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.white,
|
||||
onTap: () {
|
||||
tbContext.tbClient.logout(requestConfig: RequestConfig(ignoreErrors: true));
|
||||
tbClient.logout(requestConfig: RequestConfig(ignoreErrors: true));
|
||||
},
|
||||
label: 'Logout',
|
||||
labelStyle: TextStyle(
|
||||
@@ -326,3 +96,4 @@ class _DevicesPageState extends TbPageState<DevicesPage, _DevicesPageState> {
|
||||
],
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
15
lib/modules/device/devices_widget.dart
Normal file
15
lib/modules/device/devices_widget.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_widget.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DevicesWidget extends EntitiesWidget<DeviceInfo> with DevicesBase {
|
||||
|
||||
DevicesWidget(TbContext tbContext, {EntitiesWidgetController? controller}): super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
navigateTo('/devices');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_widget.dart';
|
||||
import 'package:thingsboard_app/modules/asset/assets_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboards_widget.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart' as dashboardUi;
|
||||
|
||||
class HomePage extends TbPageWidget<HomePage, _HomePageState> {
|
||||
class HomePage extends TbContextWidget<HomePage, _HomePageState> {
|
||||
|
||||
HomePage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@@ -14,35 +20,85 @@ class HomePage extends TbPageWidget<HomePage, _HomePageState> {
|
||||
|
||||
}
|
||||
|
||||
class _HomePageState extends TbPageState<HomePage, _HomePageState> {
|
||||
class _HomePageState extends TbContextState<HomePage, _HomePageState> {
|
||||
|
||||
final EntitiesWidgetController _entitiesWidgetController = EntitiesWidgetController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_entitiesWidgetController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var homeDashboard = tbContext.homeDashboard;
|
||||
var dashboardState = homeDashboard != null;
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: const Text('ThingsBoard'),
|
||||
showLoadingIndicator: !dashboardState,
|
||||
elevation: dashboardState ? 0 : null,
|
||||
title: const Text('Home'),
|
||||
),
|
||||
body: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Center(child:
|
||||
Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: Text('Devices'),
|
||||
onPressed: () {
|
||||
navigateTo('/devices');
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}),
|
||||
builder: (context) {
|
||||
if (dashboardState) {
|
||||
return _buildDashboardHome(context, homeDashboard!);
|
||||
} else {
|
||||
return _buildDefaultHome(context);
|
||||
}
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDashboardHome(BuildContext context, HomeDashboardInfo dashboard) {
|
||||
return dashboardUi.Dashboard(tbContext, dashboardId: dashboard.dashboardId!.id!,
|
||||
fullscreen: false, home: true, hideToolbar: dashboard.hideDashboardToolbar);
|
||||
}
|
||||
|
||||
Widget _buildDefaultHome(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => _entitiesWidgetController.refresh(),
|
||||
child: ListView(
|
||||
children: _buildUserHome(context)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildUserHome(BuildContext context) {
|
||||
if (tbClient.isSystemAdmin()) {
|
||||
return _buildSysAdminHome(context);
|
||||
} else if (tbClient.isTenantAdmin()) {
|
||||
return _buildTenantAdminHome(context);
|
||||
} else {
|
||||
return _buildCustomerUserHome(context);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildSysAdminHome(BuildContext context) {
|
||||
return [Container(child: Text('TODO: Implement'))];
|
||||
}
|
||||
|
||||
List<Widget> _buildTenantAdminHome(BuildContext context) {
|
||||
return [
|
||||
AssetsWidget(tbContext, controller: _entitiesWidgetController),
|
||||
DevicesWidget(tbContext, controller: _entitiesWidgetController),
|
||||
DashboardsWidget(tbContext, controller: _entitiesWidgetController)
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildCustomerUserHome(BuildContext context) {
|
||||
return [
|
||||
AssetsWidget(tbContext, controller: _entitiesWidgetController),
|
||||
DevicesWidget(tbContext, controller: _entitiesWidgetController),
|
||||
DashboardsWidget(tbContext, controller: _entitiesWidgetController)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
20
lib/modules/home/home_routes.dart
Normal file
20
lib/modules/home/home_routes.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
class HomeRoutes extends TbRoutes {
|
||||
|
||||
late var homeHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return MainPage(tbContext, path: '/home');
|
||||
});
|
||||
|
||||
HomeRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/home", handler: homeHandler);
|
||||
}
|
||||
|
||||
}
|
||||
176
lib/modules/main/main_page.dart
Normal file
176
lib/modules/main/main_page.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/dashboards_page.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_page.dart';
|
||||
import 'package:thingsboard_app/modules/home/home_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class TbMainNavigationItem {
|
||||
final Widget page;
|
||||
final String title;
|
||||
final Icon icon;
|
||||
final String 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', '/tenants', '/more']),
|
||||
Authority.TENANT_ADMIN: Set.unmodifiable(['/home', '/devices', '/dashboards', '/more']),
|
||||
Authority.CUSTOMER_USER: Set.unmodifiable(['/home', '/devices', '/dashboards', '/more']),
|
||||
};
|
||||
|
||||
static bool isMainPageState(TbContext tbContext, String path) {
|
||||
if (tbContext.isAuthenticated) {
|
||||
return mainPageStateMap[tbContext.tbClient.getAuthUser()!.authority]!
|
||||
.contains(path);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static List<TbMainNavigationItem> getItems(TbContext tbContext) {
|
||||
if (tbContext.isAuthenticated) {
|
||||
List<TbMainNavigationItem> items = [
|
||||
TbMainNavigationItem(
|
||||
page: HomePage(tbContext),
|
||||
title: 'Home',
|
||||
icon: Icon(Icons.home),
|
||||
path: '/home'
|
||||
)
|
||||
];
|
||||
switch(tbContext.tbClient.getAuthUser()!.authority) {
|
||||
case Authority.SYS_ADMIN:
|
||||
items.add(TbMainNavigationItem(
|
||||
page: Scaffold(body: Center(child: Text('Tenants TODO'))),
|
||||
title: 'Tenants',
|
||||
icon: Icon(Icons.supervisor_account),
|
||||
path: '/tenants'
|
||||
));
|
||||
break;
|
||||
case Authority.TENANT_ADMIN:
|
||||
case Authority.CUSTOMER_USER:
|
||||
items.addAll([
|
||||
TbMainNavigationItem(
|
||||
page: DevicesPage(tbContext),
|
||||
title: 'Devices',
|
||||
icon: Icon(Icons.devices_other),
|
||||
path: '/devices'
|
||||
),
|
||||
TbMainNavigationItem(
|
||||
page: DashboardsPage(tbContext),
|
||||
title: 'Dashboards',
|
||||
icon: Icon(Icons.dashboard),
|
||||
path: '/dashboards'
|
||||
)
|
||||
]);
|
||||
break;
|
||||
case Authority.REFRESH_TOKEN:
|
||||
break;
|
||||
case Authority.ANONYMOUS:
|
||||
break;
|
||||
}
|
||||
items.add(TbMainNavigationItem(
|
||||
page: Scaffold(body: Center(child: Text('TODO'))),
|
||||
title: 'More',
|
||||
icon: Icon(Icons.menu),
|
||||
path: '/more'
|
||||
));
|
||||
return items;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MainPage extends TbPageWidget<MainPage, _MainPageState> {
|
||||
|
||||
final String _path;
|
||||
|
||||
MainPage(TbContext tbContext, {required String path}):
|
||||
_path = path, super(tbContext) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Theme.of(tbContext.currentState!.context).colorScheme.primary,
|
||||
systemNavigationBarIconBrightness: Brightness.dark
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
_MainPageState createState() => _MainPageState();
|
||||
|
||||
}
|
||||
|
||||
class _MainPageState extends TbPageState<MainPage, _MainPageState> with TbMainState {
|
||||
|
||||
late int _currentIndex;
|
||||
|
||||
late final List<TbMainNavigationItem> _tabItems;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabItems = TbMainNavigationItem.getItems(tbContext);
|
||||
_currentIndex = _indexFromPath(widget._path);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_currentIndex > 0) {
|
||||
setState(() => _currentIndex = 0);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
/* body: IndexedStack(
|
||||
index: _currentIndex,
|
||||
children: _tabItems.map((item) => item.page).toList(),
|
||||
),*/
|
||||
body: _tabItems.elementAt(_currentIndex).page,
|
||||
bottomNavigationBar: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
canvasColor: Theme.of(context).colorScheme.primary
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: Colors.white,
|
||||
unselectedItemColor: Colors.white.withAlpha(97),
|
||||
currentIndex: _currentIndex,
|
||||
onTap: (int index) => setState(() => _currentIndex = index),
|
||||
items: _tabItems.map((item) => BottomNavigationBarItem(
|
||||
icon: item.icon,
|
||||
label: item.title
|
||||
)).toList()
|
||||
)
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int _indexFromPath(String path) {
|
||||
return _tabItems.indexWhere((item) => item.path == path);
|
||||
}
|
||||
|
||||
@override
|
||||
bool canNavigate(String path) {
|
||||
return _indexFromPath(path) > -1;
|
||||
}
|
||||
|
||||
@override
|
||||
navigateToPath(String path) {
|
||||
int targetIndex = _indexFromPath(path);
|
||||
if (_currentIndex != targetIndex) {
|
||||
setState(() => _currentIndex = targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class ProfilePage extends TbPageWidget<ProfilePage, _ProfilePageState> {
|
||||
|
||||
@@ -16,9 +17,12 @@ class ProfilePage extends TbPageWidget<ProfilePage, _ProfilePageState> {
|
||||
|
||||
class _ProfilePageState extends TbPageState<ProfilePage, _ProfilePageState> {
|
||||
|
||||
late Future<User> userFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
userFuture = tbClient.getUserService().getUser(tbClient.getAuthUser()!.userId!);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -30,10 +34,20 @@ class _ProfilePageState extends TbPageState<ProfilePage, _ProfilePageState> {
|
||||
showProfile: false,
|
||||
showLogout: true,
|
||||
),
|
||||
body: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Center(child: const Text('TODO: Implement!'));
|
||||
}),
|
||||
body: FutureBuilder<User>(
|
||||
future: userFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var user = snapshot.data!;
|
||||
return ListTile(
|
||||
title: Text('${user.email}'),
|
||||
subtitle: Text('${user.firstName} ${user.lastName}'),
|
||||
);
|
||||
} else {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
21
lib/modules/profile/profile_routes.dart
Normal file
21
lib/modules/profile/profile_routes.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
|
||||
import 'profile_page.dart';
|
||||
|
||||
class ProfileRoutes extends TbRoutes {
|
||||
|
||||
late var profileHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return ProfilePage(tbContext);
|
||||
});
|
||||
|
||||
ProfileRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/profile", handler: profileHandler);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user