From ed654e46bd9d1a27e3cc7e81b40f94138470a488 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 14 May 2021 20:29:36 +0300 Subject: [PATCH] Home and Alarms page --- assets/images/dashboard-placeholder.png | Bin 0 -> 6504 bytes assets/images/device-profile-placeholder.png | Bin 0 -> 6662 bytes lib/config/routes/router.dart | 2 + lib/config/themes/tb_theme.dart | 3 +- lib/constants/assets_path.dart | 2 + lib/core/entity/entities_base.dart | 313 ++++++++++++++---- lib/core/entity/entities_grid.dart | 58 ++++ lib/core/entity/entities_list.dart | 49 +++ ..._widget.dart => entities_list_widget.dart} | 45 +-- lib/core/entity/entities_page.dart | 220 ------------ lib/core/entity/entity_grid_card.dart | 63 ++++ lib/core/entity/entity_list_card.dart | 73 ++++ lib/main.dart | 1 - lib/modules/alarm/alarm_routes.dart | 20 ++ lib/modules/alarm/alarms_base.dart | 170 ++++++++++ lib/modules/alarm/alarms_list.dart | 13 + lib/modules/alarm/alarms_page.dart | 31 ++ lib/modules/asset/assets_base.dart | 42 ++- lib/modules/asset/assets_list.dart | 13 + lib/modules/asset/assets_list_widget.dart | 15 + lib/modules/asset/assets_page.dart | 31 +- lib/modules/asset/assets_widget.dart | 16 - lib/modules/dashboard/dashboard.dart | 2 +- lib/modules/dashboard/dashboard_routes.dart | 4 +- lib/modules/dashboard/dashboards_base.dart | 99 +++++- lib/modules/dashboard/dashboards_grid.dart | 13 + lib/modules/dashboard/dashboards_list.dart | 13 + .../dashboard/dashboards_list_widget.dart | 15 + lib/modules/dashboard/dashboards_page.dart | 33 +- lib/modules/dashboard/dashboards_widget.dart | 15 - lib/modules/device/device_routes.dart | 1 - lib/modules/device/devices_base.dart | 43 ++- lib/modules/device/devices_list.dart | 12 + lib/modules/device/devices_list_widget.dart | 15 + lib/modules/device/devices_page.dart | 101 +----- lib/modules/device/devices_widget.dart | 15 - lib/modules/home/home_page.dart | 56 ++-- lib/modules/main/main_page.dart | 15 +- lib/widgets/tb_app_bar.dart | 2 +- pubspec.lock | 11 +- pubspec.yaml | 1 + 41 files changed, 1112 insertions(+), 534 deletions(-) create mode 100644 assets/images/dashboard-placeholder.png create mode 100644 assets/images/device-profile-placeholder.png create mode 100644 lib/core/entity/entities_grid.dart create mode 100644 lib/core/entity/entities_list.dart rename lib/core/entity/{entities_widget.dart => entities_list_widget.dart} (78%) delete mode 100644 lib/core/entity/entities_page.dart create mode 100644 lib/core/entity/entity_grid_card.dart create mode 100644 lib/core/entity/entity_list_card.dart create mode 100644 lib/modules/alarm/alarm_routes.dart create mode 100644 lib/modules/alarm/alarms_base.dart create mode 100644 lib/modules/alarm/alarms_list.dart create mode 100644 lib/modules/alarm/alarms_page.dart create mode 100644 lib/modules/asset/assets_list.dart create mode 100644 lib/modules/asset/assets_list_widget.dart delete mode 100644 lib/modules/asset/assets_widget.dart create mode 100644 lib/modules/dashboard/dashboards_grid.dart create mode 100644 lib/modules/dashboard/dashboards_list.dart create mode 100644 lib/modules/dashboard/dashboards_list_widget.dart delete mode 100644 lib/modules/dashboard/dashboards_widget.dart create mode 100644 lib/modules/device/devices_list.dart create mode 100644 lib/modules/device/devices_list_widget.dart delete mode 100644 lib/modules/device/devices_widget.dart diff --git a/assets/images/dashboard-placeholder.png b/assets/images/dashboard-placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..4f98c9c9c95e8ff9ab34c815462ae04d567935b9 GIT binary patch literal 6504 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 literal 0 HcmV?d00001 diff --git a/assets/images/device-profile-placeholder.png b/assets/images/device-profile-placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..36acff29577ecb143d648eebc6f4a2f39e585036 GIT binary patch literal 6662 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 S{L!}!;Es{GA^xU&-2VWYywVQ< literal 0 HcmV?d00001 diff --git a/lib/config/routes/router.dart b/lib/config/routes/router.dart index 7fc7c22..cff4eff 100644 --- a/lib/config/routes/router.dart +++ b/lib/config/routes/router.dart @@ -2,6 +2,7 @@ import 'package:fluro/fluro.dart'; import 'package:thingsboard_app/core/auth/auth_routes.dart'; import 'package:thingsboard_app/core/context/tb_context.dart'; import 'package:thingsboard_app/core/init/init_routes.dart'; +import 'package:thingsboard_app/modules/alarm/alarm_routes.dart'; import 'package:thingsboard_app/modules/asset/asset_routes.dart'; import 'package:thingsboard_app/modules/dashboard/dashboard_routes.dart'; import 'package:thingsboard_app/modules/device/device_routes.dart'; @@ -21,6 +22,7 @@ class ThingsboardAppRouter { ProfileRoutes(_tbContext).registerRoutes(); AssetRoutes(_tbContext).registerRoutes(); DeviceRoutes(_tbContext).registerRoutes(); + AlarmRoutes(_tbContext).registerRoutes(); DashboardRoutes(_tbContext).registerRoutes(); } diff --git a/lib/config/themes/tb_theme.dart b/lib/config/themes/tb_theme.dart index 7a396fa..7a9daae 100644 --- a/lib/config/themes/tb_theme.dart +++ b/lib/config/themes/tb_theme.dart @@ -36,7 +36,8 @@ const tbDarkMatIndigo = MaterialColor( ThemeData tbTheme = ThemeData( primarySwatch: tbMatIndigo, - accentColor: Colors.deepOrange + accentColor: Colors.deepOrange, + scaffoldBackgroundColor: Color(0xFFF0F4F9) ); ThemeData tbDarkTheme = ThemeData( diff --git a/lib/constants/assets_path.dart b/lib/constants/assets_path.dart index f0750ae..cba6835 100644 --- a/lib/constants/assets_path.dart +++ b/lib/constants/assets_path.dart @@ -1,4 +1,6 @@ abstract class ThingsboardImage { static final thingsBoardLogoBlue = 'assets/images/thingsboard_logo_blue.svg'; static final thingsboard = 'assets/images/thingsboard.png'; + static final dashboardPlaceholder = 'assets/images/dashboard-placeholder.png'; + static final deviceProfilePlaceholder = 'assets/images/device-profile-placeholder.png'; } diff --git a/lib/core/entity/entities_base.dart b/lib/core/entity/entities_base.dart index 3d9d5fa..5434084 100644 --- a/lib/core/entity/entities_base.dart +++ b/lib/core/entity/entities_base.dart @@ -1,13 +1,22 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:intl/intl.dart'; import 'package:thingsboard_app/core/context/tb_context.dart'; +import 'package:thingsboard_app/core/context/tb_context_widget.dart'; import 'package:thingsboard_client/thingsboard_client.dart'; -typedef EntityDetailsFunction = Function(T entity); -typedef EntityCardWidgetBuilder = Widget Function(BuildContext context, T entity, bool briefView); +typedef EntityTapFunction = Function(T entity); +typedef EntityCardWidgetBuilder = Widget Function(BuildContext context, T entity); -mixin EntitiesBase on HasTbContext { +class EntityCardSettings { + bool dropShadow; + EntityCardSettings({this.dropShadow = true}); +} + +mixin EntitiesBase on HasTbContext { final entityDateFormat = DateFormat('yyyy-MM-dd'); @@ -15,75 +24,255 @@ mixin EntitiesBase on HasTbContext { String get noItemsFoundText; - Future> fetchEntities(PageLink pageLink); + Future> fetchEntities(P pageKey); - Widget buildEntityCard(BuildContext context, T entity, bool briefView); + Future onRefresh() => Future.value(); - void onEntityDetails(T entity); + Widget? buildHeading(BuildContext context) => null; + + Widget buildEntityListCard(BuildContext context, T entity) { + return Text('Not implemented!'); + } + + Widget buildEntityListWidgetCard(BuildContext context, T entity) { + return Text('Not implemented!'); + } + + Widget buildEntityGridCard(BuildContext context, T entity) { + return Text('Not implemented!'); + } + + P createFirstKey({int pageSize = 10}) => throw UnimplementedError('Not implemented'); + + P nextPageKey(P pageKey) => throw UnimplementedError('Not implemented'); + + EntityCardSettings entityListCardSettings(T entity) => EntityCardSettings(); + + EntityCardSettings entityGridCardSettings(T entity) => EntityCardSettings(); + + void onEntityTap(T entity); } -class EntityCard extends StatelessWidget { - final bool _briefView; - final T _entity; - final EntityDetailsFunction? _onDetails; - final EntityCardWidgetBuilder _entityCardWidgetBuilder; +mixin EntitiesBaseWithPageLink on EntitiesBase { - EntityCard(T entity, {EntityDetailsFunction? onDetails, - required EntityCardWidgetBuilder entityCardWidgetBuilder, - required bool briefView}): - this._entity = entity, - this._onDetails = onDetails, - this._entityCardWidgetBuilder = entityCardWidgetBuilder, - this._briefView = briefView; + @override + PageLink createFirstKey({int pageSize = 10}) => PageLink(pageSize, 0, null, SortOrder('createdTime', Direction.DESC)); + + @override + PageLink nextPageKey(PageLink pageKey) => pageKey.nextPageLink(); + +} + +mixin EntitiesBaseWithTimePageLink on EntitiesBase { + + @override + TimePageLink createFirstKey({int pageSize = 10}) => TimePageLink(pageSize, 0, null, SortOrder('createdTime', Direction.DESC)); + + @override + TimePageLink nextPageKey(TimePageLink pageKey) => pageKey.nextPageLink(); + +} + +abstract class BaseEntitiesPageLinkWidget extends BaseEntitiesWidget with EntitiesBaseWithPageLink { + BaseEntitiesPageLinkWidget(TbContext tbContext): super(tbContext); +} + +abstract class BaseEntitiesTimePageLinkWidget extends BaseEntitiesWidget with EntitiesBaseWithTimePageLink { + BaseEntitiesTimePageLinkWidget(TbContext tbContext): super(tbContext); +} + +abstract class BaseEntitiesWidget extends TbContextWidget, BaseEntitiesState> with EntitiesBase { + + BaseEntitiesWidget(TbContext tbContext): super(tbContext); + +} + +abstract class BaseEntitiesState extends TbContextState, BaseEntitiesState> { + + late final PagingController pagingController; + Completer? _refreshCompleter; + + @override + void initState() { + super.initState(); + pagingController = PagingController(firstPageKey: widget.createFirstKey()); + pagingController.addPageRequestListener((pageKey) { + _fetchPage(pageKey); + }); + } + + @override + void dispose() { + pagingController.dispose(); + super.dispose(); + } + + bool _dataLoading = false; + bool _scheduleRefresh = false; + + Future _refresh() { + if (_refreshCompleter == null) { + _refreshCompleter = Completer(); + } + if (_dataLoading) { + _scheduleRefresh = true; + } else { + _refreshPagingController(); + } + return _refreshCompleter!.future; + } + + void _refreshPagingController() { + _fetchPage(widget.createFirstKey(), refresh: true); + } + + Future _fetchPage(P pageKey, {bool refresh = false}) async { + if (mounted) { + _dataLoading = true; + try { + hideNotification(); + final pageData = await widget.fetchEntities(pageKey); + final isLastPage = !pageData.hasNext; + if (refresh) { + var state = pagingController.value; + if (state.itemList != null) { + state.itemList!.clear(); + } + } + if (isLastPage) { + pagingController.appendLastPage(pageData.data); + } else { + final nextPageKey = widget.nextPageKey(pageKey); + pagingController.appendPage(pageData.data, nextPageKey); + } + } catch (error) { + if (mounted) { + pagingController.error = error; + } + } finally { + _dataLoading = false; + if (refresh) { + _refreshCompleter!.complete(); + _refreshCompleter = null; + } + if (_scheduleRefresh) { + _scheduleRefresh = false; + if (mounted) { + _refreshPagingController(); + } + } + } + } + } @override Widget build(BuildContext context) { - return - GestureDetector( - behavior: HitTestBehavior.opaque, - child: - Container( - height: 64, - margin: _briefView ? EdgeInsets.only(right: 8) : EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Card( - margin: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(_briefView ? 4 : 6), - ), - elevation: 0, - child: Padding( - padding: const EdgeInsets.all(2), - child: _entityCardWidgetBuilder(context, _entity, _briefView) - ) + return RefreshIndicator( + onRefresh: () => Future.wait([ + widget.onRefresh(), + _refresh() + ]), + child: pagedViewBuilder(context) + ); + } + + Widget pagedViewBuilder(BuildContext context); + + Widget firstPageProgressIndicatorBuilder(BuildContext context) { + return Stack( children: [ + Positioned( + top: 20, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [RefreshProgressIndicator()], + ), + ) + ]); + } + + Widget newPageProgressIndicatorBuilder(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + top: 16, + bottom: 16, + ), + child: Center(child: RefreshProgressIndicator()), + ); + } + + Widget noItemsFoundIndicatorBuilder(BuildContext context) { + return FirstPageExceptionIndicator( + title: widget.noItemsFoundText, + message: 'The list is currently empty.', + onTryAgain: () => pagingController.refresh(), + ); + } + +} + +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, ), - decoration: _briefView ? BoxDecoration( - border: Border.all( - color: Color(0xFFDEDEDE), - style: BorderStyle.solid, - width: 1 + 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, + ), + ), ), - borderRadius: BorderRadius.circular(4) - ) : BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(25), - blurRadius: 10.0, - offset: Offset(0, 4) - ), - BoxShadow( - color: Colors.black.withAlpha(18), - blurRadius: 30.0, - offset: Offset(0, 10) - ), - ], - ), - ), - onTap: () { - if (_onDetails != null) { - _onDetails!(_entity); - } - } - ); + ), + ], + ), + ), + ); } } diff --git a/lib/core/entity/entities_grid.dart b/lib/core/entity/entities_grid.dart new file mode 100644 index 0000000..1ca5bb3 --- /dev/null +++ b/lib/core/entity/entities_grid.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; + +import 'entities_base.dart'; +import 'entity_grid_card.dart'; + +mixin EntitiesGridStateBase on StatefulWidget { + + @override + _EntitiesGridState createState() => _EntitiesGridState(); + +} + +class _EntitiesGridState extends BaseEntitiesState { + + @override + Widget pagedViewBuilder(BuildContext context) { + var heading = widget.buildHeading(context); + List slivers = []; + if (heading != null) { + slivers.add(SliverPadding( + padding: EdgeInsets.fromLTRB(16, 16, 16, 0), + sliver: SliverToBoxAdapter( + child: heading + ))); + } + slivers.add(SliverPadding( + padding: EdgeInsets.all(16), + sliver: PagedSliverGrid( + showNewPageProgressIndicatorAsGridChild: false, + showNewPageErrorIndicatorAsGridChild: false, + showNoMoreItemsIndicatorAsGridChild: false, + pagingController: pagingController, + // padding: EdgeInsets.all(16), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + childAspectRatio: 156 / 150, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + crossAxisCount: 2, + ), + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) => EntityGridCard( + item, + entityCardWidgetBuilder: widget.buildEntityGridCard, + onEntityTap: widget.onEntityTap, + settings: widget.entityGridCardSettings(item), + ), + firstPageProgressIndicatorBuilder: firstPageProgressIndicatorBuilder, + newPageProgressIndicatorBuilder: newPageProgressIndicatorBuilder, + noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder + ) + ))); + return CustomScrollView( + slivers: slivers + ); + } +} diff --git a/lib/core/entity/entities_list.dart b/lib/core/entity/entities_list.dart new file mode 100644 index 0000000..764fc26 --- /dev/null +++ b/lib/core/entity/entities_list.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:thingsboard_app/core/entity/entities_base.dart'; + +import 'entity_list_card.dart'; + +mixin EntitiesListStateBase on StatefulWidget { + + @override + _EntitiesListState createState() => _EntitiesListState(); + +} + +class _EntitiesListState extends BaseEntitiesState { + + @override + Widget pagedViewBuilder(BuildContext context) { + var heading = widget.buildHeading(context); + List slivers = []; + if (heading != null) { + slivers.add(SliverPadding( + padding: EdgeInsets.fromLTRB(16, 16, 16, 0), + sliver: SliverToBoxAdapter( + child: heading + ))); + } + slivers.add(SliverPadding( + padding: EdgeInsets.all(16), + sliver: PagedSliverList.separated( + pagingController: pagingController, + separatorBuilder: (context, index) => SizedBox(height: 8), + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) => EntityListCard( + item, + entityCardWidgetBuilder: widget.buildEntityListCard, + onEntityTap: widget.onEntityTap, + settings: widget.entityListCardSettings(item), + ), + firstPageProgressIndicatorBuilder: firstPageProgressIndicatorBuilder, + newPageProgressIndicatorBuilder: newPageProgressIndicatorBuilder, + noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder + ) + ))); + return CustomScrollView( + slivers: slivers + ); + } +} diff --git a/lib/core/entity/entities_widget.dart b/lib/core/entity/entities_list_widget.dart similarity index 78% rename from lib/core/entity/entities_widget.dart rename to lib/core/entity/entities_list_widget.dart index fbaaf65..a45d3a4 100644 --- a/lib/core/entity/entities_widget.dart +++ b/lib/core/entity/entities_list_widget.dart @@ -3,22 +3,23 @@ import 'dart:async'; import 'package:fading_edge_scrollview/fading_edge_scrollview.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:intl/intl.dart'; import 'package:thingsboard_app/core/context/tb_context.dart'; import 'package:thingsboard_app/core/context/tb_context_widget.dart'; import 'package:thingsboard_app/core/entity/entities_base.dart'; import 'package:thingsboard_client/thingsboard_client.dart'; -class EntitiesWidgetController { +import 'entity_list_card.dart'; - final List<_EntitiesWidgetState> states = []; +class EntitiesListWidgetController { - void _registerEntitiesWidgetState(_EntitiesWidgetState entitiesWidgetState) { - states.add(entitiesWidgetState); + final List<_EntitiesListWidgetState> states = []; + + void _registerEntitiesWidgetState(_EntitiesListWidgetState entitiesListWidgetState) { + states.add(entitiesListWidgetState); } - void _unregisterEntitiesWidgetState(_EntitiesWidgetState entitiesWidgetState) { - states.remove(entitiesWidgetState); + void _unregisterEntitiesWidgetState(_EntitiesListWidgetState entitiesListWidgetState) { + states.remove(entitiesListWidgetState); } Future refresh() { @@ -31,29 +32,32 @@ class EntitiesWidgetController { } -abstract class EntitiesWidget extends TbContextWidget, _EntitiesWidgetState> with EntitiesBase { +abstract class EntitiesListPageLinkWidget extends EntitiesListWidget with EntitiesBaseWithPageLink { + EntitiesListPageLinkWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller); +} - final entityDateFormat = DateFormat('yyyy-MM-dd'); - final EntitiesWidgetController? _controller; +abstract class EntitiesListWidget extends TbContextWidget, _EntitiesListWidgetState> with EntitiesBase { - EntitiesWidget(TbContext tbContext, {EntitiesWidgetController? controller}): + final EntitiesListWidgetController? _controller; + + EntitiesListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): _controller = controller, super(tbContext); @override - _EntitiesWidgetState createState() => _EntitiesWidgetState(_controller); + _EntitiesListWidgetState createState() => _EntitiesListWidgetState(_controller); void onViewAll(); } -class _EntitiesWidgetState extends TbContextState, _EntitiesWidgetState> { +class _EntitiesListWidgetState extends TbContextState, _EntitiesListWidgetState> { - final EntitiesWidgetController? _controller; + final EntitiesListWidgetController? _controller; final StreamController?> _entitiesStreamController = StreamController.broadcast(); - _EntitiesWidgetState(EntitiesWidgetController? controller): + _EntitiesListWidgetState(EntitiesListWidgetController? controller): _controller = controller; @override @@ -76,7 +80,7 @@ class _EntitiesWidgetState extends TbContextState _refresh() { _entitiesStreamController.add(null); - var entitiesFuture = widget.fetchEntities(PageLink(5, 0, null, SortOrder('createdTime', Direction.DESC))); + var entitiesFuture = widget.fetchEntities(widget.createFirstKey(pageSize: 5)); entitiesFuture.then((value) => _entitiesStreamController.add(value)); return entitiesFuture; } @@ -204,11 +208,12 @@ class _EntitiesWidgetState extends TbContextState EntityCard( + children: entities.map((entity) => EntityListCard( entity, - entityCardWidgetBuilder: widget.buildEntityCard, - onDetails: widget.onEntityDetails, - briefView: true + entityCardWidgetBuilder: widget.buildEntityListWidgetCard, + onEntityTap: widget.onEntityTap, + settings: widget.entityListCardSettings(entity), + listWidgetCard: true )).toList() )); } diff --git a/lib/core/entity/entities_page.dart b/lib/core/entity/entities_page.dart deleted file mode 100644 index f2356b1..0000000 --- a/lib/core/entity/entities_page.dart +++ /dev/null @@ -1,220 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:thingsboard_app/core/context/tb_context.dart'; -import 'package:thingsboard_app/core/context/tb_context_widget.dart'; -import 'package:thingsboard_app/core/entity/entities_base.dart'; -import 'package:thingsboard_app/widgets/tb_app_bar.dart'; -import 'package:thingsboard_client/thingsboard_client.dart'; - -abstract class EntitiesPage extends TbContextWidget, _EntitiesPageState> with EntitiesBase { - - EntitiesPage(TbContext tbContext): super(tbContext); - - String get searchHint; - - String get noMoreItemsText; - - @override - _EntitiesPageState createState() => _EntitiesPageState(); - -} - -class _EntitiesPageState extends TbContextState, _EntitiesPageState> { - - final _searchModeNotifier = ValueNotifier(false); - - final PagingController _pagingController = PagingController(firstPageKey: PageLink(10, 0, null, SortOrder('createdTime', Direction.DESC))); - - @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 _fetchPage(PageLink pageKey) async { - if (mounted) { - _dataLoading = true; - try { - hideNotification(); - final pageData = await widget.fetchEntities(pageKey); - final isLastPage = !pageData.hasNext; - if (isLastPage) { - _pagingController.appendLastPage(pageData.data); - } else { - final nextPageKey = pageKey.nextPageLink(); - _pagingController.appendPage(pageData.data, nextPageKey); - } - } catch (error) { - if (mounted) { - _pagingController.error = error; - } - } finally { - _dataLoading = false; - if (_scheduleRefresh) { - _scheduleRefresh = false; - if (mounted) { - _pagingController.refresh(); - } - } - } - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: TbAppBar( - tbContext, - title: Text(widget.title), - searchModeNotifier: _searchModeNotifier, - searchHint: widget.searchHint, - onSearch: (String searchText) { - _pagingController.firstPageKey.textSearch = searchText; - _pagingController.firstPageKey.page = 0; - _refresh(); - }, - ), - body: RefreshIndicator( - onRefresh: () => Future.sync( - () => _refresh(), - ), - child: PagedListView( - pagingController: _pagingController, - padding: EdgeInsets.all(0), - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) => EntityCard( - item, - entityCardWidgetBuilder: widget.buildEntityCard, - onDetails: widget.onEntityDetails, - briefView: false - ), - firstPageProgressIndicatorBuilder: (context) { - return Stack( children: [ - Positioned( - top: 20, - left: 0, - right: 0, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [RefreshProgressIndicator()], - ), - ) - ]); - }, - newPageProgressIndicatorBuilder: (context) { - return Padding( - padding: const EdgeInsets.only( - top: 16, - bottom: 16, - ), - child: Center(child: RefreshProgressIndicator()), - ); - }, - noItemsFoundIndicatorBuilder: (context) => FirstPageExceptionIndicator( - title: widget.noItemsFoundText, - message: 'The list is currently empty.', - onTryAgain: () => _refresh(), - ) - ) - ) - ) - /* bottomNavigationBar: BottomAppBar( - child: Row( - children: [ - IconButton(icon: Icon(Icons.refresh), onPressed: () { - _refresh(); - }), - Spacer(), - IconButton(icon: Icon(Icons.search), onPressed: () { - _searchModeNotifier.value = true; - }) - ] - ) - ) */ - ); - } -} - -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, - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/core/entity/entity_grid_card.dart b/lib/core/entity/entity_grid_card.dart new file mode 100644 index 0000000..3cd140f --- /dev/null +++ b/lib/core/entity/entity_grid_card.dart @@ -0,0 +1,63 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:thingsboard_client/thingsboard_client.dart'; + +import 'entities_base.dart'; + +class EntityGridCard extends StatelessWidget { + final T _entity; + final EntityTapFunction? _onEntityTap; + final EntityCardWidgetBuilder _entityCardWidgetBuilder; + final EntityCardSettings _settings; + + EntityGridCard(T entity, {EntityTapFunction? onEntityTap, + required EntityCardWidgetBuilder entityCardWidgetBuilder, + required EntityCardSettings settings}): + this._entity = entity, + this._onEntityTap = onEntityTap, + this._entityCardWidgetBuilder = entityCardWidgetBuilder, + this._settings = settings; + + @override + Widget build(BuildContext context) { + return + GestureDetector( + behavior: HitTestBehavior.opaque, + child: + Container( + child: Card( + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 0, + child: Padding( + padding: const EdgeInsets.all(4), + child: _entityCardWidgetBuilder(context, _entity) + ) + ), + decoration: _settings.dropShadow ? BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(25), + blurRadius: 10.0, + offset: Offset(0, 4) + ), + BoxShadow( + color: Colors.black.withAlpha(18), + blurRadius: 30.0, + offset: Offset(0, 10) + ), + ], + ) : null, + ), + onTap: () { + if (_onEntityTap != null) { + _onEntityTap!(_entity); + } + } + ); + } +} + diff --git a/lib/core/entity/entity_list_card.dart b/lib/core/entity/entity_list_card.dart new file mode 100644 index 0000000..f810716 --- /dev/null +++ b/lib/core/entity/entity_list_card.dart @@ -0,0 +1,73 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +import 'entities_base.dart'; + +class EntityListCard extends StatelessWidget { + final bool _listWidgetCard; + final T _entity; + final EntityTapFunction? _onEntityTap; + final EntityCardWidgetBuilder _entityCardWidgetBuilder; + final EntityCardSettings _settings; + + EntityListCard(T entity, {EntityTapFunction? onEntityTap, + required EntityCardWidgetBuilder entityCardWidgetBuilder, + required EntityCardSettings settings, + bool listWidgetCard = false}): + this._entity = entity, + this._onEntityTap = onEntityTap, + this._entityCardWidgetBuilder = entityCardWidgetBuilder, + this._settings = settings, + this._listWidgetCard = listWidgetCard; + + @override + Widget build(BuildContext context) { + return + GestureDetector( + behavior: HitTestBehavior.opaque, + child: + Container( + margin: _listWidgetCard ? EdgeInsets.only(right: 8) : EdgeInsets.zero, + child: Card( + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(_listWidgetCard ? 4 : 6), + ), + elevation: 0, + child: Padding( + padding: const EdgeInsets.all(2), + child: _entityCardWidgetBuilder(context, _entity) + ) + ), + decoration: _listWidgetCard ? BoxDecoration( + border: Border.all( + color: Color(0xFFDEDEDE), + style: BorderStyle.solid, + width: 1 + ), + borderRadius: BorderRadius.circular(4) + ) : BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(25), + blurRadius: 10.0, + offset: Offset(0, 4) + ), + BoxShadow( + color: Colors.black.withAlpha(18), + blurRadius: 30.0, + offset: Offset(0, 10) + ), + ], + ), + ), + onTap: () { + if (_onEntityTap != null) { + _onEntityTap!(_entity); + } + } + ); + } +} + diff --git a/lib/main.dart b/lib/main.dart index acf988c..127633e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:thingsboard_app/config/routes/router.dart'; import 'config/themes/tb_theme.dart'; diff --git a/lib/modules/alarm/alarm_routes.dart b/lib/modules/alarm/alarm_routes.dart new file mode 100644 index 0000000..cbc0ac3 --- /dev/null +++ b/lib/modules/alarm/alarm_routes.dart @@ -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 AlarmRoutes extends TbRoutes { + + late var alarmsHandler = Handler(handlerFunc: (BuildContext? context, Map params) { + return MainPage(tbContext, path: '/alarms'); + }); + + AlarmRoutes(TbContext tbContext) : super(tbContext); + + @override + void doRegisterRoutes(router) { + router.define("/alarms", handler: alarmsHandler); + } + +} diff --git a/lib/modules/alarm/alarms_base.dart b/lib/modules/alarm/alarms_base.dart new file mode 100644 index 0000000..d4bfeb1 --- /dev/null +++ b/lib/modules/alarm/alarms_base.dart @@ -0,0 +1,170 @@ +import 'package:auto_size_text/auto_size_text.dart'; +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'; + + +const Map alarmSeverityColors = { + AlarmSeverity.CRITICAL: Color(0xFFFF0000), + AlarmSeverity.MAJOR: Color(0xFFFFA500), + AlarmSeverity.MINOR: Color(0xFFFFCA3D), + AlarmSeverity.WARNING: Color(0xFFABAB00), + AlarmSeverity.INDETERMINATE: Color(0xFF00FF00), +}; + +const Map alarmSeverityTranslations = { + AlarmSeverity.CRITICAL: 'Critical', + AlarmSeverity.MAJOR: 'Major', + AlarmSeverity.MINOR: 'Minor', + AlarmSeverity.WARNING: 'Warning', + AlarmSeverity.INDETERMINATE: 'Indeterminate', +}; + +const Map alarmStatusTranslations = { + AlarmStatus.ACTIVE_ACK: 'Active Acknowledged', + AlarmStatus.ACTIVE_UNACK: 'Active Unacknowledged', + AlarmStatus.CLEARED_ACK: 'Cleared Acknowledged', + AlarmStatus.CLEARED_UNACK: 'Cleared Unacknowledged', +}; + +mixin AlarmsBase on EntitiesBase { + + @override + String get title => 'Alarms'; + + @override + String get noItemsFoundText => 'No alarms found'; + + @override + AlarmQuery createFirstKey({int pageSize = 10}) => AlarmQuery(TimePageLink(pageSize, 0, null, SortOrder('createdTime', Direction.DESC)), fetchOriginator: true); + + @override + AlarmQuery nextPageKey(AlarmQuery query) { + query.pageLink = query.pageLink.nextPageLink(); + return query; + } + + @override + Future> fetchEntities(AlarmQuery query) { + return tbClient.getAlarmService().getAllAlarms(query); + } + + @override + void onEntityTap(AlarmInfo alarm) { + showErrorNotification('Balalai: alarm tap not implemented!'); + } + + @override + Widget buildEntityListCard(BuildContext context, AlarmInfo alarm) { + return _buildEntityListCard(context, alarm); + } + + Widget _buildEntityListCard(BuildContext context, AlarmInfo alarm) { + return Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + fit: FlexFit.tight, + child: + Padding( + padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + fit: FlexFit.tight, + child: AutoSizeText(alarm.type, + maxLines: 2, + minFontSize: 8, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Color(0xFF282828), + fontWeight: FontWeight.w500, + fontSize: 14, + height: 20 / 14) + ) + ), + Text(alarmSeverityTranslations[alarm.severity]!, + style: TextStyle( + color: alarmSeverityColors[alarm.severity]!, + fontWeight: FontWeight.w500, + 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(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)), + style: TextStyle( + color: Color(0xFFAFAFAF), + fontWeight: FontWeight.normal, + fontSize: 12, + height: 16 / 12) + ) + ] + ), + SizedBox(height: 22), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + fit: FlexFit.tight, + child: Text(alarmStatusTranslations[alarm.status]!, + style: TextStyle( + color: Color(0xFF282828), + fontWeight: FontWeight.normal, + fontSize: 14, + height: 20 / 14) + ) + ), + Row( + children: [ + if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status)) + CircleAvatar( + radius: 24, + backgroundColor: Color(0xffF0F4F9), + child: IconButton(icon: Icon(Icons.done), padding: EdgeInsets.all(6.0), onPressed: () => {}) + ), + if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status)) + Row( + children: [ + SizedBox(width: 4), + CircleAvatar( + radius: 24, + backgroundColor: Color(0xffF0F4F9), + child: IconButton(icon: Icon(Icons.clear), padding: EdgeInsets.all(6.0), onPressed: () => {}) + ) + ] + ) + ], + ) + ], + ) + + ] + ) + ) + + ) + ] + ); + } +} + diff --git a/lib/modules/alarm/alarms_list.dart b/lib/modules/alarm/alarms_list.dart new file mode 100644 index 0000000..d0bd7d1 --- /dev/null +++ b/lib/modules/alarm/alarms_list.dart @@ -0,0 +1,13 @@ +import 'package:thingsboard_app/core/context/tb_context.dart'; +import 'package:thingsboard_app/core/entity/entities_base.dart'; +import 'package:thingsboard_app/core/entity/entities_list.dart'; +import 'package:thingsboard_client/thingsboard_client.dart'; + +import 'alarms_base.dart'; + +class AlarmsList extends BaseEntitiesWidget with AlarmsBase, EntitiesListStateBase { + + AlarmsList(TbContext tbContext) : super(tbContext); + +} + diff --git a/lib/modules/alarm/alarms_page.dart b/lib/modules/alarm/alarms_page.dart new file mode 100644 index 0000000..af58f4e --- /dev/null +++ b/lib/modules/alarm/alarms_page.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:thingsboard_app/core/context/tb_context.dart'; +import 'package:thingsboard_app/core/context/tb_context_widget.dart'; +import 'package:thingsboard_app/widgets/tb_app_bar.dart'; + +import 'alarms_list.dart'; + +class AlarmsPage extends TbContextWidget { + + AlarmsPage(TbContext tbContext) : super(tbContext); + + @override + _AlarmsPageState createState() => _AlarmsPageState(); + +} + +class _AlarmsPageState extends TbContextState { + + @override + Widget build(BuildContext context) { + var alarmsList = AlarmsList(tbContext); + return Scaffold( + appBar: TbAppBar( + tbContext, + title: Text(alarmsList.title) + ), + body: alarmsList + ); + } + +} diff --git a/lib/modules/asset/assets_base.dart b/lib/modules/asset/assets_base.dart index 0aaf4fc..beea529 100644 --- a/lib/modules/asset/assets_base.dart +++ b/lib/modules/asset/assets_base.dart @@ -3,7 +3,7 @@ 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 { +mixin AssetsBase on EntitiesBaseWithPageLink { @override String get title => 'Assets'; @@ -21,20 +21,40 @@ mixin AssetsBase on EntitiesBase { } @override - Widget buildEntityCard(BuildContext context, AssetInfo asset, bool briefView) { + void onEntityTap(AssetInfo asset) { + navigateTo('/asset/${asset.id!.id}'); + } + + @override + Widget buildEntityListCard(BuildContext context, AssetInfo asset) { + return _buildEntityListCard(context, asset, false); + } + + @override + Widget buildEntityListWidgetCard(BuildContext context, AssetInfo asset) { + return _buildEntityListCard(context, asset, true); + } + + @override + Widget buildEntityGridCard(BuildContext context, AssetInfo asset) { + return Text(asset.name); + } + + + Widget _buildEntityListCard(BuildContext context, AssetInfo asset, bool listWidgetCard) { return Row( - mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max, + mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max, children: [ Flexible( - fit: briefView ? FlexFit.loose : FlexFit.tight, + fit: listWidgetCard ? FlexFit.loose : FlexFit.tight, child: Container( - padding: EdgeInsets.symmetric(vertical: briefView ? 9 : 10, horizontal: 16), + padding: EdgeInsets.symmetric(vertical: listWidgetCard ? 9 : 10, horizontal: 16), child: Row( - mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max, + mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max, children: [ Flexible( - fit: briefView ? FlexFit.loose : FlexFit.tight, + fit: listWidgetCard ? FlexFit.loose : FlexFit.tight, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -60,7 +80,7 @@ mixin AssetsBase on EntitiesBase { ], ) ), - (!briefView ? Column( + (!listWidgetCard ? Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(asset.createdTime!)), @@ -80,10 +100,4 @@ mixin AssetsBase on EntitiesBase { ] ); } - - @override - void onEntityDetails(AssetInfo asset) { - navigateTo('/asset/${asset.id!.id}'); - } - } diff --git a/lib/modules/asset/assets_list.dart b/lib/modules/asset/assets_list.dart new file mode 100644 index 0000000..190b95d --- /dev/null +++ b/lib/modules/asset/assets_list.dart @@ -0,0 +1,13 @@ +import 'package:thingsboard_app/core/context/tb_context.dart'; +import 'package:thingsboard_app/core/entity/entities_base.dart'; +import 'package:thingsboard_app/core/entity/entities_list.dart'; +import 'package:thingsboard_client/thingsboard_client.dart'; + +import 'assets_base.dart'; + +class AssetsList extends BaseEntitiesPageLinkWidget with AssetsBase, EntitiesListStateBase { + + AssetsList(TbContext tbContext) : super(tbContext); + +} + diff --git a/lib/modules/asset/assets_list_widget.dart b/lib/modules/asset/assets_list_widget.dart new file mode 100644 index 0000000..85dcbf4 --- /dev/null +++ b/lib/modules/asset/assets_list_widget.dart @@ -0,0 +1,15 @@ +import 'package:thingsboard_app/core/context/tb_context.dart'; +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 with AssetsBase { + + AssetsListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller); + + @override + void onViewAll() { + navigateTo('/assets'); + } + +} diff --git a/lib/modules/asset/assets_page.dart b/lib/modules/asset/assets_page.dart index 5d763b7..ce254bd 100644 --- a/lib/modules/asset/assets_page.dart +++ b/lib/modules/asset/assets_page.dart @@ -1,16 +1,31 @@ +import 'package:flutter/material.dart'; 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'; +import 'package:thingsboard_app/core/context/tb_context_widget.dart'; +import 'package:thingsboard_app/widgets/tb_app_bar.dart'; -class AssetsPage extends EntitiesPage with AssetsBase { +import 'assets_list.dart'; + +class AssetsPage extends TbPageWidget { AssetsPage(TbContext tbContext) : super(tbContext); @override - String get noMoreItemsText => 'No more assets'; - - @override - String get searchHint => 'Search assets'; + _AssetsPageState createState() => _AssetsPageState(); + +} + +class _AssetsPageState extends TbPageState { + + @override + Widget build(BuildContext context) { + var assetsList = AssetsList(tbContext); + return Scaffold( + appBar: TbAppBar( + tbContext, + title: Text(assetsList.title) + ), + body: assetsList + ); + } } diff --git a/lib/modules/asset/assets_widget.dart b/lib/modules/asset/assets_widget.dart deleted file mode 100644 index d0f93f7..0000000 --- a/lib/modules/asset/assets_widget.dart +++ /dev/null @@ -1,16 +0,0 @@ -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 with AssetsBase { - - AssetsWidget(TbContext tbContext, {EntitiesWidgetController? controller}): super(tbContext, controller: controller); - - @override - void onViewAll() { - navigateTo('/assets'); - } - -} diff --git a/lib/modules/dashboard/dashboard.dart b/lib/modules/dashboard/dashboard.dart index 22e3652..a54a44f 100644 --- a/lib/modules/dashboard/dashboard.dart +++ b/lib/modules/dashboard/dashboard.dart @@ -209,7 +209,7 @@ class _DashboardState extends TbContextState { return Container( decoration: BoxDecoration(color: Colors.white), child: Center( - child: CircularProgressIndicator() + child: RefreshProgressIndicator() ), ); } diff --git a/lib/modules/dashboard/dashboard_routes.dart b/lib/modules/dashboard/dashboard_routes.dart index 91718d7..5a961a5 100644 --- a/lib/modules/dashboard/dashboard_routes.dart +++ b/lib/modules/dashboard/dashboard_routes.dart @@ -2,14 +2,14 @@ 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 'package:thingsboard_app/modules/dashboard/dashboards_page.dart'; import 'dashboard_page.dart'; class DashboardRoutes extends TbRoutes { late var dashboardsHandler = Handler(handlerFunc: (BuildContext? context, Map params) { - return MainPage(tbContext, path: '/dashboards'); + return DashboardsPage(tbContext); }); late var dashboardDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { diff --git a/lib/modules/dashboard/dashboards_base.dart b/lib/modules/dashboard/dashboards_base.dart index dcee4d9..d26fb89 100644 --- a/lib/modules/dashboard/dashboards_base.dart +++ b/lib/modules/dashboard/dashboards_base.dart @@ -1,9 +1,11 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:thingsboard_app/constants/assets_path.dart'; import 'package:thingsboard_app/core/entity/entities_base.dart'; import 'package:thingsboard_client/thingsboard_client.dart'; -mixin DashboardsBase on EntitiesBase { +mixin DashboardsBase on EntitiesBaseWithPageLink { @override String get title => 'Dashboards'; @@ -21,20 +23,95 @@ mixin DashboardsBase on EntitiesBase { } @override - Widget buildEntityCard(BuildContext context, DashboardInfo dashboard, bool briefView) { + void onEntityTap(DashboardInfo dashboard) { + navigateTo('/dashboard/${dashboard.id!.id}?title=${dashboard.title}'); + } + + @override + Widget buildEntityListCard(BuildContext context, DashboardInfo dashboard) { + return _buildEntityListCard(context, dashboard, false); + } + + @override + Widget buildEntityListWidgetCard(BuildContext context, DashboardInfo dashboard) { + return _buildEntityListCard(context, dashboard, true); + } + + @override + EntityCardSettings entityGridCardSettings(DashboardInfo dashboard) => EntityCardSettings(dropShadow: true); //dashboard.image != null); + + @override + Widget buildEntityGridCard(BuildContext context, DashboardInfo entity) { + var hasImage = entity.image != null; + Widget image; + if (hasImage) { + var uriData = UriData.parse(entity.image!); + image = Image.memory(uriData.contentAsBytes()); + } else { + image = Image.asset(ThingsboardImage.dashboardPlaceholder); + } + return + ClipRRect( + borderRadius: BorderRadius.circular(6), + child: Stack( + children: [ + Positioned.fill( + child: FittedBox( + fit: BoxFit.cover, + child: image, + ) + ), + hasImage ? Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0x00000000), + Color(0xb7000000) + ], + stops: [0.4219, 1] + ) + ) + ), + ) : Container(), + Positioned( + bottom: 16, + left: 16, + right: 16, + child: AutoSizeText(entity.title, + textAlign: TextAlign.center, + maxLines: 2, + minFontSize: 8, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: hasImage ? Colors.white : Color(0xFF282828), + fontWeight: FontWeight.w500, + fontSize: 14, + height: 20 / 14 + ), + ) + ) + ], + ) + ); + } + + Widget _buildEntityListCard(BuildContext context, DashboardInfo dashboard, bool listWidgetCard) { return Row( - mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max, + mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max, children: [ Flexible( - fit: briefView ? FlexFit.loose : FlexFit.tight, + fit: listWidgetCard ? FlexFit.loose : FlexFit.tight, child: Container( - padding: EdgeInsets.symmetric(vertical: briefView ? 9 : 10, horizontal: 16), + padding: EdgeInsets.symmetric(vertical: listWidgetCard ? 9 : 10, horizontal: 16), child: Row( - mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max, + mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max, children: [ Flexible( - fit: briefView ? FlexFit.loose : FlexFit.tight, + fit: listWidgetCard ? FlexFit.loose : FlexFit.tight, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -60,7 +137,7 @@ mixin DashboardsBase on EntitiesBase { ], ) ), - (!briefView ? Column( + (!listWidgetCard ? Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(dashboard.createdTime!)), @@ -95,10 +172,4 @@ mixin DashboardsBase on EntitiesBase { return dashboard.assignedCustomers.any((element) => element.isPublic); } - @override - void onEntityDetails(DashboardInfo dashboard) { - navigateTo('/dashboard/${dashboard.id!.id}?title=${dashboard.title}'); - } - - } diff --git a/lib/modules/dashboard/dashboards_grid.dart b/lib/modules/dashboard/dashboards_grid.dart new file mode 100644 index 0000000..ab312b8 --- /dev/null +++ b/lib/modules/dashboard/dashboards_grid.dart @@ -0,0 +1,13 @@ +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_grid.dart'; +import 'package:thingsboard_client/thingsboard_client.dart'; + +import 'dashboards_base.dart'; + +class DashboardsGrid extends BaseEntitiesPageLinkWidget with DashboardsBase, EntitiesGridStateBase { + + DashboardsGrid(TbContext tbContext) : super(tbContext); + +} + diff --git a/lib/modules/dashboard/dashboards_list.dart b/lib/modules/dashboard/dashboards_list.dart new file mode 100644 index 0000000..5c510bb --- /dev/null +++ b/lib/modules/dashboard/dashboards_list.dart @@ -0,0 +1,13 @@ +import 'package:thingsboard_app/core/context/tb_context.dart'; +import 'package:thingsboard_app/core/entity/entities_list.dart'; +import 'package:thingsboard_app/core/entity/entities_base.dart'; +import 'package:thingsboard_client/thingsboard_client.dart'; + +import 'dashboards_base.dart'; + +class DashboardsList extends BaseEntitiesPageLinkWidget with DashboardsBase, EntitiesListStateBase { + + DashboardsList(TbContext tbContext) : super(tbContext); + +} + diff --git a/lib/modules/dashboard/dashboards_list_widget.dart b/lib/modules/dashboard/dashboards_list_widget.dart new file mode 100644 index 0000000..bec58d1 --- /dev/null +++ b/lib/modules/dashboard/dashboards_list_widget.dart @@ -0,0 +1,15 @@ +import 'package:thingsboard_app/core/context/tb_context.dart'; +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 with DashboardsBase { + + DashboardsListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller); + + @override + void onViewAll() { + navigateTo('/dashboards'); + } + +} diff --git a/lib/modules/dashboard/dashboards_page.dart b/lib/modules/dashboard/dashboards_page.dart index c8876f6..5c93924 100644 --- a/lib/modules/dashboard/dashboards_page.dart +++ b/lib/modules/dashboard/dashboards_page.dart @@ -1,18 +1,31 @@ +import 'package:flutter/material.dart'; 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'; +import 'package:thingsboard_app/core/context/tb_context_widget.dart'; +import 'package:thingsboard_app/widgets/tb_app_bar.dart'; -class DashboardsPage extends EntitiesPage with DashboardsBase { +import 'dashboards_list.dart'; - DashboardsPage(TbContext tbContext) : - super(tbContext); +class DashboardsPage extends TbPageWidget { + DashboardsPage(TbContext tbContext) : super(tbContext); @override - String get noMoreItemsText => 'No more dashboards'; - - @override - String get searchHint => 'Search dashboards'; + _DashboardsPageState createState() => _DashboardsPageState(); + +} + +class _DashboardsPageState extends TbPageState { + + @override + Widget build(BuildContext context) { + var dashboardsList = DashboardsList(tbContext); + return Scaffold( + appBar: TbAppBar( + tbContext, + title: Text(dashboardsList.title) + ), + body: dashboardsList + ); + } } diff --git a/lib/modules/dashboard/dashboards_widget.dart b/lib/modules/dashboard/dashboards_widget.dart deleted file mode 100644 index 6e3bf64..0000000 --- a/lib/modules/dashboard/dashboards_widget.dart +++ /dev/null @@ -1,15 +0,0 @@ -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 with DashboardsBase { - - DashboardsWidget(TbContext tbContext, {EntitiesWidgetController? controller}): super(tbContext, controller: controller); - - @override - void onViewAll() { - navigateTo('/dashboards'); - } - -} diff --git a/lib/modules/device/device_routes.dart b/lib/modules/device/device_routes.dart index c318587..532ab18 100644 --- a/lib/modules/device/device_routes.dart +++ b/lib/modules/device/device_routes.dart @@ -5,7 +5,6 @@ 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 { diff --git a/lib/modules/device/devices_base.dart b/lib/modules/device/devices_base.dart index 8a7ee11..8bca318 100644 --- a/lib/modules/device/devices_base.dart +++ b/lib/modules/device/devices_base.dart @@ -3,7 +3,7 @@ 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 { +mixin DevicesBase on EntitiesBaseWithPageLink { @override String get title => 'Devices'; @@ -21,35 +21,56 @@ mixin DevicesBase on EntitiesBase { } @override - void onEntityDetails(DeviceInfo device) { + void onEntityTap(DeviceInfo device) { navigateTo('/device/${device.id!.id}'); } @override - Widget buildEntityCard(BuildContext context, DeviceInfo device, bool briefView) { + Widget? buildHeading(BuildContext context) { + return Text('Hobo Devices!'); + } + + + @override + Widget buildEntityListCard(BuildContext context, DeviceInfo device) { + return _buildEntityListCard(context, device, false); + } + + @override + Widget buildEntityListWidgetCard(BuildContext context, DeviceInfo device) { + return _buildEntityListCard(context, device, true); + } + + @override + Widget buildEntityGridCard(BuildContext context, DeviceInfo device) { + return Text(device.name); + } + + Widget _buildEntityListCard(BuildContext context, DeviceInfo device, bool listWidgetCard) { return Row( - mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max, + mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max, children: [ Container( - width: briefView ? 58 : 60, + width: listWidgetCard ? 58 : 60, + height: listWidgetCard ? 58 : 60, decoration: BoxDecoration( color: Color(0xFFEEEEEE), - borderRadius: BorderRadius.horizontal(left: Radius.circular(briefView ? 4 : 6)) + borderRadius: BorderRadius.horizontal(left: Radius.circular(listWidgetCard ? 4 : 6)) ), child: Center( child: Icon(Icons.devices_other, color: Color(0xFFC2C2C2)) ), ), Flexible( - fit: briefView ? FlexFit.loose : FlexFit.tight, + fit: listWidgetCard ? FlexFit.loose : FlexFit.tight, child: Container( - padding: EdgeInsets.symmetric(vertical: briefView ? 9 : 10, horizontal: 16), + padding: EdgeInsets.symmetric(vertical: listWidgetCard ? 9 : 10, horizontal: 16), child: Row( - mainAxisSize: briefView ? MainAxisSize.min : MainAxisSize.max, + mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max, children: [ Flexible( - fit: briefView ? FlexFit.loose : FlexFit.tight, + fit: listWidgetCard ? FlexFit.loose : FlexFit.tight, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -75,7 +96,7 @@ mixin DevicesBase on EntitiesBase { ], ) ), - (!briefView ? Column( + (!listWidgetCard ? Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(device.createdTime!)), diff --git a/lib/modules/device/devices_list.dart b/lib/modules/device/devices_list.dart new file mode 100644 index 0000000..5913055 --- /dev/null +++ b/lib/modules/device/devices_list.dart @@ -0,0 +1,12 @@ +import 'package:thingsboard_app/core/context/tb_context.dart'; +import 'package:thingsboard_app/core/entity/entities_base.dart'; +import 'package:thingsboard_app/core/entity/entities_list.dart'; +import 'package:thingsboard_app/modules/device/devices_base.dart'; +import 'package:thingsboard_client/thingsboard_client.dart'; + +class DevicesList extends BaseEntitiesPageLinkWidget with DevicesBase, EntitiesListStateBase { + + DevicesList(TbContext tbContext) : super(tbContext); + +} + diff --git a/lib/modules/device/devices_list_widget.dart b/lib/modules/device/devices_list_widget.dart new file mode 100644 index 0000000..7ee28a9 --- /dev/null +++ b/lib/modules/device/devices_list_widget.dart @@ -0,0 +1,15 @@ +import 'package:thingsboard_app/core/context/tb_context.dart'; +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 EntitiesListPageLinkWidget with DevicesBase { + + DevicesListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller); + + @override + void onViewAll() { + navigateTo('/devices'); + } + +} diff --git a/lib/modules/device/devices_page.dart b/lib/modules/device/devices_page.dart index 0bbcc08..5f53d14 100644 --- a/lib/modules/device/devices_page.dart +++ b/lib/modules/device/devices_page.dart @@ -1,99 +1,30 @@ +import 'package:flutter/material.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_widget.dart'; +import 'package:thingsboard_app/modules/device/devices_list.dart'; +import 'package:thingsboard_app/widgets/tb_app_bar.dart'; -class DevicesPage extends EntitiesPage with DevicesBase { +class DevicesPage extends TbContextWidget { DevicesPage(TbContext tbContext) : super(tbContext); @override - String get noMoreItemsText => 'No more devices'; - - @override - String get searchHint => 'Search devices'; + _DevicesPageState createState() => _DevicesPageState(); } -/* bottomNavigationBar: BottomAppBar( - shape: CircularNotchedRectangle(), - notchMargin: 4.0, - child: new Row( - children: [ - IconButton(icon: Icon(Icons.refresh), onPressed: () { - refresh(); - },), - Spacer(), - IconButton(icon: Icon(Icons.search), onPressed: () { - _searchModeNotifier.value = true; - }), - _simplePopup(), - ], +class _DevicesPageState extends TbContextState { + + @override + Widget build(BuildContext context) { + var devicesList = DevicesList(tbContext); + return Scaffold( + appBar: TbAppBar( + tbContext, + title: Text(devicesList.title) ), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.add), onPressed: () {},), + body: devicesList ); } - Widget _simplePopup() => PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - value: 1, - child: Text("First"), - ), - PopupMenuItem( - value: 2, - child: ListTile( - leading: Icon(Icons.work), - title: Text('Second'), - ) - ), - ], - icon: Icon(Icons.settings), - ); - - SpeedDial speedDial(context) => 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: () { - tbClient.logout(requestConfig: RequestConfig(ignoreErrors: true)); - }, - label: 'Logout', - labelStyle: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16.0), - ) - ], - ); } -*/ diff --git a/lib/modules/device/devices_widget.dart b/lib/modules/device/devices_widget.dart deleted file mode 100644 index 7ddffbc..0000000 --- a/lib/modules/device/devices_widget.dart +++ /dev/null @@ -1,15 +0,0 @@ -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 with DevicesBase { - - DevicesWidget(TbContext tbContext, {EntitiesWidgetController? controller}): super(tbContext, controller: controller); - - @override - void onViewAll() { - navigateTo('/devices'); - } - -} diff --git a/lib/modules/home/home_page.dart b/lib/modules/home/home_page.dart index 8cfeb58..f78dfae 100644 --- a/lib/modules/home/home_page.dart +++ b/lib/modules/home/home_page.dart @@ -1,15 +1,12 @@ 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/core/entity/entities_list_widget.dart'; import 'package:thingsboard_app/modules/dashboard/dashboard.dart' as dashboardUi; +import 'package:thingsboard_app/modules/dashboard/dashboards_grid.dart'; +import 'package:thingsboard_app/widgets/tb_app_bar.dart'; +import 'package:thingsboard_client/thingsboard_client.dart'; class HomePage extends TbContextWidget { @@ -22,7 +19,7 @@ class HomePage extends TbContextWidget { class _HomePageState extends TbContextState { - final EntitiesWidgetController _entitiesWidgetController = EntitiesWidgetController(); + final EntitiesListWidgetController _entitiesWidgetController = EntitiesListWidgetController(); @override void initState() { @@ -42,7 +39,6 @@ class _HomePageState extends TbContextState { return Scaffold( appBar: TbAppBar( tbContext, - showLoadingIndicator: !dashboardState, elevation: dashboardState ? 0 : null, title: const Text('Home'), ), @@ -64,15 +60,14 @@ class _HomePageState extends TbContextState { } Widget _buildDefaultHome(BuildContext context) { - return RefreshIndicator( - onRefresh: () => _entitiesWidgetController.refresh(), - child: ListView( - children: _buildUserHome(context) - ) - ); + if (tbClient.isSystemAdmin()) { + return _buildSysAdminHome(context); + } else { + return DashboardsGrid(tbContext); + } } - List _buildUserHome(BuildContext context) { +/* List _buildUserHome(BuildContext context) { if (tbClient.isSystemAdmin()) { return _buildSysAdminHome(context); } else if (tbClient.isTenantAdmin()) { @@ -80,25 +75,30 @@ class _HomePageState extends TbContextState { } else { return _buildCustomerUserHome(context); } + } */ + + Widget _buildSysAdminHome(BuildContext context) { + return RefreshIndicator( + onRefresh: () => _entitiesWidgetController.refresh(), + child: ListView( + children: [Container(child: Text('TODO: Implement'))] + ) + ); } - List _buildSysAdminHome(BuildContext context) { - return [Container(child: Text('TODO: Implement'))]; - } - - List _buildTenantAdminHome(BuildContext context) { +/* List _buildTenantAdminHome(BuildContext context) { return [ - AssetsWidget(tbContext, controller: _entitiesWidgetController), - DevicesWidget(tbContext, controller: _entitiesWidgetController), - DashboardsWidget(tbContext, controller: _entitiesWidgetController) + AssetsListWidget(tbContext, controller: _entitiesWidgetController), + DevicesListWidget(tbContext, controller: _entitiesWidgetController), + DashboardsListWidget(tbContext, controller: _entitiesWidgetController) ]; } List _buildCustomerUserHome(BuildContext context) { return [ - AssetsWidget(tbContext, controller: _entitiesWidgetController), - DevicesWidget(tbContext, controller: _entitiesWidgetController), - DashboardsWidget(tbContext, controller: _entitiesWidgetController) + AssetsListWidget(tbContext, controller: _entitiesWidgetController), + DevicesListWidget(tbContext, controller: _entitiesWidgetController), + DashboardsListWidget(tbContext, controller: _entitiesWidgetController) ]; - } + } */ } diff --git a/lib/modules/main/main_page.dart b/lib/modules/main/main_page.dart index 513f260..19ee742 100644 --- a/lib/modules/main/main_page.dart +++ b/lib/modules/main/main_page.dart @@ -3,6 +3,7 @@ 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/alarm/alarms_page.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'; @@ -58,18 +59,18 @@ class TbMainNavigationItem { case Authority.TENANT_ADMIN: case Authority.CUSTOMER_USER: items.addAll([ + TbMainNavigationItem( + page: AlarmsPage(tbContext), + title: 'Alarms', + icon: Icon(Icons.notifications), + path: '/alarms' + ), 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: diff --git a/lib/widgets/tb_app_bar.dart b/lib/widgets/tb_app_bar.dart index 43a37d4..409d78a 100644 --- a/lib/widgets/tb_app_bar.dart +++ b/lib/widgets/tb_app_bar.dart @@ -24,7 +24,7 @@ class TbAppBar extends TbContextWidget implements Pref final Size preferredSize; TbAppBar(TbContext tbContext, {this.title, this.elevation, this.showProfile = true, this.showLogout = false, - this.showLoadingIndicator = true, this.searchModeNotifier, this.searchHint, this.onSearch, this.onSearchClosed}) : + this.showLoadingIndicator = false, this.searchModeNotifier, this.searchHint, this.onSearch, this.onSearchClosed}) : preferredSize = Size.fromHeight(kToolbarHeight + (showLoadingIndicator ? 4 : 0)), super(tbContext); diff --git a/pubspec.lock b/pubspec.lock index 305d89a..96d8533 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.5.0" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0-nullsafety.0" boolean_selector: dependency: transitive description: @@ -218,7 +225,7 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.7.4" + version: "0.7.5" image_picker_for_web: dependency: transitive description: @@ -404,7 +411,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "4c2463beceb4f397c9de79ac1b0dee628aaf8add" + resolved-ref: "0627f3aea8e9252d7c178cec864908e564ddee0a" url: "git@github.com:thingsboard/dart_thingsboard_client.git" source: git version: "1.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 454f6b5..9438896 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: cupertino_icons: ^1.0.2 fluro: ^2.0.3 flutter_svg: ^0.22.0 + auto_size_text: ^3.0.0-nullsafety.0 infinite_scroll_pagination: ^3.0.1 fading_edge_scrollview: ^2.0.0 stream_transform: ^2.0.0