diff --git a/assets/images/dashboard-placeholder.png b/assets/images/dashboard-placeholder.png new file mode 100644 index 0000000..4f98c9c Binary files /dev/null and b/assets/images/dashboard-placeholder.png differ diff --git a/assets/images/device-profile-placeholder.png b/assets/images/device-profile-placeholder.png new file mode 100644 index 0000000..36acff2 Binary files /dev/null and b/assets/images/device-profile-placeholder.png differ 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