Redesign. Improve dashboard page loading.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user