Redesign. Improve dashboard page loading.

This commit is contained in:
Igor Kulikov
2021-06-03 18:53:17 +03:00
parent 00038f6c35
commit 068dbbdf0c
43 changed files with 1573 additions and 811 deletions

View File

@@ -1,10 +1,10 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:thingsboard_app/constants/assets_path.dart';
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
@@ -12,12 +12,7 @@ import 'package:thingsboard_app/core/context/tb_context_widget.dart';
class LoginPage extends TbPageWidget<LoginPage, _LoginPageState> {
LoginPage(TbContext tbContext) : super(tbContext) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.light
));
}
LoginPage(TbContext tbContext) : super(tbContext);
@override
_LoginPageState createState() => _LoginPageState();
@@ -26,6 +21,8 @@ class LoginPage extends TbPageWidget<LoginPage, _LoginPageState> {
class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
final _isLoginNotifier = ValueNotifier<bool>(false);
final usernameController = TextEditingController();
final passwordController = TextEditingController();
@@ -49,7 +46,7 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
title: const Text('Login to ThingsBoard'),
),
body: ValueListenableBuilder(
valueListenable: loadingNotifier,
valueListenable: _isLoginNotifier,
builder: (BuildContext context, bool loading, child) {
List<Widget> children = [
SingleChildScrollView(
@@ -61,7 +58,8 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
child: Container(
width: 300,
height: 150,
child: SvgPicture.asset(ThingsboardImage.thingsBoardLogoBlue,
child: SvgPicture.asset(ThingsboardImage.thingsBoardWithTitle,
color: Theme.of(context).primaryColor,
semanticsLabel: 'ThingsBoard Logo')
)
)
@@ -107,9 +105,15 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
decoration: BoxDecoration(
color: loading ? Colors.black12 : Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(4)),
child: TextButton(
onPressed: loading ? null : () {
tbClient.login(
LoginRequest(usernameController.text, passwordController.text));
onPressed: loading ? null : () async {
_isLoginNotifier.value = true;
try {
await tbClient.login(
LoginRequest(usernameController.text,
passwordController.text));
} catch (e) {
_isLoginNotifier.value = false;
}
},
child: Text(
'Login',
@@ -126,6 +130,9 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
)
];
if (loading) {
var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
var bottomPadding = data.padding.top;
bottomPadding += kToolbarHeight;
children.add(
SizedBox.expand(
child: ClipRect(
@@ -135,15 +142,16 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
decoration: new BoxDecoration(
color: Colors.grey.shade200.withOpacity(0.2)
),
child: Center(
child: CircularProgressIndicator(),
child: Container(
padding: EdgeInsets.only(bottom: bottomPadding),
alignment: Alignment.center,
child: TbProgressIndicator(size: 50.0),
),
)
)
)
)
);
//children.add(Center(child: CircularProgressIndicator()));
}
return Stack(
children: children,

View File

@@ -81,12 +81,31 @@ class TbLogger {
}
}
typedef OpenDashboardCallback = void Function(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar});
abstract class TbMainDashboardHolder {
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true});
Future<bool> openMain({bool animate});
Future<bool> closeMain({bool animate});
Future<bool> openDashboard({bool animate});
Future<bool> closeDashboard({bool animate});
bool isDashboardOpen();
Future<bool> dashboardGoBack();
}
class TbContext {
static final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
bool _initialized = false;
bool isUserLoaded = false;
bool isAuthenticated = false;
final ValueNotifier<bool> _isAuthenticated = ValueNotifier(false);
User? userDetails;
HomeDashboardInfo? homeDashboard;
final _isLoadingNotifier = ValueNotifier<bool>(false);
@@ -94,6 +113,7 @@ class TbContext {
late final _widgetActionHandler;
late final AndroidDeviceInfo? _androidInfo;
late final IosDeviceInfo? _iosInfo;
TbMainDashboardHolder? _mainDashboardHolder;
GlobalKey<ScaffoldMessengerState> messengerKey = GlobalKey<ScaffoldMessengerState>();
late ThingsboardClient tbClient;
@@ -137,6 +157,10 @@ class TbContext {
}
}
void setMainDashboardHolder(TbMainDashboardHolder holder) {
_mainDashboardHolder = holder;
}
void onError(ThingsboardError tbError) {
log.error('onError', tbError, tbError.getStackTrace());
showErrorNotification(tbError.message!);
@@ -214,8 +238,8 @@ class TbContext {
try {
log.debug('onUserLoaded: isAuthenticated=${tbClient.isAuthenticated()}');
isUserLoaded = true;
isAuthenticated = tbClient.isAuthenticated();
if (tbClient.isAuthenticated()) {
_isAuthenticated.value = tbClient.isAuthenticated();
if (isAuthenticated) {
log.debug('authUser: ${tbClient.getAuthUser()}');
if (tbClient.getAuthUser()!.userId != null) {
try {
@@ -230,20 +254,33 @@ class TbContext {
userDetails = null;
homeDashboard = null;
}
updateRouteState();
await updateRouteState();
} catch (e, s) {
log.error('Error: $e', e, s);
}
}
void updateRouteState() {
Listenable get isAuthenticatedListenable => _isAuthenticated;
bool get isAuthenticated => _isAuthenticated.value;
Future<void> updateRouteState() async {
if (currentState != null) {
if (tbClient.isAuthenticated()) {
var defaultDashboardId = _defaultDashboardId();
if (defaultDashboardId != null) {
bool fullscreen = _userForceFullscreen();
navigateTo('/dashboard/$defaultDashboardId?fullscreen=$fullscreen', replace: true, transition: TransitionType.fadeIn, transitionDuration: Duration(milliseconds: 750));
if (!fullscreen) {
await navigateToDashboard(defaultDashboardId, animate: false);
navigateTo('/home',
replace: true,
transition: TransitionType.none);
} else {
navigateTo('/fullscreenDashboard/$defaultDashboardId',
replace: true,
transition: TransitionType.fadeIn);
}
} else {
navigateTo('/home', replace: true, transition: TransitionType.fadeIn, transitionDuration: Duration(milliseconds: 750));
}
@@ -276,9 +313,24 @@ class TbContext {
}
}
Future<dynamic> navigateTo(String path, {bool replace = false, bool clearStack = false, TransitionType? transition, Duration? transitionDuration}) async {
bool isHomePage() {
if (currentState != null) {
if (currentState is TbMainState) {
var mainState = currentState as TbMainState;
return mainState.isHomePage();
}
}
return false;
}
Future<dynamic> navigateTo(String path, {bool replace = false, bool clearStack = false,
TransitionType? transition, Duration? transitionDuration, bool restoreDashboard = true}) async {
if (currentState != null) {
hideNotification();
bool isOpenedDashboard = _mainDashboardHolder?.isDashboardOpen() == true;
if (isOpenedDashboard) {
_mainDashboardHolder?.openMain();
}
if (currentState is TbMainState) {
var mainState = currentState as TbMainState;
if (mainState.canNavigate(path) && !replace) {
@@ -290,23 +342,48 @@ class TbContext {
replace = true;
clearStack = true;
}
if (transition == null) {
if (isOpenedDashboard) {
transition = TransitionType.none;
} else if (transition == null) {
if (replace) {
transition = TransitionType.fadeIn;
} else {
transition = TransitionType.inFromRight;
}
}
return await router.navigateTo(currentState!.context, path, transition: transition, transitionDuration: transitionDuration, replace: replace, clearStack: clearStack);
var res = await router.navigateTo(currentState!.context, path, transition: transition, transitionDuration: transitionDuration, replace: replace, clearStack: clearStack);
if (isOpenedDashboard) {
await _mainDashboardHolder?.closeMain();
}
return res;
}
}
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true}) async {
await _mainDashboardHolder?.navigateToDashboard(dashboardId, dashboardTitle: dashboardTitle, state: state, hideToolbar: hideToolbar, animate: animate);
}
void pop<T>([T? result]) {
if (currentState != null) {
router.pop<T>(currentState!.context, result);
}
}
Future<bool> maybePop<T extends Object?>([ T? result ]) async {
if (currentState != null) {
return Navigator.of(currentState!.context).maybePop(result);
} else {
return true;
}
}
Future<bool> willPop() async {
if (_mainDashboardHolder != null) {
return await _mainDashboardHolder!.dashboardGoBack();
}
return true;
}
Future<bool?> confirm({required String title, required String message, String cancel = 'Cancel', String ok = 'Ok'}) {
return showDialog<bool>(context: currentState!.context,
builder: (context) => AlertDialog(
@@ -330,7 +407,13 @@ mixin HasTbContext {
}
void setupCurrentState(TbContextState currentState) {
if (_tbContext.currentState != null) {
ModalRoute.of(_tbContext.currentState!.context)?.removeScopedWillPopCallback(_tbContext.willPop);
}
_tbContext.currentState = currentState;
if (_tbContext.currentState != null) {
ModalRoute.of(_tbContext.currentState!.context)?.addScopedWillPopCallback(_tbContext.willPop);
}
}
void setupTbContext(TbContextState currentState) {
@@ -357,6 +440,11 @@ mixin HasTbContext {
void pop<T>([T? result]) => _tbContext.pop<T>(result);
Future<bool> maybePop<T extends Object?>([ T? result ]) => _tbContext.maybePop<T>(result);
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true}) =>
_tbContext.navigateToDashboard(dashboardId, dashboardTitle: dashboardTitle, state: state, hideToolbar: hideToolbar, animate: animate);
Future<bool?> confirm({required String title, required String message, String cancel = 'Cancel', String ok = 'Ok'}) => _tbContext.confirm(title: title, message: message, cancel: cancel, ok: ok);
void hideNotification() => _tbContext.hideNotification();

View File

@@ -43,6 +43,8 @@ mixin TbMainState {
navigateToPath(String path);
bool isHomePage();
}
abstract class TbPageWidget<W extends TbPageWidget<W,S>, S extends TbPageState<W,S>> extends TbContextWidget<W,S> {

View File

@@ -6,6 +6,7 @@ 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_app/widgets/tb_progress_indicator.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
typedef EntityTapFunction<T> = Function(T entity);
@@ -44,6 +45,8 @@ mixin EntitiesBase<T, P> on HasTbContext {
return Text('Not implemented!');
}
double? gridChildAspectRatio() => null;
EntityCardSettings entityListCardSettings(T entity) => EntityCardSettings();
EntityCardSettings entityGridCardSettings(T entity) => EntityCardSettings();

View File

@@ -19,6 +19,7 @@ class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
@override
Widget pagedViewBuilder(BuildContext context) {
var heading = widget.buildHeading(context);
var gridChildAspectRatio = widget.gridChildAspectRatio() ?? 156 / 150;
List<Widget> slivers = [];
if (heading != null) {
slivers.add(SliverPadding(
@@ -35,8 +36,8 @@ class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
showNoMoreItemsIndicatorAsGridChild: false,
pagingController: pagingController,
// padding: EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 156 / 150,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: gridChildAspectRatio,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
crossAxisCount: 2,

View File

@@ -4,6 +4,7 @@ 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/widgets/tb_app_bar.dart';
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
abstract class EntityDetailsPage<T extends BaseData> extends TbPageWidget<EntityDetailsPage<T>, _EntityDetailsPageState<T>> {
@@ -87,7 +88,9 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
var entity = snapshot.data!;
return widget.buildEntityDetails(context, entity);
} else {
return Center(child: CircularProgressIndicator());
return Center(child: TbProgressIndicator(
size: 50.0,
));
}
},
),

View File

@@ -30,26 +30,18 @@ class EntityGridCard<T> extends StatelessWidget {
child: Card(
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(4),
),
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(4),
child: _entityCardWidgetBuilder(context, _entity)
)
child: _entityCardWidgetBuilder(context, _entity)
),
decoration: _settings.dropShadow ? BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(25),
blurRadius: 10.0,
color: Colors.black.withAlpha((255 * 0.05).ceil()),
blurRadius: 6.0,
offset: Offset(0, 4)
),
BoxShadow(
color: Colors.black.withAlpha(18),
blurRadius: 30.0,
offset: Offset(0, 10)
),
)
],
) : null,
),

View File

@@ -33,13 +33,10 @@ class EntityListCard<T> extends StatelessWidget {
child: Card(
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_listWidgetCard ? 4 : 6),
borderRadius: BorderRadius.circular(4),
),
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(2),
child: _entityCardWidgetBuilder(context, _entity)
)
child: _entityCardWidgetBuilder(context, _entity)
),
decoration: _listWidgetCard ? BoxDecoration(
border: Border.all(
@@ -51,15 +48,10 @@ class EntityListCard<T> extends StatelessWidget {
) : BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(25),
blurRadius: 10.0,
color: Colors.black.withAlpha((255 * 0.05).ceil()),
blurRadius: 6.0,
offset: Offset(0, 4)
),
BoxShadow(
color: Colors.black.withAlpha(18),
blurRadius: 30.0,
offset: Offset(0, 10)
),
],
),
),

View File

@@ -1,50 +1,24 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:thingsboard_app/constants/assets_path.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
class ThingsboardInitApp extends TbPageWidget<ThingsboardInitApp, _ThingsboardInitAppState> {
ThingsboardInitApp(TbContext tbContext, {Key? key}) : super(tbContext, key: key) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.light
));
}
ThingsboardInitApp(TbContext tbContext, {Key? key}) : super(tbContext, key: key);
@override
_ThingsboardInitAppState createState() => _ThingsboardInitAppState();
}
class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp, _ThingsboardInitAppState> with TickerProviderStateMixin {
late final AnimationController rotationController;
late final CurvedAnimation animation;
class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp, _ThingsboardInitAppState> {
@override
void initState() {
rotationController = AnimationController(duration: Duration(milliseconds: 2000),
vsync: this, upperBound: 1, animationBehavior: AnimationBehavior.preserve);
animation = CurvedAnimation(parent: rotationController, curve: Curves.easeInOutCirc);
super.initState();
initTbContext();
rotationController.forward(from: 0.0);
rotationController.addListener(() {
if (rotationController.status == AnimationStatus.completed) {
rotationController.repeat();
}
});
}
@override
void dispose() {
rotationController.dispose();
super.dispose();
}
@override
@@ -52,20 +26,10 @@ class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp, _Thingsbo
return Container(
alignment: Alignment.center,
color: Colors.white,
child: AnimatedBuilder(
animation: animation,
child: Container(
height: 50.0,
width: 50.0,
child: Image.asset(ThingsboardImage.thingsboard),
),
builder: (BuildContext context, Widget? _widget) {
return Transform.rotate(
angle: animation.value * pi * 2,
child: _widget,
);
},
child: TbProgressIndicator(
size: 50.0
),
);
}
}