Add flutter 3+ support. Update dependencies. Fix code style and format issues.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.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';
|
||||
@@ -20,15 +19,12 @@ class ThingsboardAppRouter {
|
||||
late final _tbContext = TbContext(router);
|
||||
|
||||
ThingsboardAppRouter() {
|
||||
router.notFoundHandler = Handler(handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||
router.notFoundHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||
var settings = context!.settings;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Not Found')
|
||||
),
|
||||
body: Center(
|
||||
child: Text('Route not defined: ${settings!.name}')
|
||||
),
|
||||
appBar: AppBar(title: Text('Not Found')),
|
||||
body: Center(child: Text('Route not defined: ${settings!.name}')),
|
||||
);
|
||||
});
|
||||
InitRoutes(_tbContext).registerRoutes();
|
||||
@@ -49,7 +45,6 @@ class ThingsboardAppRouter {
|
||||
}
|
||||
|
||||
abstract class TbRoutes {
|
||||
|
||||
final TbContext _tbContext;
|
||||
|
||||
TbRoutes(this._tbContext);
|
||||
@@ -61,5 +56,4 @@ abstract class TbRoutes {
|
||||
void doRegisterRoutes(FluroRouter router);
|
||||
|
||||
TbContext get tbContext => _tbContext;
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ const tbMatIndigo = MaterialColor(
|
||||
700: Color(0xFF303F9F),
|
||||
800: Color(0xFF283593),
|
||||
900: Color(0xFF1A237E),
|
||||
},);
|
||||
},
|
||||
);
|
||||
|
||||
const tbDarkMatIndigo = MaterialColor(
|
||||
_tbPrimaryColorValue,
|
||||
@@ -39,44 +40,40 @@ const tbDarkMatIndigo = MaterialColor(
|
||||
700: Color(0xFF303F9F),
|
||||
800: _tbPrimaryColor,
|
||||
900: Color(0xFF1A237E),
|
||||
},);
|
||||
},
|
||||
);
|
||||
|
||||
final ThemeData theme = ThemeData();
|
||||
|
||||
ThemeData tbTheme = ThemeData(
|
||||
primarySwatch: tbMatIndigo,
|
||||
accentColor: Colors.deepOrange,
|
||||
colorScheme: theme.colorScheme.copyWith(secondary: Colors.deepOrange),
|
||||
scaffoldBackgroundColor: Color(0xFFFAFAFA),
|
||||
textTheme: tbTypography.black,
|
||||
primaryTextTheme: tbTypography.black,
|
||||
typography: tbTypography,
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: _tbTextColor,
|
||||
/* titleTextStyle: TextStyle(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: _tbTextColor,
|
||||
/* titleTextStyle: TextStyle(
|
||||
color: _tbTextColor
|
||||
),
|
||||
toolbarTextStyle: TextStyle(
|
||||
color: _tbTextColor
|
||||
), */
|
||||
iconTheme: IconThemeData(
|
||||
color: _tbTextColor
|
||||
)
|
||||
|
||||
),
|
||||
iconTheme: IconThemeData(color: _tbTextColor)),
|
||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||
backgroundColor: Colors.white,
|
||||
selectedItemColor: _tbPrimaryColor,
|
||||
unselectedItemColor: _tbPrimaryColor.withAlpha((255 * 0.38).ceil()),
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: true
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
selectedItemColor: _tbPrimaryColor,
|
||||
unselectedItemColor: _tbPrimaryColor.withAlpha((255 * 0.38).ceil()),
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: true),
|
||||
pageTransitionsTheme: PageTransitionsTheme(builders: {
|
||||
TargetPlatform.iOS: FadeOpenPageTransitionsBuilder(),
|
||||
TargetPlatform.android: FadeOpenPageTransitionsBuilder(),
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
ThemeData tbDarkTheme = ThemeData(
|
||||
primarySwatch: tbDarkMatIndigo,
|
||||
accentColor: Colors.deepOrange,
|
||||
brightness: Brightness.dark
|
||||
);
|
||||
colorScheme: theme.colorScheme.copyWith(secondary: Colors.deepOrange),
|
||||
brightness: Brightness.dark);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
abstract class ThingsboardImage {
|
||||
static final thingsBoardWithTitle = 'assets/images/thingsboard_with_title.svg';
|
||||
static final thingsBoardWithTitle =
|
||||
'assets/images/thingsboard_with_title.svg';
|
||||
static final thingsboard = 'assets/images/thingsboard.svg';
|
||||
static final thingsboardOuter = 'assets/images/thingsboard_outer.svg';
|
||||
static final thingsboardCenter = 'assets/images/thingsboard_center.svg';
|
||||
static final dashboardPlaceholder = 'assets/images/dashboard-placeholder.svg';
|
||||
static final deviceProfilePlaceholder = 'assets/images/device-profile-placeholder.svg';
|
||||
static final deviceProfilePlaceholder =
|
||||
'assets/images/device-profile-placeholder.svg';
|
||||
|
||||
static final oauth2Logos = <String,String>{
|
||||
static final oauth2Logos = <String, String>{
|
||||
'google-logo': 'assets/images/google-logo.svg',
|
||||
'github-logo': 'assets/images/github-logo.svg',
|
||||
'facebook-logo': 'assets/images/facebook-logo.svg',
|
||||
'apple-logo': 'assets/images/apple-logo.svg'
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'login/login_page.dart';
|
||||
|
||||
class AuthRoutes extends TbRoutes {
|
||||
|
||||
late var loginHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var loginHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return LoginPage(tbContext);
|
||||
});
|
||||
|
||||
late var resetPasswordRequestHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var resetPasswordRequestHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return ResetPasswordRequestPage(tbContext);
|
||||
});
|
||||
|
||||
@@ -22,7 +23,7 @@ class AuthRoutes extends TbRoutes {
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/login", handler: loginHandler);
|
||||
router.define("/login/resetPasswordRequest", handler: resetPasswordRequestHandler);
|
||||
router.define("/login/resetPasswordRequest",
|
||||
handler: resetPasswordRequestHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -16,23 +14,20 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'login_page_background.dart';
|
||||
|
||||
class LoginPage extends TbPageWidget {
|
||||
|
||||
LoginPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
|
||||
}
|
||||
|
||||
class _LoginPageState extends TbPageState<LoginPage> {
|
||||
final ButtonStyle _oauth2ButtonWithTextStyle = OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft,
|
||||
primary: Colors.black87);
|
||||
|
||||
final ButtonStyle _oauth2ButtonWithTextStyle =
|
||||
OutlinedButton.styleFrom(padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft, primary: Colors.black87);
|
||||
|
||||
final ButtonStyle _oauth2IconButtonStyle =
|
||||
OutlinedButton.styleFrom(padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.center);
|
||||
final ButtonStyle _oauth2IconButtonStyle = OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.all(16), alignment: Alignment.center);
|
||||
|
||||
final _isLoginNotifier = ValueNotifier<bool>(false);
|
||||
final _showPasswordNotifier = ValueNotifier<bool>(false);
|
||||
@@ -54,174 +49,185 @@ class _LoginPageState extends TbPageState<LoginPage> {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
children: [
|
||||
LoginPageBackground(),
|
||||
Positioned.fill(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(24, 71, 24, 24),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: constraints.maxHeight - (71 + 24)),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(ThingsboardImage.thingsBoardWithTitle,
|
||||
height: 25,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticsLabel: 'ThingsBoard Logo')
|
||||
]
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Login to your account',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 28,
|
||||
height: 36 / 28
|
||||
)
|
||||
)]
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
if (tbContext.hasOAuthClients)
|
||||
_buildOAuth2Buttons(tbContext.oauth2ClientInfos!),
|
||||
if (tbContext.hasOAuthClients)
|
||||
Padding(padding: EdgeInsets.only(top: 10, bottom: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(child: Divider()),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text('OR'),
|
||||
),
|
||||
Flexible(child: Divider())
|
||||
],
|
||||
)
|
||||
body: Stack(children: [
|
||||
LoginPageBackground(),
|
||||
Positioned.fill(child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(24, 71, 24, 24),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight - (71 + 24)),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(children: [
|
||||
SvgPicture.asset(
|
||||
ThingsboardImage.thingsBoardWithTitle,
|
||||
height: 25,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticsLabel: 'ThingsBoard Logo')
|
||||
]),
|
||||
SizedBox(height: 32),
|
||||
Row(children: [
|
||||
Text('Login to your account',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 28,
|
||||
height: 36 / 28))
|
||||
]),
|
||||
SizedBox(height: 48),
|
||||
if (tbContext.hasOAuthClients)
|
||||
_buildOAuth2Buttons(
|
||||
tbContext.oauth2ClientInfos!),
|
||||
if (tbContext.hasOAuthClients)
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: 10, bottom: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(child: Divider()),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16),
|
||||
child: Text('OR'),
|
||||
),
|
||||
Flexible(child: Divider())
|
||||
],
|
||||
)),
|
||||
FormBuilder(
|
||||
key: _loginFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
name: 'username',
|
||||
validator:
|
||||
FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(
|
||||
errorText:
|
||||
'Invalid email format.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email'),
|
||||
),
|
||||
FormBuilder(
|
||||
key: _loginFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
name: 'username',
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(context, errorText: 'Invalid email format.')
|
||||
SizedBox(height: 28),
|
||||
ValueListenableBuilder(
|
||||
valueListenable:
|
||||
_showPasswordNotifier,
|
||||
builder: (BuildContext context,
|
||||
bool showPassword, child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'password',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators
|
||||
.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText:
|
||||
'Password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showPasswordNotifier
|
||||
.value =
|
||||
!_showPasswordNotifier
|
||||
.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 28),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showPasswordNotifier,
|
||||
builder: (BuildContext context, bool showPassword, child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'password',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'Password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showPasswordNotifier.value = !_showPasswordNotifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Password'
|
||||
),
|
||||
);
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
labelText: 'Password'),
|
||||
);
|
||||
})
|
||||
],
|
||||
)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_forgotPassword();
|
||||
},
|
||||
child: Text(
|
||||
'Forgot Password?',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
letterSpacing: 1,
|
||||
fontSize: 12,
|
||||
height: 16 / 12),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_forgotPassword();
|
||||
},
|
||||
child: Text(
|
||||
'Forgot Password?',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary,
|
||||
letterSpacing: 1,
|
||||
fontSize: 12,
|
||||
height: 16 / 12),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
ElevatedButton(
|
||||
child: Text('Log In'),
|
||||
style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 16)),
|
||||
onPressed: () {
|
||||
_login();
|
||||
},
|
||||
),
|
||||
SizedBox(height: 48)
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
Spacer(),
|
||||
ElevatedButton(
|
||||
child: Text('Log In'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding:
|
||||
EdgeInsets.symmetric(vertical: 16)),
|
||||
onPressed: () {
|
||||
_login();
|
||||
},
|
||||
),
|
||||
SizedBox(height: 48)
|
||||
]),
|
||||
)));
|
||||
},
|
||||
)),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isLoginNotifier,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (loading) {
|
||||
var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
var data =
|
||||
MediaQueryData.fromWindow(WidgetsBinding.instance.window);
|
||||
var bottomPadding = data.padding.top;
|
||||
bottomPadding += kToolbarHeight;
|
||||
return SizedBox.expand(
|
||||
child: ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
|
||||
filter:
|
||||
ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
|
||||
child: Container(
|
||||
decoration: new BoxDecoration(
|
||||
color: Colors.grey.shade200.withOpacity(0.2)
|
||||
),
|
||||
color:
|
||||
Colors.grey.shade200.withOpacity(0.2)),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
padding:
|
||||
EdgeInsets.only(bottom: bottomPadding),
|
||||
alignment: Alignment.center,
|
||||
child: TbProgressIndicator(size: 50.0),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
))));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
})
|
||||
]));
|
||||
}
|
||||
|
||||
Widget _buildOAuth2Buttons(List<OAuth2ClientInfo> clients) {
|
||||
if (clients.length == 1 || clients.length > 6) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: clients.asMap().map((index, client) =>
|
||||
MapEntry(index, _buildOAuth2Button(client, 'Login with ${client.name}', false, index == clients.length - 1))).values.toList()
|
||||
);
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: clients
|
||||
.asMap()
|
||||
.map((index, client) => MapEntry(
|
||||
index,
|
||||
_buildOAuth2Button(client, 'Login with ${client.name}', false,
|
||||
index == clients.length - 1)))
|
||||
.values
|
||||
.toList());
|
||||
} else {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
@@ -231,18 +237,24 @@ class _LoginPageState extends TbPageState<LoginPage> {
|
||||
child: Center(child: Text('LOGIN WITH')),
|
||||
),
|
||||
Row(
|
||||
children: clients.asMap().map((index, client) =>
|
||||
MapEntry(index, _buildOAuth2Button(client, clients.length == 2 ? client.name : null, true, index == clients.length - 1))).values.toList()
|
||||
)
|
||||
children: clients
|
||||
.asMap()
|
||||
.map((index, client) => MapEntry(
|
||||
index,
|
||||
_buildOAuth2Button(
|
||||
client,
|
||||
clients.length == 2 ? client.name : null,
|
||||
true,
|
||||
index == clients.length - 1)))
|
||||
.values
|
||||
.toList())
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildOAuth2Button(OAuth2ClientInfo client,
|
||||
String? text,
|
||||
bool expand,
|
||||
bool isLast) {
|
||||
Widget _buildOAuth2Button(
|
||||
OAuth2ClientInfo client, String? text, bool expand, bool isLast) {
|
||||
Widget? icon;
|
||||
if (client.icon != null) {
|
||||
if (ThingsboardImage.oauth2Logos.containsKey(client.icon)) {
|
||||
@@ -255,7 +267,8 @@ class _LoginPageState extends TbPageState<LoginPage> {
|
||||
}
|
||||
var iconData = MdiIcons.fromString(strIcon);
|
||||
if (iconData != null) {
|
||||
icon = Icon(iconData, size: 24, color: Theme.of(context).primaryColor);
|
||||
icon =
|
||||
Icon(iconData, size: 24, color: Theme.of(context).primaryColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,10 +292,9 @@ class _LoginPageState extends TbPageState<LoginPage> {
|
||||
if (expand) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: isLast ? 0 : 8),
|
||||
child: button,
|
||||
)
|
||||
);
|
||||
padding: EdgeInsets.only(right: isLast ? 0 : 8),
|
||||
child: button,
|
||||
));
|
||||
} else {
|
||||
return button;
|
||||
}
|
||||
@@ -293,7 +305,8 @@ class _LoginPageState extends TbPageState<LoginPage> {
|
||||
try {
|
||||
final result = await tbContext.oauth2Client.authenticate(client.url);
|
||||
if (result.success) {
|
||||
await tbClient.setUserFromJwtToken(result.accessToken, result.refreshToken, true);
|
||||
await tbClient.setUserFromJwtToken(
|
||||
result.accessToken, result.refreshToken, true);
|
||||
} else {
|
||||
_isLoginNotifier.value = false;
|
||||
showErrorNotification(result.error!);
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class LoginPageBackground extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox.expand(
|
||||
child: CustomPaint(
|
||||
painter: _LoginPageBackgroundPainter(color: Theme.of(context).primaryColor),
|
||||
)
|
||||
);
|
||||
painter:
|
||||
_LoginPageBackgroundPainter(color: Theme.of(context).primaryColor),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _LoginPageBackgroundPainter extends CustomPainter {
|
||||
|
||||
final Color color;
|
||||
|
||||
const _LoginPageBackgroundPainter({required this.color});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:thingsboard_app/core/auth/login/login_page_background.dart';
|
||||
@@ -9,16 +8,15 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
|
||||
class ResetPasswordRequestPage extends TbPageWidget {
|
||||
|
||||
ResetPasswordRequestPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_ResetPasswordRequestPageState createState() => _ResetPasswordRequestPageState();
|
||||
|
||||
_ResetPasswordRequestPageState createState() =>
|
||||
_ResetPasswordRequestPageState();
|
||||
}
|
||||
|
||||
class _ResetPasswordRequestPageState extends TbPageState<ResetPasswordRequestPage> {
|
||||
|
||||
class _ResetPasswordRequestPageState
|
||||
extends TbPageState<ResetPasswordRequestPage> {
|
||||
final _isLoadingNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
final _resetPasswordFormKey = GlobalKey<FormBuilderState>();
|
||||
@@ -26,82 +24,74 @@ class _ResetPasswordRequestPageState extends TbPageState<ResetPasswordRequestPag
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack( children: [
|
||||
LoginPageBackground(),
|
||||
SizedBox.expand(
|
||||
body: Stack(children: [
|
||||
LoginPageBackground(),
|
||||
SizedBox.expand(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text('Reset password'),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
body: Stack(children: [
|
||||
SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
padding: EdgeInsets.all(24),
|
||||
child: FormBuilder(
|
||||
key: _resetPasswordFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
Text('Enter the email associated with your account and we\'ll send an email with password reset link',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 14,
|
||||
height: 24 / 14
|
||||
),
|
||||
),
|
||||
SizedBox(height: 61),
|
||||
FormBuilderTextField(
|
||||
name: 'email',
|
||||
autofocus: true,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(context, errorText: 'Invalid email format.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email *'
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
ElevatedButton(
|
||||
child: Text('Request password reset'),
|
||||
style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 16)),
|
||||
onPressed: () {
|
||||
_requestPasswordReset();
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isLoadingNotifier,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
child: Container(
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
key: _resetPasswordFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Enter the email associated with your account and we\'ll send an email with password reset link',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 14,
|
||||
height: 24 / 14),
|
||||
),
|
||||
SizedBox(height: 61),
|
||||
FormBuilderTextField(
|
||||
name: 'email',
|
||||
autofocus: true,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(
|
||||
errorText: 'Invalid email format.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email *'),
|
||||
),
|
||||
Spacer(),
|
||||
ElevatedButton(
|
||||
child: Text('Request password reset'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding:
|
||||
EdgeInsets.symmetric(vertical: 16)),
|
||||
onPressed: () {
|
||||
_requestPasswordReset();
|
||||
},
|
||||
)
|
||||
])))),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isLoadingNotifier,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
child: Container(
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
);
|
||||
})
|
||||
])))
|
||||
]));
|
||||
}
|
||||
|
||||
void _requestPasswordReset() async {
|
||||
@@ -115,7 +105,7 @@ class _ResetPasswordRequestPageState extends TbPageState<ResetPasswordRequestPag
|
||||
await tbClient.sendResetPasswordLink(email);
|
||||
_isLoadingNotifier.value = false;
|
||||
showSuccessNotification('Password reset link was successfully sent!');
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
_isLoadingNotifier.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import 'package:thingsboard_app/constants/app_constants.dart';
|
||||
|
||||
abstract class AppSecretProvider {
|
||||
|
||||
Future<String> getAppSecret();
|
||||
|
||||
factory AppSecretProvider.local() => _LocalAppSecretProvider();
|
||||
|
||||
}
|
||||
|
||||
/// Not for production (only for debugging)
|
||||
class _LocalAppSecretProvider implements AppSecretProvider {
|
||||
|
||||
@override
|
||||
Future<String> getAppSecret() async {
|
||||
return ThingsboardAppConstants.thingsboardOAuth2AppSecret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,39 +19,42 @@ class TbOAuth2AuthenticateResult {
|
||||
TbOAuth2AuthenticateResult.failed(this.error);
|
||||
|
||||
bool get success => error == null;
|
||||
|
||||
}
|
||||
|
||||
class TbOAuth2Client {
|
||||
|
||||
final TbContext _tbContext;
|
||||
final AppSecretProvider _appSecretProvider;
|
||||
|
||||
TbOAuth2Client(
|
||||
{ required TbContext tbContext,
|
||||
required AppSecretProvider appSecretProvider} ):
|
||||
_tbContext = tbContext,
|
||||
_appSecretProvider = appSecretProvider;
|
||||
{required TbContext tbContext,
|
||||
required AppSecretProvider appSecretProvider})
|
||||
: _tbContext = tbContext,
|
||||
_appSecretProvider = appSecretProvider;
|
||||
|
||||
Future<TbOAuth2AuthenticateResult> authenticate(String oauth2Url) async {
|
||||
final appSecret = await _appSecretProvider.getAppSecret();
|
||||
final pkgName = _tbContext.packageName;
|
||||
final jwt = JWT(
|
||||
{
|
||||
'callbackUrlScheme': ThingsboardAppConstants.thingsboardOAuth2CallbackUrlScheme
|
||||
'callbackUrlScheme':
|
||||
ThingsboardAppConstants.thingsboardOAuth2CallbackUrlScheme
|
||||
},
|
||||
issuer: pkgName,
|
||||
);
|
||||
final key = SecretKey(appSecret);
|
||||
final appToken = jwt.sign(key, algorithm: _HMACBase64Algorithm.HS512, expiresIn: Duration(minutes: 2));
|
||||
var url = Uri.parse(ThingsboardAppConstants.thingsBoardApiEndpoint + oauth2Url);
|
||||
final params = Map<String,String>.from(url.queryParameters);
|
||||
final appToken = jwt.sign(key,
|
||||
algorithm: _HMACBase64Algorithm.HS512, expiresIn: Duration(minutes: 2));
|
||||
var url =
|
||||
Uri.parse(ThingsboardAppConstants.thingsBoardApiEndpoint + oauth2Url);
|
||||
final params = Map<String, String>.from(url.queryParameters);
|
||||
params['pkg'] = pkgName;
|
||||
params['appToken'] = appToken;
|
||||
url = url.replace(queryParameters: params);
|
||||
final result = await TbWebAuth.authenticate(
|
||||
url: url.toString(),
|
||||
callbackUrlScheme: ThingsboardAppConstants.thingsboardOAuth2CallbackUrlScheme, saveHistory: false);
|
||||
callbackUrlScheme:
|
||||
ThingsboardAppConstants.thingsboardOAuth2CallbackUrlScheme,
|
||||
saveHistory: false);
|
||||
final resultUri = Uri.parse(result);
|
||||
final error = resultUri.queryParameters['error'];
|
||||
if (error != null) {
|
||||
@@ -62,14 +65,14 @@ class TbOAuth2Client {
|
||||
if (accessToken != null && refreshToken != null) {
|
||||
return TbOAuth2AuthenticateResult.success(accessToken, refreshToken);
|
||||
} else {
|
||||
return TbOAuth2AuthenticateResult.failed('No authentication credentials in response.');
|
||||
return TbOAuth2AuthenticateResult.failed(
|
||||
'No authentication credentials in response.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _HMACBase64Algorithm extends JWTAlgorithm {
|
||||
|
||||
static const HS512 = _HMACBase64Algorithm('HS512');
|
||||
|
||||
final String _name;
|
||||
|
||||
@@ -19,13 +19,18 @@ class _OnAppLifecycleResumeObserver extends WidgetsBindingObserver {
|
||||
class TbWebAuth {
|
||||
static const MethodChannel _channel = const MethodChannel('tb_web_auth');
|
||||
|
||||
static final _OnAppLifecycleResumeObserver _resumedObserver = _OnAppLifecycleResumeObserver(() {
|
||||
static final _OnAppLifecycleResumeObserver _resumedObserver =
|
||||
_OnAppLifecycleResumeObserver(() {
|
||||
_cleanUpDanglingCalls();
|
||||
});
|
||||
|
||||
static Future<String> authenticate({required String url, required String callbackUrlScheme, bool? saveHistory}) async {
|
||||
WidgetsBinding.instance?.removeObserver(_resumedObserver); // safety measure so we never add this observer twice
|
||||
WidgetsBinding.instance?.addObserver(_resumedObserver);
|
||||
static Future<String> authenticate(
|
||||
{required String url,
|
||||
required String callbackUrlScheme,
|
||||
bool? saveHistory}) async {
|
||||
WidgetsBinding.instance.removeObserver(
|
||||
_resumedObserver); // safety measure so we never add this observer twice
|
||||
WidgetsBinding.instance.addObserver(_resumedObserver);
|
||||
return await _channel.invokeMethod('authenticate', <String, dynamic>{
|
||||
'url': url,
|
||||
'callbackUrlScheme': callbackUrlScheme,
|
||||
@@ -35,6 +40,6 @@ class TbWebAuth {
|
||||
|
||||
static Future<void> _cleanUpDanglingCalls() async {
|
||||
await _channel.invokeMethod('cleanUpDanglingCalls');
|
||||
WidgetsBinding.instance?.removeObserver(_resumedObserver);
|
||||
WidgetsBinding.instance.removeObserver(_resumedObserver);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,7 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'package:thingsboard_app/utils/services/tb_app_storage.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
enum NotificationType {
|
||||
info,
|
||||
warn,
|
||||
success,
|
||||
error
|
||||
}
|
||||
enum NotificationType { info, warn, success, error }
|
||||
|
||||
class TbLogOutput extends LogOutput {
|
||||
@override
|
||||
@@ -45,18 +40,14 @@ class TbLogsFilter extends LogFilter {
|
||||
class TbLogger {
|
||||
final _logger = Logger(
|
||||
filter: TbLogsFilter(),
|
||||
printer: PrefixPrinter(
|
||||
PrettyPrinter(
|
||||
methodCount: 0,
|
||||
errorMethodCount: 8,
|
||||
lineLength: 200,
|
||||
colors: false,
|
||||
printEmojis: true,
|
||||
printTime: false
|
||||
)
|
||||
),
|
||||
output: TbLogOutput()
|
||||
);
|
||||
printer: PrefixPrinter(PrettyPrinter(
|
||||
methodCount: 0,
|
||||
errorMethodCount: 8,
|
||||
lineLength: 200,
|
||||
colors: false,
|
||||
printEmojis: true,
|
||||
printTime: false)),
|
||||
output: TbLogOutput());
|
||||
|
||||
void verbose(dynamic message, [dynamic error, StackTrace? stackTrace]) {
|
||||
_logger.v(message, error, stackTrace);
|
||||
@@ -83,11 +74,15 @@ class TbLogger {
|
||||
}
|
||||
}
|
||||
|
||||
typedef OpenDashboardCallback = void Function(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar});
|
||||
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<void> navigateToDashboard(String dashboardId,
|
||||
{String? dashboardTitle,
|
||||
String? state,
|
||||
bool? hideToolbar,
|
||||
bool animate = true});
|
||||
|
||||
Future<bool> openMain({bool animate});
|
||||
|
||||
@@ -100,7 +95,6 @@ abstract class TbMainDashboardHolder {
|
||||
bool isDashboardOpen();
|
||||
|
||||
Future<bool> dashboardGoBack();
|
||||
|
||||
}
|
||||
|
||||
class TbContext {
|
||||
@@ -121,7 +115,8 @@ class TbContext {
|
||||
TbMainDashboardHolder? _mainDashboardHolder;
|
||||
bool _closeMainFirst = false;
|
||||
|
||||
GlobalKey<ScaffoldMessengerState> messengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
GlobalKey<ScaffoldMessengerState> messengerKey =
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
late final ThingsboardClient tbClient;
|
||||
late final TbOAuth2Client oauth2Client;
|
||||
|
||||
@@ -147,14 +142,15 @@ class TbContext {
|
||||
_initialized = true;
|
||||
var storage = createAppStorage();
|
||||
tbClient = ThingsboardClient(ThingsboardAppConstants.thingsBoardApiEndpoint,
|
||||
storage: storage,
|
||||
onUserLoaded: onUserLoaded,
|
||||
onError: onError,
|
||||
onLoadStarted: onLoadStarted,
|
||||
onLoadFinished: onLoadFinished,
|
||||
computeFunc: <Q, R>(callback, message) => compute(callback, message));
|
||||
storage: storage,
|
||||
onUserLoaded: onUserLoaded,
|
||||
onError: onError,
|
||||
onLoadStarted: onLoadStarted,
|
||||
onLoadFinished: onLoadFinished,
|
||||
computeFunc: <Q, R>(callback, message) => compute(callback, message));
|
||||
|
||||
oauth2Client = TbOAuth2Client(tbContext: this, appSecretProvider: AppSecretProvider.local());
|
||||
oauth2Client = TbOAuth2Client(
|
||||
tbContext: this, appSecretProvider: AppSecretProvider.local());
|
||||
|
||||
try {
|
||||
if (UniversalPlatform.isAndroid) {
|
||||
@@ -203,11 +199,12 @@ class TbContext {
|
||||
showNotification(message, NotificationType.success, duration: duration);
|
||||
}
|
||||
|
||||
void showNotification(String message, NotificationType type, {Duration? duration}) {
|
||||
void showNotification(String message, NotificationType type,
|
||||
{Duration? duration}) {
|
||||
duration ??= const Duration(days: 1);
|
||||
Color backgroundColor;
|
||||
var textColor = Color(0xFFFFFFFF);
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case NotificationType.info:
|
||||
backgroundColor = Color(0xFF323232);
|
||||
break;
|
||||
@@ -224,16 +221,16 @@ class TbContext {
|
||||
final snackBar = SnackBar(
|
||||
duration: duration,
|
||||
backgroundColor: backgroundColor,
|
||||
content: Text(message,
|
||||
style: TextStyle(
|
||||
color: textColor
|
||||
),
|
||||
content: Text(
|
||||
message,
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Close',
|
||||
textColor: textColor,
|
||||
onPressed: () {
|
||||
messengerKey.currentState!.hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
|
||||
messengerKey.currentState!
|
||||
.hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -264,7 +261,8 @@ class TbContext {
|
||||
if (tbClient.getAuthUser()!.userId != null) {
|
||||
try {
|
||||
userDetails = await tbClient.getUserService().getUser();
|
||||
homeDashboard = await tbClient.getDashboardService().getHomeDashboardInfo();
|
||||
homeDashboard =
|
||||
await tbClient.getDashboardService().getHomeDashboardInfo();
|
||||
} catch (e) {
|
||||
if (!_isConnectionError(e)) {
|
||||
tbClient.logout();
|
||||
@@ -276,33 +274,44 @@ class TbContext {
|
||||
} else {
|
||||
userDetails = null;
|
||||
homeDashboard = null;
|
||||
oauth2ClientInfos = await tbClient.getOAuth2Service().getOAuth2Clients(pkgName: packageName, platform: _oauth2PlatformType);
|
||||
oauth2ClientInfos = await tbClient.getOAuth2Service().getOAuth2Clients(
|
||||
pkgName: packageName, platform: _oauth2PlatformType);
|
||||
}
|
||||
_isAuthenticated.value = tbClient.isAuthenticated();
|
||||
await updateRouteState();
|
||||
|
||||
} catch (e, s) {
|
||||
log.error('Error: $e', e, s);
|
||||
if (_isConnectionError(e)) {
|
||||
var res = await confirm(title: 'Connection error', message: 'Failed to connect to server', cancel: 'Cancel', ok: 'Retry');
|
||||
var res = await confirm(
|
||||
title: 'Connection error',
|
||||
message: 'Failed to connect to server',
|
||||
cancel: 'Cancel',
|
||||
ok: 'Retry');
|
||||
if (res == true) {
|
||||
onUserLoaded();
|
||||
} else {
|
||||
navigateTo('/login', replace: true, clearStack: true, transition: TransitionType.fadeIn, transitionDuration: Duration(milliseconds: 750));
|
||||
navigateTo('/login',
|
||||
replace: true,
|
||||
clearStack: true,
|
||||
transition: TransitionType.fadeIn,
|
||||
transitionDuration: Duration(milliseconds: 750));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isConnectionError(e) {
|
||||
return e is ThingsboardError && e.errorCode == ThingsBoardErrorCode.general && e.message == 'Unable to connect';
|
||||
return e is ThingsboardError &&
|
||||
e.errorCode == ThingsBoardErrorCode.general &&
|
||||
e.message == 'Unable to connect';
|
||||
}
|
||||
|
||||
Listenable get isAuthenticatedListenable => _isAuthenticated;
|
||||
|
||||
bool get isAuthenticated => _isAuthenticated.value;
|
||||
|
||||
bool get hasOAuthClients => oauth2ClientInfos != null && oauth2ClientInfos!.isNotEmpty;
|
||||
bool get hasOAuthClients =>
|
||||
oauth2ClientInfos != null && oauth2ClientInfos!.isNotEmpty;
|
||||
|
||||
Future<void> updateRouteState() async {
|
||||
if (currentState != null) {
|
||||
@@ -318,14 +327,20 @@ class TbContext {
|
||||
transition: TransitionType.none);
|
||||
} else {
|
||||
navigateTo('/fullscreenDashboard/$defaultDashboardId',
|
||||
replace: true,
|
||||
transition: TransitionType.fadeIn);
|
||||
replace: true, transition: TransitionType.fadeIn);
|
||||
}
|
||||
} else {
|
||||
navigateTo('/home', replace: true, transition: TransitionType.fadeIn, transitionDuration: Duration(milliseconds: 750));
|
||||
navigateTo('/home',
|
||||
replace: true,
|
||||
transition: TransitionType.fadeIn,
|
||||
transitionDuration: Duration(milliseconds: 750));
|
||||
}
|
||||
} else {
|
||||
navigateTo('/login', replace: true, clearStack: true, transition: TransitionType.fadeIn, transitionDuration: Duration(milliseconds: 750));
|
||||
navigateTo('/login',
|
||||
replace: true,
|
||||
clearStack: true,
|
||||
transition: TransitionType.fadeIn,
|
||||
transitionDuration: Duration(milliseconds: 750));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,9 +353,10 @@ class TbContext {
|
||||
}
|
||||
|
||||
bool _userForceFullscreen() {
|
||||
return tbClient.getAuthUser()!.isPublic ||
|
||||
(userDetails != null && userDetails!.additionalInfo != null &&
|
||||
userDetails!.additionalInfo!['defaultDashboardFullscreen'] == true);
|
||||
return tbClient.getAuthUser()!.isPublic! ||
|
||||
(userDetails != null &&
|
||||
userDetails!.additionalInfo != null &&
|
||||
userDetails!.additionalInfo!['defaultDashboardFullscreen'] == true);
|
||||
}
|
||||
|
||||
bool isPhysicalDevice() {
|
||||
@@ -356,11 +372,13 @@ class TbContext {
|
||||
String userAgent() {
|
||||
String userAgent = 'Mozilla/5.0';
|
||||
if (UniversalPlatform.isAndroid) {
|
||||
userAgent += ' (Linux; Android ${_androidInfo!.version.release}; ${_androidInfo!.model})';
|
||||
userAgent +=
|
||||
' (Linux; Android ${_androidInfo!.version.release}; ${_androidInfo!.model})';
|
||||
} else if (UniversalPlatform.isIOS) {
|
||||
userAgent += ' (${_iosInfo!.model})';
|
||||
}
|
||||
userAgent += ' AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36';
|
||||
userAgent +=
|
||||
' AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36';
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
@@ -374,11 +392,17 @@ class TbContext {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<dynamic> navigateTo(String path, {bool replace = false, bool clearStack = false, closeDashboard = true,
|
||||
TransitionType? transition, Duration? transitionDuration, bool restoreDashboard = true}) async {
|
||||
Future<dynamic> navigateTo(String path,
|
||||
{bool replace = false,
|
||||
bool clearStack = false,
|
||||
closeDashboard = true,
|
||||
TransitionType? transition,
|
||||
Duration? transitionDuration,
|
||||
bool restoreDashboard = true}) async {
|
||||
if (currentState != null) {
|
||||
hideNotification();
|
||||
bool isOpenedDashboard = _mainDashboardHolder?.isDashboardOpen() == true && closeDashboard;
|
||||
bool isOpenedDashboard =
|
||||
_mainDashboardHolder?.isDashboardOpen() == true && closeDashboard;
|
||||
if (isOpenedDashboard) {
|
||||
_mainDashboardHolder?.openMain();
|
||||
}
|
||||
@@ -403,12 +427,24 @@ class TbContext {
|
||||
}
|
||||
}
|
||||
_closeMainFirst = isOpenedDashboard;
|
||||
return await router.navigateTo(currentState!.context, path, transition: transition, transitionDuration: transitionDuration, replace: replace, clearStack: clearStack);
|
||||
return await router.navigateTo(currentState!.context, path,
|
||||
transition: transition,
|
||||
transitionDuration: transitionDuration,
|
||||
replace: replace,
|
||||
clearStack: clearStack);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
Future<T?> showFullScreenDialog<T>(Widget dialog) {
|
||||
@@ -416,8 +452,7 @@ class TbContext {
|
||||
builder: (BuildContext context) {
|
||||
return dialog;
|
||||
},
|
||||
fullscreenDialog: true
|
||||
));
|
||||
fullscreenDialog: true));
|
||||
}
|
||||
|
||||
void pop<T>([T? result, BuildContext? context]) async {
|
||||
@@ -428,7 +463,7 @@ class TbContext {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> maybePop<T extends Object?>([ T? result ]) async {
|
||||
Future<bool> maybePop<T extends Object?>([T? result]) async {
|
||||
if (currentState != null) {
|
||||
return Navigator.of(currentState!.context).maybePop(result);
|
||||
} else {
|
||||
@@ -441,7 +476,7 @@ class TbContext {
|
||||
return true;
|
||||
}
|
||||
if (_mainDashboardHolder != null) {
|
||||
return await _mainDashboardHolder!.dashboardGoBack();
|
||||
return await _mainDashboardHolder!.dashboardGoBack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -456,18 +491,22 @@ class TbContext {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool?> confirm({required String title, required String message, String cancel = 'Cancel', String ok = 'Ok'}) {
|
||||
return showDialog<bool>(context: currentState!.context,
|
||||
Future<bool?> confirm(
|
||||
{required String title,
|
||||
required String message,
|
||||
String cancel = 'Cancel',
|
||||
String ok = 'Ok'}) {
|
||||
return showDialog<bool>(
|
||||
context: currentState!.context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(onPressed: () => pop(false, context),
|
||||
child: Text(cancel)),
|
||||
TextButton(onPressed: () => pop(true, context),
|
||||
child: Text(ok))
|
||||
],
|
||||
));
|
||||
title: Text(title),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => pop(false, context), child: Text(cancel)),
|
||||
TextButton(onPressed: () => pop(true, context), child: Text(ok))
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,11 +519,13 @@ mixin HasTbContext {
|
||||
|
||||
void setupCurrentState(TbContextState currentState) {
|
||||
if (_tbContext.currentState != null) {
|
||||
ModalRoute.of(_tbContext.currentState!.context)?.removeScopedWillPopCallback(_tbContext.willPop);
|
||||
ModalRoute.of(_tbContext.currentState!.context)
|
||||
?.removeScopedWillPopCallback(_tbContext.willPop);
|
||||
}
|
||||
_tbContext.currentState = currentState;
|
||||
if (_tbContext.currentState != null) {
|
||||
ModalRoute.of(_tbContext.currentState!.context)?.addScopedWillPopCallback(_tbContext.willPop);
|
||||
ModalRoute.of(_tbContext.currentState!.context)
|
||||
?.addScopedWillPopCallback(_tbContext.willPop);
|
||||
}
|
||||
if (_tbContext._closeMainFirst) {
|
||||
_tbContext._closeMainFirst = false;
|
||||
@@ -514,33 +555,55 @@ mixin HasTbContext {
|
||||
await _tbContext.init();
|
||||
}
|
||||
|
||||
Future<dynamic> navigateTo(String path, {bool replace = false, bool clearStack = false}) => _tbContext.navigateTo(path, replace: replace, clearStack: clearStack);
|
||||
Future<dynamic> navigateTo(String path,
|
||||
{bool replace = false, bool clearStack = false}) =>
|
||||
_tbContext.navigateTo(path, replace: replace, clearStack: clearStack);
|
||||
|
||||
void pop<T>([T? result, BuildContext? context]) => _tbContext.pop<T>(result, context);
|
||||
void pop<T>([T? result, BuildContext? context]) =>
|
||||
_tbContext.pop<T>(result, context);
|
||||
|
||||
Future<bool> maybePop<T extends Object?>([ T? result ]) => _tbContext.maybePop<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<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);
|
||||
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();
|
||||
|
||||
void showErrorNotification(String message, {Duration? duration}) => _tbContext.showErrorNotification(message, duration: duration);
|
||||
void showErrorNotification(String message, {Duration? duration}) =>
|
||||
_tbContext.showErrorNotification(message, duration: duration);
|
||||
|
||||
void showInfoNotification(String message, {Duration? duration}) => _tbContext.showInfoNotification(message, duration: duration);
|
||||
void showInfoNotification(String message, {Duration? duration}) =>
|
||||
_tbContext.showInfoNotification(message, duration: duration);
|
||||
|
||||
void showWarnNotification(String message, {Duration? duration}) => _tbContext.showWarnNotification(message, duration: duration);
|
||||
void showWarnNotification(String message, {Duration? duration}) =>
|
||||
_tbContext.showWarnNotification(message, duration: duration);
|
||||
|
||||
void showSuccessNotification(String message, {Duration? duration}) => _tbContext.showSuccessNotification(message, duration: duration);
|
||||
void showSuccessNotification(String message, {Duration? duration}) =>
|
||||
_tbContext.showSuccessNotification(message, duration: duration);
|
||||
|
||||
void subscribeRouteObserver(TbPageState pageState) {
|
||||
_tbContext.routeObserver.subscribe(pageState, ModalRoute.of(pageState.context) as PageRoute);
|
||||
_tbContext.routeObserver
|
||||
.subscribe(pageState, ModalRoute.of(pageState.context) as PageRoute);
|
||||
}
|
||||
|
||||
void unsubscribeRouteObserver(TbPageState pageState) {
|
||||
_tbContext.routeObserver.unsubscribe(pageState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
|
||||
abstract class RefreshableWidget extends Widget {
|
||||
refresh();
|
||||
}
|
||||
|
||||
abstract class TbContextStatelessWidget extends StatelessWidget with HasTbContext {
|
||||
abstract class TbContextStatelessWidget extends StatelessWidget
|
||||
with HasTbContext {
|
||||
TbContextStatelessWidget(TbContext tbContext, {Key? key}) : super(key: key) {
|
||||
setTbContext(tbContext);
|
||||
}
|
||||
@@ -18,8 +18,8 @@ abstract class TbContextWidget extends StatefulWidget with HasTbContext {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TbContextState<T extends TbContextWidget> extends State<T> with HasTbContext {
|
||||
|
||||
abstract class TbContextState<T extends TbContextWidget> extends State<T>
|
||||
with HasTbContext {
|
||||
final bool handleLoading;
|
||||
bool closeMainFirst = false;
|
||||
|
||||
@@ -35,25 +35,23 @@ abstract class TbContextState<T extends TbContextWidget> extends State<T> with H
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mixin TbMainState {
|
||||
|
||||
bool canNavigate(String path);
|
||||
|
||||
navigateToPath(String path);
|
||||
|
||||
bool isHomePage();
|
||||
|
||||
}
|
||||
|
||||
abstract class TbPageWidget extends TbContextWidget {
|
||||
TbPageWidget(TbContext tbContext, {Key? key}) : super(tbContext, key: key);
|
||||
}
|
||||
|
||||
abstract class TbPageState<W extends TbPageWidget> extends TbContextState<W> with RouteAware {
|
||||
TbPageState({bool handleUserLoaded = false}): super(handleLoading: true);
|
||||
abstract class TbPageState<W extends TbPageWidget> extends TbContextState<W>
|
||||
with RouteAware {
|
||||
TbPageState({bool handleUserLoaded = false}) : super(handleLoading: true);
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -77,25 +75,20 @@ abstract class TbPageState<W extends TbPageWidget> extends TbContextState<W> wit
|
||||
hideNotification();
|
||||
setupCurrentState(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TextContextWidget extends TbContextWidget {
|
||||
|
||||
final String text;
|
||||
|
||||
TextContextWidget(TbContext tbContext, this.text) : super(tbContext);
|
||||
|
||||
@override
|
||||
_TextContextWidgetState createState() => _TextContextWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _TextContextWidgetState extends TbContextState<TextContextWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(body: Center(child: Text(widget.text)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
@@ -31,7 +30,8 @@ const Map<EntityType, String> entityTypeTranslations = {
|
||||
};
|
||||
|
||||
typedef EntityTapFunction<T> = Function(T entity);
|
||||
typedef EntityCardWidgetBuilder<T> = Widget Function(BuildContext context, T entity);
|
||||
typedef EntityCardWidgetBuilder<T> = Widget Function(
|
||||
BuildContext context, T entity);
|
||||
|
||||
class EntityCardSettings {
|
||||
bool dropShadow;
|
||||
@@ -39,7 +39,6 @@ class EntityCardSettings {
|
||||
}
|
||||
|
||||
mixin EntitiesBase<T, P> on HasTbContext {
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
String get title;
|
||||
@@ -73,11 +72,9 @@ mixin EntitiesBase<T, P> on HasTbContext {
|
||||
EntityCardSettings entityGridCardSettings(T entity) => EntityCardSettings();
|
||||
|
||||
void onEntityTap(T entity);
|
||||
|
||||
}
|
||||
|
||||
mixin ContactBasedBase<T extends ContactBased, P> on EntitiesBase<T,P> {
|
||||
|
||||
mixin ContactBasedBase<T extends ContactBased, P> on EntitiesBase<T, P> {
|
||||
@override
|
||||
Widget buildEntityListCard(BuildContext context, T contact) {
|
||||
var address = Utils.contactToShortAddress(contact);
|
||||
@@ -89,8 +86,7 @@ mixin ContactBasedBase<T extends ContactBased, P> on EntitiesBase<T,P> {
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Column(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
@@ -105,39 +101,36 @@ mixin ContactBasedBase<T extends ContactBased, P> on EntitiesBase<T,P> {
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(contact.createdTime!)),
|
||||
height: 20 / 14))),
|
||||
Text(
|
||||
entityDateFormat.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
contact.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
),
|
||||
height: 16 / 12))
|
||||
]),
|
||||
SizedBox(height: 4),
|
||||
if (contact.email != null) Text(contact.email!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
if (contact.email == null)
|
||||
SizedBox(height: 16),
|
||||
if (contact.email != null)
|
||||
Text(contact.email!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12)),
|
||||
if (contact.email == null) SizedBox(height: 16),
|
||||
if (address != null) SizedBox(height: 4),
|
||||
if (address != null) Text(address,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
if (address != null)
|
||||
Text(address,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12)),
|
||||
],
|
||||
)
|
||||
),
|
||||
)),
|
||||
SizedBox(width: 16),
|
||||
Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
SizedBox(width: 8)
|
||||
@@ -148,24 +141,21 @@ mixin ContactBasedBase<T extends ContactBased, P> on EntitiesBase<T,P> {
|
||||
}
|
||||
|
||||
abstract class PageKeyController<P> extends ValueNotifier<PageKeyValue<P>> {
|
||||
|
||||
PageKeyController(P initialPageKey) : super(PageKeyValue(initialPageKey));
|
||||
|
||||
P nextPageKey(P pageKey);
|
||||
|
||||
}
|
||||
|
||||
class PageKeyValue<P> {
|
||||
|
||||
final P pageKey;
|
||||
|
||||
PageKeyValue(this.pageKey);
|
||||
|
||||
}
|
||||
|
||||
class PageLinkController extends PageKeyController<PageLink> {
|
||||
|
||||
PageLinkController({int pageSize = 20, String? searchText}) : super(PageLink(pageSize, 0, searchText, SortOrder('createdTime', Direction.DESC)));
|
||||
PageLinkController({int pageSize = 20, String? searchText})
|
||||
: super(PageLink(
|
||||
pageSize, 0, searchText, SortOrder('createdTime', Direction.DESC)));
|
||||
|
||||
@override
|
||||
PageLink nextPageKey(PageLink pageKey) => pageKey.nextPageLink();
|
||||
@@ -175,12 +165,12 @@ class PageLinkController extends PageKeyController<PageLink> {
|
||||
value.pageKey.textSearch = searchText;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TimePageLinkController extends PageKeyController<TimePageLink> {
|
||||
|
||||
TimePageLinkController({int pageSize = 20, String? searchText}) : super(TimePageLink(pageSize, 0, searchText, SortOrder('createdTime', Direction.DESC)));
|
||||
TimePageLinkController({int pageSize = 20, String? searchText})
|
||||
: super(TimePageLink(
|
||||
pageSize, 0, searchText, SortOrder('createdTime', Direction.DESC)));
|
||||
|
||||
@override
|
||||
TimePageLink nextPageKey(TimePageLink pageKey) => pageKey.nextPageLink();
|
||||
@@ -190,29 +180,27 @@ class TimePageLinkController extends PageKeyController<TimePageLink> {
|
||||
value.pageKey.textSearch = searchText;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class BaseEntitiesWidget<T, P> extends TbContextWidget with EntitiesBase<T, P> {
|
||||
|
||||
abstract class BaseEntitiesWidget<T, P> extends TbContextWidget
|
||||
with EntitiesBase<T, P> {
|
||||
final bool searchMode;
|
||||
final PageKeyController<P> pageKeyController;
|
||||
|
||||
BaseEntitiesWidget(TbContext tbContext, this.pageKeyController, {this.searchMode = false}):
|
||||
super(tbContext);
|
||||
BaseEntitiesWidget(TbContext tbContext, this.pageKeyController,
|
||||
{this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
Widget? buildHeading(BuildContext context) => searchMode ? Text('Search results', style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 16,
|
||||
height: 24 / 16
|
||||
)) : null;
|
||||
|
||||
|
||||
Widget? buildHeading(BuildContext context) => searchMode
|
||||
? Text('Search results',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF), fontSize: 16, height: 24 / 16))
|
||||
: null;
|
||||
}
|
||||
|
||||
abstract class BaseEntitiesState<T, P> extends TbContextState<BaseEntitiesWidget<T, P>> {
|
||||
|
||||
abstract class BaseEntitiesState<T, P>
|
||||
extends TbContextState<BaseEntitiesWidget<T, P>> {
|
||||
late final PagingController<P, T> pagingController;
|
||||
Completer<void>? _refreshCompleter;
|
||||
bool _dataLoading = false;
|
||||
@@ -224,7 +212,8 @@ abstract class BaseEntitiesState<T, P> extends TbContextState<BaseEntitiesWidget
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
pagingController = PagingController(firstPageKey: widget.pageKeyController.value.pageKey);
|
||||
pagingController =
|
||||
PagingController(firstPageKey: widget.pageKeyController.value.pageKey);
|
||||
widget.pageKeyController.addListener(_didChangePageKeyValue);
|
||||
pagingController.addPageRequestListener((pageKey) {
|
||||
_fetchPage(pageKey);
|
||||
@@ -315,18 +304,14 @@ abstract class BaseEntitiesState<T, P> extends TbContextState<BaseEntitiesWidget
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => Future.wait([
|
||||
widget.onRefresh(),
|
||||
_refresh()
|
||||
]),
|
||||
child: pagedViewBuilder(context)
|
||||
);
|
||||
onRefresh: () => Future.wait([widget.onRefresh(), _refresh()]),
|
||||
child: pagedViewBuilder(context));
|
||||
}
|
||||
|
||||
|
||||
Widget pagedViewBuilder(BuildContext context);
|
||||
|
||||
Widget firstPageProgressIndicatorBuilder(BuildContext context) {
|
||||
return Stack( children: [
|
||||
return Stack(children: [
|
||||
Positioned(
|
||||
top: 20,
|
||||
left: 0,
|
||||
@@ -338,7 +323,7 @@ abstract class BaseEntitiesState<T, P> extends TbContextState<BaseEntitiesWidget
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Widget newPageProgressIndicatorBuilder(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
@@ -348,7 +333,7 @@ abstract class BaseEntitiesState<T, P> extends TbContextState<BaseEntitiesWidget
|
||||
child: Center(child: RefreshProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget noItemsFoundIndicatorBuilder(BuildContext context) {
|
||||
return FirstPageExceptionIndicator(
|
||||
title: widget.noItemsFoundText,
|
||||
@@ -356,7 +341,6 @@ abstract class BaseEntitiesState<T, P> extends TbContextState<BaseEntitiesWidget
|
||||
onTryAgain: widget.searchMode ? null : () => pagingController.refresh(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FirstPageExceptionIndicator extends StatelessWidget {
|
||||
|
||||
@@ -6,14 +6,11 @@ import 'entities_base.dart';
|
||||
import 'entity_grid_card.dart';
|
||||
|
||||
mixin EntitiesGridStateBase on StatefulWidget {
|
||||
|
||||
@override
|
||||
_EntitiesGridState createState() => _EntitiesGridState();
|
||||
|
||||
}
|
||||
|
||||
class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
|
||||
|
||||
_EntitiesGridState() : super();
|
||||
|
||||
@override
|
||||
@@ -24,9 +21,7 @@ class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
|
||||
if (heading != null) {
|
||||
slivers.add(SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: heading
|
||||
)));
|
||||
sliver: SliverToBoxAdapter(child: heading)));
|
||||
}
|
||||
slivers.add(SliverPadding(
|
||||
padding: EdgeInsets.all(16),
|
||||
@@ -44,19 +39,17 @@ class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
|
||||
),
|
||||
builderDelegate: PagedChildBuilderDelegate<T>(
|
||||
itemBuilder: (context, item, index) => EntityGridCard<T>(
|
||||
item,
|
||||
key: widget.getKey(item),
|
||||
entityCardWidgetBuilder: widget.buildEntityGridCard,
|
||||
onEntityTap: widget.onEntityTap,
|
||||
settings: widget.entityGridCardSettings(item),
|
||||
),
|
||||
firstPageProgressIndicatorBuilder: firstPageProgressIndicatorBuilder,
|
||||
newPageProgressIndicatorBuilder: newPageProgressIndicatorBuilder,
|
||||
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder
|
||||
)
|
||||
)));
|
||||
return CustomScrollView(
|
||||
slivers: slivers
|
||||
);
|
||||
item,
|
||||
key: widget.getKey(item),
|
||||
entityCardWidgetBuilder: widget.buildEntityGridCard,
|
||||
onEntityTap: widget.onEntityTap,
|
||||
settings: widget.entityGridCardSettings(item),
|
||||
),
|
||||
firstPageProgressIndicatorBuilder:
|
||||
firstPageProgressIndicatorBuilder,
|
||||
newPageProgressIndicatorBuilder:
|
||||
newPageProgressIndicatorBuilder,
|
||||
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder))));
|
||||
return CustomScrollView(slivers: slivers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,11 @@ import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'entity_list_card.dart';
|
||||
|
||||
mixin EntitiesListStateBase on StatefulWidget {
|
||||
|
||||
@override
|
||||
_EntitiesListState createState() => _EntitiesListState();
|
||||
|
||||
}
|
||||
|
||||
class _EntitiesListState<T,P> extends BaseEntitiesState<T, P> {
|
||||
|
||||
class _EntitiesListState<T, P> extends BaseEntitiesState<T, P> {
|
||||
_EntitiesListState() : super();
|
||||
|
||||
@override
|
||||
@@ -23,9 +20,7 @@ class _EntitiesListState<T,P> extends BaseEntitiesState<T, P> {
|
||||
if (heading != null) {
|
||||
slivers.add(SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: heading
|
||||
)));
|
||||
sliver: SliverToBoxAdapter(child: heading)));
|
||||
}
|
||||
slivers.add(SliverPadding(
|
||||
padding: EdgeInsets.all(16),
|
||||
@@ -34,19 +29,16 @@ class _EntitiesListState<T,P> extends BaseEntitiesState<T, P> {
|
||||
separatorBuilder: (context, index) => SizedBox(height: 8),
|
||||
builderDelegate: PagedChildBuilderDelegate<T>(
|
||||
itemBuilder: (context, item, index) => EntityListCard<T>(
|
||||
item,
|
||||
key: widget.getKey(item),
|
||||
entityCardWidgetBuilder: widget.buildEntityListCard,
|
||||
onEntityTap: widget.onEntityTap,
|
||||
settings: widget.entityListCardSettings(item),
|
||||
),
|
||||
firstPageProgressIndicatorBuilder: firstPageProgressIndicatorBuilder,
|
||||
newPageProgressIndicatorBuilder: newPageProgressIndicatorBuilder,
|
||||
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder
|
||||
)
|
||||
)));
|
||||
return CustomScrollView(
|
||||
slivers: slivers
|
||||
);
|
||||
item,
|
||||
key: widget.getKey(item),
|
||||
entityCardWidgetBuilder: widget.buildEntityListCard,
|
||||
onEntityTap: widget.onEntityTap,
|
||||
),
|
||||
firstPageProgressIndicatorBuilder:
|
||||
firstPageProgressIndicatorBuilder,
|
||||
newPageProgressIndicatorBuilder:
|
||||
newPageProgressIndicatorBuilder,
|
||||
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder))));
|
||||
return CustomScrollView(slivers: slivers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:fading_edge_scrollview/fading_edge_scrollview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
@@ -11,14 +10,15 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'entity_list_card.dart';
|
||||
|
||||
class EntitiesListWidgetController {
|
||||
|
||||
final List<_EntitiesListWidgetState> states = [];
|
||||
|
||||
void _registerEntitiesWidgetState(_EntitiesListWidgetState entitiesListWidgetState) {
|
||||
void _registerEntitiesWidgetState(
|
||||
_EntitiesListWidgetState entitiesListWidgetState) {
|
||||
states.add(entitiesListWidgetState);
|
||||
}
|
||||
|
||||
void _unregisterEntitiesWidgetState(_EntitiesListWidgetState entitiesListWidgetState) {
|
||||
void _unregisterEntitiesWidgetState(
|
||||
_EntitiesListWidgetState entitiesListWidgetState) {
|
||||
states.remove(entitiesListWidgetState);
|
||||
}
|
||||
|
||||
@@ -29,45 +29,48 @@ class EntitiesListWidgetController {
|
||||
void dispose() {
|
||||
states.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class EntitiesListPageLinkWidget<T> extends EntitiesListWidget<T, PageLink> {
|
||||
|
||||
EntitiesListPageLinkWidget(TbContext tbContext, {EntitiesListWidgetController? controller}) : super(tbContext, controller: controller);
|
||||
abstract class EntitiesListPageLinkWidget<T>
|
||||
extends EntitiesListWidget<T, PageLink> {
|
||||
EntitiesListPageLinkWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
PageKeyController<PageLink> createPageKeyController() => PageLinkController(pageSize: 5);
|
||||
|
||||
PageKeyController<PageLink> createPageKeyController() =>
|
||||
PageLinkController(pageSize: 5);
|
||||
}
|
||||
|
||||
abstract class EntitiesListWidget<T, P> extends TbContextWidget with EntitiesBase<T,P> {
|
||||
|
||||
abstract class EntitiesListWidget<T, P> extends TbContextWidget
|
||||
with EntitiesBase<T, P> {
|
||||
final EntitiesListWidgetController? _controller;
|
||||
|
||||
EntitiesListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}):
|
||||
_controller = controller,
|
||||
super(tbContext);
|
||||
EntitiesListWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: _controller = controller,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_EntitiesListWidgetState createState() => _EntitiesListWidgetState(_controller);
|
||||
_EntitiesListWidgetState createState() =>
|
||||
_EntitiesListWidgetState(_controller);
|
||||
|
||||
PageKeyController<P> createPageKeyController();
|
||||
|
||||
void onViewAll();
|
||||
|
||||
}
|
||||
|
||||
class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,P>> {
|
||||
|
||||
class _EntitiesListWidgetState<T, P>
|
||||
extends TbContextState<EntitiesListWidget<T, P>> {
|
||||
final EntitiesListWidgetController? _controller;
|
||||
|
||||
late final PageKeyController<P> _pageKeyController;
|
||||
|
||||
final StreamController<PageData<T>?> _entitiesStreamController = StreamController.broadcast();
|
||||
final StreamController<PageData<T>?> _entitiesStreamController =
|
||||
StreamController.broadcast();
|
||||
|
||||
_EntitiesListWidgetState(EntitiesListWidgetController? controller):
|
||||
_controller = controller;
|
||||
_EntitiesListWidgetState(EntitiesListWidgetController? controller)
|
||||
: _controller = controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -76,7 +79,7 @@ class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,
|
||||
if (_controller != null) {
|
||||
_controller!._registerEntitiesWidgetState(this);
|
||||
}
|
||||
_refresh();
|
||||
_refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -121,17 +124,15 @@ class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,
|
||||
builder: (context, snapshot) {
|
||||
var title = widget.title;
|
||||
if (snapshot.hasData) {
|
||||
var data = snapshot.data;
|
||||
title += ' (${data!.totalElements})';
|
||||
var data = snapshot.data;
|
||||
title += ' (${data!.totalElements})';
|
||||
}
|
||||
return Text(title,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.5
|
||||
)
|
||||
);
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.5));
|
||||
},
|
||||
),
|
||||
Spacer(),
|
||||
@@ -141,73 +142,62 @@ class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero),
|
||||
child: Text('View all')
|
||||
)
|
||||
child: Text('View all'))
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 64,
|
||||
child: StreamBuilder<PageData<T>?>(
|
||||
stream: _entitiesStreamController.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var data = snapshot.data!;
|
||||
if (data.data.isEmpty) {
|
||||
return _buildNoEntitiesFound(); //return Text('Loaded');
|
||||
stream: _entitiesStreamController.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var data = snapshot.data!;
|
||||
if (data.data.isEmpty) {
|
||||
return _buildNoEntitiesFound(); //return Text('Loaded');
|
||||
} else {
|
||||
return _buildEntitiesView(context, data.data);
|
||||
}
|
||||
} else {
|
||||
return _buildEntitiesView(context, data.data);
|
||||
return Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
}),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
),
|
||||
))),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(25),
|
||||
blurRadius: 10.0,
|
||||
offset: Offset(0, 4)
|
||||
),
|
||||
offset: Offset(0, 4)),
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(18),
|
||||
blurRadius: 30.0,
|
||||
offset: Offset(0, 10)
|
||||
),
|
||||
offset: Offset(0, 10)),
|
||||
],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildNoEntitiesFound() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Color(0xFFDEDEDE),
|
||||
style: BorderStyle.solid,
|
||||
width: 1
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4)
|
||||
),
|
||||
child: Center(
|
||||
child:
|
||||
Text(widget.noItemsFoundText,
|
||||
color: Color(0xFFDEDEDE), style: BorderStyle.solid, width: 1),
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
child: Center(
|
||||
child: Text(widget.noItemsFoundText,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 14,
|
||||
)
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -219,13 +209,11 @@ class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: ScrollController(),
|
||||
children: entities.map((entity) => EntityListCard<T>(
|
||||
entity,
|
||||
entityCardWidgetBuilder: widget.buildEntityListWidgetCard,
|
||||
onEntityTap: widget.onEntityTap,
|
||||
settings: widget.entityListCardSettings(entity),
|
||||
listWidgetCard: true
|
||||
)).toList()
|
||||
));
|
||||
children: entities
|
||||
.map((entity) => EntityListCard<T>(entity,
|
||||
entityCardWidgetBuilder: widget.buildEntityListWidgetCard,
|
||||
onEntityTap: widget.onEntityTap,
|
||||
listWidgetCard: true))
|
||||
.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
@@ -8,18 +6,11 @@ import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
abstract class EntityDetailsPage<T extends BaseData> extends TbPageWidget {
|
||||
final labelTextStyle =
|
||||
TextStyle(color: Color(0xFF757575), fontSize: 14, height: 20 / 14);
|
||||
|
||||
final labelTextStyle = TextStyle(
|
||||
color: Color(0xFF757575),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
|
||||
final valueTextStyle = TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
final valueTextStyle =
|
||||
TextStyle(color: Color(0xFF282828), fontSize: 14, height: 20 / 14);
|
||||
|
||||
final String _defaultTitle;
|
||||
final String _entityId;
|
||||
@@ -29,19 +20,19 @@ abstract class EntityDetailsPage<T extends BaseData> extends TbPageWidget {
|
||||
final double? _appBarElevation;
|
||||
|
||||
EntityDetailsPage(TbContext tbContext,
|
||||
{required String defaultTitle,
|
||||
required String entityId,
|
||||
String? subTitle,
|
||||
bool showLoadingIndicator = true,
|
||||
bool hideAppBar = false,
|
||||
double? appBarElevation}):
|
||||
this._defaultTitle = defaultTitle,
|
||||
this._entityId = entityId,
|
||||
this._subTitle = subTitle,
|
||||
this._showLoadingIndicator = showLoadingIndicator,
|
||||
this._hideAppBar = hideAppBar,
|
||||
this._appBarElevation = appBarElevation,
|
||||
super(tbContext);
|
||||
{required String defaultTitle,
|
||||
required String entityId,
|
||||
String? subTitle,
|
||||
bool showLoadingIndicator = true,
|
||||
bool hideAppBar = false,
|
||||
double? appBarElevation})
|
||||
: this._defaultTitle = defaultTitle,
|
||||
this._entityId = entityId,
|
||||
this._subTitle = subTitle,
|
||||
this._showLoadingIndicator = showLoadingIndicator,
|
||||
this._hideAppBar = hideAppBar,
|
||||
this._appBarElevation = appBarElevation,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_EntityDetailsPageState createState() => _EntityDetailsPageState();
|
||||
@@ -53,11 +44,10 @@ abstract class EntityDetailsPage<T extends BaseData> extends TbPageWidget {
|
||||
}
|
||||
|
||||
Widget buildEntityDetails(BuildContext context, T entity);
|
||||
|
||||
}
|
||||
|
||||
class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDetailsPage<T>> {
|
||||
|
||||
class _EntityDetailsPageState<T extends BaseData>
|
||||
extends TbPageState<EntityDetailsPage<T>> {
|
||||
late Future<T?> entityFuture;
|
||||
late ValueNotifier<String> titleValue;
|
||||
|
||||
@@ -70,7 +60,7 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
|
||||
titleValue = ValueNotifier(widget._defaultTitle);
|
||||
entityFuture.then((value) {
|
||||
if (value is HasName) {
|
||||
titleValue.value = (value as HasName).getName();
|
||||
titleValue.value = (value as HasName).getName();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -82,36 +72,43 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: widget._hideAppBar ? null : TbAppBar(
|
||||
tbContext,
|
||||
showLoadingIndicator: widget._showLoadingIndicator,
|
||||
elevation: widget._appBarElevation,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: titleValue,
|
||||
builder: (context, title, _widget) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title,
|
||||
style: widget._subTitle != null ? Theme.of(context).primaryTextTheme.headline6!.copyWith(
|
||||
fontSize: 16
|
||||
) : null
|
||||
)
|
||||
),
|
||||
if (widget._subTitle != null) Text(widget._subTitle!, style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline6!.color!.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
appBar: widget._hideAppBar
|
||||
? null
|
||||
: TbAppBar(
|
||||
tbContext,
|
||||
showLoadingIndicator: widget._showLoadingIndicator,
|
||||
elevation: widget._appBarElevation,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: titleValue,
|
||||
builder: (context, title, _widget) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title,
|
||||
style: widget._subTitle != null
|
||||
? Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline6!
|
||||
.copyWith(fontSize: 16)
|
||||
: null)),
|
||||
if (widget._subTitle != null)
|
||||
Text(widget._subTitle!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline6!
|
||||
.color!
|
||||
.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12))
|
||||
]);
|
||||
},
|
||||
),
|
||||
),
|
||||
body: FutureBuilder<T?>(
|
||||
future: entityFuture,
|
||||
builder: (context, snapshot) {
|
||||
@@ -123,7 +120,8 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
|
||||
return Center(child: Text('Requested entity does not exists.'));
|
||||
}
|
||||
} else {
|
||||
return Center(child: TbProgressIndicator(
|
||||
return Center(
|
||||
child: TbProgressIndicator(
|
||||
size: 50.0,
|
||||
));
|
||||
}
|
||||
@@ -131,21 +129,24 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class ContactBasedDetailsPage<T extends ContactBased> extends EntityDetailsPage<T> {
|
||||
|
||||
abstract class ContactBasedDetailsPage<T extends ContactBased>
|
||||
extends EntityDetailsPage<T> {
|
||||
ContactBasedDetailsPage(TbContext tbContext,
|
||||
{ required String defaultTitle,
|
||||
required String entityId,
|
||||
String? subTitle,
|
||||
bool showLoadingIndicator = true,
|
||||
bool hideAppBar = false,
|
||||
double? appBarElevation}):
|
||||
super(tbContext, defaultTitle: defaultTitle, entityId: entityId,
|
||||
subTitle: subTitle, showLoadingIndicator: showLoadingIndicator,
|
||||
hideAppBar: hideAppBar, appBarElevation: appBarElevation);
|
||||
{required String defaultTitle,
|
||||
required String entityId,
|
||||
String? subTitle,
|
||||
bool showLoadingIndicator = true,
|
||||
bool hideAppBar = false,
|
||||
double? appBarElevation})
|
||||
: super(tbContext,
|
||||
defaultTitle: defaultTitle,
|
||||
entityId: entityId,
|
||||
subTitle: subTitle,
|
||||
showLoadingIndicator: showLoadingIndicator,
|
||||
hideAppBar: hideAppBar,
|
||||
appBarElevation: appBarElevation);
|
||||
|
||||
@override
|
||||
Widget buildEntityDetails(BuildContext context, T contact) {
|
||||
@@ -201,9 +202,6 @@ abstract class ContactBasedDetailsPage<T extends ContactBased> extends EntityDet
|
||||
SizedBox(height: 16),
|
||||
Text('Email', style: labelTextStyle),
|
||||
Text(contact.email ?? '', style: valueTextStyle),
|
||||
]
|
||||
)
|
||||
);
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'entities_base.dart';
|
||||
|
||||
@@ -11,10 +8,12 @@ class EntityGridCard<T> extends StatelessWidget {
|
||||
final EntityCardWidgetBuilder<T> _entityCardWidgetBuilder;
|
||||
final EntityCardSettings _settings;
|
||||
|
||||
EntityGridCard(T entity, {Key? key, EntityTapFunction<T>? onEntityTap,
|
||||
required EntityCardWidgetBuilder<T> entityCardWidgetBuilder,
|
||||
required EntityCardSettings settings}):
|
||||
this._entity = entity,
|
||||
EntityGridCard(T entity,
|
||||
{Key? key,
|
||||
EntityTapFunction<T>? onEntityTap,
|
||||
required EntityCardWidgetBuilder<T> entityCardWidgetBuilder,
|
||||
required EntityCardSettings settings})
|
||||
: this._entity = entity,
|
||||
this._onEntityTap = onEntityTap,
|
||||
this._entityCardWidgetBuilder = entityCardWidgetBuilder,
|
||||
this._settings = settings,
|
||||
@@ -22,35 +21,31 @@ class EntityGridCard<T> extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child:
|
||||
Container(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: _entityCardWidgetBuilder(context, _entity)
|
||||
),
|
||||
decoration: _settings.dropShadow ? BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: _entityCardWidgetBuilder(context, _entity)),
|
||||
decoration: _settings.dropShadow
|
||||
? BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4))
|
||||
],
|
||||
)
|
||||
],
|
||||
) : null,
|
||||
),
|
||||
onTap: () {
|
||||
if (_onEntityTap != null) {
|
||||
_onEntityTap!(_entity);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
onTap: () {
|
||||
if (_onEntityTap != null) {
|
||||
_onEntityTap!(_entity);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'entities_base.dart';
|
||||
|
||||
@@ -9,58 +7,51 @@ class EntityListCard<T> extends StatelessWidget {
|
||||
final T _entity;
|
||||
final EntityTapFunction<T>? _onEntityTap;
|
||||
final EntityCardWidgetBuilder<T> _entityCardWidgetBuilder;
|
||||
final EntityCardSettings _settings;
|
||||
|
||||
EntityListCard(T entity, {Key? key, EntityTapFunction<T>? onEntityTap,
|
||||
required EntityCardWidgetBuilder<T> entityCardWidgetBuilder,
|
||||
required EntityCardSettings settings,
|
||||
bool listWidgetCard = false}):
|
||||
this._entity = entity,
|
||||
EntityListCard(T entity,
|
||||
{Key? key,
|
||||
EntityTapFunction<T>? onEntityTap,
|
||||
required EntityCardWidgetBuilder<T> entityCardWidgetBuilder,
|
||||
bool listWidgetCard = false})
|
||||
: this._entity = entity,
|
||||
this._onEntityTap = onEntityTap,
|
||||
this._entityCardWidgetBuilder = entityCardWidgetBuilder,
|
||||
this._settings = settings,
|
||||
this._listWidgetCard = listWidgetCard,
|
||||
super(key: key);
|
||||
|
||||
@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(4),
|
||||
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(4),
|
||||
),
|
||||
elevation: 0,
|
||||
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((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)),
|
||||
],
|
||||
),
|
||||
elevation: 0,
|
||||
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((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (_onEntityTap != null) {
|
||||
_onEntityTap!(_entity);
|
||||
}
|
||||
),
|
||||
onTap: () {
|
||||
if (_onEntityTap != null) {
|
||||
_onEntityTap!(_entity);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
|
||||
class ThingsboardInitApp extends TbPageWidget {
|
||||
|
||||
ThingsboardInitApp(TbContext tbContext, {Key? key}) : super(tbContext, key: key);
|
||||
ThingsboardInitApp(TbContext tbContext, {Key? key})
|
||||
: super(tbContext, key: key);
|
||||
|
||||
@override
|
||||
_ThingsboardInitAppState createState() => _ThingsboardInitAppState();
|
||||
|
||||
}
|
||||
|
||||
class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -26,10 +23,7 @@ class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp> {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
color: Colors.white,
|
||||
child: TbProgressIndicator(
|
||||
size: 50.0
|
||||
),
|
||||
child: TbProgressIndicator(size: 50.0),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'init_app.dart';
|
||||
|
||||
class InitRoutes extends TbRoutes {
|
||||
|
||||
late var initHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var initHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return ThingsboardInitApp(tbContext);
|
||||
});
|
||||
|
||||
@@ -18,5 +18,4 @@ class InitRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/", handler: initHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
@@ -14,7 +13,6 @@ import 'config/themes/tb_theme.dart';
|
||||
final appRouter = ThingsboardAppRouter();
|
||||
|
||||
void main() async {
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// await FlutterDownloader.initialize();
|
||||
// await Permission.storage.request();
|
||||
@@ -27,18 +25,18 @@ void main() async {
|
||||
}
|
||||
|
||||
class ThingsboardApp extends StatefulWidget {
|
||||
|
||||
ThingsboardApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ThingsboardAppState createState() => ThingsboardAppState();
|
||||
|
||||
}
|
||||
|
||||
class ThingsboardAppState extends State<ThingsboardApp> with TickerProviderStateMixin implements TbMainDashboardHolder {
|
||||
|
||||
class ThingsboardAppState extends State<ThingsboardApp>
|
||||
with TickerProviderStateMixin
|
||||
implements TbMainDashboardHolder {
|
||||
final TwoPageViewController _mainPageViewController = TwoPageViewController();
|
||||
final MainDashboardPageController _mainDashboardPageController = MainDashboardPageController();
|
||||
final MainDashboardPageController _mainDashboardPageController =
|
||||
MainDashboardPageController();
|
||||
|
||||
final GlobalKey mainAppKey = GlobalKey();
|
||||
final GlobalKey dashboardKey = GlobalKey();
|
||||
@@ -50,8 +48,13 @@ class ThingsboardAppState extends State<ThingsboardApp> with TickerProviderState
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true}) async {
|
||||
await _mainDashboardPageController.openDashboard(dashboardId, dashboardTitle: dashboardTitle, state: state, hideToolbar: hideToolbar);
|
||||
Future<void> navigateToDashboard(String dashboardId,
|
||||
{String? dashboardTitle,
|
||||
String? state,
|
||||
bool? hideToolbar,
|
||||
bool animate = true}) async {
|
||||
await _mainDashboardPageController.openDashboard(dashboardId,
|
||||
dashboardTitle: dashboardTitle, state: state, hideToolbar: hideToolbar);
|
||||
_openDashboard(animate: animate);
|
||||
}
|
||||
|
||||
@@ -121,40 +124,36 @@ class ThingsboardAppState extends State<ThingsboardApp> with TickerProviderState
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.white,
|
||||
statusBarColor: Colors.white,
|
||||
systemNavigationBarIconBrightness: Brightness.light
|
||||
));
|
||||
systemNavigationBarIconBrightness: Brightness.light));
|
||||
return MaterialApp(
|
||||
title: 'ThingsBoard',
|
||||
title: 'ThingsBoard',
|
||||
themeMode: ThemeMode.light,
|
||||
home: TwoPageView(
|
||||
controller: _mainPageViewController,
|
||||
first: MaterialApp(
|
||||
key: mainAppKey,
|
||||
scaffoldMessengerKey: appRouter.tbContext.messengerKey,
|
||||
title: 'ThingsBoard',
|
||||
theme: tbTheme,
|
||||
themeMode: ThemeMode.light,
|
||||
darkTheme: tbDarkTheme,
|
||||
onGenerateRoute: appRouter.router.generator,
|
||||
navigatorObservers: [appRouter.tbContext.routeObserver],
|
||||
),
|
||||
second: MaterialApp(
|
||||
key: dashboardKey,
|
||||
// scaffoldMessengerKey: appRouter.tbContext.messengerKey,
|
||||
title: 'ThingsBoard',
|
||||
theme: tbTheme,
|
||||
themeMode: ThemeMode.light,
|
||||
darkTheme: tbDarkTheme,
|
||||
home: MainDashboardPage(appRouter.tbContext, controller: _mainDashboardPageController),
|
||||
)
|
||||
)
|
||||
);
|
||||
controller: _mainPageViewController,
|
||||
first: MaterialApp(
|
||||
key: mainAppKey,
|
||||
scaffoldMessengerKey: appRouter.tbContext.messengerKey,
|
||||
title: 'ThingsBoard',
|
||||
theme: tbTheme,
|
||||
themeMode: ThemeMode.light,
|
||||
darkTheme: tbDarkTheme,
|
||||
onGenerateRoute: appRouter.router.generator,
|
||||
navigatorObservers: [appRouter.tbContext.routeObserver],
|
||||
),
|
||||
second: MaterialApp(
|
||||
key: dashboardKey,
|
||||
// scaffoldMessengerKey: appRouter.tbContext.messengerKey,
|
||||
title: 'ThingsBoard',
|
||||
theme: tbTheme,
|
||||
themeMode: ThemeMode.light,
|
||||
darkTheme: tbDarkTheme,
|
||||
home: MainDashboardPage(appRouter.tbContext,
|
||||
controller: _mainDashboardPageController),
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:thingsboard_app/modules/alarm/alarms_page.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
class AlarmRoutes extends TbRoutes {
|
||||
|
||||
late var alarmsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var alarmsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
if (searchMode) {
|
||||
return AlarmsPage(tbContext, searchMode: true);
|
||||
@@ -22,5 +22,4 @@ class AlarmRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/alarms", handler: alarmsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:auto_size_text/auto_size_text.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';
|
||||
@@ -8,7 +7,6 @@ import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
|
||||
const Map<AlarmSeverity, Color> alarmSeverityColors = {
|
||||
AlarmSeverity.CRITICAL: Color(0xFFFF0000),
|
||||
AlarmSeverity.MAJOR: Color(0xFFFFA500),
|
||||
@@ -33,7 +31,6 @@ const Map<AlarmStatus, String> alarmStatusTranslations = {
|
||||
};
|
||||
|
||||
mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
|
||||
|
||||
@override
|
||||
String get title => 'Alarms';
|
||||
|
||||
@@ -49,11 +46,14 @@ mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
|
||||
void onEntityTap(AlarmInfo alarm) {
|
||||
String? dashboardId = alarm.details?['dashboardId'];
|
||||
if (dashboardId != null) {
|
||||
var state = Utils.createDashboardEntityState(alarm.originator, entityName: alarm.originatorName);
|
||||
navigateToDashboard(dashboardId, dashboardTitle: alarm.originatorName, state: state);
|
||||
var state = Utils.createDashboardEntityState(alarm.originator,
|
||||
entityName: alarm.originatorName);
|
||||
navigateToDashboard(dashboardId,
|
||||
dashboardTitle: alarm.originatorName, state: state);
|
||||
} else {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
showWarnNotification('Mobile dashboard should be configured in device profile alarm rules!');
|
||||
showWarnNotification(
|
||||
'Mobile dashboard should be configured in device profile alarm rules!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,11 @@ mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
|
||||
}
|
||||
|
||||
class AlarmQueryController extends PageKeyController<AlarmQuery> {
|
||||
|
||||
AlarmQueryController({int pageSize = 20, String? searchText}) : super(AlarmQuery(TimePageLink(pageSize, 0, searchText, SortOrder('createdTime', Direction.DESC)), fetchOriginator: true));
|
||||
AlarmQueryController({int pageSize = 20, String? searchText})
|
||||
: super(AlarmQuery(
|
||||
TimePageLink(pageSize, 0, searchText,
|
||||
SortOrder('createdTime', Direction.DESC)),
|
||||
fetchOriginator: true));
|
||||
|
||||
@override
|
||||
AlarmQuery nextPageKey(AlarmQuery pageKey) {
|
||||
@@ -83,28 +86,24 @@ class AlarmQueryController extends PageKeyController<AlarmQuery> {
|
||||
query.pageLink.textSearch = searchText;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AlarmCard extends TbContextWidget {
|
||||
|
||||
final AlarmInfo alarm;
|
||||
|
||||
AlarmCard(TbContext tbContext, {required this.alarm}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AlarmCardState createState() => _AlarmCardState(alarm);
|
||||
|
||||
}
|
||||
|
||||
class _AlarmCardState extends TbContextState<AlarmCard> {
|
||||
|
||||
bool loading = false;
|
||||
AlarmInfo alarm;
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
_AlarmCardState(this.alarm): super();
|
||||
_AlarmCardState(this.alarm) : super();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -121,161 +120,176 @@ class _AlarmCardState extends TbContextState<AlarmCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (this.loading) {
|
||||
return Container( height: 134, alignment: Alignment.center, child: RefreshProgressIndicator());
|
||||
return Container(
|
||||
height: 134,
|
||||
alignment: Alignment.center,
|
||||
child: RefreshProgressIndicator());
|
||||
} else {
|
||||
bool hasDashboard = alarm.details?['dashboardId'] != null;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
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(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
bottomLeft: Radius.circular(4))),
|
||||
))),
|
||||
Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
SizedBox(width: 4),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
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(
|
||||
entityDateFormat.format(DateTime
|
||||
.fromMillisecondsSinceEpoch(
|
||||
alarm.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
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,
|
||||
fontWeight:
|
||||
FontWeight.normal,
|
||||
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(alarmSeverityTranslations[alarm.severity]!,
|
||||
style: TextStyle(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 12)],
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
if (hasDashboard) Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
if (hasDashboard) SizedBox(width: 16),
|
||||
]
|
||||
),
|
||||
Divider(height: 1),
|
||||
SizedBox(height: 8),
|
||||
height: 16 / 12))),
|
||||
Text(
|
||||
alarmSeverityTranslations[
|
||||
alarm.severity]!,
|
||||
style: TextStyle(
|
||||
color: alarmSeverityColors[
|
||||
alarm.severity]!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12))
|
||||
]),
|
||||
SizedBox(height: 12)
|
||||
],
|
||||
)),
|
||||
SizedBox(width: 16),
|
||||
if (hasDashboard)
|
||||
Icon(Icons.chevron_right,
|
||||
color: Color(0xFFACACAC)),
|
||||
if (hasDashboard) SizedBox(width: 16),
|
||||
]),
|
||||
Divider(height: 1),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(
|
||||
alarmStatusTranslations[alarm.status]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14))),
|
||||
SizedBox(height: 32),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarmStatusTranslations[alarm.status]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Row(
|
||||
children: [
|
||||
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.done, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _ackAlarm(alarm))
|
||||
),
|
||||
if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status))
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.clear, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _clearAlarm(alarm))
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(width: 8)
|
||||
if ([
|
||||
AlarmStatus.CLEARED_UNACK,
|
||||
AlarmStatus.ACTIVE_UNACK
|
||||
].contains(alarm.status))
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.done, size: 18),
|
||||
padding: EdgeInsets.all(7.0),
|
||||
onPressed: () => _ackAlarm(alarm))),
|
||||
if ([
|
||||
AlarmStatus.ACTIVE_UNACK,
|
||||
AlarmStatus.ACTIVE_ACK
|
||||
].contains(alarm.status))
|
||||
Row(children: [
|
||||
SizedBox(width: 4),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.clear, size: 18),
|
||||
padding: EdgeInsets.all(7.0),
|
||||
onPressed: () => _clearAlarm(alarm)))
|
||||
])
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
SizedBox(width: 8)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8)
|
||||
]))
|
||||
])
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_clearAlarm(AlarmInfo alarm) async {
|
||||
var res = await confirm(title: 'Clear Alarm', message: 'Are you sure you want to clear Alarm?', cancel: 'No', ok: 'Yes');
|
||||
var res = await confirm(
|
||||
title: 'Clear Alarm',
|
||||
message: 'Are you sure you want to clear Alarm?',
|
||||
cancel: 'No',
|
||||
ok: 'Yes');
|
||||
if (res != null && res) {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
await tbClient.getAlarmService().clearAlarm(alarm.id!.id!);
|
||||
var newAlarm = await tbClient.getAlarmService().getAlarmInfo(
|
||||
alarm.id!.id!);
|
||||
var newAlarm =
|
||||
await tbClient.getAlarmService().getAlarmInfo(alarm.id!.id!);
|
||||
setState(() {
|
||||
loading = false;
|
||||
this.alarm = newAlarm!;
|
||||
@@ -284,19 +298,22 @@ class _AlarmCardState extends TbContextState<AlarmCard> {
|
||||
}
|
||||
|
||||
_ackAlarm(AlarmInfo alarm) async {
|
||||
var res = await confirm(title: 'Acknowledge Alarm', message: 'Are you sure you want to acknowledge Alarm?', cancel: 'No', ok: 'Yes');
|
||||
var res = await confirm(
|
||||
title: 'Acknowledge Alarm',
|
||||
message: 'Are you sure you want to acknowledge Alarm?',
|
||||
cancel: 'No',
|
||||
ok: 'Yes');
|
||||
if (res != null && res) {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
await tbClient.getAlarmService().ackAlarm(alarm.id!.id!);
|
||||
var newAlarm = await tbClient.getAlarmService().getAlarmInfo(
|
||||
alarm.id!.id!);
|
||||
var newAlarm =
|
||||
await tbClient.getAlarmService().getAlarmInfo(alarm.id!.id!);
|
||||
setState(() {
|
||||
loading = false;
|
||||
this.alarm = newAlarm!;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
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';
|
||||
@@ -6,9 +5,10 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'alarms_base.dart';
|
||||
|
||||
class AlarmsList extends BaseEntitiesWidget<AlarmInfo, AlarmQuery> with AlarmsBase, EntitiesListStateBase {
|
||||
|
||||
AlarmsList(TbContext tbContext, PageKeyController<AlarmQuery> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class AlarmsList extends BaseEntitiesWidget<AlarmInfo, AlarmQuery>
|
||||
with AlarmsBase, EntitiesListStateBase {
|
||||
AlarmsList(
|
||||
TbContext tbContext, PageKeyController<AlarmQuery> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,18 +7,16 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'alarms_list.dart';
|
||||
|
||||
class AlarmsPage extends TbContextWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
AlarmsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AlarmsPageState createState() => _AlarmsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AlarmsPageState extends TbContextState<AlarmsPage> with AutomaticKeepAliveClientMixin<AlarmsPage> {
|
||||
|
||||
class _AlarmsPageState extends TbContextState<AlarmsPage>
|
||||
with AutomaticKeepAliveClientMixin<AlarmsPage> {
|
||||
final AlarmQueryController _alarmQueryController = AlarmQueryController();
|
||||
|
||||
@override
|
||||
@@ -29,32 +27,26 @@ class _AlarmsPageState extends TbContextState<AlarmsPage> with AutomaticKeepAliv
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
var alarmsList = AlarmsList(tbContext, _alarmQueryController, searchMode: widget.searchMode);
|
||||
var alarmsList = AlarmsList(tbContext, _alarmQueryController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _alarmQueryController.onSearchText(searchText),
|
||||
onSearch: (searchText) =>
|
||||
_alarmQueryController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(alarmsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/alarms?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(alarmsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/alarms?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: alarmsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: alarmsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -62,5 +54,4 @@ class _AlarmsPageState extends TbContextState<AlarmsPage> with AutomaticKeepAliv
|
||||
_alarmQueryController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AssetDetailsPage extends EntityDetailsPage<AssetInfo> {
|
||||
|
||||
AssetDetailsPage(TbContext tbContext, String assetId):
|
||||
super(tbContext,
|
||||
entityId: assetId,
|
||||
defaultTitle: 'Asset', subTitle: 'Asset details');
|
||||
AssetDetailsPage(TbContext tbContext, String assetId)
|
||||
: super(tbContext,
|
||||
entityId: assetId,
|
||||
defaultTitle: 'Asset',
|
||||
subTitle: 'Asset details');
|
||||
|
||||
@override
|
||||
Future<AssetInfo?> fetchEntity(String assetId) {
|
||||
@@ -35,9 +35,6 @@ class AssetDetailsPage extends EntityDetailsPage<AssetInfo> {
|
||||
SizedBox(height: 16),
|
||||
Text('Assigned to customer', style: labelTextStyle),
|
||||
Text(asset.customerTitle ?? '', style: valueTextStyle),
|
||||
]
|
||||
)
|
||||
);
|
||||
]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import 'package:thingsboard_app/modules/asset/assets_page.dart';
|
||||
import 'asset_details_page.dart';
|
||||
|
||||
class AssetRoutes extends TbRoutes {
|
||||
|
||||
late var assetsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var assetsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return AssetsPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
|
||||
late var assetDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var assetDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return AssetDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -24,5 +25,4 @@ class AssetRoutes extends TbRoutes {
|
||||
router.define("/assets", handler: assetsHandler);
|
||||
router.define("/asset/:id", handler: assetDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
|
||||
|
||||
@override
|
||||
String get title => 'Assets';
|
||||
|
||||
@@ -16,7 +14,9 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
return tbClient.getAssetService().getTenantAssetInfos(pageLink);
|
||||
} else {
|
||||
return tbClient.getAssetService().getCustomerAssetInfos(tbClient.getAuthUser()!.customerId, pageLink);
|
||||
return tbClient
|
||||
.getAssetService()
|
||||
.getCustomerAssetInfos(tbClient.getAuthUser()!.customerId!, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,115 +41,91 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
|
||||
}
|
||||
|
||||
Widget _buildCard(context, AssetInfo asset) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(asset.createdTime!)),
|
||||
return Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text('${asset.type}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
SizedBox(width: 16)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
)
|
||||
]
|
||||
);
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14))),
|
||||
Text(
|
||||
entityDateFormat.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
asset.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12))
|
||||
]),
|
||||
SizedBox(height: 4),
|
||||
Text('${asset.type}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33))
|
||||
],
|
||||
)),
|
||||
SizedBox(width: 16),
|
||||
Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
SizedBox(width: 16)
|
||||
],
|
||||
),
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildListWidgetCard(BuildContext context, AssetInfo asset) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7
|
||||
))
|
||||
),
|
||||
Text('${asset.type}',
|
||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${asset.name}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7))),
|
||||
Text('${asset.type}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33))
|
||||
],
|
||||
))
|
||||
])))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'assets_base.dart';
|
||||
|
||||
class AssetsList extends BaseEntitiesWidget<AssetInfo, PageLink> with AssetsBase, EntitiesListStateBase {
|
||||
|
||||
AssetsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class AssetsList extends BaseEntitiesWidget<AssetInfo, PageLink>
|
||||
with AssetsBase, EntitiesListStateBase {
|
||||
AssetsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ 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<AssetInfo> with AssetsBase {
|
||||
|
||||
AssetsListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller);
|
||||
class AssetsListWidget extends EntitiesListPageLinkWidget<AssetInfo>
|
||||
with AssetsBase {
|
||||
AssetsListWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
navigateTo('/assets');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,23 +7,21 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'assets_list.dart';
|
||||
|
||||
class AssetsPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
AssetsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AssetsPageState createState() => _AssetsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AssetsPageState extends TbPageState<AssetsPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var assetsList = AssetsList(tbContext, _pageLinkController, searchMode: widget.searchMode);
|
||||
var assetsList = AssetsList(tbContext, _pageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
@@ -31,24 +29,16 @@ class _AssetsPageState extends TbPageState<AssetsPage> {
|
||||
onSearch: (searchText) => _pageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(assetsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/assets?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(assetsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/assets?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: assetsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: assetsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -56,5 +46,4 @@ class _AssetsPageState extends TbPageState<AssetsPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,29 +9,20 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AuditLogDetailsPage extends TbContextWidget {
|
||||
|
||||
final AuditLog auditLog;
|
||||
|
||||
AuditLogDetailsPage(TbContext tbContext, this.auditLog) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogDetailsPageState createState() => _AuditLogDetailsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
final labelTextStyle =
|
||||
TextStyle(color: Color(0xFF757575), fontSize: 14, height: 20 / 14);
|
||||
|
||||
final labelTextStyle = TextStyle(
|
||||
color: Color(0xFF757575),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
|
||||
final valueTextStyle = TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
final valueTextStyle =
|
||||
TextStyle(color: Color(0xFF282828), fontSize: 14, height: 20 / 14);
|
||||
|
||||
final JsonEncoder encoder = new JsonEncoder.withIndent(' ');
|
||||
|
||||
@@ -39,51 +30,52 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.auditLog.entityName != null)
|
||||
Text(widget.auditLog.entityName!, style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
height: 20 / 16
|
||||
)),
|
||||
Text('Audit log details', style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline6!.color!.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: TbAppBar(tbContext,
|
||||
title:
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
if (widget.auditLog.entityName != null)
|
||||
Text(widget.auditLog.entityName!,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
height: 20 / 16)),
|
||||
Text('Audit log details',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline6!
|
||||
.color!
|
||||
.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12))
|
||||
])),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text('Entity Type', style: labelTextStyle),
|
||||
Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!, style: valueTextStyle),
|
||||
SizedBox(height: 16),
|
||||
Text('Type', style: labelTextStyle),
|
||||
Text(actionTypeTranslations[widget.auditLog.actionType]!, style: valueTextStyle),
|
||||
SizedBox(height: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: buildBorderedText('Action data', encoder.convert(widget.auditLog.actionData))
|
||||
),
|
||||
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text('Entity Type', style: labelTextStyle),
|
||||
Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!,
|
||||
style: valueTextStyle),
|
||||
SizedBox(height: 16),
|
||||
Text('Type', style: labelTextStyle),
|
||||
Text(actionTypeTranslations[widget.auditLog.actionType]!,
|
||||
style: valueTextStyle),
|
||||
SizedBox(height: 16),
|
||||
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: buildBorderedText('Failure details', widget.auditLog.actionFailureDetails!)
|
||||
)
|
||||
]
|
||||
),
|
||||
fit: FlexFit.loose,
|
||||
child: buildBorderedText('Action data',
|
||||
encoder.convert(widget.auditLog.actionData))),
|
||||
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
|
||||
SizedBox(height: 16),
|
||||
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: buildBorderedText('Failure details',
|
||||
widget.auditLog.actionFailureDetails!))
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -96,8 +88,7 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
padding: EdgeInsets.fromLTRB(16, 18, 48, 18),
|
||||
margin: EdgeInsets.only(top: 6),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Color(0xFFDEDEDE), width: 1),
|
||||
border: Border.all(color: Color(0xFFDEDEDE), width: 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
shape: BoxShape.rectangle,
|
||||
),
|
||||
@@ -105,10 +96,7 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
child: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
color: Color(0xFF282828), fontSize: 14, height: 20 / 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -120,11 +108,11 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
|
||||
color: Colors.white,
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(color: Color(0xFF757575), fontSize: 12, height: 14 / 12),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF757575), fontSize: 12, height: 14 / 12),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:auto_size_text/auto_size_text.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';
|
||||
@@ -46,7 +45,6 @@ const Map<ActionStatus, String> actionStatusTranslations = {
|
||||
};
|
||||
|
||||
mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
|
||||
|
||||
@override
|
||||
String get title => 'Audit Logs';
|
||||
|
||||
@@ -59,8 +57,7 @@ mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
|
||||
}
|
||||
|
||||
@override
|
||||
void onEntityTap(AuditLog auditLog) {
|
||||
}
|
||||
void onEntityTap(AuditLog auditLog) {}
|
||||
|
||||
@override
|
||||
Widget buildEntityListCard(BuildContext context, AuditLog auditLog) {
|
||||
@@ -73,21 +70,18 @@ mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
|
||||
}
|
||||
|
||||
class AuditLogCard extends TbContextWidget {
|
||||
|
||||
final AuditLog auditLog;
|
||||
|
||||
AuditLogCard(TbContext tbContext, {required this.auditLog}) : super(tbContext);
|
||||
AuditLogCard(TbContext tbContext, {required this.auditLog})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogCardState createState() => _AuditLogCardState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogCardState extends TbContextState<AuditLogCard> {
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -100,131 +94,141 @@ class _AuditLogCardState extends TbContextState<AuditLogCard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.auditLog.actionStatus == ActionStatus.SUCCESS ? Color(0xFF008A00) : Color(0xFFFF0000),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
widget.auditLog.actionStatus == ActionStatus.SUCCESS
|
||||
? Color(0xFF008A00)
|
||||
: Color(0xFFFF0000),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
bottomLeft: Radius.circular(4))),
|
||||
))),
|
||||
Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
SizedBox(width: 4),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AutoSizeText(widget.auditLog.entityName ?? '',
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.auditLog.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
),
|
||||
Text(actionStatusTranslations[widget.auditLog.actionStatus]!,
|
||||
style: TextStyle(
|
||||
color: widget.auditLog.actionStatus == ActionStatus.SUCCESS ? Color(0xFF008A00) : Color(0xFFFF0000),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 12)],
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(actionTypeTranslations[widget.auditLog.actionType]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.code, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _auditLogDetails(widget.auditLog))
|
||||
),
|
||||
SizedBox(width: 8)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AutoSizeText(
|
||||
widget.auditLog.entityName ??
|
||||
'',
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14))),
|
||||
Text(
|
||||
entityDateFormat.format(DateTime
|
||||
.fromMillisecondsSinceEpoch(
|
||||
widget.auditLog
|
||||
.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12))
|
||||
]),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(
|
||||
entityTypeTranslations[widget
|
||||
.auditLog
|
||||
.entityId
|
||||
.entityType]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight:
|
||||
FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12))),
|
||||
Text(
|
||||
actionStatusTranslations[
|
||||
widget.auditLog.actionStatus]!,
|
||||
style: TextStyle(
|
||||
color: widget.auditLog
|
||||
.actionStatus ==
|
||||
ActionStatus.SUCCESS
|
||||
? Color(0xFF008A00)
|
||||
: Color(0xFFFF0000),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12))
|
||||
]),
|
||||
SizedBox(height: 12)
|
||||
],
|
||||
)),
|
||||
SizedBox(width: 16)
|
||||
]),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(
|
||||
actionTypeTranslations[
|
||||
widget.auditLog.actionType]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14))),
|
||||
SizedBox(height: 32),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.code, size: 18),
|
||||
padding: EdgeInsets.all(7.0),
|
||||
onPressed: () =>
|
||||
_auditLogDetails(widget.auditLog))),
|
||||
SizedBox(width: 8)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8)
|
||||
]))
|
||||
])
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_auditLogDetails(AuditLog auditLog) {
|
||||
tbContext.showFullScreenDialog(new AuditLogDetailsPage(tbContext, auditLog));
|
||||
tbContext
|
||||
.showFullScreenDialog(new AuditLogDetailsPage(tbContext, auditLog));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import 'package:thingsboard_app/core/entity/entities_list.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_logs_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AuditLogsList extends BaseEntitiesWidget<AuditLog, TimePageLink> with AuditLogsBase, EntitiesListStateBase {
|
||||
|
||||
AuditLogsList(TbContext tbContext, PageKeyController<TimePageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class AuditLogsList extends BaseEntitiesWidget<AuditLog, TimePageLink>
|
||||
with AuditLogsBase, EntitiesListStateBase {
|
||||
AuditLogsList(
|
||||
TbContext tbContext, PageKeyController<TimePageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
@@ -6,48 +6,41 @@ import 'package:thingsboard_app/modules/audit_log/audit_logs_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class AuditLogsPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
AuditLogsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
AuditLogsPage(TbContext tbContext, {this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogsPageState createState() => _AuditLogsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogsPageState extends TbPageState<AuditLogsPage> {
|
||||
|
||||
final TimePageLinkController _timePageLinkController = TimePageLinkController();
|
||||
final TimePageLinkController _timePageLinkController =
|
||||
TimePageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var auditLogsList = AuditLogsList(tbContext, _timePageLinkController, searchMode: widget.searchMode);
|
||||
var auditLogsList = AuditLogsList(tbContext, _timePageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _timePageLinkController.onSearchText(searchText),
|
||||
onSearch: (searchText) =>
|
||||
_timePageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(auditLogsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/auditLogs?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(auditLogsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/auditLogs?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: auditLogsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: auditLogsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -55,5 +48,4 @@ class _AuditLogsPageState extends TbPageState<AuditLogsPage> {
|
||||
_timePageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_logs_page.dart';
|
||||
|
||||
class AuditLogsRoutes extends TbRoutes {
|
||||
|
||||
late var auditLogsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var auditLogsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return AuditLogsPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
@@ -17,5 +17,4 @@ class AuditLogsRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/auditLogs", handler: auditLogsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class CustomerDetailsPage extends ContactBasedDetailsPage<Customer> {
|
||||
|
||||
CustomerDetailsPage(TbContext tbContext, String customerId):
|
||||
super(tbContext, entityId: customerId, defaultTitle: 'Customer', subTitle: 'Customer details');
|
||||
CustomerDetailsPage(TbContext tbContext, String customerId)
|
||||
: super(tbContext,
|
||||
entityId: customerId,
|
||||
defaultTitle: 'Customer',
|
||||
subTitle: 'Customer details');
|
||||
|
||||
@override
|
||||
Future<Customer?> fetchEntity(String customerId) {
|
||||
return tbClient.getCustomerService().getCustomer(customerId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import 'customer_details_page.dart';
|
||||
import 'customers_page.dart';
|
||||
|
||||
class CustomerRoutes extends TbRoutes {
|
||||
|
||||
late var customersHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var customersHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return CustomersPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
|
||||
late var customerDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var customerDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return CustomerDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -23,5 +24,4 @@ class CustomerRoutes extends TbRoutes {
|
||||
router.define("/customers", handler: customersHandler);
|
||||
router.define("/customer/:id", handler: customerDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin CustomersBase on EntitiesBase<Customer, PageLink> {
|
||||
|
||||
mixin CustomersBase on EntitiesBase<Customer, PageLink> {
|
||||
@override
|
||||
String get title => 'Customers';
|
||||
|
||||
@@ -18,5 +17,4 @@ mixin CustomersBase on EntitiesBase<Customer, PageLink> {
|
||||
void onEntityTap(Customer customer) {
|
||||
navigateTo('/customer/${customer.id!.id}');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'customers_base.dart';
|
||||
|
||||
class CustomersList extends BaseEntitiesWidget<Customer, PageLink> with CustomersBase, ContactBasedBase, EntitiesListStateBase {
|
||||
|
||||
CustomersList(TbContext tbContext, PageKeyController<PageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class CustomersList extends BaseEntitiesWidget<Customer, PageLink>
|
||||
with CustomersBase, ContactBasedBase, EntitiesListStateBase {
|
||||
CustomersList(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
@@ -6,23 +6,22 @@ import 'package:thingsboard_app/modules/customer/customers_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class CustomersPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
CustomersPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
CustomersPage(TbContext tbContext, {this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_CustomersPageState createState() => _CustomersPageState();
|
||||
|
||||
}
|
||||
|
||||
class _CustomersPageState extends TbPageState<CustomersPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var customersList = CustomersList(tbContext, _pageLinkController, searchMode: widget.searchMode);
|
||||
var customersList = CustomersList(tbContext, _pageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
@@ -30,24 +29,16 @@ class _CustomersPageState extends TbPageState<CustomersPage> {
|
||||
onSearch: (searchText) => _pageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(customersList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/customers?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(customersList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/customers?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: customersList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: customersList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -55,5 +46,4 @@ class _CustomersPageState extends TbPageState<CustomersPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:thingsboard_app/constants/app_constants.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
@@ -11,10 +10,9 @@ import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_app/widgets/two_value_listenable_builder.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class DashboardController {
|
||||
|
||||
final ValueNotifier<bool> canGoBack = ValueNotifier(false);
|
||||
final ValueNotifier<bool> hasRightLayout = ValueNotifier(false);
|
||||
final ValueNotifier<bool> rightLayoutOpened = ValueNotifier(false);
|
||||
@@ -22,8 +20,10 @@ class DashboardController {
|
||||
final _DashboardState dashboardState;
|
||||
DashboardController(this.dashboardState);
|
||||
|
||||
Future<void> openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
return await dashboardState._openDashboard(dashboardId, state: state, hideToolbar: hideToolbar, fullscreen: fullscreen);
|
||||
Future<void> openDashboard(String dashboardId,
|
||||
{String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
return await dashboardState._openDashboard(dashboardId,
|
||||
state: state, hideToolbar: hideToolbar, fullscreen: fullscreen);
|
||||
}
|
||||
|
||||
Future<bool> goBack() async {
|
||||
@@ -59,22 +59,26 @@ class DashboardController {
|
||||
hasRightLayout.dispose();
|
||||
rightLayoutOpened.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
typedef DashboardTitleCallback = void Function(String title);
|
||||
|
||||
typedef DashboardControllerCallback = void Function(DashboardController controller);
|
||||
typedef DashboardControllerCallback = void Function(
|
||||
DashboardController controller);
|
||||
|
||||
class Dashboard extends TbContextWidget {
|
||||
|
||||
final bool? _home;
|
||||
final bool _activeByDefault;
|
||||
final DashboardTitleCallback? _titleCallback;
|
||||
final DashboardControllerCallback? _controllerCallback;
|
||||
|
||||
Dashboard(TbContext tbContext, {Key? key, bool? home, bool activeByDefault = true, DashboardTitleCallback? titleCallback, DashboardControllerCallback? controllerCallback}):
|
||||
this._home = home,
|
||||
Dashboard(TbContext tbContext,
|
||||
{Key? key,
|
||||
bool? home,
|
||||
bool activeByDefault = true,
|
||||
DashboardTitleCallback? titleCallback,
|
||||
DashboardControllerCallback? controllerCallback})
|
||||
: this._home = home,
|
||||
this._activeByDefault = activeByDefault,
|
||||
this._titleCallback = titleCallback,
|
||||
this._controllerCallback = controllerCallback,
|
||||
@@ -82,12 +86,11 @@ class Dashboard extends TbContextWidget {
|
||||
|
||||
@override
|
||||
_DashboardState createState() => _DashboardState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardState extends TbContextState<Dashboard> {
|
||||
|
||||
final Completer<InAppWebViewController> _controller = Completer<InAppWebViewController>();
|
||||
final Completer<InAppWebViewController> _controller =
|
||||
Completer<InAppWebViewController>();
|
||||
|
||||
bool webViewLoading = true;
|
||||
final ValueNotifier<bool> dashboardLoading = ValueNotifier(true);
|
||||
@@ -98,8 +101,6 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
|
||||
late final DashboardController _dashboardController;
|
||||
|
||||
bool _fullscreen = false;
|
||||
|
||||
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(
|
||||
useShouldOverrideUrlLoading: true,
|
||||
@@ -110,13 +111,10 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
// useOnDownloadStart: true
|
||||
),
|
||||
android: AndroidInAppWebViewOptions(
|
||||
useHybridComposition: true,
|
||||
thirdPartyCookiesEnabled: true
|
||||
),
|
||||
useHybridComposition: true, thirdPartyCookiesEnabled: true),
|
||||
ios: IOSInAppWebViewOptions(
|
||||
allowsInlineMediaPlayback: true,
|
||||
allowsBackForwardNavigationGestures: false
|
||||
));
|
||||
allowsInlineMediaPlayback: true,
|
||||
allowsBackForwardNavigationGestures: false));
|
||||
|
||||
late Uri _initialUrl;
|
||||
|
||||
@@ -137,7 +135,8 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
void _onAuthenticated() async {
|
||||
if (tbContext.isAuthenticated) {
|
||||
if (!readyState.value) {
|
||||
_initialUrl = Uri.parse(ThingsboardAppConstants.thingsBoardApiEndpoint + '?accessToken=${tbClient.getJwtToken()!}&refreshToken=${tbClient.getRefreshToken()!}');
|
||||
_initialUrl = Uri.parse(ThingsboardAppConstants.thingsBoardApiEndpoint +
|
||||
'?accessToken=${tbClient.getJwtToken()!}&refreshToken=${tbClient.getRefreshToken()!}');
|
||||
readyState.value = true;
|
||||
} else {
|
||||
var windowMessage = <String, dynamic>{
|
||||
@@ -193,8 +192,8 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
_fullscreen = fullscreen;
|
||||
Future<void> _openDashboard(String dashboardId,
|
||||
{String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
dashboardLoading.value = true;
|
||||
InAppWebViewController? controller;
|
||||
if (!UniversalPlatform.isWeb) {
|
||||
@@ -202,9 +201,7 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
}
|
||||
var windowMessage = <String, dynamic>{
|
||||
'type': 'openDashboardMessage',
|
||||
'data': <String, dynamic>{
|
||||
'dashboardId': dashboardId
|
||||
}
|
||||
'data': <String, dynamic>{'dashboardId': dashboardId}
|
||||
};
|
||||
if (state != null) {
|
||||
windowMessage['data']['state'] = state;
|
||||
@@ -217,18 +214,17 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
}
|
||||
var webMessage = WebMessage(data: jsonEncode(windowMessage));
|
||||
if (!UniversalPlatform.isWeb) {
|
||||
await controller!.postWebMessage(
|
||||
message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
await controller!
|
||||
.postWebMessage(message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleRightLayout() async {
|
||||
var controller = await _controller.future;
|
||||
var windowMessage = <String, dynamic>{
|
||||
'type': 'toggleDashboardLayout'
|
||||
};
|
||||
var windowMessage = <String, dynamic>{'type': 'toggleDashboardLayout'};
|
||||
var webMessage = WebMessage(data: jsonEncode(windowMessage));
|
||||
await controller.postWebMessage(message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
await controller.postWebMessage(
|
||||
message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
}
|
||||
|
||||
Future<void> tryLocalNavigation(String? path) async {
|
||||
@@ -244,7 +240,8 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
'customers',
|
||||
'auditLogs'
|
||||
].contains(parts[0])) {
|
||||
if ((parts[0] == 'dashboard' || parts[0] == 'dashboards') && parts.length > 1) {
|
||||
if ((parts[0] == 'dashboard' || parts[0] == 'dashboards') &&
|
||||
parts.length > 1) {
|
||||
var dashboardId = parts[1];
|
||||
await navigateToDashboard(dashboardId);
|
||||
} else if (parts[0] != 'dashboard') {
|
||||
@@ -261,147 +258,176 @@ class _DashboardState extends TbContextState<Dashboard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (widget._home == true && !tbContext.isHomePage()) {
|
||||
return true;
|
||||
}
|
||||
if (readyState.value) {
|
||||
return await _goBack();
|
||||
onWillPop: () async {
|
||||
if (widget._home == true && !tbContext.isHomePage()) {
|
||||
return true;
|
||||
}
|
||||
if (readyState.value) {
|
||||
return await _goBack();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: readyState,
|
||||
builder: (BuildContext context, bool ready, child) {
|
||||
if (!ready) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child:
|
||||
ValueListenableBuilder(
|
||||
valueListenable: readyState,
|
||||
builder: (BuildContext context, bool ready, child) {
|
||||
if (!ready) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
return Stack(
|
||||
children: [
|
||||
UniversalPlatform.isWeb ? Center(child: Text('Not implemented!')) :
|
||||
InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: _initialUrl),
|
||||
initialOptions: options,
|
||||
onWebViewCreated: (webViewController) {
|
||||
log.debug("onWebViewCreated");
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLoadedHandler", callback: (args) async {
|
||||
bool hasRightLayout = args[0];
|
||||
bool rightLayoutOpened = args[1];
|
||||
log.debug("Invoked tbMobileDashboardLoadedHandler: hasRightLayout: $hasRightLayout, rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController.onHasRightLayout(hasRightLayout);
|
||||
_dashboardController.onRightLayoutOpened(rightLayoutOpened);
|
||||
dashboardLoading.value = false;
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLayoutHandler", callback: (args) async {
|
||||
bool rightLayoutOpened = args[0];
|
||||
log.debug("Invoked tbMobileDashboardLayoutHandler: rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController.onRightLayoutOpened(rightLayoutOpened);
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardStateNameHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileDashboardStateNameHandler: $args");
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
if (widget._titleCallback != null) {
|
||||
widget._titleCallback!(args[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileNavigationHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileNavigationHandler: $args");
|
||||
if (args.length > 0) {
|
||||
String? path = args[0];
|
||||
Map<String, dynamic>? params;
|
||||
if (args.length > 1) {
|
||||
params = args[1];
|
||||
}
|
||||
log.debug("path: $path");
|
||||
log.debug("params: $params");
|
||||
tryLocalNavigation(path);
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileHandler: $args");
|
||||
return await widgetActionHandler.handleWidgetMobileAction(args, webViewController);
|
||||
});
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
var uri = navigationAction.request.url!;
|
||||
var uriString = uri.toString();
|
||||
log.debug('shouldOverrideUrlLoading $uriString');
|
||||
if (Platform.isAndroid || Platform.isIOS && navigationAction.iosWKNavigationType == IOSWKNavigationType.LINK_ACTIVATED) {
|
||||
if (uriString.startsWith(ThingsboardAppConstants.thingsBoardApiEndpoint)) {
|
||||
var target = uriString.substring(ThingsboardAppConstants.thingsBoardApiEndpoint.length);
|
||||
if (!target.startsWith("?accessToken")) {
|
||||
if (target.startsWith("/")) {
|
||||
target = target.substring(1);
|
||||
return Stack(children: [
|
||||
UniversalPlatform.isWeb
|
||||
? Center(child: Text('Not implemented!'))
|
||||
: InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: _initialUrl),
|
||||
initialOptions: options,
|
||||
onWebViewCreated: (webViewController) {
|
||||
log.debug("onWebViewCreated");
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileDashboardLoadedHandler",
|
||||
callback: (args) async {
|
||||
bool hasRightLayout = args[0];
|
||||
bool rightLayoutOpened = args[1];
|
||||
log.debug(
|
||||
"Invoked tbMobileDashboardLoadedHandler: hasRightLayout: $hasRightLayout, rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController
|
||||
.onHasRightLayout(hasRightLayout);
|
||||
_dashboardController
|
||||
.onRightLayoutOpened(rightLayoutOpened);
|
||||
dashboardLoading.value = false;
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileDashboardLayoutHandler",
|
||||
callback: (args) async {
|
||||
bool rightLayoutOpened = args[0];
|
||||
log.debug(
|
||||
"Invoked tbMobileDashboardLayoutHandler: rightLayoutOpened: $rightLayoutOpened");
|
||||
_dashboardController
|
||||
.onRightLayoutOpened(rightLayoutOpened);
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName:
|
||||
"tbMobileDashboardStateNameHandler",
|
||||
callback: (args) async {
|
||||
log.debug(
|
||||
"Invoked tbMobileDashboardStateNameHandler: $args");
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
if (widget._titleCallback != null) {
|
||||
widget._titleCallback!(args[0]);
|
||||
}
|
||||
await tryLocalNavigation(target);
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
} else if (await canLaunch(uriString)) {
|
||||
await launch(
|
||||
uriString,
|
||||
);
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileNavigationHandler",
|
||||
callback: (args) async {
|
||||
log.debug(
|
||||
"Invoked tbMobileNavigationHandler: $args");
|
||||
if (args.length > 0) {
|
||||
String? path = args[0];
|
||||
Map<String, dynamic>? params;
|
||||
if (args.length > 1) {
|
||||
params = args[1];
|
||||
}
|
||||
log.debug("path: $path");
|
||||
log.debug("params: $params");
|
||||
tryLocalNavigation(path);
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(
|
||||
handlerName: "tbMobileHandler",
|
||||
callback: (args) async {
|
||||
log.debug("Invoked tbMobileHandler: $args");
|
||||
return await widgetActionHandler
|
||||
.handleWidgetMobileAction(
|
||||
args, webViewController);
|
||||
});
|
||||
},
|
||||
shouldOverrideUrlLoading:
|
||||
(controller, navigationAction) async {
|
||||
var uri = navigationAction.request.url!;
|
||||
var uriString = uri.toString();
|
||||
log.debug('shouldOverrideUrlLoading $uriString');
|
||||
if (Platform.isAndroid ||
|
||||
Platform.isIOS &&
|
||||
navigationAction.iosWKNavigationType ==
|
||||
IOSWKNavigationType.LINK_ACTIVATED) {
|
||||
if (uriString.startsWith(ThingsboardAppConstants
|
||||
.thingsBoardApiEndpoint)) {
|
||||
var target = uriString.substring(
|
||||
ThingsboardAppConstants
|
||||
.thingsBoardApiEndpoint.length);
|
||||
if (!target.startsWith("?accessToken")) {
|
||||
if (target.startsWith("/")) {
|
||||
target = target.substring(1);
|
||||
}
|
||||
await tryLocalNavigation(target);
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
} else if (await canLaunchUrlString(uriString)) {
|
||||
await launchUrlString(
|
||||
uriString,
|
||||
);
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
return Platform.isIOS ? NavigationActionPolicy.ALLOW : NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
onUpdateVisitedHistory: (controller, url, androidIsReload) async {
|
||||
log.debug('onUpdateVisitedHistory: $url');
|
||||
_dashboardController.onHistoryUpdated(controller.canGoBack());
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
log.debug('[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
|
||||
},
|
||||
onLoadStart: (controller, url) async {
|
||||
log.debug('onLoadStart: $url');
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
log.debug('onLoadStop: $url');
|
||||
if (webViewLoading) {
|
||||
webViewLoading = false;
|
||||
_controller.complete(controller);
|
||||
}
|
||||
},
|
||||
androidOnPermissionRequest: (controller, origin, resources) async {
|
||||
log.debug('androidOnPermissionRequest origin: $origin, resources: $resources');
|
||||
return PermissionRequestResponse(
|
||||
resources: resources,
|
||||
action: PermissionRequestResponseAction.GRANT);
|
||||
},
|
||||
),
|
||||
if (!UniversalPlatform.isWeb)
|
||||
TwoValueListenableBuilder(
|
||||
firstValueListenable: dashboardLoading,
|
||||
secondValueListenable: dashboardActive,
|
||||
builder: (BuildContext context, bool loading, bool active, child) {
|
||||
if (!loading && active) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
var bottomPadding = data.padding.top;
|
||||
if (widget._home != true) {
|
||||
bottomPadding += kToolbarHeight;
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
alignment: Alignment.center,
|
||||
color: Colors.white,
|
||||
child: TbProgressIndicator(
|
||||
size: 50.0
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return Platform.isIOS
|
||||
? NavigationActionPolicy.ALLOW
|
||||
: NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
onUpdateVisitedHistory:
|
||||
(controller, url, androidIsReload) async {
|
||||
log.debug('onUpdateVisitedHistory: $url');
|
||||
_dashboardController
|
||||
.onHistoryUpdated(controller.canGoBack());
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
log.debug(
|
||||
'[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
|
||||
},
|
||||
onLoadStart: (controller, url) async {
|
||||
log.debug('onLoadStart: $url');
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
log.debug('onLoadStop: $url');
|
||||
if (webViewLoading) {
|
||||
webViewLoading = false;
|
||||
_controller.complete(controller);
|
||||
}
|
||||
},
|
||||
androidOnPermissionRequest:
|
||||
(controller, origin, resources) async {
|
||||
log.debug(
|
||||
'androidOnPermissionRequest origin: $origin, resources: $resources');
|
||||
return PermissionRequestResponse(
|
||||
resources: resources,
|
||||
action: PermissionRequestResponseAction.GRANT);
|
||||
},
|
||||
),
|
||||
if (!UniversalPlatform.isWeb)
|
||||
TwoValueListenableBuilder(
|
||||
firstValueListenable: dashboardLoading,
|
||||
secondValueListenable: dashboardActive,
|
||||
builder: (BuildContext context, bool loading,
|
||||
bool active, child) {
|
||||
if (!loading && active) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
var data = MediaQueryData.fromWindow(
|
||||
WidgetsBinding.instance.window);
|
||||
var bottomPadding = data.padding.top;
|
||||
if (widget._home != true) {
|
||||
bottomPadding += kToolbarHeight;
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
alignment: Alignment.center,
|
||||
color: Colors.white,
|
||||
child: TbProgressIndicator(size: 50.0),
|
||||
);
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DashboardPage extends TbPageWidget {
|
||||
|
||||
final String? _dashboardTitle;
|
||||
final String? _dashboardId;
|
||||
final String? _state;
|
||||
final bool? _fullscreen;
|
||||
// final String? _dashboardId;
|
||||
// final String? _state;
|
||||
// final bool? _fullscreen;
|
||||
|
||||
DashboardPage(TbContext tbContext, {String? dashboardId, bool? fullscreen, String? dashboardTitle, String? state}):
|
||||
_dashboardId = dashboardId,
|
||||
_fullscreen = fullscreen,
|
||||
DashboardPage(TbContext tbContext,
|
||||
{String? dashboardId,
|
||||
bool? fullscreen,
|
||||
String? dashboardTitle,
|
||||
String? state})
|
||||
:
|
||||
// _dashboardId = dashboardId,
|
||||
// _fullscreen = fullscreen,
|
||||
_dashboardTitle = dashboardTitle,
|
||||
_state = state,
|
||||
// _state = state,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardPageState createState() => _DashboardPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardPageState extends TbPageState<DashboardPage> {
|
||||
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
|
||||
@override
|
||||
@@ -37,27 +37,26 @@ class _DashboardPageState extends TbPageState<DashboardPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
showLoadingIndicator: false,
|
||||
elevation: 0,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
},
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
showLoadingIndicator: false,
|
||||
elevation: 0,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Text('Deprecated') //Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
|
||||
//fullscreen: widget._fullscreen, titleCallback: (title) {
|
||||
body: Text(
|
||||
'Deprecated') //Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
|
||||
//fullscreen: widget._fullscreen, titleCallback: (title) {
|
||||
//dashboardTitleValue.value = title;
|
||||
//}
|
||||
//),
|
||||
);
|
||||
//}
|
||||
//),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,20 +8,25 @@ import 'package:thingsboard_app/modules/dashboard/fullscreen_dashboard_page.dart
|
||||
import 'dashboard_page.dart';
|
||||
|
||||
class DashboardRoutes extends TbRoutes {
|
||||
|
||||
late var dashboardsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var dashboardsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DashboardsPage(tbContext);
|
||||
});
|
||||
|
||||
late var dashboardDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||
late var dashboardDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||
var fullscreen = params['fullscreen']?.first == 'true';
|
||||
var dashboardTitle = params['title']?.first;
|
||||
var state = params['state']?.first;
|
||||
return DashboardPage(tbContext, dashboardId: params["id"]![0], fullscreen: fullscreen,
|
||||
dashboardTitle: dashboardTitle, state: state);
|
||||
return DashboardPage(tbContext,
|
||||
dashboardId: params["id"]![0],
|
||||
fullscreen: fullscreen,
|
||||
dashboardTitle: dashboardTitle,
|
||||
state: state);
|
||||
});
|
||||
|
||||
late var fullscreenDashboardHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var fullscreenDashboardHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return FullscreenDashboardPage(tbContext, params["id"]![0]);
|
||||
});
|
||||
|
||||
@@ -31,7 +36,7 @@ class DashboardRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/dashboards", handler: dashboardsHandler);
|
||||
router.define("/dashboard/:id", handler: dashboardDetailsHandler);
|
||||
router.define("/fullscreenDashboard/:id", handler: fullscreenDashboardHandler);
|
||||
router.define("/fullscreenDashboard/:id",
|
||||
handler: fullscreenDashboardHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.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/core/context/tb_context.dart';
|
||||
@@ -10,7 +9,6 @@ import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
|
||||
@override
|
||||
String get title => 'Dashboards';
|
||||
|
||||
@@ -20,9 +18,13 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
@override
|
||||
Future<PageData<DashboardInfo>> fetchEntities(PageLink pageLink) {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
return tbClient.getDashboardService().getTenantDashboards(pageLink, mobile: true);
|
||||
return tbClient
|
||||
.getDashboardService()
|
||||
.getTenantDashboards(pageLink, mobile: true);
|
||||
} else {
|
||||
return tbClient.getDashboardService().getCustomerDashboards(tbClient.getAuthUser()!.customerId, pageLink, mobile: true);
|
||||
return tbClient.getDashboardService().getCustomerDashboards(
|
||||
tbClient.getAuthUser()!.customerId!, pageLink,
|
||||
mobile: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,34 +41,37 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityListWidgetCard(BuildContext context, DashboardInfo dashboard) {
|
||||
Widget buildEntityListWidgetCard(
|
||||
BuildContext context, DashboardInfo dashboard) {
|
||||
return _buildEntityListCard(context, dashboard, true);
|
||||
}
|
||||
|
||||
@override
|
||||
EntityCardSettings entityGridCardSettings(DashboardInfo dashboard) => EntityCardSettings(dropShadow: true); //dashboard.image != null);
|
||||
EntityCardSettings entityGridCardSettings(DashboardInfo dashboard) =>
|
||||
EntityCardSettings(dropShadow: true); //dashboard.image != null);
|
||||
|
||||
@override
|
||||
Widget buildEntityGridCard(BuildContext context, DashboardInfo dashboard) {
|
||||
return DashboardGridCard(tbContext, dashboard: dashboard);
|
||||
}
|
||||
|
||||
Widget _buildEntityListCard(BuildContext context, DashboardInfo dashboard, bool listWidgetCard) {
|
||||
Widget _buildEntityListCard(
|
||||
BuildContext context, DashboardInfo dashboard, bool listWidgetCard) {
|
||||
return Row(
|
||||
mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: listWidgetCard ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: listWidgetCard ? 9 : 10, horizontal: 16),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: listWidgetCard ? 9 : 10, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
mainAxisSize:
|
||||
listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: listWidgetCard ? FlexFit.loose : FlexFit.tight,
|
||||
child:
|
||||
Column(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
@@ -77,37 +82,35 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.7
|
||||
))
|
||||
),
|
||||
height: 1.7))),
|
||||
Text('${_dashboardDetailsText(dashboard)}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
height: 1.33))
|
||||
],
|
||||
)
|
||||
),
|
||||
(!listWidgetCard ? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(dashboard.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33
|
||||
))
|
||||
],
|
||||
) : Container())
|
||||
)),
|
||||
(!listWidgetCard
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
entityDateFormat.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
dashboard.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.33))
|
||||
],
|
||||
)
|
||||
: Container())
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
String _dashboardDetailsText(DashboardInfo dashboard) {
|
||||
@@ -124,23 +127,20 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
bool _isPublicDashboard(DashboardInfo dashboard) {
|
||||
return dashboard.assignedCustomers.any((element) => element.isPublic);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DashboardGridCard extends TbContextWidget {
|
||||
|
||||
final DashboardInfo dashboard;
|
||||
|
||||
DashboardGridCard(TbContext tbContext, {required this.dashboard}) : super(tbContext);
|
||||
DashboardGridCard(TbContext tbContext, {required this.dashboard})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardGridCardState createState() => _DashboardGridCardState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardGridCardState extends TbContextState<DashboardGridCard> {
|
||||
|
||||
_DashboardGridCardState(): super();
|
||||
_DashboardGridCardState() : super();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -164,48 +164,37 @@ class _DashboardGridCardState extends TbContextState<DashboardGridCard> {
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Dashboard');
|
||||
}
|
||||
return
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack (
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: BoxFit.cover,
|
||||
child: image
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
Divider(height: 1),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child:
|
||||
Center(
|
||||
child: AutoSizeText(widget.dashboard.title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack(children: [
|
||||
SizedBox.expand(
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: BoxFit.cover,
|
||||
child: image))
|
||||
])),
|
||||
Divider(height: 1),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Center(
|
||||
child: AutoSizeText(
|
||||
widget.dashboard.title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14),
|
||||
))),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,16 +8,13 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'dashboards_base.dart';
|
||||
|
||||
class DashboardsGridWidget extends TbContextWidget {
|
||||
|
||||
DashboardsGridWidget(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardsGridWidgetState createState() => _DashboardsGridWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardsGridWidgetState extends TbContextState<DashboardsGridWidget> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
@@ -30,13 +27,11 @@ class _DashboardsGridWidgetState extends TbContextState<DashboardsGridWidget> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class DashboardsGrid extends BaseEntitiesWidget<DashboardInfo, PageLink> with DashboardsBase, EntitiesGridStateBase {
|
||||
|
||||
DashboardsGrid(TbContext tbContext, PageKeyController<PageLink> pageKeyController) : super(tbContext, pageKeyController);
|
||||
|
||||
class DashboardsGrid extends BaseEntitiesWidget<DashboardInfo, PageLink>
|
||||
with DashboardsBase, EntitiesGridStateBase {
|
||||
DashboardsGrid(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController)
|
||||
: super(tbContext, pageKeyController);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'dashboards_base.dart';
|
||||
|
||||
class DashboardsList extends BaseEntitiesWidget<DashboardInfo, PageLink> with DashboardsBase, EntitiesListStateBase {
|
||||
|
||||
DashboardsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController) : super(tbContext, pageKeyController);
|
||||
|
||||
class DashboardsList extends BaseEntitiesWidget<DashboardInfo, PageLink>
|
||||
with DashboardsBase, EntitiesListStateBase {
|
||||
DashboardsList(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController)
|
||||
: super(tbContext, pageKeyController);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ 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<DashboardInfo> with DashboardsBase {
|
||||
|
||||
DashboardsListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller);
|
||||
class DashboardsListWidget extends EntitiesListPageLinkWidget<DashboardInfo>
|
||||
with DashboardsBase {
|
||||
DashboardsListWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
navigateTo('/dashboards');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,30 +6,22 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'dashboards_grid.dart';
|
||||
|
||||
class DashboardsPage extends TbPageWidget {
|
||||
|
||||
DashboardsPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DashboardsPageState createState() => _DashboardsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DashboardsPageState extends TbPageState<DashboardsPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text('Dashboards')
|
||||
),
|
||||
body: DashboardsGridWidget(tbContext)
|
||||
);
|
||||
appBar: TbAppBar(tbContext, title: Text('Dashboards')),
|
||||
body: DashboardsGridWidget(tbContext));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class FullscreenDashboardPage extends TbPageWidget {
|
||||
|
||||
final String fullscreenDashboardId;
|
||||
final String? _dashboardTitle;
|
||||
|
||||
FullscreenDashboardPage(TbContext tbContext, this.fullscreenDashboardId, {String? dashboardTitle}):
|
||||
_dashboardTitle = dashboardTitle,
|
||||
FullscreenDashboardPage(TbContext tbContext, this.fullscreenDashboardId,
|
||||
{String? dashboardTitle})
|
||||
: _dashboardTitle = dashboardTitle,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_FullscreenDashboardPageState createState() => _FullscreenDashboardPageState();
|
||||
|
||||
_FullscreenDashboardPageState createState() =>
|
||||
_FullscreenDashboardPageState();
|
||||
}
|
||||
|
||||
class _FullscreenDashboardPageState extends TbPageState<FullscreenDashboardPage> {
|
||||
|
||||
class _FullscreenDashboardPageState
|
||||
extends TbPageState<FullscreenDashboardPage> {
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
final ValueNotifier<bool> showBackValue = ValueNotifier(false);
|
||||
|
||||
@@ -47,13 +46,12 @@ class _FullscreenDashboardPageState extends TbPageState<FullscreenDashboardPage>
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: showBackValue,
|
||||
builder: (context, canGoBack, widget) {
|
||||
return TbAppBar(
|
||||
tbContext,
|
||||
leading: canGoBack ? BackButton(
|
||||
onPressed: () {
|
||||
maybePop();
|
||||
}
|
||||
) : null,
|
||||
return TbAppBar(tbContext,
|
||||
leading: canGoBack
|
||||
? BackButton(onPressed: () {
|
||||
maybePop();
|
||||
})
|
||||
: null,
|
||||
showLoadingIndicator: false,
|
||||
elevation: 1,
|
||||
shadowColor: Colors.transparent,
|
||||
@@ -63,29 +61,25 @@ class _FullscreenDashboardPageState extends TbPageState<FullscreenDashboardPage>
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
child: Text(title));
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(icon: Icon(Icons.settings), onPressed: () => navigateTo('/profile?fullscreen=true'))
|
||||
]
|
||||
);
|
||||
}
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings),
|
||||
onPressed: () =>
|
||||
navigateTo('/profile?fullscreen=true'))
|
||||
]);
|
||||
}),
|
||||
),
|
||||
body: Dashboard(
|
||||
tbContext,
|
||||
titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
},
|
||||
controllerCallback: (controller) {
|
||||
controller.canGoBack.addListener(() {
|
||||
_onCanGoBack(controller.canGoBack.value);
|
||||
});
|
||||
controller.openDashboard(widget.fullscreenDashboardId, fullscreen: true);
|
||||
}
|
||||
)
|
||||
);
|
||||
body: Dashboard(tbContext, titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
}, controllerCallback: (controller) {
|
||||
controller.canGoBack.addListener(() {
|
||||
_onCanGoBack(controller.canGoBack.value);
|
||||
});
|
||||
controller.openDashboard(widget.fullscreenDashboardId,
|
||||
fullscreen: true);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class MainDashboardPageController {
|
||||
|
||||
DashboardController? _dashboardController;
|
||||
_MainDashboardPageState? _mainDashboardPageState;
|
||||
|
||||
@@ -26,11 +24,13 @@ class MainDashboardPageController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar}) async {
|
||||
Future<void> openDashboard(String dashboardId,
|
||||
{String? dashboardTitle, String? state, bool? hideToolbar}) async {
|
||||
if (dashboardTitle != null) {
|
||||
_mainDashboardPageState?._updateTitle(dashboardTitle);
|
||||
}
|
||||
await _dashboardController?.openDashboard(dashboardId, state: state, hideToolbar: hideToolbar);
|
||||
await _dashboardController?.openDashboard(dashboardId,
|
||||
state: state, hideToolbar: hideToolbar);
|
||||
}
|
||||
|
||||
Future<void> activateDashboard() async {
|
||||
@@ -40,28 +40,24 @@ class MainDashboardPageController {
|
||||
Future<void> deactivateDashboard() async {
|
||||
await _dashboardController?.deactivateDashboard();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MainDashboardPage extends TbContextWidget {
|
||||
|
||||
final String? _dashboardTitle;
|
||||
final MainDashboardPageController? _controller;
|
||||
|
||||
MainDashboardPage(TbContext tbContext,
|
||||
{MainDashboardPageController? controller,
|
||||
String? dashboardTitle}):
|
||||
_controller = controller,
|
||||
{MainDashboardPageController? controller, String? dashboardTitle})
|
||||
: _controller = controller,
|
||||
_dashboardTitle = dashboardTitle,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_MainDashboardPageState createState() => _MainDashboardPageState();
|
||||
|
||||
}
|
||||
|
||||
class _MainDashboardPageState extends TbContextState<MainDashboardPage> with TickerProviderStateMixin {
|
||||
|
||||
class _MainDashboardPageState extends TbContextState<MainDashboardPage>
|
||||
with TickerProviderStateMixin {
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
final ValueNotifier<bool> hasRightLayout = ValueNotifier(false);
|
||||
DashboardController? _dashboardController;
|
||||
@@ -76,9 +72,7 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
|
||||
duration: Duration(milliseconds: 200),
|
||||
);
|
||||
rightLayoutMenuAnimation = CurvedAnimation(
|
||||
curve: Curves.linear,
|
||||
parent: rightLayoutMenuController
|
||||
);
|
||||
curve: Curves.linear, parent: rightLayoutMenuController);
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setMainDashboardPageState(this);
|
||||
}
|
||||
@@ -99,68 +93,57 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
maybePop();
|
||||
}
|
||||
),
|
||||
showLoadingIndicator: false,
|
||||
elevation: 1,
|
||||
shadowColor: Colors.transparent,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
ValueListenableBuilder<bool>(
|
||||
tbContext,
|
||||
leading: BackButton(onPressed: () {
|
||||
maybePop();
|
||||
}),
|
||||
showLoadingIndicator: false,
|
||||
elevation: 1,
|
||||
shadowColor: Colors.transparent,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title));
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: hasRightLayout,
|
||||
builder: (context, _hasRightLayout, widget) {
|
||||
if (_hasRightLayout) {
|
||||
return IconButton(
|
||||
onPressed: () => _dashboardController?.toggleRightLayout(),
|
||||
onPressed: () =>
|
||||
_dashboardController?.toggleRightLayout(),
|
||||
icon: AnimatedIcon(
|
||||
progress: rightLayoutMenuAnimation,
|
||||
icon: AnimatedIcons.menu_close
|
||||
)
|
||||
);
|
||||
progress: rightLayoutMenuAnimation,
|
||||
icon: AnimatedIcons.menu_close));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
)
|
||||
],
|
||||
})
|
||||
],
|
||||
),
|
||||
body: Dashboard(
|
||||
tbContext,
|
||||
activeByDefault: false,
|
||||
body: Dashboard(tbContext, activeByDefault: false,
|
||||
titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
},
|
||||
controllerCallback: (controller) {
|
||||
_dashboardController = controller;
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setDashboardController(controller);
|
||||
controller.hasRightLayout.addListener(() {
|
||||
hasRightLayout.value = controller.hasRightLayout.value;
|
||||
});
|
||||
controller.rightLayoutOpened.addListener(() {
|
||||
if(controller.rightLayoutOpened.value) {
|
||||
rightLayoutMenuController.forward();
|
||||
} else {
|
||||
rightLayoutMenuController.reverse();
|
||||
}
|
||||
});
|
||||
dashboardTitleValue.value = title;
|
||||
}, controllerCallback: (controller) {
|
||||
_dashboardController = controller;
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setDashboardController(controller);
|
||||
controller.hasRightLayout.addListener(() {
|
||||
hasRightLayout.value = controller.hasRightLayout.value;
|
||||
});
|
||||
controller.rightLayoutOpened.addListener(() {
|
||||
if (controller.rightLayoutOpened.value) {
|
||||
rightLayoutMenuController.forward();
|
||||
} else {
|
||||
rightLayoutMenuController.reverse();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class DeviceDetailsPage extends EntityDetailsPage<DeviceInfo> {
|
||||
|
||||
DeviceDetailsPage(TbContext tbContext, String deviceId):
|
||||
super(tbContext,
|
||||
entityId: deviceId,
|
||||
defaultTitle: 'Device');
|
||||
DeviceDetailsPage(TbContext tbContext, String deviceId)
|
||||
: super(tbContext, entityId: deviceId, defaultTitle: 'Device');
|
||||
|
||||
@override
|
||||
Future<DeviceInfo?> fetchEntity(String deviceId) {
|
||||
@@ -23,5 +19,4 @@ class DeviceDetailsPage extends EntityDetailsPage<DeviceInfo> {
|
||||
subtitle: Text('${device.type}'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.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/core/context/tb_context.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
|
||||
final RefreshDeviceCounts refreshDeviceCounts = RefreshDeviceCounts();
|
||||
|
||||
@override
|
||||
@@ -48,7 +46,8 @@ mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityGridCard(BuildContext context, DeviceProfileInfo deviceProfile) {
|
||||
Widget buildEntityGridCard(
|
||||
BuildContext context, DeviceProfileInfo deviceProfile) {
|
||||
return DeviceProfileCard(tbContext, deviceProfile);
|
||||
}
|
||||
|
||||
@@ -56,7 +55,6 @@ mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
double? gridChildAspectRatio() {
|
||||
return 156 / 200;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RefreshDeviceCounts {
|
||||
@@ -64,20 +62,20 @@ class RefreshDeviceCounts {
|
||||
}
|
||||
|
||||
class AllDevicesCard extends TbContextWidget {
|
||||
|
||||
final RefreshDeviceCounts refreshDeviceCounts;
|
||||
|
||||
AllDevicesCard(TbContext tbContext, this.refreshDeviceCounts) : super(tbContext);
|
||||
AllDevicesCard(TbContext tbContext, this.refreshDeviceCounts)
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_AllDevicesCardState createState() => _AllDevicesCardState();
|
||||
|
||||
}
|
||||
|
||||
class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
|
||||
final StreamController<int?> _activeDevicesCount = StreamController.broadcast();
|
||||
final StreamController<int?> _inactiveDevicesCount = StreamController.broadcast();
|
||||
final StreamController<int?> _activeDevicesCount =
|
||||
StreamController.broadcast();
|
||||
final StreamController<int?> _inactiveDevicesCount =
|
||||
StreamController.broadcast();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -103,9 +101,12 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
Future<void> _countDevices() {
|
||||
_activeDevicesCount.add(null);
|
||||
_inactiveDevicesCount.add(null);
|
||||
Future<int> activeDevicesCount = EntityQueryApi.countDevices(tbClient, active: true);
|
||||
Future<int> inactiveDevicesCount = EntityQueryApi.countDevices(tbClient, active: false);
|
||||
Future<List<int>> countsFuture = Future.wait([activeDevicesCount, inactiveDevicesCount]);
|
||||
Future<int> activeDevicesCount =
|
||||
EntityQueryApi.countDevices(tbClient, active: true);
|
||||
Future<int> inactiveDevicesCount =
|
||||
EntityQueryApi.countDevices(tbClient, active: false);
|
||||
Future<List<int>> countsFuture =
|
||||
Future.wait([activeDevicesCount, inactiveDevicesCount]);
|
||||
countsFuture.then((counts) {
|
||||
if (this.mounted) {
|
||||
_activeDevicesCount.add(counts[0]);
|
||||
@@ -117,20 +118,19 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child:
|
||||
Container(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.fromLTRB(16, 12, 16, 15),
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 12, 16, 15),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -139,120 +139,126 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
)
|
||||
),
|
||||
height: 20 / 14)),
|
||||
Icon(Icons.arrow_forward, size: 18)
|
||||
],
|
||||
)
|
||||
),
|
||||
Divider(height: 1),
|
||||
Padding(padding: EdgeInsets.all(0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _activeDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true');
|
||||
}
|
||||
),
|
||||
),
|
||||
// SizedBox(width: 4),
|
||||
Container(width: 1,
|
||||
height: 40,
|
||||
child: VerticalDivider(width: 1)
|
||||
),
|
||||
Flexible(fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _inactiveDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false');
|
||||
}
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
Divider(height: 1),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _activeDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(
|
||||
context, true, deviceCount);
|
||||
} else {
|
||||
return Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext
|
||||
.currentState!
|
||||
.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true');
|
||||
}),
|
||||
),
|
||||
// SizedBox(width: 4),
|
||||
Container(
|
||||
width: 1,
|
||||
height: 40,
|
||||
child: VerticalDivider(width: 1)),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: StreamBuilder<int?>(
|
||||
stream: _inactiveDevicesCount.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(
|
||||
context, false, deviceCount);
|
||||
} else {
|
||||
return Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext
|
||||
.currentState!
|
||||
.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false');
|
||||
}),
|
||||
)
|
||||
],
|
||||
))
|
||||
],
|
||||
)),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4))
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList');
|
||||
}
|
||||
);
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeviceProfileCard extends TbContextWidget {
|
||||
|
||||
final DeviceProfileInfo deviceProfile;
|
||||
|
||||
DeviceProfileCard(TbContext tbContext, this.deviceProfile) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DeviceProfileCardState createState() => _DeviceProfileCardState();
|
||||
|
||||
}
|
||||
|
||||
class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
|
||||
|
||||
late Future<int> activeDevicesCount;
|
||||
late Future<int> inactiveDevicesCount;
|
||||
|
||||
@@ -269,8 +275,10 @@ class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
|
||||
}
|
||||
|
||||
_countDevices() {
|
||||
activeDevicesCount = EntityQueryApi.countDevices(tbClient, deviceType: widget.deviceProfile.name, active: true);
|
||||
inactiveDevicesCount = EntityQueryApi.countDevices(tbClient, deviceType: widget.deviceProfile.name, active: false);
|
||||
activeDevicesCount = EntityQueryApi.countDevices(tbClient,
|
||||
deviceType: widget.deviceProfile.name, active: true);
|
||||
inactiveDevicesCount = EntityQueryApi.countDevices(tbClient,
|
||||
deviceType: widget.deviceProfile.name, active: false);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -292,99 +300,95 @@ class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
|
||||
imageFit = BoxFit.cover;
|
||||
padding = 0;
|
||||
}
|
||||
return
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack (
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: imageFit,
|
||||
child: image
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Center(
|
||||
child: AutoSizeText(entity.name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: activeDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Container(height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20, width: 20,
|
||||
child:
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true&deviceType=${entity.name}');
|
||||
}
|
||||
),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: inactiveDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Container(height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20, width: 20,
|
||||
child:
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false&deviceType=${entity.name}');
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(children: [
|
||||
Expanded(
|
||||
child: Stack(children: [
|
||||
SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: imageFit,
|
||||
child: image)))
|
||||
])),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Center(
|
||||
child: AutoSizeText(
|
||||
entity.name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14),
|
||||
)))),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: activeDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Container(
|
||||
height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(
|
||||
tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true&deviceType=${entity.name}');
|
||||
}),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: inactiveDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Container(
|
||||
height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(
|
||||
tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo(
|
||||
'/deviceList?active=false&deviceType=${entity.name}');
|
||||
})
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,26 +406,27 @@ Widget _buildDeviceCount(BuildContext context, bool active, int count) {
|
||||
Stack(
|
||||
children: [
|
||||
Icon(Icons.devices_other, size: 16, color: color),
|
||||
if (!active) CustomPaint(
|
||||
size: Size.square(16),
|
||||
painter: StrikeThroughPainter(color: color, offset: 2),
|
||||
)
|
||||
if (!active)
|
||||
CustomPaint(
|
||||
size: Size.square(16),
|
||||
painter: StrikeThroughPainter(color: color, offset: 2),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(width: 8.67),
|
||||
Text(active ? 'Active' : 'Inactive', style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color
|
||||
)),
|
||||
SizedBox(width: 8.67),
|
||||
Text(count.toString(), style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color
|
||||
))
|
||||
SizedBox(width: 8.67),
|
||||
Text(active ? 'Active' : 'Inactive',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color)),
|
||||
SizedBox(width: 8.67),
|
||||
Text(count.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color))
|
||||
],
|
||||
),
|
||||
Icon(Icons.chevron_right, size: 16, color: Color(0xFFACACAC))
|
||||
@@ -431,7 +436,6 @@ Widget _buildDeviceCount(BuildContext context, bool active, int count) {
|
||||
}
|
||||
|
||||
class StrikeThroughPainter extends CustomPainter {
|
||||
|
||||
final Color color;
|
||||
final double offset;
|
||||
|
||||
@@ -441,7 +445,8 @@ class StrikeThroughPainter extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
paint.strokeWidth = 1.5;
|
||||
canvas.drawLine(Offset(offset, offset), Offset(size.width - offset, size.height - offset), paint);
|
||||
canvas.drawLine(Offset(offset, offset),
|
||||
Offset(size.width - offset, size.height - offset), paint);
|
||||
paint.color = Colors.white;
|
||||
canvas.drawLine(Offset(2, 0), Offset(size.width + 2, size.height), paint);
|
||||
}
|
||||
@@ -450,5 +455,4 @@ class StrikeThroughPainter extends CustomPainter {
|
||||
bool shouldRepaint(covariant StrikeThroughPainter oldDelegate) {
|
||||
return color != oldDelegate.color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'device_profiles_base.dart';
|
||||
|
||||
class DeviceProfilesGrid extends BaseEntitiesWidget<DeviceProfileInfo, PageLink> with DeviceProfilesBase, EntitiesGridStateBase {
|
||||
|
||||
DeviceProfilesGrid(TbContext tbContext, PageKeyController<PageLink> pageKeyController) : super(tbContext, pageKeyController);
|
||||
|
||||
class DeviceProfilesGrid extends BaseEntitiesWidget<DeviceProfileInfo, PageLink>
|
||||
with DeviceProfilesBase, EntitiesGridStateBase {
|
||||
DeviceProfilesGrid(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController)
|
||||
: super(tbContext, pageKeyController);
|
||||
}
|
||||
|
||||
@@ -9,24 +9,28 @@ import 'device_details_page.dart';
|
||||
import 'devices_list_page.dart';
|
||||
|
||||
class DeviceRoutes extends TbRoutes {
|
||||
|
||||
late var devicesHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var devicesHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return MainPage(tbContext, path: '/devices');
|
||||
});
|
||||
|
||||
late var devicesPageHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var devicesPageHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DevicesPage(tbContext);
|
||||
});
|
||||
|
||||
late var deviceListHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var deviceListHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
var deviceType = params['deviceType']?.first;
|
||||
String? activeStr = params['active']?.first;
|
||||
bool? active = activeStr != null ? activeStr == 'true' : null;
|
||||
return DevicesListPage(tbContext, searchMode: searchMode, deviceType: deviceType, active: active);
|
||||
return DevicesListPage(tbContext,
|
||||
searchMode: searchMode, deviceType: deviceType, active: active);
|
||||
});
|
||||
|
||||
late var deviceDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var deviceDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DeviceDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -39,5 +43,4 @@ class DeviceRoutes extends TbRoutes {
|
||||
router.define("/deviceList", handler: deviceListHandler);
|
||||
router.define("/device/:id", handler: deviceDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:core';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
|
||||
@override
|
||||
String get title => 'Devices';
|
||||
|
||||
@@ -28,14 +26,19 @@ mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
|
||||
@override
|
||||
void onEntityTap(EntityData device) async {
|
||||
var profile = await DeviceProfileCache.getDeviceProfileInfo(tbClient, device.field('type')!, device.entityId.id!);
|
||||
var profile = await DeviceProfileCache.getDeviceProfileInfo(
|
||||
tbClient, device.field('type')!, device.entityId.id!);
|
||||
if (profile.defaultDashboardId != null) {
|
||||
var dashboardId = profile.defaultDashboardId!.id!;
|
||||
var state = Utils.createDashboardEntityState(device.entityId, entityName: device.field('name')!, entityLabel: device.field('label')!);
|
||||
navigateToDashboard(dashboardId, dashboardTitle: device.field('name'), state: state);
|
||||
var state = Utils.createDashboardEntityState(device.entityId,
|
||||
entityName: device.field('name')!,
|
||||
entityLabel: device.field('label')!);
|
||||
navigateToDashboard(dashboardId,
|
||||
dashboardTitle: device.field('name'), state: state);
|
||||
} else {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
showWarnNotification('Mobile dashboard should be configured in device profile!');
|
||||
showWarnNotification(
|
||||
'Mobile dashboard should be configured in device profile!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,42 +60,51 @@ mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
|
||||
bool displayCardImage(bool listWidgetCard) => listWidgetCard;
|
||||
|
||||
Widget _buildEntityListCard(BuildContext context, EntityData device, bool listWidgetCard) {
|
||||
return DeviceCard(tbContext, device: device, listWidgetCard: listWidgetCard, displayImage: displayCardImage(listWidgetCard));
|
||||
Widget _buildEntityListCard(
|
||||
BuildContext context, EntityData device, bool listWidgetCard) {
|
||||
return DeviceCard(tbContext,
|
||||
device: device,
|
||||
listWidgetCard: listWidgetCard,
|
||||
displayImage: displayCardImage(listWidgetCard));
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceQueryController extends PageKeyController<EntityDataQuery> {
|
||||
|
||||
DeviceQueryController({int pageSize = 20, String? searchText, String? deviceType, bool? active}):
|
||||
super(EntityQueryApi.createDefaultDeviceQuery(pageSize: pageSize, searchText: searchText, deviceType: deviceType, active: active));
|
||||
DeviceQueryController(
|
||||
{int pageSize = 20, String? searchText, String? deviceType, bool? active})
|
||||
: super(EntityQueryApi.createDefaultDeviceQuery(
|
||||
pageSize: pageSize,
|
||||
searchText: searchText,
|
||||
deviceType: deviceType,
|
||||
active: active));
|
||||
|
||||
@override
|
||||
EntityDataQuery nextPageKey(EntityDataQuery deviceQuery) => deviceQuery.next();
|
||||
EntityDataQuery nextPageKey(EntityDataQuery deviceQuery) =>
|
||||
deviceQuery.next();
|
||||
|
||||
onSearchText(String searchText) {
|
||||
value.pageKey.pageLink.page = 0;
|
||||
value.pageKey.pageLink.textSearch = searchText;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeviceCard extends TbContextWidget {
|
||||
|
||||
final EntityData device;
|
||||
final bool listWidgetCard;
|
||||
final bool displayImage;
|
||||
|
||||
DeviceCard(TbContext tbContext, {required this.device, this.listWidgetCard = false, this.displayImage = false}) : super(tbContext);
|
||||
DeviceCard(TbContext tbContext,
|
||||
{required this.device,
|
||||
this.listWidgetCard = false,
|
||||
this.displayImage = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_DeviceCardState createState() => _DeviceCardState();
|
||||
|
||||
}
|
||||
|
||||
class _DeviceCardState extends TbContextState<DeviceCard> {
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
late Future<DeviceProfileInfo> deviceProfileFuture;
|
||||
@@ -129,250 +141,256 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
|
||||
}
|
||||
|
||||
Widget buildCard(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
return Stack(children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
FutureBuilder<DeviceProfileInfo>(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.device.attribute('active') == 'true'
|
||||
? Color(0xFF008A00)
|
||||
: Color(0xFFAFAFAF),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
bottomLeft: Radius.circular(4))),
|
||||
))),
|
||||
FutureBuilder<DeviceProfileInfo>(
|
||||
future: deviceProfileFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
bool hasDashboard = profile.defaultDashboardId != null;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
image = Utils.imageFromBase64(profile.image!);
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = SvgPicture.asset(
|
||||
ThingsboardImage.deviceProfilePlaceholder,
|
||||
color: Theme.of(context).primaryColor,
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Device');
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 20),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.displayImage)
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(4))),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
))
|
||||
],
|
||||
))),
|
||||
SizedBox(width: 12),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment:
|
||||
Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${widget.device.field('name')!}',
|
||||
style: TextStyle(
|
||||
color: Color(
|
||||
0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.w500,
|
||||
height:
|
||||
20 / 14)))),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
entityDateFormat.format(DateTime
|
||||
.fromMillisecondsSinceEpoch(
|
||||
widget.device
|
||||
.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.normal,
|
||||
height: 16 / 12))
|
||||
]),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${widget.device.field('type')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.normal,
|
||||
height: 16 / 12)),
|
||||
Text(
|
||||
widget.device.attribute(
|
||||
'active') ==
|
||||
'true'
|
||||
? 'Active'
|
||||
: 'Inactive',
|
||||
style: TextStyle(
|
||||
color: widget.device
|
||||
.attribute(
|
||||
'active') ==
|
||||
'true'
|
||||
? Color(0xFF008A00)
|
||||
: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
height: 16 / 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
))
|
||||
],
|
||||
)
|
||||
])),
|
||||
SizedBox(width: 16),
|
||||
if (hasDashboard)
|
||||
Icon(Icons.chevron_right,
|
||||
color: Color(0xFFACACAC)),
|
||||
if (hasDashboard) SizedBox(width: 16),
|
||||
]),
|
||||
SizedBox(height: 12)
|
||||
],
|
||||
))
|
||||
]);
|
||||
} else {
|
||||
return Container(
|
||||
height: 64,
|
||||
child: Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary))));
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
Widget buildListWidgetCard(BuildContext context) {
|
||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
if (widget.displayImage)
|
||||
Container(
|
||||
width: 58,
|
||||
height: 58,
|
||||
decoration: BoxDecoration(
|
||||
// color: Color(0xFFEEEEEE),
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4))),
|
||||
child: FutureBuilder<DeviceProfileInfo>(
|
||||
future: deviceProfileFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
bool hasDashboard = profile.defaultDashboardId != null;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
image = Utils.imageFromBase64(profile.image!);
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = SvgPicture.asset(ThingsboardImage.deviceProfilePlaceholder,
|
||||
image = SvgPicture.asset(
|
||||
ThingsboardImage.deviceProfilePlaceholder,
|
||||
color: Theme.of(context).primaryColor,
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Device');
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 20),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.displayImage) Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4))
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${widget.device.field('name')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
)
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${widget.device.field('type')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
Text(widget.device.attribute('active') == 'true' ? 'Active' : 'Inactive',
|
||||
style: TextStyle(
|
||||
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
height: 16 / 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
))
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
if (hasDashboard) Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
if (hasDashboard) SizedBox(width: 16),
|
||||
]
|
||||
),
|
||||
SizedBox(height: 12)
|
||||
],
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
))
|
||||
],
|
||||
));
|
||||
} else {
|
||||
return Container(
|
||||
height: 64,
|
||||
child: Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
|
||||
)
|
||||
)
|
||||
);
|
||||
return Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Theme.of(tbContext.currentState!.context)
|
||||
.colorScheme
|
||||
.primary)));
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildListWidgetCard(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.displayImage) Container(
|
||||
width: 58,
|
||||
height: 58,
|
||||
decoration: BoxDecoration(
|
||||
// color: Color(0xFFEEEEEE),
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4))
|
||||
),
|
||||
child: FutureBuilder<DeviceProfileInfo>(
|
||||
future: deviceProfileFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
image = Utils.imageFromBase64(profile.image!);
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = SvgPicture.asset(ThingsboardImage.deviceProfilePlaceholder,
|
||||
color: Theme.of(context).primaryColor,
|
||||
colorBlendMode: BlendMode.overlay,
|
||||
semanticsLabel: 'Device');
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return Center(child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${widget.device.field('name')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${widget.device.field('type')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
]
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${widget.device.field('name')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14)))
|
||||
]),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${widget.device.field('type')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12)),
|
||||
])
|
||||
],
|
||||
)))
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ 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 BaseEntitiesWidget<EntityData, EntityDataQuery> with DevicesBase, EntitiesListStateBase {
|
||||
|
||||
class DevicesList extends BaseEntitiesWidget<EntityData, EntityDataQuery>
|
||||
with DevicesBase, EntitiesListStateBase {
|
||||
final bool displayDeviceImage;
|
||||
|
||||
DevicesList(TbContext tbContext, PageKeyController<EntityDataQuery> pageKeyController, {searchMode = false, this.displayDeviceImage = false}):
|
||||
super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
DevicesList(
|
||||
TbContext tbContext, PageKeyController<EntityDataQuery> pageKeyController,
|
||||
{searchMode = false, this.displayDeviceImage = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
@override
|
||||
bool displayCardImage(bool listWidgetCard) => displayDeviceImage;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,87 +6,85 @@ import 'package:thingsboard_app/modules/device/devices_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesListPage extends TbPageWidget {
|
||||
|
||||
final String? deviceType;
|
||||
final bool? active;
|
||||
final bool searchMode;
|
||||
|
||||
DevicesListPage(TbContext tbContext, {this.deviceType, this.active, this.searchMode = false}) : super(tbContext);
|
||||
DevicesListPage(TbContext tbContext,
|
||||
{this.deviceType, this.active, this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesListPageState createState() => _DevicesListPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DevicesListPageState extends TbPageState<DevicesListPage> {
|
||||
|
||||
late final DeviceQueryController _deviceQueryController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_deviceQueryController = DeviceQueryController(deviceType: widget.deviceType, active: widget.active);
|
||||
_deviceQueryController = DeviceQueryController(
|
||||
deviceType: widget.deviceType, active: widget.active);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var devicesList = DevicesList(tbContext, _deviceQueryController, searchMode: widget.searchMode, displayDeviceImage: widget.deviceType == null);
|
||||
var devicesList = DevicesList(tbContext, _deviceQueryController,
|
||||
searchMode: widget.searchMode,
|
||||
displayDeviceImage: widget.deviceType == null);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _deviceQueryController.onSearchText(searchText),
|
||||
onSearch: (searchText) =>
|
||||
_deviceQueryController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
String titleText = widget.deviceType != null ? widget.deviceType! : 'All devices';
|
||||
String titleText =
|
||||
widget.deviceType != null ? widget.deviceType! : 'All devices';
|
||||
String? subTitleText;
|
||||
if (widget.active != null) {
|
||||
subTitleText = widget.active == true ? 'Active' : 'Inactive';
|
||||
}
|
||||
Column title = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(titleText, style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: subTitleText != null ? 16 : 20,
|
||||
height: subTitleText != null ? 20 / 16 : 24 / 20
|
||||
)),
|
||||
if (subTitleText != null)
|
||||
Text(subTitleText, style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline6!.color!.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
);
|
||||
Column title =
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(titleText,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: subTitleText != null ? 16 : 20,
|
||||
height: subTitleText != null ? 20 / 16 : 24 / 20)),
|
||||
if (subTitleText != null)
|
||||
Text(subTitleText,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline6!
|
||||
.color!
|
||||
.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12))
|
||||
]);
|
||||
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: title,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
List<String> params = [];
|
||||
params.add('search=true');
|
||||
if (widget.deviceType != null) {
|
||||
params.add('deviceType=${widget.deviceType}');
|
||||
}
|
||||
if (widget.active != null) {
|
||||
params.add('active=${widget.active}');
|
||||
}
|
||||
navigateTo('/deviceList?${params.join('&')}');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: title, actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
List<String> params = [];
|
||||
params.add('search=true');
|
||||
if (widget.deviceType != null) {
|
||||
params.add('deviceType=${widget.deviceType}');
|
||||
}
|
||||
if (widget.active != null) {
|
||||
params.add('active=${widget.active}');
|
||||
}
|
||||
navigateTo('/deviceList?${params.join('&')}');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: devicesList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: devicesList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,5 +92,4 @@ class _DevicesListPageState extends TbPageState<DevicesListPage> {
|
||||
_deviceQueryController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ 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 EntitiesListWidget<EntityData, EntityDataQuery> with DevicesBase {
|
||||
|
||||
DevicesListWidget(TbContext tbContext, {EntitiesListWidgetController? controller}): super(tbContext, controller: controller);
|
||||
class DevicesListWidget extends EntitiesListWidget<EntityData, EntityDataQuery>
|
||||
with DevicesBase {
|
||||
DevicesListWidget(TbContext tbContext,
|
||||
{EntitiesListWidgetController? controller})
|
||||
: super(tbContext, controller: controller);
|
||||
|
||||
@override
|
||||
void onViewAll() {
|
||||
@@ -14,6 +16,6 @@ class DevicesListWidget extends EntitiesListWidget<EntityData, EntityDataQuery>
|
||||
}
|
||||
|
||||
@override
|
||||
PageKeyController<EntityDataQuery> createPageKeyController() => DeviceQueryController(pageSize: 5);
|
||||
|
||||
PageKeyController<EntityDataQuery> createPageKeyController() =>
|
||||
DeviceQueryController(pageSize: 5);
|
||||
}
|
||||
|
||||
@@ -6,16 +6,14 @@ import 'package:thingsboard_app/modules/device/device_profiles_grid.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesMainPage extends TbContextWidget {
|
||||
|
||||
DevicesMainPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesMainPageState createState() => _DevicesMainPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DevicesMainPageState extends TbContextState<DevicesMainPage> with AutomaticKeepAliveClientMixin<DevicesMainPage> {
|
||||
|
||||
class _DevicesMainPageState extends TbContextState<DevicesMainPage>
|
||||
with AutomaticKeepAliveClientMixin<DevicesMainPage> {
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
@@ -28,12 +26,8 @@ class _DevicesMainPageState extends TbContextState<DevicesMainPage> with Automat
|
||||
super.build(context);
|
||||
var deviceProfilesList = DeviceProfilesGrid(tbContext, _pageLinkController);
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text(deviceProfilesList.title)
|
||||
),
|
||||
body: deviceProfilesList
|
||||
);
|
||||
appBar: TbAppBar(tbContext, title: Text(deviceProfilesList.title)),
|
||||
body: deviceProfilesList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -41,5 +35,4 @@ class _DevicesMainPageState extends TbContextState<DevicesMainPage> with Automat
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,28 +6,21 @@ import 'package:thingsboard_app/modules/device/device_profiles_grid.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesPage extends TbPageWidget {
|
||||
|
||||
DevicesPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesPageState createState() => _DevicesPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DevicesPageState extends TbPageState<DevicesPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var deviceProfilesList = DeviceProfilesGrid(tbContext, _pageLinkController);
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text(deviceProfilesList.title)
|
||||
),
|
||||
body: deviceProfilesList
|
||||
);
|
||||
appBar: TbAppBar(tbContext, title: Text(deviceProfilesList.title)),
|
||||
body: deviceProfilesList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -35,5 +28,4 @@ class _DevicesPageState extends TbPageState<DevicesPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import 'package:flutter/material.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/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart' as dashboardUi;
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart'
|
||||
as dashboardUi;
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboards_grid.dart';
|
||||
import 'package:thingsboard_app/modules/tenant/tenants_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class HomePage extends TbContextWidget {
|
||||
|
||||
HomePage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
|
||||
}
|
||||
|
||||
class _HomePageState extends TbContextState<HomePage> with AutomaticKeepAliveClientMixin<HomePage> {
|
||||
|
||||
class _HomePageState extends TbContextState<HomePage>
|
||||
with AutomaticKeepAliveClientMixin<HomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -50,33 +48,29 @@ class _HomePageState extends TbContextState<HomePage> with AutomaticKeepAliveCli
|
||||
height: 24,
|
||||
child: SvgPicture.asset(ThingsboardImage.thingsBoardWithTitle,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticsLabel: 'ThingsBoard Logo')
|
||||
)
|
||||
),
|
||||
semanticsLabel: 'ThingsBoard Logo'))),
|
||||
actions: [
|
||||
if (tbClient.isSystemAdmin()) IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
if (tbClient.isSystemAdmin())
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
if (dashboardState) {
|
||||
return _buildDashboardHome(context, homeDashboard!);
|
||||
} else {
|
||||
return _buildDefaultHome(context);
|
||||
}
|
||||
}
|
||||
),
|
||||
body: Builder(builder: (context) {
|
||||
if (dashboardState) {
|
||||
return _buildDashboardHome(context, homeDashboard!);
|
||||
} else {
|
||||
return _buildDefaultHome(context);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDashboardHome(BuildContext context, HomeDashboardInfo dashboard) {
|
||||
Widget _buildDashboardHome(
|
||||
BuildContext context, HomeDashboardInfo dashboard) {
|
||||
return HomeDashboard(tbContext, dashboard);
|
||||
}
|
||||
|
||||
@@ -91,31 +85,24 @@ class _HomePageState extends TbContextState<HomePage> with AutomaticKeepAliveCli
|
||||
Widget _buildSysAdminHome(BuildContext context) {
|
||||
return TenantsWidget(tbContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class HomeDashboard extends TbContextWidget {
|
||||
|
||||
final HomeDashboardInfo dashboard;
|
||||
|
||||
HomeDashboard(TbContext tbContext, this.dashboard) : super(tbContext);
|
||||
|
||||
@override
|
||||
_HomeDashboardState createState() => _HomeDashboardState();
|
||||
|
||||
}
|
||||
|
||||
class _HomeDashboardState extends TbContextState<HomeDashboard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return dashboardUi.Dashboard(tbContext,
|
||||
home: true,
|
||||
controllerCallback: (controller) {
|
||||
controller.openDashboard(widget.dashboard.dashboardId!.id!,
|
||||
hideToolbar: widget.dashboard.hideDashboardToolbar);
|
||||
}
|
||||
);
|
||||
return dashboardUi.Dashboard(tbContext, home: true,
|
||||
controllerCallback: (controller) {
|
||||
controller.openDashboard(widget.dashboard.dashboardId!.id!,
|
||||
hideToolbar: widget.dashboard.hideDashboardToolbar);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
class HomeRoutes extends TbRoutes {
|
||||
|
||||
late var homeHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var homeHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return MainPage(tbContext, path: '/home');
|
||||
});
|
||||
|
||||
@@ -16,5 +16,4 @@ class HomeRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/home", handler: homeHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/alarm/alarms_page.dart';
|
||||
@@ -15,17 +13,18 @@ class TbMainNavigationItem {
|
||||
final Icon icon;
|
||||
final String path;
|
||||
|
||||
TbMainNavigationItem({
|
||||
required this.page,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.path
|
||||
});
|
||||
TbMainNavigationItem(
|
||||
{required this.page,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.path});
|
||||
|
||||
static Map<Authority, Set<String>> mainPageStateMap = {
|
||||
Authority.SYS_ADMIN: Set.unmodifiable(['/home', '/more']),
|
||||
Authority.TENANT_ADMIN: Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
Authority.CUSTOMER_USER: Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
Authority.TENANT_ADMIN:
|
||||
Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
Authority.CUSTOMER_USER:
|
||||
Set.unmodifiable(['/home', '/alarms', '/devices', '/more']),
|
||||
};
|
||||
|
||||
static bool isMainPageState(TbContext tbContext, String path) {
|
||||
@@ -44,40 +43,38 @@ class TbMainNavigationItem {
|
||||
page: HomePage(tbContext),
|
||||
title: 'Home',
|
||||
icon: Icon(Icons.home),
|
||||
path: '/home'
|
||||
)
|
||||
path: '/home')
|
||||
];
|
||||
switch(tbContext.tbClient.getAuthUser()!.authority) {
|
||||
switch (tbContext.tbClient.getAuthUser()!.authority) {
|
||||
case Authority.SYS_ADMIN:
|
||||
break;
|
||||
case Authority.TENANT_ADMIN:
|
||||
case Authority.CUSTOMER_USER:
|
||||
items.addAll([
|
||||
TbMainNavigationItem(
|
||||
page: AlarmsPage(tbContext),
|
||||
title: 'Alarms',
|
||||
icon: Icon(Icons.notifications),
|
||||
path: '/alarms'
|
||||
),
|
||||
TbMainNavigationItem(
|
||||
page: DevicesMainPage(tbContext),
|
||||
title: 'Devices',
|
||||
icon: Icon(Icons.devices_other),
|
||||
path: '/devices'
|
||||
)
|
||||
]);
|
||||
break;
|
||||
items.addAll([
|
||||
TbMainNavigationItem(
|
||||
page: AlarmsPage(tbContext),
|
||||
title: 'Alarms',
|
||||
icon: Icon(Icons.notifications),
|
||||
path: '/alarms'),
|
||||
TbMainNavigationItem(
|
||||
page: DevicesMainPage(tbContext),
|
||||
title: 'Devices',
|
||||
icon: Icon(Icons.devices_other),
|
||||
path: '/devices')
|
||||
]);
|
||||
break;
|
||||
case Authority.REFRESH_TOKEN:
|
||||
break;
|
||||
case Authority.ANONYMOUS:
|
||||
break;
|
||||
case Authority.PRE_VERIFICATION_TOKEN:
|
||||
break;
|
||||
}
|
||||
items.add(TbMainNavigationItem(
|
||||
page: MorePage(tbContext),
|
||||
title: 'More',
|
||||
icon: Icon(Icons.menu),
|
||||
path: '/more'
|
||||
));
|
||||
path: '/more'));
|
||||
return items;
|
||||
} else {
|
||||
return [];
|
||||
@@ -86,19 +83,18 @@ class TbMainNavigationItem {
|
||||
}
|
||||
|
||||
class MainPage extends TbPageWidget {
|
||||
|
||||
final String _path;
|
||||
|
||||
MainPage(TbContext tbContext, {required String path}):
|
||||
_path = path, super(tbContext);
|
||||
MainPage(TbContext tbContext, {required String path})
|
||||
: _path = path,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_MainPageState createState() => _MainPageState();
|
||||
|
||||
}
|
||||
|
||||
class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProviderStateMixin {
|
||||
|
||||
class _MainPageState extends TbPageState<MainPage>
|
||||
with TbMainState, TickerProviderStateMixin {
|
||||
late ValueNotifier<int> _currentIndexNotifier;
|
||||
late final List<TbMainNavigationItem> _tabItems;
|
||||
late TabController _tabController;
|
||||
@@ -108,7 +104,8 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
super.initState();
|
||||
_tabItems = TbMainNavigationItem.getItems(tbContext);
|
||||
int currentIndex = _indexFromPath(widget._path);
|
||||
_tabController = TabController(initialIndex: currentIndex, length: _tabItems.length, vsync: this);
|
||||
_tabController = TabController(
|
||||
initialIndex: currentIndex, length: _tabItems.length, vsync: this);
|
||||
_currentIndexNotifier = ValueNotifier(currentIndex);
|
||||
_tabController.animation!.addListener(_onTabAnimation);
|
||||
}
|
||||
@@ -119,7 +116,7 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onTabAnimation () {
|
||||
_onTabAnimation() {
|
||||
var value = _tabController.animation!.value;
|
||||
var targetIndex;
|
||||
if (value >= _tabController.previousIndex) {
|
||||
@@ -142,24 +139,24 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
},
|
||||
child: Scaffold(
|
||||
body: TabBarView(
|
||||
physics: tbContext.homeDashboard != null ? NeverScrollableScrollPhysics() : null,
|
||||
physics: tbContext.homeDashboard != null
|
||||
? NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
controller: _tabController,
|
||||
children: _tabItems.map((item) => item.page).toList(),
|
||||
),
|
||||
bottomNavigationBar: ValueListenableBuilder<int>(
|
||||
valueListenable: _currentIndexNotifier,
|
||||
builder: (context, index, child) => BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: index,
|
||||
onTap: (int index) => _setIndex(index) /*_currentIndex = index*/,
|
||||
items: _tabItems.map((item) => BottomNavigationBarItem(
|
||||
icon: item.icon,
|
||||
label: item.title
|
||||
)).toList()
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
valueListenable: _currentIndexNotifier,
|
||||
builder: (context, index, child) => BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: index,
|
||||
onTap: (int index) =>
|
||||
_setIndex(index) /*_currentIndex = index*/,
|
||||
items: _tabItems
|
||||
.map((item) => BottomNavigationBarItem(
|
||||
icon: item.icon, label: item.title))
|
||||
.toList()),
|
||||
)));
|
||||
}
|
||||
|
||||
int _indexFromPath(String path) {
|
||||
@@ -175,7 +172,7 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
navigateToPath(String path) {
|
||||
int targetIndex = _indexFromPath(path);
|
||||
_setIndex(targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool isHomePage() {
|
||||
@@ -185,5 +182,4 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
|
||||
_setIndex(int index) {
|
||||
_tabController.index = index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,129 +4,108 @@ import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class MorePage extends TbContextWidget {
|
||||
|
||||
MorePage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_MorePageState createState() => _MorePageState();
|
||||
|
||||
}
|
||||
|
||||
class _MorePageState extends TbContextState<MorePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Container(
|
||||
padding: EdgeInsets.fromLTRB(16, 40, 16, 20),
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.account_circle, size: 48, color: Color(0xFFAFAFAF)),
|
||||
Spacer(),
|
||||
IconButton(icon: Icon(Icons.settings, color: Color(0xFFAFAFAF)), onPressed: () async {
|
||||
await navigateTo('/profile');
|
||||
setState(() {});
|
||||
})
|
||||
],
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Text(_getUserDisplayName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 20,
|
||||
height: 23 / 20
|
||||
)
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(_getAuthorityName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 16 / 14
|
||||
)
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
buildMoreMenuItems(context),
|
||||
SizedBox(height: 8),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.account_circle,
|
||||
size: 48, color: Color(0xFFAFAFAF)),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings, color: Color(0xFFAFAFAF)),
|
||||
onPressed: () async {
|
||||
await navigateTo('/profile');
|
||||
setState(() {});
|
||||
})
|
||||
],
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Text(_getUserDisplayName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 20,
|
||||
height: 23 / 20)),
|
||||
SizedBox(height: 2),
|
||||
Text(_getAuthorityName(),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 16 / 14)),
|
||||
SizedBox(height: 24),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
buildMoreMenuItems(context),
|
||||
SizedBox(height: 8),
|
||||
Divider(color: Color(0xFFEDEDED)),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 48,
|
||||
height: 48,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Icon(Icons.logout, color: Color(0xFFE04B2F)),
|
||||
SizedBox(width: 34),
|
||||
Text('Log out',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFE04B2F),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
))
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
padding:
|
||||
EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
Icon(Icons.logout, color: Color(0xFFE04B2F)),
|
||||
SizedBox(width: 34),
|
||||
Text('Log out',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFE04B2F),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14))
|
||||
]))),
|
||||
onTap: () {
|
||||
tbClient.logout(
|
||||
requestConfig: RequestConfig(ignoreErrors: true));
|
||||
}
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget buildMoreMenuItems(BuildContext context) {
|
||||
List<Widget> items = MoreMenuItem.getItems(tbContext).map((menuItem) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 48,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Icon(menuItem.icon, color: Color(0xFF282828)),
|
||||
SizedBox(width: 34),
|
||||
Text(menuItem.title,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
))
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo(menuItem.path);
|
||||
}
|
||||
);
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 48,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
|
||||
child: Row(mainAxisSize: MainAxisSize.max, children: [
|
||||
Icon(menuItem.icon, color: Color(0xFF282828)),
|
||||
SizedBox(width: 34),
|
||||
Text(menuItem.title,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14))
|
||||
]))),
|
||||
onTap: () {
|
||||
navigateTo(menuItem.path);
|
||||
});
|
||||
}).toList();
|
||||
return Column(
|
||||
children: items
|
||||
);
|
||||
return Column(children: items);
|
||||
}
|
||||
|
||||
String _getUserDisplayName() {
|
||||
@@ -156,7 +135,7 @@ class _MorePageState extends TbContextState<MorePage> {
|
||||
var name = '';
|
||||
if (user != null) {
|
||||
var authority = user.authority;
|
||||
switch(authority) {
|
||||
switch (authority) {
|
||||
case Authority.SYS_ADMIN:
|
||||
name = 'System Administrator';
|
||||
break;
|
||||
@@ -166,11 +145,12 @@ class _MorePageState extends TbContextState<MorePage> {
|
||||
case Authority.CUSTOMER_USER:
|
||||
name = 'Customer';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MoreMenuItem {
|
||||
@@ -178,11 +158,7 @@ class MoreMenuItem {
|
||||
final IconData icon;
|
||||
final String path;
|
||||
|
||||
MoreMenuItem({
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.path
|
||||
});
|
||||
MoreMenuItem({required this.title, required this.icon, required this.path});
|
||||
|
||||
static List<MoreMenuItem> getItems(TbContext tbContext) {
|
||||
if (tbContext.isAuthenticated) {
|
||||
@@ -195,33 +171,25 @@ class MoreMenuItem {
|
||||
MoreMenuItem(
|
||||
title: 'Customers',
|
||||
icon: Icons.supervisor_account,
|
||||
path: '/customers'
|
||||
),
|
||||
MoreMenuItem(
|
||||
title: 'Assets',
|
||||
icon: Icons.domain,
|
||||
path: '/assets'
|
||||
),
|
||||
path: '/customers'),
|
||||
MoreMenuItem(title: 'Assets', icon: Icons.domain, path: '/assets'),
|
||||
MoreMenuItem(
|
||||
title: 'Audit Logs',
|
||||
icon: Icons.track_changes,
|
||||
path: '/auditLogs'
|
||||
)
|
||||
path: '/auditLogs')
|
||||
]);
|
||||
break;
|
||||
case Authority.CUSTOMER_USER:
|
||||
items.addAll([
|
||||
MoreMenuItem(
|
||||
title: 'Assets',
|
||||
icon: Icons.domain,
|
||||
path: '/assets'
|
||||
)
|
||||
MoreMenuItem(title: 'Assets', icon: Icons.domain, path: '/assets')
|
||||
]);
|
||||
break;
|
||||
case Authority.REFRESH_TOKEN:
|
||||
break;
|
||||
case Authority.ANONYMOUS:
|
||||
break;
|
||||
case Authority.PRE_VERIFICATION_TOKEN:
|
||||
break;
|
||||
}
|
||||
return items;
|
||||
} else {
|
||||
|
||||
@@ -7,16 +7,13 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
|
||||
class ChangePasswordPage extends TbContextWidget {
|
||||
|
||||
ChangePasswordPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_ChangePasswordPageState createState() => _ChangePasswordPageState();
|
||||
|
||||
}
|
||||
|
||||
class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
|
||||
final _isLoadingNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
final _showCurrentPasswordNotifier = ValueNotifier<bool>(false);
|
||||
@@ -39,96 +36,105 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SingleChildScrollView(
|
||||
child: FormBuilder(
|
||||
key: _changePasswordFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
ValueListenableBuilder(
|
||||
child: FormBuilder(
|
||||
key: _changePasswordFormKey,
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showCurrentPasswordNotifier,
|
||||
builder: (BuildContext context, bool showPassword, child) {
|
||||
builder: (BuildContext context, bool showPassword,
|
||||
child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'currentPassword',
|
||||
obscureText: !showPassword,
|
||||
autofocus: true,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'Current password is required.')
|
||||
FormBuilderValidators.required(
|
||||
errorText:
|
||||
'Current password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
|
||||
icon: Icon(showPassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showCurrentPasswordNotifier.value = !_showCurrentPasswordNotifier.value;
|
||||
_showCurrentPasswordNotifier.value =
|
||||
!_showCurrentPasswordNotifier
|
||||
.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Current password *'
|
||||
),
|
||||
labelText: 'Current password *'),
|
||||
);
|
||||
}
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPasswordNotifier,
|
||||
builder: (BuildContext context, bool showPassword, child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'New password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPasswordNotifier.value = !_showNewPasswordNotifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password *'
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPassword2Notifier,
|
||||
builder: (BuildContext context, bool showPassword, child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword2',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'New password again is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPassword2Notifier.value = !_showNewPassword2Notifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password again *'
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password'))
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPasswordNotifier,
|
||||
builder: (BuildContext context, bool showPassword,
|
||||
child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText: 'New password is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPasswordNotifier.value =
|
||||
!_showNewPasswordNotifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password *'),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showNewPassword2Notifier,
|
||||
builder: (BuildContext context, bool showPassword,
|
||||
child) {
|
||||
return FormBuilderTextField(
|
||||
name: 'newPassword2',
|
||||
obscureText: !showPassword,
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText:
|
||||
'New password again is required.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(showPassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off),
|
||||
onPressed: () {
|
||||
_showNewPassword2Notifier.value =
|
||||
!_showNewPassword2Notifier.value;
|
||||
},
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'New password again *'),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password')))
|
||||
]),
|
||||
))),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isLoadingNotifier,
|
||||
@@ -136,18 +142,15 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
child: Container(
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
)
|
||||
);
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _changePassword() async {
|
||||
@@ -165,11 +168,10 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
await tbClient.changePassword(currentPassword, newPassword);
|
||||
pop(true);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
_isLoadingNotifier.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:thingsboard_app/modules/profile/change_password_page.dart';
|
||||
@@ -11,18 +10,17 @@ import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class ProfilePage extends TbPageWidget {
|
||||
|
||||
final bool _fullscreen;
|
||||
|
||||
ProfilePage(TbContext tbContext, {bool fullscreen = false}) : _fullscreen = fullscreen, super(tbContext);
|
||||
ProfilePage(TbContext tbContext, {bool fullscreen = false})
|
||||
: _fullscreen = fullscreen,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_ProfilePageState createState() => _ProfilePageState();
|
||||
|
||||
}
|
||||
|
||||
class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
|
||||
final _isLoadingNotifier = ValueNotifier<bool>(true);
|
||||
|
||||
final _profileFormKey = GlobalKey<FormBuilderState>();
|
||||
@@ -49,21 +47,16 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
title: const Text('Profile'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.check
|
||||
),
|
||||
icon: Icon(Icons.check),
|
||||
onPressed: () {
|
||||
_saveProfile();
|
||||
}
|
||||
),
|
||||
if (widget._fullscreen) IconButton(
|
||||
icon: Icon(
|
||||
Icons.logout
|
||||
),
|
||||
onPressed: () {
|
||||
tbClient.logout();
|
||||
}
|
||||
)
|
||||
}),
|
||||
if (widget._fullscreen)
|
||||
IconButton(
|
||||
icon: Icon(Icons.logout),
|
||||
onPressed: () {
|
||||
tbClient.logout();
|
||||
})
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
@@ -72,73 +65,66 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SingleChildScrollView(
|
||||
child: FormBuilder(
|
||||
key: _profileFormKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
FormBuilderTextField(
|
||||
name: 'email',
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(context, errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(context, errorText: 'Invalid email format.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email *'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'firstName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'First Name'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'lastName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Last Name'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password'))
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _profileFormKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
FormBuilderTextField(
|
||||
name: 'email',
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(
|
||||
errorText: 'Email is required.'),
|
||||
FormBuilderValidators.email(
|
||||
errorText: 'Invalid email format.')
|
||||
]),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Email *'),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'firstName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'First Name'),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
FormBuilderTextField(
|
||||
name: 'lastName',
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Last Name'),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.all(16),
|
||||
alignment: Alignment.centerLeft),
|
||||
onPressed: () {
|
||||
_changePassword();
|
||||
},
|
||||
child: Center(child: Text('Change Password')))
|
||||
]),
|
||||
))),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isLoadingNotifier,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
if (loading) {
|
||||
return SizedBox.expand(
|
||||
child: Container(
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
)
|
||||
color: Color(0x99FFFFFF),
|
||||
child: Center(child: TbProgressIndicator(size: 50.0)),
|
||||
));
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _loadUser() async {
|
||||
@@ -170,15 +156,18 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
|
||||
_setUser();
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
_isLoadingNotifier.value = false;
|
||||
showSuccessNotification('Profile successfully updated', duration: Duration(milliseconds: 1500));
|
||||
showSuccessNotification('Profile successfully updated',
|
||||
duration: Duration(milliseconds: 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_changePassword() async {
|
||||
var res = await tbContext.showFullScreenDialog<bool>(new ChangePasswordPage(tbContext));
|
||||
var res = await tbContext
|
||||
.showFullScreenDialog<bool>(new ChangePasswordPage(tbContext));
|
||||
if (res == true) {
|
||||
showSuccessNotification('Password successfully changed', duration: Duration(milliseconds: 1500));
|
||||
showSuccessNotification('Password successfully changed',
|
||||
duration: Duration(milliseconds: 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'profile_page.dart';
|
||||
|
||||
class ProfileRoutes extends TbRoutes {
|
||||
|
||||
late var profileHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var profileHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var fullscreen = params['fullscreen']?.first == 'true';
|
||||
return ProfilePage(tbContext, fullscreen: fullscreen);
|
||||
});
|
||||
@@ -18,5 +18,4 @@ class ProfileRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/profile", handler: profileHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import 'package:thingsboard_app/core/entity/entity_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class TenantDetailsPage extends ContactBasedDetailsPage<Tenant> {
|
||||
|
||||
TenantDetailsPage(TbContext tbContext, String tenantId):
|
||||
super(tbContext, entityId: tenantId, defaultTitle: 'Tenant', subTitle: 'Tenant details');
|
||||
TenantDetailsPage(TbContext tbContext, String tenantId)
|
||||
: super(tbContext,
|
||||
entityId: tenantId,
|
||||
defaultTitle: 'Tenant',
|
||||
subTitle: 'Tenant details');
|
||||
|
||||
@override
|
||||
Future<Tenant?> fetchEntity(String tenantId) {
|
||||
return tbClient.getTenantService().getTenant(tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import 'tenant_details_page.dart';
|
||||
import 'tenants_page.dart';
|
||||
|
||||
class TenantRoutes extends TbRoutes {
|
||||
|
||||
late var tenantsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var tenantsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return TenantsPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
|
||||
late var tenantDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var tenantDetailsHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return TenantDetailsPage(tbContext, params["id"][0]);
|
||||
});
|
||||
|
||||
@@ -23,5 +24,4 @@ class TenantRoutes extends TbRoutes {
|
||||
router.define("/tenants", handler: tenantsHandler);
|
||||
router.define("/tenant/:id", handler: tenantDetailsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin TenantsBase on EntitiesBase<Tenant, PageLink> {
|
||||
|
||||
mixin TenantsBase on EntitiesBase<Tenant, PageLink> {
|
||||
@override
|
||||
String get title => 'Tenants';
|
||||
|
||||
@@ -18,5 +17,4 @@ mixin TenantsBase on EntitiesBase<Tenant, PageLink> {
|
||||
void onEntityTap(Tenant tenant) {
|
||||
navigateTo('/tenant/${tenant.id!.id}');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'tenants_base.dart';
|
||||
|
||||
class TenantsList extends BaseEntitiesWidget<Tenant, PageLink> with TenantsBase, ContactBasedBase, EntitiesListStateBase {
|
||||
|
||||
TenantsList(TbContext tbContext, PageKeyController<PageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
class TenantsList extends BaseEntitiesWidget<Tenant, PageLink>
|
||||
with TenantsBase, ContactBasedBase, EntitiesListStateBase {
|
||||
TenantsList(
|
||||
TbContext tbContext, PageKeyController<PageLink> pageKeyController,
|
||||
{searchMode = false})
|
||||
: super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
}
|
||||
|
||||
@@ -7,23 +7,22 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'tenants_list.dart';
|
||||
|
||||
class TenantsPage extends TbPageWidget {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
TenantsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
TenantsPage(TbContext tbContext, {this.searchMode = false})
|
||||
: super(tbContext);
|
||||
|
||||
@override
|
||||
_TenantsPageState createState() => _TenantsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _TenantsPageState extends TbPageState<TenantsPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var tenantsList = TenantsList(tbContext, _pageLinkController, searchMode: widget.searchMode);
|
||||
var tenantsList = TenantsList(tbContext, _pageLinkController,
|
||||
searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
@@ -31,24 +30,16 @@ class _TenantsPageState extends TbPageState<TenantsPage> {
|
||||
onSearch: (searchText) => _pageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(tenantsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
appBar = TbAppBar(tbContext, title: Text(tenantsList.title), actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
onPressed: () {
|
||||
navigateTo('/tenants?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: tenantsList
|
||||
);
|
||||
return Scaffold(appBar: appBar, body: tenantsList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -56,5 +47,4 @@ class _TenantsPageState extends TbPageState<TenantsPage> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,21 +6,18 @@ import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'tenants_list.dart';
|
||||
|
||||
class TenantsWidget extends TbContextWidget {
|
||||
|
||||
TenantsWidget(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_TenantsWidgetState createState() => _TenantsWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _TenantsWidgetState extends TbContextState<TenantsWidget> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TenantsList(tbContext, _pageLinkController);
|
||||
return TenantsList(tbContext, _pageLinkController);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -28,5 +25,4 @@ class _TenantsWidgetState extends TbContextState<TenantsWidget> {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
TbStorage createAppStorage() => TbSecureStorage();
|
||||
|
||||
class TbSecureStorage implements TbStorage {
|
||||
|
||||
final flutterStorage = FlutterSecureStorage();
|
||||
|
||||
@override
|
||||
@@ -21,5 +20,4 @@ class TbSecureStorage implements TbStorage {
|
||||
Future<void> setItem(String key, String value) async {
|
||||
return await flutterStorage.write(key: key, value: value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'dart:html';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
TbStorage createAppStorage() => TbWebLocalStorage();
|
||||
|
||||
class TbWebLocalStorage implements TbStorage {
|
||||
|
||||
final Storage _localStorage = window.localStorage;
|
||||
final html.Storage _localStorage = html.window.localStorage;
|
||||
|
||||
@override
|
||||
Future<void> deleteItem(String key) async {
|
||||
@@ -21,5 +20,4 @@ class TbWebLocalStorage implements TbStorage {
|
||||
Future<void> setItem(String key, String value) async {
|
||||
_localStorage[key] = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +1,29 @@
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
abstract class DeviceProfileCache {
|
||||
|
||||
static final _cache = Map<String, DeviceProfileInfo>();
|
||||
|
||||
static Future<DeviceProfileInfo> getDeviceProfileInfo(ThingsboardClient tbClient, String name, String deviceId) async {
|
||||
static Future<DeviceProfileInfo> getDeviceProfileInfo(
|
||||
ThingsboardClient tbClient, String name, String deviceId) async {
|
||||
var deviceProfile = _cache[name];
|
||||
if (deviceProfile == null) {
|
||||
var device = await tbClient.getDeviceService().getDevice(deviceId);
|
||||
deviceProfile = await tbClient.getDeviceProfileService().getDeviceProfileInfo(device!.deviceProfileId!.id!);
|
||||
deviceProfile = await tbClient
|
||||
.getDeviceProfileService()
|
||||
.getDeviceProfileInfo(device!.deviceProfileId!.id!);
|
||||
_cache[name] = deviceProfile!;
|
||||
}
|
||||
return deviceProfile;
|
||||
}
|
||||
|
||||
static Future<PageData<DeviceProfileInfo>> getDeviceProfileInfos(ThingsboardClient tbClient, PageLink pageLink) async {
|
||||
var deviceProfileInfos = await tbClient.getDeviceProfileService().getDeviceProfileInfos(pageLink);
|
||||
static Future<PageData<DeviceProfileInfo>> getDeviceProfileInfos(
|
||||
ThingsboardClient tbClient, PageLink pageLink) async {
|
||||
var deviceProfileInfos = await tbClient
|
||||
.getDeviceProfileService()
|
||||
.getDeviceProfileInfos(pageLink);
|
||||
deviceProfileInfos.data.forEach((deviceProfile) {
|
||||
_cache[deviceProfile.name] = deviceProfile;
|
||||
});
|
||||
return deviceProfileInfos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
abstract class EntityQueryApi {
|
||||
|
||||
static final activeDeviceKeyFilter = KeyFilter(
|
||||
key: EntityKey(type: EntityKeyType.ATTRIBUTE, key: 'active'),
|
||||
valueType: EntityKeyValueType.BOOLEAN,
|
||||
@@ -27,36 +26,54 @@ abstract class EntityQueryApi {
|
||||
EntityKey(type: EntityKeyType.ATTRIBUTE, key: 'active')
|
||||
];
|
||||
|
||||
static Future<int> countDevices(ThingsboardClient tbClient, {String? deviceType, bool? active}) {
|
||||
static Future<int> countDevices(ThingsboardClient tbClient,
|
||||
{String? deviceType, bool? active}) {
|
||||
EntityFilter deviceFilter;
|
||||
if (deviceType != null) {
|
||||
deviceFilter = DeviceTypeFilter(deviceType: deviceType, deviceNameFilter: '');
|
||||
deviceFilter =
|
||||
DeviceTypeFilter(deviceType: deviceType, deviceNameFilter: '');
|
||||
} else {
|
||||
deviceFilter = EntityTypeFilter(entityType: EntityType.DEVICE);
|
||||
}
|
||||
EntityCountQuery deviceCountQuery = EntityCountQuery(entityFilter: deviceFilter);
|
||||
EntityCountQuery deviceCountQuery =
|
||||
EntityCountQuery(entityFilter: deviceFilter);
|
||||
if (active != null) {
|
||||
deviceCountQuery.keyFilters = [active ? activeDeviceKeyFilter : inactiveDeviceKeyFilter];
|
||||
deviceCountQuery.keyFilters = [
|
||||
active ? activeDeviceKeyFilter : inactiveDeviceKeyFilter
|
||||
];
|
||||
}
|
||||
return tbClient.getEntityQueryService().countEntitiesByQuery(deviceCountQuery);
|
||||
return tbClient
|
||||
.getEntityQueryService()
|
||||
.countEntitiesByQuery(deviceCountQuery);
|
||||
}
|
||||
|
||||
static EntityDataQuery createDefaultDeviceQuery({int pageSize = 20, String? searchText, String? deviceType, bool? active}) {
|
||||
static EntityDataQuery createDefaultDeviceQuery(
|
||||
{int pageSize = 20,
|
||||
String? searchText,
|
||||
String? deviceType,
|
||||
bool? active}) {
|
||||
EntityFilter deviceFilter;
|
||||
List<KeyFilter>? keyFilters;
|
||||
if (deviceType != null) {
|
||||
deviceFilter = DeviceTypeFilter(deviceType: deviceType, deviceNameFilter: '');
|
||||
deviceFilter =
|
||||
DeviceTypeFilter(deviceType: deviceType, deviceNameFilter: '');
|
||||
} else {
|
||||
deviceFilter = EntityTypeFilter(entityType: EntityType.DEVICE);
|
||||
}
|
||||
if (active != null) {
|
||||
keyFilters = [active ? activeDeviceKeyFilter : inactiveDeviceKeyFilter];
|
||||
}
|
||||
return EntityDataQuery(entityFilter: deviceFilter, keyFilters: keyFilters,
|
||||
entityFields: defaultDeviceFields, latestValues: defaultDeviceAttributes, pageLink: EntityDataPageLink(pageSize: pageSize,
|
||||
return EntityDataQuery(
|
||||
entityFilter: deviceFilter,
|
||||
keyFilters: keyFilters,
|
||||
entityFields: defaultDeviceFields,
|
||||
latestValues: defaultDeviceAttributes,
|
||||
pageLink: EntityDataPageLink(
|
||||
pageSize: pageSize,
|
||||
textSearch: searchText,
|
||||
sortOrder: EntityDataSortOrder(key: EntityKey(type: EntityKeyType.ENTITY_FIELD, key: 'createdTime'),
|
||||
sortOrder: EntityDataSortOrder(
|
||||
key: EntityKey(
|
||||
type: EntityKeyType.ENTITY_FIELD, key: 'createdTime'),
|
||||
direction: EntityDataSortOrderDirection.DESC)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export '_tb_app_storage.dart'
|
||||
if (dart.library.io) 'tb_secure_storage.dart'
|
||||
if (dart.library.html) 'tb_web_local_storage.dart';
|
||||
if (dart.library.io) '_tb_secure_storage.dart'
|
||||
if (dart.library.html) '_tb_web_local_storage.dart';
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:image_picker/image_picker.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class WidgetMobileActionResult<T extends MobileActionResult> {
|
||||
T? result;
|
||||
@@ -16,11 +16,17 @@ class WidgetMobileActionResult<T extends MobileActionResult> {
|
||||
String? error;
|
||||
bool hasError = false;
|
||||
|
||||
WidgetMobileActionResult.errorResult(this.error): hasError = true, hasResult = false;
|
||||
WidgetMobileActionResult.errorResult(this.error)
|
||||
: hasError = true,
|
||||
hasResult = false;
|
||||
|
||||
WidgetMobileActionResult.successResult(this.result): hasError = false, hasResult = true;
|
||||
WidgetMobileActionResult.successResult(this.result)
|
||||
: hasError = false,
|
||||
hasResult = true;
|
||||
|
||||
WidgetMobileActionResult.emptyResult(): hasError = false, hasResult = false;
|
||||
WidgetMobileActionResult.emptyResult()
|
||||
: hasError = false,
|
||||
hasResult = false;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
var json = <String, dynamic>{};
|
||||
@@ -33,7 +39,6 @@ class WidgetMobileActionResult<T extends MobileActionResult> {
|
||||
}
|
||||
|
||||
class MobileActionResult {
|
||||
|
||||
MobileActionResult();
|
||||
|
||||
factory MobileActionResult.launched(bool launched) {
|
||||
@@ -123,24 +128,27 @@ enum WidgetMobileActionType {
|
||||
}
|
||||
|
||||
WidgetMobileActionType widgetMobileActionTypeFromString(String value) {
|
||||
return WidgetMobileActionType.values.firstWhere((e)=>e.toString().split('.')[1].toUpperCase()==value.toUpperCase(), orElse: () => WidgetMobileActionType.unknown);
|
||||
return WidgetMobileActionType.values.firstWhere(
|
||||
(e) => e.toString().split('.')[1].toUpperCase() == value.toUpperCase(),
|
||||
orElse: () => WidgetMobileActionType.unknown);
|
||||
}
|
||||
|
||||
class WidgetActionHandler with HasTbContext {
|
||||
|
||||
WidgetActionHandler(TbContext tbContext) {
|
||||
setTbContext(tbContext);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> handleWidgetMobileAction(List<dynamic> args, InAppWebViewController controller) async {
|
||||
Future<Map<String, dynamic>> handleWidgetMobileAction(
|
||||
List<dynamic> args, InAppWebViewController controller) async {
|
||||
var result = await _handleWidgetMobileAction(args, controller);
|
||||
return result.toJson();
|
||||
}
|
||||
|
||||
Future<WidgetMobileActionResult> _handleWidgetMobileAction(List<dynamic> args, InAppWebViewController controller) async {
|
||||
Future<WidgetMobileActionResult> _handleWidgetMobileAction(
|
||||
List<dynamic> args, InAppWebViewController controller) async {
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
var actionType = widgetMobileActionTypeFromString(args[0]);
|
||||
switch(actionType) {
|
||||
switch (actionType) {
|
||||
case WidgetMobileActionType.takePictureFromGallery:
|
||||
return await _takePicture(ImageSource.gallery);
|
||||
case WidgetMobileActionType.takePhoto:
|
||||
@@ -158,24 +166,26 @@ class WidgetActionHandler with HasTbContext {
|
||||
case WidgetMobileActionType.takeScreenshot:
|
||||
return await _takeScreenshot(controller);
|
||||
case WidgetMobileActionType.unknown:
|
||||
return WidgetMobileActionResult.errorResult('Unknown actionType: ${args[0]}');
|
||||
return WidgetMobileActionResult.errorResult(
|
||||
'Unknown actionType: ${args[0]}');
|
||||
}
|
||||
} else {
|
||||
return WidgetMobileActionResult.errorResult('actionType is not provided.');
|
||||
return WidgetMobileActionResult.errorResult(
|
||||
'actionType is not provided.');
|
||||
}
|
||||
}
|
||||
|
||||
Future<WidgetMobileActionResult> _takePicture(ImageSource source) async {
|
||||
try {
|
||||
final picker = ImagePicker();
|
||||
final pickedFile = await picker.getImage(source: source);
|
||||
final pickedFile = await picker.pickImage(source: source);
|
||||
if (pickedFile != null) {
|
||||
var mimeType = lookupMimeType(pickedFile.path);
|
||||
if (mimeType != null) {
|
||||
var image = File(pickedFile.path);
|
||||
List<int> imageBytes = await image.readAsBytes();
|
||||
String imageUrl = UriData.fromBytes(imageBytes, mimeType: mimeType)
|
||||
.toString();
|
||||
String imageUrl =
|
||||
UriData.fromBytes(imageBytes, mimeType: mimeType).toString();
|
||||
return WidgetMobileActionResult.successResult(
|
||||
MobileActionResult.image(imageUrl));
|
||||
} else {
|
||||
@@ -190,7 +200,8 @@ class WidgetActionHandler with HasTbContext {
|
||||
}
|
||||
}
|
||||
|
||||
Future<WidgetMobileActionResult> _launchMap(List<dynamic> args, bool directionElseLocation) async {
|
||||
Future<WidgetMobileActionResult> _launchMap(
|
||||
List<dynamic> args, bool directionElseLocation) async {
|
||||
try {
|
||||
num? lat;
|
||||
num? lon;
|
||||
@@ -213,9 +224,11 @@ class WidgetActionHandler with HasTbContext {
|
||||
|
||||
Future<WidgetMobileActionResult> _scanQrCode() async {
|
||||
try {
|
||||
Barcode? barcode = await tbContext.navigateTo('/qrCodeScan', transition: TransitionType.nativeModal);
|
||||
Barcode? barcode = await tbContext.navigateTo('/qrCodeScan',
|
||||
transition: TransitionType.nativeModal);
|
||||
if (barcode != null && barcode.code != null) {
|
||||
return WidgetMobileActionResult.successResult(MobileActionResult.qrCode(barcode.code!, describeEnum(barcode.format)));
|
||||
return WidgetMobileActionResult.successResult(MobileActionResult.qrCode(
|
||||
barcode.code!, describeEnum(barcode.format)));
|
||||
} else {
|
||||
return WidgetMobileActionResult.emptyResult();
|
||||
}
|
||||
@@ -261,19 +274,24 @@ class WidgetActionHandler with HasTbContext {
|
||||
return WidgetMobileActionResult.errorResult(
|
||||
'Location permissions are permanently denied, we cannot request permissions.');
|
||||
}
|
||||
var position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
|
||||
return WidgetMobileActionResult.successResult(MobileActionResult.location(position.latitude, position.longitude));
|
||||
var position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
return WidgetMobileActionResult.successResult(
|
||||
MobileActionResult.location(position.latitude, position.longitude));
|
||||
} catch (e) {
|
||||
return _handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<WidgetMobileActionResult> _takeScreenshot(InAppWebViewController controller) async {
|
||||
Future<WidgetMobileActionResult> _takeScreenshot(
|
||||
InAppWebViewController controller) async {
|
||||
try {
|
||||
List<int>? imageBytes = await controller.takeScreenshot();
|
||||
if (imageBytes != null) {
|
||||
String imageUrl = UriData.fromBytes(imageBytes, mimeType: 'image/png').toString();
|
||||
return WidgetMobileActionResult.successResult(MobileActionResult.image(imageUrl));
|
||||
String imageUrl =
|
||||
UriData.fromBytes(imageBytes, mimeType: 'image/png').toString();
|
||||
return WidgetMobileActionResult.successResult(
|
||||
MobileActionResult.image(imageUrl));
|
||||
} else {
|
||||
return WidgetMobileActionResult.emptyResult();
|
||||
}
|
||||
@@ -283,8 +301,8 @@ class WidgetActionHandler with HasTbContext {
|
||||
}
|
||||
|
||||
Future<MobileActionResult> _tryLaunch(String url) async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
if (await canLaunchUrlString(url)) {
|
||||
await launchUrlString(url);
|
||||
return MobileActionResult.launched(true);
|
||||
} else {
|
||||
log.error('Could not launch $url');
|
||||
@@ -301,5 +319,4 @@ class WidgetActionHandler with HasTbContext {
|
||||
}
|
||||
return WidgetMobileActionResult.errorResult(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class FadeOpenPageTransitionsBuilder extends PageTransitionsBuilder {
|
||||
/// Constructs a page transition animation that slides the page up.
|
||||
@@ -7,12 +6,12 @@ class FadeOpenPageTransitionsBuilder extends PageTransitionsBuilder {
|
||||
|
||||
@override
|
||||
Widget buildTransitions<T>(
|
||||
PageRoute<T>? route,
|
||||
BuildContext? context,
|
||||
Animation<double> animation,
|
||||
Animation<double>? secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
PageRoute<T>? route,
|
||||
BuildContext? context,
|
||||
Animation<double> animation,
|
||||
Animation<double>? secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
return FadeOpenPageTransition(routeAnimation: animation, child: child);
|
||||
}
|
||||
}
|
||||
@@ -20,9 +19,11 @@ class FadeOpenPageTransitionsBuilder extends PageTransitionsBuilder {
|
||||
class FadeOpenPageTransition extends StatelessWidget {
|
||||
FadeOpenPageTransition({
|
||||
Key? key,
|
||||
required Animation<double> routeAnimation, // The route's linear 0.0 - 1.0 animation.
|
||||
required Animation<double>
|
||||
routeAnimation, // The route's linear 0.0 - 1.0 animation.
|
||||
required this.child,
|
||||
}) : _positionAnimation = routeAnimation.drive(_leftRightTween.chain(_fastOutSlowInTween)),
|
||||
}) : _positionAnimation =
|
||||
routeAnimation.drive(_leftRightTween.chain(_fastOutSlowInTween)),
|
||||
_opacityAnimation = routeAnimation.drive(_easeInTween),
|
||||
super(key: key);
|
||||
|
||||
@@ -31,8 +32,10 @@ class FadeOpenPageTransition extends StatelessWidget {
|
||||
begin: const Offset(0.5, 0.0),
|
||||
end: Offset.zero,
|
||||
);
|
||||
static final Animatable<double> _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn);
|
||||
static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
|
||||
static final Animatable<double> _fastOutSlowInTween =
|
||||
CurveTween(curve: Curves.fastOutSlowIn);
|
||||
static final Animatable<double> _easeInTween =
|
||||
CurveTween(curve: Curves.easeIn);
|
||||
|
||||
final Animation<Offset> _positionAnimation;
|
||||
final Animation<double> _opacityAnimation;
|
||||
|
||||
@@ -2,22 +2,18 @@ import 'dart:io';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
class QrCodeScannerPage extends TbPageWidget {
|
||||
|
||||
QrCodeScannerPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_QrCodeScannerPageState createState() => _QrCodeScannerPageState();
|
||||
|
||||
}
|
||||
|
||||
class _QrCodeScannerPageState extends TbPageState<QrCodeScannerPage> {
|
||||
|
||||
Timer? simulatedQrTimer;
|
||||
QRViewController? controller;
|
||||
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||
@@ -48,64 +44,63 @@ class _QrCodeScannerPageState extends TbPageState<QrCodeScannerPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
_buildQrView(context),
|
||||
Positioned(
|
||||
body: Stack(
|
||||
children: [
|
||||
_buildQrView(context),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: kToolbarHeight,
|
||||
child: Center(child: Text('Scan a code', style: TextStyle(color: Colors.white, fontSize: 20)))
|
||||
child: Center(
|
||||
child: Text('Scan a code',
|
||||
style: TextStyle(color: Colors.white, fontSize: 20)))),
|
||||
Positioned(
|
||||
child: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: IconThemeData(color: Colors.white),
|
||||
elevation: 0,
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: FutureBuilder(
|
||||
future: controller?.getFlashStatus(),
|
||||
builder: (context, snapshot) {
|
||||
return Icon(snapshot.data == false
|
||||
? Icons.flash_on
|
||||
: Icons.flash_off);
|
||||
}),
|
||||
onPressed: () async {
|
||||
await controller?.toggleFlash();
|
||||
setState(() {});
|
||||
},
|
||||
tooltip: 'Toggle flash',
|
||||
),
|
||||
IconButton(
|
||||
icon: FutureBuilder(
|
||||
future: controller?.getCameraInfo(),
|
||||
builder: (context, snapshot) {
|
||||
return Icon(snapshot.data == CameraFacing.front
|
||||
? Icons.camera_rear
|
||||
: Icons.camera_front);
|
||||
}),
|
||||
onPressed: () async {
|
||||
await controller?.flipCamera();
|
||||
setState(() {});
|
||||
},
|
||||
tooltip: 'Toggle camera',
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
child:
|
||||
AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white
|
||||
),
|
||||
elevation: 0,
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: FutureBuilder(
|
||||
future: controller?.getFlashStatus(),
|
||||
builder: (context, snapshot) {
|
||||
return Icon(snapshot.data == false ? Icons.flash_on : Icons.flash_off);
|
||||
}
|
||||
),
|
||||
onPressed: () async {
|
||||
await controller?.toggleFlash();
|
||||
setState(() {});
|
||||
},
|
||||
tooltip: 'Toggle flash',
|
||||
),
|
||||
IconButton(
|
||||
icon: FutureBuilder(
|
||||
future: controller?.getCameraInfo(),
|
||||
builder: (context, snapshot) {
|
||||
return Icon(snapshot.data == CameraFacing.front ? Icons.camera_rear : Icons.camera_front);
|
||||
}
|
||||
),
|
||||
onPressed: () async {
|
||||
await controller?.flipCamera();
|
||||
setState(() {});
|
||||
},
|
||||
tooltip: 'Toggle camera',
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildQrView(BuildContext context) {
|
||||
// For this example we check how width or tall the device is and change the scanArea and overlay accordingly.
|
||||
var scanArea = (MediaQuery.of(context).size.width < 400 ||
|
||||
MediaQuery.of(context).size.height < 400)
|
||||
MediaQuery.of(context).size.height < 400)
|
||||
? 150.0
|
||||
: 300.0;
|
||||
// To ensure the Scanner view is properly sizes after rotation
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/utils/ui/qr_code_scanner.dart';
|
||||
|
||||
class UiUtilsRoutes extends TbRoutes {
|
||||
|
||||
late var qrCodeScannerHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
late var qrCodeScannerHandler = Handler(
|
||||
handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return QrCodeScannerPage(tbContext);
|
||||
});
|
||||
|
||||
@@ -16,5 +16,4 @@ class UiUtilsRoutes extends TbRoutes {
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/qrCodeScan", handler: qrCodeScannerHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
abstract class Utils {
|
||||
|
||||
static String createDashboardEntityState(EntityId entityId, {String? entityName, String? entityLabel}) {
|
||||
var stateObj = [<String, dynamic>{
|
||||
'params': <String, dynamic>{
|
||||
'entityId': entityId.toJson()
|
||||
static String createDashboardEntityState(EntityId entityId,
|
||||
{String? entityName, String? entityLabel}) {
|
||||
var stateObj = [
|
||||
<String, dynamic>{
|
||||
'params': <String, dynamic>{'entityId': entityId.toJson()}
|
||||
}
|
||||
}];
|
||||
];
|
||||
if (entityName != null) {
|
||||
stateObj[0]['params']['entityName'] = entityName;
|
||||
}
|
||||
@@ -19,14 +19,13 @@ abstract class Utils {
|
||||
stateObj[0]['params']['entityLabel'] = entityLabel;
|
||||
}
|
||||
var stateJson = json.encode(stateObj);
|
||||
var encodedUri = Uri.encodeComponent(stateJson);
|
||||
encodedUri = encodedUri.replaceAllMapped(RegExp(r'%([0-9A-F]{2})'), (match) {
|
||||
var encodedUri = Uri.encodeComponent(stateJson);
|
||||
encodedUri =
|
||||
encodedUri.replaceAllMapped(RegExp(r'%([0-9A-F]{2})'), (match) {
|
||||
var p1 = match.group(1)!;
|
||||
return String.fromCharCode(int.parse(p1, radix: 16));
|
||||
});
|
||||
return Uri.encodeComponent(
|
||||
base64.encode(utf8.encode(encodedUri))
|
||||
);
|
||||
return Uri.encodeComponent(base64.encode(utf8.encode(encodedUri)));
|
||||
}
|
||||
|
||||
static String? contactToShortAddress(ContactBased contact) {
|
||||
@@ -47,13 +46,21 @@ abstract class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static Widget imageFromBase64(String base64, {Color? color, double? width, double? height, String? semanticLabel}) {
|
||||
static Widget imageFromBase64(String base64,
|
||||
{Color? color, double? width, double? height, String? semanticLabel}) {
|
||||
var uriData = UriData.parse(base64);
|
||||
if (uriData.mimeType == 'image/svg+xml') {
|
||||
return SvgPicture.memory(uriData.contentAsBytes(), color: color, width: width, height: height, semanticsLabel: semanticLabel);
|
||||
return SvgPicture.memory(uriData.contentAsBytes(),
|
||||
color: color,
|
||||
width: width,
|
||||
height: height,
|
||||
semanticsLabel: semanticLabel);
|
||||
} else {
|
||||
return Image.memory(uriData.contentAsBytes(), color: color, width: width, height: height, semanticLabel: semanticLabel);
|
||||
return Image.memory(uriData.contentAsBytes(),
|
||||
color: color,
|
||||
width: width,
|
||||
height: height,
|
||||
semanticLabel: semanticLabel);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ import 'dart:async';
|
||||
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
class TbAppBar extends TbContextWidget implements PreferredSizeWidget {
|
||||
|
||||
final Widget? leading;
|
||||
final Widget? title;
|
||||
final List<Widget>? actions;
|
||||
@@ -18,18 +16,22 @@ class TbAppBar extends TbContextWidget implements PreferredSizeWidget {
|
||||
@override
|
||||
final Size preferredSize;
|
||||
|
||||
TbAppBar(TbContext tbContext, {this.leading, this.title, this.actions, this.elevation = 8,
|
||||
this.shadowColor, this.showLoadingIndicator = false}) :
|
||||
preferredSize = Size.fromHeight(kToolbarHeight + (showLoadingIndicator ? 4 : 0)),
|
||||
super(tbContext);
|
||||
TbAppBar(TbContext tbContext,
|
||||
{this.leading,
|
||||
this.title,
|
||||
this.actions,
|
||||
this.elevation = 8,
|
||||
this.shadowColor,
|
||||
this.showLoadingIndicator = false})
|
||||
: preferredSize =
|
||||
Size.fromHeight(kToolbarHeight + (showLoadingIndicator ? 4 : 0)),
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_TbAppBarState createState() => _TbAppBarState();
|
||||
|
||||
}
|
||||
|
||||
class _TbAppBarState extends TbContextState<TbAppBar> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -45,18 +47,15 @@ class _TbAppBarState extends TbContextState<TbAppBar> {
|
||||
List<Widget> children = <Widget>[];
|
||||
children.add(buildDefaultBar());
|
||||
if (widget.showLoadingIndicator) {
|
||||
children.add(
|
||||
ValueListenableBuilder(
|
||||
valueListenable: loadingNotifier,
|
||||
builder: (context, bool loading, child) {
|
||||
if (loading) {
|
||||
return LinearProgressIndicator();
|
||||
} else {
|
||||
return Container(height: 4);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
children.add(ValueListenableBuilder(
|
||||
valueListenable: loadingNotifier,
|
||||
builder: (context, bool loading, child) {
|
||||
if (loading) {
|
||||
return LinearProgressIndicator();
|
||||
} else {
|
||||
return Container(height: 4);
|
||||
}
|
||||
}));
|
||||
}
|
||||
return Column(
|
||||
children: children,
|
||||
@@ -75,7 +74,6 @@ class _TbAppBarState extends TbContextState<TbAppBar> {
|
||||
}
|
||||
|
||||
class TbAppSearchBar extends TbContextWidget implements PreferredSizeWidget {
|
||||
|
||||
final double? elevation;
|
||||
final Color? shadowColor;
|
||||
final bool showLoadingIndicator;
|
||||
@@ -85,9 +83,14 @@ class TbAppSearchBar extends TbContextWidget implements PreferredSizeWidget {
|
||||
@override
|
||||
final Size preferredSize;
|
||||
|
||||
TbAppSearchBar(TbContext tbContext, {this.elevation = 8,
|
||||
this.shadowColor, this.showLoadingIndicator = false, this.searchHint, this.onSearch}) :
|
||||
preferredSize = Size.fromHeight(kToolbarHeight + (showLoadingIndicator ? 4 : 0)),
|
||||
TbAppSearchBar(TbContext tbContext,
|
||||
{this.elevation = 8,
|
||||
this.shadowColor,
|
||||
this.showLoadingIndicator = false,
|
||||
this.searchHint,
|
||||
this.onSearch})
|
||||
: preferredSize =
|
||||
Size.fromHeight(kToolbarHeight + (showLoadingIndicator ? 4 : 0)),
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
@@ -95,7 +98,6 @@ class TbAppSearchBar extends TbContextWidget implements PreferredSizeWidget {
|
||||
}
|
||||
|
||||
class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
|
||||
|
||||
final TextEditingController _filter = new TextEditingController();
|
||||
final _textUpdates = StreamController<String>();
|
||||
|
||||
@@ -106,7 +108,11 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
|
||||
_filter.addListener(() {
|
||||
_textUpdates.add(_filter.text);
|
||||
});
|
||||
_textUpdates.stream.skip(1).debounce(const Duration(milliseconds: 150)).distinct().forEach((element) => widget.onSearch!(element));
|
||||
_textUpdates.stream
|
||||
.skip(1)
|
||||
.debounce(const Duration(milliseconds: 150))
|
||||
.distinct()
|
||||
.forEach((element) => widget.onSearch!(element));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -120,18 +126,15 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
|
||||
List<Widget> children = <Widget>[];
|
||||
children.add(buildSearchBar());
|
||||
if (widget.showLoadingIndicator) {
|
||||
children.add(
|
||||
ValueListenableBuilder(
|
||||
valueListenable: loadingNotifier,
|
||||
builder: (context, bool loading, child) {
|
||||
if (loading) {
|
||||
return LinearProgressIndicator();
|
||||
} else {
|
||||
return Container(height: 4);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
children.add(ValueListenableBuilder(
|
||||
valueListenable: loadingNotifier,
|
||||
builder: (context, bool loading, child) {
|
||||
if (loading) {
|
||||
return LinearProgressIndicator();
|
||||
} else {
|
||||
return Container(height: 4);
|
||||
}
|
||||
}));
|
||||
}
|
||||
return Column(
|
||||
children: children,
|
||||
@@ -140,40 +143,37 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
|
||||
|
||||
AppBar buildSearchBar() {
|
||||
return AppBar(
|
||||
centerTitle: true,
|
||||
elevation: widget.elevation ?? 8,
|
||||
shadowColor: widget.shadowColor ?? Color(0xFFFFFFFF).withAlpha(150),
|
||||
title: TextField(
|
||||
controller: _filter,
|
||||
autofocus: true,
|
||||
// cursorColor: Colors.white,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: Color(0xFF282828).withAlpha((255 * 0.38).ceil()),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 15, bottom: 11, top: 15, right: 15),
|
||||
hintText: widget.searchHint ?? 'Search',
|
||||
)
|
||||
),
|
||||
actions: [
|
||||
ValueListenableBuilder(valueListenable: _filter,
|
||||
builder: (context, value, child) {
|
||||
if (_filter.text.isNotEmpty) {
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
Icons.clear
|
||||
),
|
||||
onPressed: () {
|
||||
_filter.text = '';
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
centerTitle: true,
|
||||
elevation: widget.elevation ?? 8,
|
||||
shadowColor: widget.shadowColor ?? Color(0xFFFFFFFF).withAlpha(150),
|
||||
title: TextField(
|
||||
controller: _filter,
|
||||
autofocus: true,
|
||||
// cursorColor: Colors.white,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: Color(0xFF282828).withAlpha((255 * 0.38).ceil()),
|
||||
),
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 15, bottom: 11, top: 15, right: 15),
|
||||
hintText: widget.searchHint ?? 'Search',
|
||||
)),
|
||||
actions: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _filter,
|
||||
builder: (context, value, child) {
|
||||
if (_filter.text.isNotEmpty) {
|
||||
return IconButton(
|
||||
icon: Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_filter.text = '';
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
|
||||
class TbProgressIndicator extends ProgressIndicator {
|
||||
|
||||
final double size;
|
||||
|
||||
const TbProgressIndicator({
|
||||
@@ -16,22 +14,22 @@ class TbProgressIndicator extends ProgressIndicator {
|
||||
String? semanticsLabel,
|
||||
String? semanticsValue,
|
||||
}) : super(
|
||||
key: key,
|
||||
value: null,
|
||||
valueColor: valueColor,
|
||||
semanticsLabel: semanticsLabel,
|
||||
semanticsValue: semanticsValue,
|
||||
);
|
||||
key: key,
|
||||
value: null,
|
||||
valueColor: valueColor,
|
||||
semanticsLabel: semanticsLabel,
|
||||
semanticsValue: semanticsValue,
|
||||
);
|
||||
|
||||
@override
|
||||
_TbProgressIndicatorState createState() => _TbProgressIndicatorState();
|
||||
|
||||
Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).primaryColor;
|
||||
|
||||
Color _getValueColor(BuildContext context) =>
|
||||
valueColor?.value ?? Theme.of(context).primaryColor;
|
||||
}
|
||||
|
||||
class _TbProgressIndicatorState extends State<TbProgressIndicator> with SingleTickerProviderStateMixin {
|
||||
|
||||
class _TbProgressIndicatorState extends State<TbProgressIndicator>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late CurvedAnimation _rotation;
|
||||
|
||||
@@ -39,8 +37,10 @@ class _TbProgressIndicatorState extends State<TbProgressIndicator> with SingleTi
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this, upperBound: 1, animationBehavior: AnimationBehavior.preserve);
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
upperBound: 1,
|
||||
animationBehavior: AnimationBehavior.preserve);
|
||||
_rotation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
|
||||
_controller.repeat();
|
||||
}
|
||||
@@ -48,8 +48,7 @@ class _TbProgressIndicatorState extends State<TbProgressIndicator> with SingleTi
|
||||
@override
|
||||
void didUpdateWidget(TbProgressIndicator oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!_controller.isAnimating)
|
||||
_controller.repeat();
|
||||
if (!_controller.isAnimating) _controller.repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -74,13 +73,10 @@ class _TbProgressIndicatorState extends State<TbProgressIndicator> with SingleTi
|
||||
color: widget._getValueColor(context)),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Transform.rotate(
|
||||
angle: _rotation.value * pi * 2,
|
||||
child: child
|
||||
);
|
||||
angle: _rotation.value * pi * 2, child: child);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:preload_page_view/preload_page_view.dart';
|
||||
|
||||
class TwoPageViewController {
|
||||
|
||||
_TwoPageViewState? _state;
|
||||
|
||||
setTransitionIndexedStackState(_TwoPageViewState state) {
|
||||
@@ -24,7 +23,6 @@ class TwoPageViewController {
|
||||
}
|
||||
|
||||
int? get index => _state?._selectedIndex;
|
||||
|
||||
}
|
||||
|
||||
class TwoPageView extends StatefulWidget {
|
||||
@@ -33,21 +31,19 @@ class TwoPageView extends StatefulWidget {
|
||||
final Duration duration;
|
||||
final TwoPageViewController? controller;
|
||||
|
||||
const TwoPageView({
|
||||
Key? key,
|
||||
required this.first,
|
||||
required this.second,
|
||||
this.controller,
|
||||
this.duration = const Duration(milliseconds: 250)
|
||||
}) : super(key: key);
|
||||
const TwoPageView(
|
||||
{Key? key,
|
||||
required this.first,
|
||||
required this.second,
|
||||
this.controller,
|
||||
this.duration = const Duration(milliseconds: 250)})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_TwoPageViewState createState() => _TwoPageViewState();
|
||||
|
||||
}
|
||||
|
||||
class _TwoPageViewState extends State<TwoPageView> {
|
||||
|
||||
late List<Widget> _pages;
|
||||
bool _reverse = false;
|
||||
int _selectedIndex = 0;
|
||||
@@ -68,7 +64,8 @@ class _TwoPageViewState extends State<TwoPageView> {
|
||||
_reverse = true;
|
||||
});
|
||||
}
|
||||
await _pageController.animateToPage(_selectedIndex, duration: widget.duration, curve: Curves.fastOutSlowIn);
|
||||
await _pageController.animateToPage(_selectedIndex,
|
||||
duration: widget.duration, curve: Curves.fastOutSlowIn);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -77,7 +74,8 @@ class _TwoPageViewState extends State<TwoPageView> {
|
||||
Future<bool> _close(int index, {bool animate = true}) async {
|
||||
if (_selectedIndex == index) {
|
||||
_selectedIndex = index == 1 ? 0 : 1;
|
||||
await _pageController.animateToPage(_selectedIndex, duration: widget.duration, curve: Curves.fastOutSlowIn);
|
||||
await _pageController.animateToPage(_selectedIndex,
|
||||
duration: widget.duration, curve: Curves.fastOutSlowIn);
|
||||
if (index == 0) {
|
||||
setState(() {
|
||||
_reverse = false;
|
||||
@@ -106,5 +104,4 @@ class _TwoPageViewState extends State<TwoPageView> {
|
||||
controller: _pageController,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class TwoValueListenableBuilder<A, B> extends StatelessWidget {
|
||||
TwoValueListenableBuilder(
|
||||
{
|
||||
Key? key,
|
||||
required this.firstValueListenable,
|
||||
required this.secondValueListenable,
|
||||
required this.builder,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
TwoValueListenableBuilder({
|
||||
Key? key,
|
||||
required this.firstValueListenable,
|
||||
required this.secondValueListenable,
|
||||
required this.builder,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
final ValueListenable<A> firstValueListenable;
|
||||
final ValueListenable<B> secondValueListenable;
|
||||
|
||||
Reference in New Issue
Block a user