Add flutter 3+ support. Update dependencies. Fix code style and format issues.

This commit is contained in:
Igor Kulikov
2022-08-12 13:55:27 +03:00
parent 1a07bcd7a0
commit 944c36ce7b
94 changed files with 3167 additions and 3173 deletions

View File

@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 31
compileSdkVersion 33
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -36,7 +36,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "org.thingsboard.app"
minSdkVersion 21
targetSdkVersion 31
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

View File

@@ -62,5 +62,15 @@
/>
<meta-data android:name="io.flutter.network-policy"
android:resource="@xml/network_security_config"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.flutter_inappwebview.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>

View File

@@ -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;
}

View File

@@ -24,7 +24,8 @@ const tbMatIndigo = MaterialColor(
700: Color(0xFF303F9F),
800: Color(0xFF283593),
900: Color(0xFF1A237E),
},);
},
);
const tbDarkMatIndigo = MaterialColor(
_tbPrimaryColorValue,
@@ -39,11 +40,14 @@ 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,
@@ -57,26 +61,19 @@ ThemeData tbTheme = ThemeData(
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
),
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);

View File

@@ -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'
};
}

View File

@@ -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);
}
}

View File

@@ -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,99 +49,107 @@ class _LoginPageState extends TbPageState<LoginPage> {
return Scaffold(
backgroundColor: Colors.white,
resizeToAvoidBottomInset: false,
body: Stack(
children: [
body: Stack(children: [
LoginPageBackground(),
Positioned.fill(
child: LayoutBuilder(
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)),
constraints: BoxConstraints(
minHeight: constraints.maxHeight - (71 + 24)),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
SvgPicture.asset(ThingsboardImage.thingsBoardWithTitle,
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',
Row(children: [
Text('Login to your account',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 28,
height: 36 / 28
)
)]
),
height: 36 / 28))
]),
SizedBox(height: 48),
if (tbContext.hasOAuthClients)
_buildOAuth2Buttons(tbContext.oauth2ClientInfos!),
_buildOAuth2Buttons(
tbContext.oauth2ClientInfos!),
if (tbContext.hasOAuthClients)
Padding(padding: EdgeInsets.only(top: 10, bottom: 16),
Padding(
padding:
EdgeInsets.only(top: 10, bottom: 16),
child: Row(
children: [
Flexible(child: Divider()),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
padding: EdgeInsets.symmetric(
horizontal: 16),
child: Text('OR'),
),
Flexible(child: Divider())
],
)
),
)),
FormBuilder(
key: _loginFormKey,
autovalidateMode: AutovalidateMode.disabled,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
FormBuilderTextField(
name: 'username',
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context, errorText: 'Email is required.'),
FormBuilderValidators.email(context, errorText: 'Invalid email format.')
validator:
FormBuilderValidators.compose([
FormBuilderValidators.required(
errorText: 'Email is required.'),
FormBuilderValidators.email(
errorText:
'Invalid email format.')
]),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email'
),
labelText: 'Email'),
),
SizedBox(height: 28),
ValueListenableBuilder(
valueListenable: _showPasswordNotifier,
builder: (BuildContext context, bool showPassword, child) {
valueListenable:
_showPasswordNotifier,
builder: (BuildContext context,
bool showPassword, child) {
return FormBuilderTextField(
name: 'password',
obscureText: !showPassword,
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context, errorText: 'Password is required.')
validator: FormBuilderValidators
.compose([
FormBuilderValidators.required(
errorText:
'Password is required.')
]),
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
icon: Icon(showPassword
? Icons.visibility
: Icons.visibility_off),
onPressed: () {
_showPasswordNotifier.value = !_showPasswordNotifier.value;
_showPasswordNotifier
.value =
!_showPasswordNotifier
.value;
},
),
border: OutlineInputBorder(),
labelText: 'Password'
),
labelText: 'Password'),
);
}
)
})
],
)
),
)),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@@ -156,7 +159,10 @@ class _LoginPageState extends TbPageState<LoginPage> {
},
child: Text(
'Forgot Password?',
style: TextStyle(color: Theme.of(context).colorScheme.primary,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.primary,
letterSpacing: 1,
fontSize: 12,
height: 16 / 12),
@@ -167,61 +173,61 @@ class _LoginPageState extends TbPageState<LoginPage> {
Spacer(),
ElevatedButton(
child: Text('Log In'),
style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 16)),
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()
);
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);
}
}
}
@@ -281,8 +294,7 @@ class _LoginPageState extends TbPageState<LoginPage> {
child: Padding(
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!);

View File

@@ -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});

View File

@@ -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,7 +24,7 @@ class _ResetPasswordRequestPageState extends TbPageState<ResetPasswordRequestPag
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack( children: [
body: Stack(children: [
LoginPageBackground(),
SizedBox.expand(
child: Scaffold(
@@ -35,8 +33,7 @@ class _ResetPasswordRequestPageState extends TbPageState<ResetPasswordRequestPag
tbContext,
title: Text('Reset password'),
),
body: Stack(
children: [
body: Stack(children: [
SizedBox.expand(
child: Padding(
padding: EdgeInsets.all(24),
@@ -47,40 +44,39 @@ class _ResetPasswordRequestPageState extends TbPageState<ResetPasswordRequestPag
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',
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
),
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.')
FormBuilderValidators.required(
errorText: 'Email is required.'),
FormBuilderValidators.email(
errorText: 'Invalid email format.')
]),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email *'
),
labelText: 'Email *'),
),
Spacer(),
ElevatedButton(
child: Text('Request password reset'),
style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 16)),
style: ElevatedButton.styleFrom(
padding:
EdgeInsets.symmetric(vertical: 16)),
onPressed: () {
_requestPasswordReset();
},
)
]
)
)
)
),
])))),
ValueListenableBuilder<bool>(
valueListenable: _isLoadingNotifier,
builder: (BuildContext context, bool loading, child) {
@@ -89,19 +85,13 @@ class _ResetPasswordRequestPageState extends TbPageState<ResetPasswordRequestPag
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;
}
}

View File

@@ -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;
}
}

View File

@@ -19,18 +19,16 @@ 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,
{required TbContext tbContext,
required AppSecretProvider appSecretProvider})
: _tbContext = tbContext,
_appSecretProvider = appSecretProvider;
Future<TbOAuth2AuthenticateResult> authenticate(String oauth2Url) async {
@@ -38,20 +36,25 @@ class TbOAuth2Client {
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;

View File

@@ -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);
}
}

View File

@@ -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(
printer: PrefixPrinter(PrettyPrinter(
methodCount: 0,
errorMethodCount: 8,
lineLength: 200,
colors: false,
printEmojis: true,
printTime: false
)
),
output: TbLogOutput()
);
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;
@@ -154,7 +149,8 @@ class TbContext {
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);
}
} else {
navigateTo('/home',
replace: true,
transition: TransitionType.fadeIn);
transition: TransitionType.fadeIn,
transitionDuration: Duration(milliseconds: 750));
}
} else {
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,8 +353,9 @@ class TbContext {
}
bool _userForceFullscreen() {
return tbClient.getAuthUser()!.isPublic ||
(userDetails != null && userDetails!.additionalInfo != null &&
return tbClient.getAuthUser()!.isPublic! ||
(userDetails != null &&
userDetails!.additionalInfo != null &&
userDetails!.additionalInfo!['defaultDashboardFullscreen'] == true);
}
@@ -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 {
@@ -456,16 +491,20 @@ 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))
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);
}
}

View File

@@ -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)));
}
}

View File

@@ -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!,
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),
height: 16 / 12)),
if (contact.email == null) SizedBox(height: 16),
if (address != null) SizedBox(height: 4),
if (address != null) Text(address,
if (address != null)
Text(address,
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
)),
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,
@@ -356,7 +341,6 @@ abstract class BaseEntitiesState<T, P> extends TbContextState<BaseEntitiesWidget
onTryAgain: widget.searchMode ? null : () => pagingController.refresh(),
);
}
}
class FirstPageExceptionIndicator extends StatelessWidget {

View File

@@ -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),
@@ -50,13 +45,11 @@ class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
onEntityTap: widget.onEntityTap,
settings: widget.entityGridCardSettings(item),
),
firstPageProgressIndicatorBuilder: firstPageProgressIndicatorBuilder,
newPageProgressIndicatorBuilder: newPageProgressIndicatorBuilder,
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder
)
)));
return CustomScrollView(
slivers: slivers
);
firstPageProgressIndicatorBuilder:
firstPageProgressIndicatorBuilder,
newPageProgressIndicatorBuilder:
newPageProgressIndicatorBuilder,
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder))));
return CustomScrollView(slivers: slivers);
}
}

View File

@@ -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),
@@ -38,15 +33,12 @@ class _EntitiesListState<T,P> extends BaseEntitiesState<T, P> {
key: widget.getKey(item),
entityCardWidgetBuilder: widget.buildEntityListCard,
onEntityTap: widget.onEntityTap,
settings: widget.entityListCardSettings(item),
),
firstPageProgressIndicatorBuilder: firstPageProgressIndicatorBuilder,
newPageProgressIndicatorBuilder: newPageProgressIndicatorBuilder,
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder
)
)));
return CustomScrollView(
slivers: slivers
);
firstPageProgressIndicatorBuilder:
firstPageProgressIndicatorBuilder,
newPageProgressIndicatorBuilder:
newPageProgressIndicatorBuilder,
noItemsFoundIndicatorBuilder: noItemsFoundIndicatorBuilder))));
return CustomScrollView(slivers: slivers);
}
}

View File

@@ -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,
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() {
@@ -129,9 +132,7 @@ class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,
color: Color(0xFF282828),
fontSize: 16,
fontWeight: FontWeight.normal,
height: 1.5
)
);
height: 1.5));
},
),
Spacer(),
@@ -141,8 +142,7 @@ class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,
},
style: TextButton.styleFrom(
padding: EdgeInsets.zero),
child: Text('View all')
)
child: Text('View all'))
],
),
),
@@ -161,52 +161,42 @@ class _EntitiesListWidgetState<T,P> extends TbContextState<EntitiesListWidget<T,
} else {
return Center(
child: RefreshProgressIndicator(
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
)
);
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(
border: Border.all(
color: Color(0xFFDEDEDE),
style: BorderStyle.solid,
width: 1
),
borderRadius: BorderRadius.circular(4)
),
color: Color(0xFFDEDEDE), style: BorderStyle.solid, width: 1),
borderRadius: BorderRadius.circular(4)),
child: Center(
child:
Text(widget.noItemsFoundText,
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,
children: entities
.map((entity) => EntityListCard<T>(entity,
entityCardWidgetBuilder: widget.buildEntityListWidgetCard,
onEntityTap: widget.onEntityTap,
settings: widget.entityListCardSettings(entity),
listWidgetCard: true
)).toList()
));
listWidgetCard: true))
.toList()));
}
}

View File

@@ -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;
@@ -34,8 +25,8 @@ abstract class EntityDetailsPage<T extends BaseData> extends TbPageWidget {
String? subTitle,
bool showLoadingIndicator = true,
bool hideAppBar = false,
double? appBarElevation}):
this._defaultTitle = defaultTitle,
double? appBarElevation})
: this._defaultTitle = defaultTitle,
this._entityId = entityId,
this._subTitle = subTitle,
this._showLoadingIndicator = showLoadingIndicator,
@@ -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;
@@ -82,7 +72,9 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: widget._hideAppBar ? null : TbAppBar(
appBar: widget._hideAppBar
? null
: TbAppBar(
tbContext,
showLoadingIndicator: widget._showLoadingIndicator,
elevation: widget._appBarElevation,
@@ -96,19 +88,24 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
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()),
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
))
]
);
height: 16 / 12))
]);
},
),
),
@@ -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 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);
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),
]
)
);
]));
}
}

View File

@@ -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,
EntityGridCard(T entity,
{Key? key,
EntityTapFunction<T>? onEntityTap,
required EntityCardWidgetBuilder<T> entityCardWidgetBuilder,
required EntityCardSettings settings}):
this._entity = entity,
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(
return GestureDetector(
behavior: HitTestBehavior.opaque,
child:
Container(
child: Container(
child: Card(
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
elevation: 0,
child: _entityCardWidgetBuilder(context, _entity)
),
decoration: _settings.dropShadow ? BoxDecoration(
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)
)
offset: Offset(0, 4))
],
) : null,
)
: null,
),
onTap: () {
if (_onEntityTap != null) {
_onEntityTap!(_entity);
}
}
);
});
}
}

View File

@@ -1,6 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'entities_base.dart';
@@ -9,26 +7,23 @@ 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,
EntityListCard(T entity,
{Key? key,
EntityTapFunction<T>? onEntityTap,
required EntityCardWidgetBuilder<T> entityCardWidgetBuilder,
required EntityCardSettings settings,
bool listWidgetCard = false}):
this._entity = entity,
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(
return GestureDetector(
behavior: HitTestBehavior.opaque,
child:
Container(
child: Container(
margin: _listWidgetCard ? EdgeInsets.only(right: 8) : EdgeInsets.zero,
child: Card(
margin: EdgeInsets.zero,
@@ -36,22 +31,20 @@ class EntityListCard<T> extends StatelessWidget {
borderRadius: BorderRadius.circular(4),
),
elevation: 0,
child: _entityCardWidgetBuilder(context, _entity)
),
decoration: _listWidgetCard ? BoxDecoration(
child: _entityCardWidgetBuilder(context, _entity)),
decoration: _listWidgetCard
? BoxDecoration(
border: Border.all(
color: Color(0xFFDEDEDE),
style: BorderStyle.solid,
width: 1
),
borderRadius: BorderRadius.circular(4)
) : BoxDecoration(
width: 1),
borderRadius: BorderRadius.circular(4))
: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha((255 * 0.05).ceil()),
blurRadius: 6.0,
offset: Offset(0, 4)
),
offset: Offset(0, 4)),
],
),
),
@@ -59,8 +52,6 @@ class EntityListCard<T> extends StatelessWidget {
if (_onEntityTap != null) {
_onEntityTap!(_entity);
}
}
);
});
}
}

View File

@@ -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),
);
}
}

View File

@@ -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);
}
}

View File

@@ -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,14 +124,12 @@ 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',
themeMode: ThemeMode.light,
@@ -151,10 +152,8 @@ class ThingsboardAppState extends State<ThingsboardApp> with TickerProviderState
theme: tbTheme,
themeMode: ThemeMode.light,
darkTheme: tbDarkTheme,
home: MainDashboardPage(appRouter.tbContext, controller: _mainDashboardPageController),
)
)
);
home: MainDashboardPage(appRouter.tbContext,
controller: _mainDashboardPageController),
)));
}
}

View File

@@ -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);
}
}

View File

@@ -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,7 +120,10 @@ 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(
@@ -133,14 +135,11 @@ class _AlarmCardState extends TbContextState<AlarmCard> {
width: 4,
decoration: BoxDecoration(
color: alarmSeverityColors[alarm.severity]!,
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
),
)
)
),
Row(
mainAxisSize: MainAxisSize.max,
children: [
borderRadius: BorderRadius.only(
topLeft: Radius.circular(4),
bottomLeft: Radius.circular(4))),
))),
Row(mainAxisSize: MainAxisSize.max, children: [
SizedBox(width: 4),
Flexible(
fit: FlexFit.tight,
@@ -160,61 +159,68 @@ class _AlarmCardState extends TbContextState<AlarmCard> {
SizedBox(height: 12),
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: AutoSizeText(alarm.type,
maxLines: 2,
minFontSize: 8,
overflow: TextOverflow.ellipsis,
overflow:
TextOverflow.ellipsis,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.w500,
fontWeight:
FontWeight.w500,
fontSize: 14,
height: 20 / 14)
)
),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
height: 20 / 14))),
Text(
entityDateFormat.format(DateTime
.fromMillisecondsSinceEpoch(
alarm.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
]
),
height: 16 / 12))
]),
SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: Text(alarm.originatorName != null ? alarm.originatorName! : '',
child: Text(
alarm.originatorName != null
? alarm.originatorName!
: '',
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontWeight:
FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
),
Text(alarmSeverityTranslations[alarm.severity]!,
height: 16 / 12))),
Text(
alarmSeverityTranslations[
alarm.severity]!,
style: TextStyle(
color: alarmSeverityColors[alarm.severity]!,
color: alarmSeverityColors[
alarm.severity]!,
fontWeight: FontWeight.w500,
fontSize: 12,
height: 16 / 12)
)
]
),
SizedBox(height: 12)],
)
),
height: 16 / 12))
]),
SizedBox(height: 12)
],
)),
SizedBox(width: 16),
if (hasDashboard) Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
if (hasDashboard)
Icon(Icons.chevron_right,
color: Color(0xFFACACAC)),
if (hasDashboard) SizedBox(width: 16),
]
),
]),
Divider(height: 1),
SizedBox(height: 8),
Row(
@@ -223,59 +229,67 @@ class _AlarmCardState extends TbContextState<AlarmCard> {
SizedBox(width: 16),
Flexible(
fit: FlexFit.tight,
child: Text(alarmStatusTranslations[alarm.status]!,
child: Text(
alarmStatusTranslations[alarm.status]!,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.normal,
fontSize: 14,
height: 20 / 14)
)
),
height: 20 / 14))),
SizedBox(height: 32),
Row(
children: [
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
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: [
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))
)
]
)
child: IconButton(
icon: Icon(Icons.clear, size: 18),
padding: EdgeInsets.all(7.0),
onPressed: () => _clearAlarm(alarm)))
])
],
),
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!;
});
}
}
}

View File

@@ -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);
}

View File

@@ -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: [
appBar = TbAppBar(tbContext, title: Text(alarmsList.title), actions: [
IconButton(
icon: Icon(
Icons.search
),
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();
}
}

View File

@@ -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,
AssetDetailsPage(TbContext tbContext, String assetId)
: super(tbContext,
entityId: assetId,
defaultTitle: 'Asset', subTitle: 'Asset details');
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),
]
)
);
]));
}
}

View File

@@ -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);
}
}

View File

@@ -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,13 +41,10 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
}
Widget _buildCard(context, AssetInfo asset) {
return Row(
mainAxisSize: MainAxisSize.max,
children: [
return Row(mainAxisSize: MainAxisSize.max, children: [
Flexible(
fit: FlexFit.tight,
child:
Container(
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
child: Row(
mainAxisSize: MainAxisSize.max,
@@ -55,8 +52,7 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
SizedBox(width: 16),
Flexible(
fit: FlexFit.tight,
child:
Column(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
@@ -71,57 +67,45 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
color: Color(0xFF282828),
fontSize: 14,
fontWeight: FontWeight.w500,
height: 20 / 14
))
),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(asset.createdTime!)),
height: 20 / 14))),
Text(
entityDateFormat.format(
DateTime.fromMillisecondsSinceEpoch(
asset.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
))
]
),
height: 16 / 12))
]),
SizedBox(height: 4),
Text('${asset.type}',
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 1.33
))
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: [
return Row(mainAxisSize: MainAxisSize.min, children: [
Flexible(
fit: FlexFit.loose,
child:
Container(
child: Container(
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
child: Row(mainAxisSize: MainAxisSize.min, children: [
Flexible(
fit: FlexFit.loose,
child:
Column(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FittedBox(
@@ -132,24 +116,16 @@ mixin AssetsBase on EntitiesBase<AssetInfo, PageLink> {
color: Color(0xFF282828),
fontSize: 14,
fontWeight: FontWeight.w500,
height: 1.7
))
),
height: 1.7))),
Text('${asset.type}',
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 1.33
))
height: 1.33))
],
)
)
]
)
)
)
]
);
))
])))
]);
}
}

View File

@@ -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);
}

View File

@@ -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');
}
}

View File

@@ -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: [
appBar = TbAppBar(tbContext, title: Text(assetsList.title), actions: [
IconButton(
icon: Icon(
Icons.search
),
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();
}
}

View File

@@ -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,26 +30,26 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: TbAppBar(
tbContext,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
appBar: TbAppBar(tbContext,
title:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
if (widget.auditLog.entityName != null)
Text(widget.auditLog.entityName!, style: TextStyle(
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()),
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
))
]
)
),
height: 16 / 12))
])),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
@@ -66,24 +57,25 @@ class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage> {
mainAxisSize: MainAxisSize.max,
children: [
Text('Entity Type', style: labelTextStyle),
Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!, style: valueTextStyle),
Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!,
style: valueTextStyle),
SizedBox(height: 16),
Text('Type', style: labelTextStyle),
Text(actionTypeTranslations[widget.auditLog.actionType]!, style: valueTextStyle),
Text(actionTypeTranslations[widget.auditLog.actionType]!,
style: valueTextStyle),
SizedBox(height: 16),
Flexible(
fit: FlexFit.loose,
child: buildBorderedText('Action data', encoder.convert(widget.auditLog.actionData))
),
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!)
)
]
),
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),
),
)),
],
);
}
}

View File

@@ -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();
@@ -108,15 +102,15 @@ class _AuditLogCardState extends TbContextState<AuditLogCard> {
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: [
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,
@@ -136,11 +130,14 @@ class _AuditLogCardState extends TbContextState<AuditLogCard> {
SizedBox(height: 12),
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: AutoSizeText(widget.auditLog.entityName ?? '',
child: AutoSizeText(
widget.auditLog.entityName ??
'',
maxLines: 2,
minFontSize: 8,
overflow: TextOverflow.ellipsis,
@@ -148,47 +145,54 @@ class _AuditLogCardState extends TbContextState<AuditLogCard> {
color: Color(0xFF282828),
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 14)
)
),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.auditLog.createdTime!)),
height: 20 / 14))),
Text(
entityDateFormat.format(DateTime
.fromMillisecondsSinceEpoch(
widget.auditLog
.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
]
),
height: 16 / 12))
]),
SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
child: Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!,
child: Text(
entityTypeTranslations[widget
.auditLog
.entityId
.entityType]!,
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontWeight:
FontWeight.normal,
fontSize: 12,
height: 16 / 12)
)
),
Text(actionStatusTranslations[widget.auditLog.actionStatus]!,
height: 16 / 12))),
Text(
actionStatusTranslations[
widget.auditLog.actionStatus]!,
style: TextStyle(
color: widget.auditLog.actionStatus == ActionStatus.SUCCESS ? Color(0xFF008A00) : Color(0xFFFF0000),
color: widget.auditLog
.actionStatus ==
ActionStatus.SUCCESS
? Color(0xFF008A00)
: Color(0xFFFF0000),
fontWeight: FontWeight.w500,
fontSize: 12,
height: 16 / 12)
)
]
),
SizedBox(height: 12)],
)
),
height: 16 / 12))
]),
SizedBox(height: 12)
],
)),
SizedBox(width: 16)
]
),
]),
SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
@@ -196,35 +200,35 @@ class _AuditLogCardState extends TbContextState<AuditLogCard> {
SizedBox(width: 16),
Flexible(
fit: FlexFit.tight,
child: Text(actionTypeTranslations[widget.auditLog.actionType]!,
child: Text(
actionTypeTranslations[
widget.auditLog.actionType]!,
style: TextStyle(
color: Color(0xFF282828),
fontWeight: FontWeight.normal,
fontSize: 14,
height: 20 / 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))
),
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));
}
}

View File

@@ -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);
}

View File

@@ -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: [
appBar = TbAppBar(tbContext, title: Text(auditLogsList.title), actions: [
IconButton(
icon: Icon(
Icons.search
),
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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -2,7 +2,6 @@ import 'package:thingsboard_app/core/entity/entities_base.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
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}');
}
}

View File

@@ -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);
}

View File

@@ -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: [
appBar = TbAppBar(tbContext, title: Text(customersList.title), actions: [
IconButton(
icon: Icon(
Icons.search
),
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();
}
}

View File

@@ -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
));
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') {
@@ -271,45 +268,60 @@ class _DashboardState extends TbContextState<Dashboard> {
return true;
}
},
child:
ValueListenableBuilder(
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(
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 {
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);
log.debug(
"Invoked tbMobileDashboardLoadedHandler: hasRightLayout: $hasRightLayout, rightLayoutOpened: $rightLayoutOpened");
_dashboardController
.onHasRightLayout(hasRightLayout);
_dashboardController
.onRightLayoutOpened(rightLayoutOpened);
dashboardLoading.value = false;
});
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLayoutHandler", callback: (args) async {
webViewController.addJavaScriptHandler(
handlerName: "tbMobileDashboardLayoutHandler",
callback: (args) async {
bool rightLayoutOpened = args[0];
log.debug("Invoked tbMobileDashboardLayoutHandler: rightLayoutOpened: $rightLayoutOpened");
_dashboardController.onRightLayoutOpened(rightLayoutOpened);
log.debug(
"Invoked tbMobileDashboardLayoutHandler: rightLayoutOpened: $rightLayoutOpened");
_dashboardController
.onRightLayoutOpened(rightLayoutOpened);
});
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardStateNameHandler", callback: (args) async {
log.debug("Invoked tbMobileDashboardStateNameHandler: $args");
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");
webViewController.addJavaScriptHandler(
handlerName: "tbMobileNavigationHandler",
callback: (args) async {
log.debug(
"Invoked tbMobileNavigationHandler: $args");
if (args.length > 0) {
String? path = args[0];
Map<String, dynamic>? params;
@@ -321,18 +333,29 @@ class _DashboardState extends TbContextState<Dashboard> {
tryLocalNavigation(path);
}
});
webViewController.addJavaScriptHandler(handlerName: "tbMobileHandler", callback: (args) async {
webViewController.addJavaScriptHandler(
handlerName: "tbMobileHandler",
callback: (args) async {
log.debug("Invoked tbMobileHandler: $args");
return await widgetActionHandler.handleWidgetMobileAction(args, webViewController);
return await widgetActionHandler
.handleWidgetMobileAction(
args, webViewController);
});
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
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 (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);
@@ -340,21 +363,26 @@ class _DashboardState extends TbContextState<Dashboard> {
await tryLocalNavigation(target);
return NavigationActionPolicy.CANCEL;
}
} else if (await canLaunch(uriString)) {
await launch(
} else if (await canLaunchUrlString(uriString)) {
await launchUrlString(
uriString,
);
return NavigationActionPolicy.CANCEL;
}
}
return Platform.isIOS ? NavigationActionPolicy.ALLOW : NavigationActionPolicy.CANCEL;
return Platform.isIOS
? NavigationActionPolicy.ALLOW
: NavigationActionPolicy.CANCEL;
},
onUpdateVisitedHistory: (controller, url, androidIsReload) async {
onUpdateVisitedHistory:
(controller, url, androidIsReload) async {
log.debug('onUpdateVisitedHistory: $url');
_dashboardController.onHistoryUpdated(controller.canGoBack());
_dashboardController
.onHistoryUpdated(controller.canGoBack());
},
onConsoleMessage: (controller, consoleMessage) {
log.debug('[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
log.debug(
'[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
},
onLoadStart: (controller, url) async {
log.debug('onLoadStart: $url');
@@ -366,8 +394,10 @@ class _DashboardState extends TbContextState<Dashboard> {
_controller.complete(controller);
}
},
androidOnPermissionRequest: (controller, origin, resources) async {
log.debug('androidOnPermissionRequest origin: $origin, resources: $resources');
androidOnPermissionRequest:
(controller, origin, resources) async {
log.debug(
'androidOnPermissionRequest origin: $origin, resources: $resources');
return PermissionRequestResponse(
resources: resources,
action: PermissionRequestResponseAction.GRANT);
@@ -377,11 +407,13 @@ class _DashboardState extends TbContextState<Dashboard> {
TwoValueListenableBuilder(
firstValueListenable: dashboardLoading,
secondValueListenable: dashboardActive,
builder: (BuildContext context, bool loading, bool active, child) {
builder: (BuildContext context, bool loading,
bool active, child) {
if (!loading && active) {
return SizedBox.shrink();
} else {
var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
var data = MediaQueryData.fromWindow(
WidgetsBinding.instance.window);
var bottomPadding = data.padding.top;
if (widget._home != true) {
bottomPadding += kToolbarHeight;
@@ -390,18 +422,12 @@ class _DashboardState extends TbContextState<Dashboard> {
padding: EdgeInsets.only(bottom: bottomPadding),
alignment: Alignment.center,
color: Colors.white,
child: TbProgressIndicator(
size: 50.0
),
child: TbProgressIndicator(size: 50.0),
);
}
})
]);
}
)
]
);
}
}
)
);
}));
}
}

View File

@@ -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
@@ -47,17 +47,16 @@ class _DashboardPageState extends TbPageState<DashboardPage> {
return FittedBox(
fit: BoxFit.fitWidth,
alignment: Alignment.centerLeft,
child: Text(title)
);
child: Text(title));
},
),
),
body: Text('Deprecated') //Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
body: Text(
'Deprecated') //Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
//fullscreen: widget._fullscreen, titleCallback: (title) {
//dashboardTitleValue.value = title;
//}
//),
);
}
}

View File

@@ -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);
}
}

View File

@@ -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(
)),
(!listWidgetCard
? Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(dashboard.createdTime!)),
Text(
entityDateFormat.format(
DateTime.fromMillisecondsSinceEpoch(
dashboard.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 1.33
))
height: 1.33))
],
) : Container())
)
: 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,32 +164,26 @@ class _DashboardGridCardState extends TbContextState<DashboardGridCard> {
colorBlendMode: BlendMode.overlay,
semanticsLabel: 'Dashboard');
}
return
ClipRRect(
return ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Column(
children: [
Expanded(
child: Stack (
children: [
child: Stack(children: [
SizedBox.expand(
child: FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
child: image
)
)
]
)
),
child: image))
])),
Divider(height: 1),
Container(
height: 44,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child:
Center(
child: AutoSizeText(widget.dashboard.title,
child: Center(
child: AutoSizeText(
widget.dashboard.title,
textAlign: TextAlign.center,
maxLines: 1,
minFontSize: 12,
@@ -197,15 +191,10 @@ class _DashboardGridCardState extends TbContextState<DashboardGridCard> {
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 14
),
)
)
),
height: 20 / 14),
))),
)
],
)
);
));
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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');
}
}

View File

@@ -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();
}
}

View File

@@ -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: () {
return TbAppBar(tbContext,
leading: canGoBack
? BackButton(onPressed: () {
maybePop();
}
) : null,
})
: 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) {
body: Dashboard(tbContext, titleCallback: (title) {
dashboardTitleValue.value = title;
},
controllerCallback: (controller) {
}, controllerCallback: (controller) {
controller.canGoBack.addListener(() {
_onCanGoBack(controller.canGoBack.value);
});
controller.openDashboard(widget.fullscreenDashboardId, fullscreen: true);
}
)
);
controller.openDashboard(widget.fullscreenDashboardId,
fullscreen: true);
}));
}
}

View File

@@ -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);
}
@@ -100,11 +94,9 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
return Scaffold(
appBar: TbAppBar(
tbContext,
leading: BackButton(
onPressed: () {
leading: BackButton(onPressed: () {
maybePop();
}
),
}),
showLoadingIndicator: false,
elevation: 1,
shadowColor: Colors.transparent,
@@ -114,8 +106,7 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
return FittedBox(
fit: BoxFit.fitWidth,
alignment: Alignment.centerLeft,
child: Text(title)
);
child: Text(title));
},
),
actions: [
@@ -124,26 +115,21 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
builder: (context, _hasRightLayout, widget) {
if (_hasRightLayout) {
return IconButton(
onPressed: () => _dashboardController?.toggleRightLayout(),
onPressed: () =>
_dashboardController?.toggleRightLayout(),
icon: AnimatedIcon(
progress: rightLayoutMenuAnimation,
icon: AnimatedIcons.menu_close
)
);
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) {
}, controllerCallback: (controller) {
_dashboardController = controller;
if (widget._controller != null) {
widget._controller!._setDashboardController(controller);
@@ -151,16 +137,13 @@ class _MainDashboardPageState extends TbContextState<MainDashboardPage> with Tic
hasRightLayout.value = controller.hasRightLayout.value;
});
controller.rightLayoutOpened.addListener(() {
if(controller.rightLayoutOpened.value) {
if (controller.rightLayoutOpened.value) {
rightLayoutMenuController.forward();
} else {
rightLayoutMenuController.reverse();
}
});
}
}));
}
)
);
}
}

View File

@@ -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}'),
);
}
}

View File

@@ -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,11 +118,9 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
@override
Widget build(BuildContext context) {
return
GestureDetector(
return GestureDetector(
behavior: HitTestBehavior.opaque,
child:
Container(
child: Container(
child: Card(
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
@@ -130,7 +129,8 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
elevation: 0,
child: Column(
children: [
Padding(padding: EdgeInsets.fromLTRB(16, 12, 16, 15),
Padding(
padding: EdgeInsets.fromLTRB(16, 12, 16, 15),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -139,19 +139,18 @@ 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),
Padding(
padding: EdgeInsets.all(0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Flexible(fit: FlexFit.tight,
Flexible(
fit: FlexFit.tight,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
child: Container(
@@ -165,28 +164,36 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
builder: (context, snapshot) {
if (snapshot.hasData) {
var deviceCount = snapshot.data!;
return _buildDeviceCount(context, true, deviceCount);
return _buildDeviceCount(
context, true, deviceCount);
} else {
return Center(child:
Container(height: 20, width: 20,
return Center(
child: Container(
height: 20,
width: 20,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
valueColor:
AlwaysStoppedAnimation(
Theme.of(tbContext
.currentState!
.context)
.colorScheme
.primary),
strokeWidth: 2.5)));
}
},
)
),
)),
onTap: () {
navigateTo('/deviceList?active=true');
}
),
}),
),
// SizedBox(width: 4),
Container(width: 1,
Container(
width: 1,
height: 40,
child: VerticalDivider(width: 1)
),
Flexible(fit: FlexFit.tight,
child: VerticalDivider(width: 1)),
Flexible(
fit: FlexFit.tight,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
child: Container(
@@ -200,59 +207,58 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard> {
builder: (context, snapshot) {
if (snapshot.hasData) {
var deviceCount = snapshot.data!;
return _buildDeviceCount(context, false, deviceCount);
return _buildDeviceCount(
context, false, deviceCount);
} else {
return Center(child:
Container(height: 20, width: 20,
return Center(
child: Container(
height: 20,
width: 20,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
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)
)
offset: Offset(0, 4))
],
),
),
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,33 +300,26 @@ class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
imageFit = BoxFit.cover;
padding = 0;
}
return
ClipRRect(
return ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Column(
children: [
child: Column(children: [
Expanded(
child: Stack (
children: [
child: Stack(children: [
SizedBox.expand(
child: Padding(
padding: EdgeInsets.all(padding),
child: FittedBox(
clipBehavior: Clip.hardEdge,
fit: imageFit,
child: image
)
)
)
]
)
),
child: image)))
])),
Container(
height: 44,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Center(
child: AutoSizeText(entity.name,
child: AutoSizeText(
entity.name,
textAlign: TextAlign.center,
maxLines: 1,
minFontSize: 12,
@@ -326,65 +327,68 @@ class _DeviceProfileCardState extends TbContextState<DeviceProfileCard> {
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 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) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
var deviceCount = snapshot.data!;
return _buildDeviceCount(context, true, deviceCount);
} else {
return Container(height: 40,
return Container(
height: 40,
child: Center(
child: Container(
height: 20, width: 20,
child:
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
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) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
var deviceCount = snapshot.data!;
return _buildDeviceCount(context, false, deviceCount);
} else {
return Container(height: 40,
return Container(
height: 40,
child: Center(
child: Container(
height: 20, width: 20,
child:
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
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}');
}
)
]
)
);
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(
if (!active)
CustomPaint(
size: Size.square(16),
painter: StrikeThroughPainter(color: color, offset: 2),
)
],
),
SizedBox(width: 8.67),
Text(active ? 'Active' : 'Inactive', style: TextStyle(
Text(active ? 'Active' : 'Inactive',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
height: 16 / 12,
color: color
)),
color: color)),
SizedBox(width: 8.67),
Text(count.toString(), style: TextStyle(
Text(count.toString(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
height: 16 / 12,
color: color
))
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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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,24 +141,25 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
}
Widget buildCard(BuildContext context) {
return Stack(
children: [
return Stack(children: [
Positioned.fill(
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))
),
)
)
),
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) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
var profile = snapshot.data!;
bool hasDashboard = profile.defaultDashboardId != null;
Widget image;
@@ -155,7 +168,8 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
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');
@@ -176,125 +190,136 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (widget.displayImage) Container(
if (widget.displayImage)
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4))
),
borderRadius: BorderRadius.all(
Radius.circular(4))),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(4)),
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: [
child: Column(children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
fit: FlexFit.tight,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text('${widget.device.field('name')!}',
alignment:
Alignment.centerLeft,
child: Text(
'${widget.device.field('name')!}',
style: TextStyle(
color: Color(0xFF282828),
color: Color(
0xFF282828),
fontSize: 14,
fontWeight: FontWeight.w500,
height: 20 / 14
))
)
),
fontWeight:
FontWeight
.w500,
height:
20 / 14)))),
SizedBox(width: 12),
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
Text(
entityDateFormat.format(DateTime
.fromMillisecondsSinceEpoch(
widget.device
.createdTime!)),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
))
]
),
fontWeight:
FontWeight.normal,
height: 16 / 12))
]),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('${widget.device.field('type')!}',
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',
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),
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)
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)
)
)
);
valueColor: AlwaysStoppedAnimation(
Theme.of(tbContext.currentState!.context)
.colorScheme
.primary))));
}
}
)
]
);
})
]);
}
Widget buildListWidgetCard(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.displayImage) Container(
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))
),
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!;
Widget image;
BoxFit imageFit;
@@ -302,37 +327,39 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
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 ClipRRect(
borderRadius: BorderRadius.horizontal(left: Radius.circular(4)),
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)
));
} else {
return Center(
child: RefreshProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(tbContext.currentState!.context)
.colorScheme
.primary)));
}
},
),
),
Flexible(
fit: FlexFit.loose,
child:
Container(
child: Container(
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
child: Column(
children: [
@@ -348,11 +375,8 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
color: Color(0xFF282828),
fontSize: 14,
fontWeight: FontWeight.w500,
height: 20 / 14
))
)
]
),
height: 20 / 14)))
]),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
@@ -363,16 +387,10 @@ class _DeviceCardState extends TbContextState<DeviceCard> {
color: Color(0xFFAFAFAF),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
)),
]
)
height: 16 / 12)),
])
],
)
)
)
]
);
)))
]);
}
}

View File

@@ -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;
}

View File

@@ -6,69 +6,70 @@ 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(
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
)),
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()),
Text(subTitleText,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline6!
.color!
.withAlpha((0.38 * 255).ceil()),
fontSize: 12,
fontWeight: FontWeight.normal,
height: 16 / 12
))
]
);
height: 16 / 12))
]);
appBar = TbAppBar(
tbContext,
title: title,
actions: [
appBar = TbAppBar(tbContext, title: title, actions: [
IconButton(
icon: Icon(
Icons.search
),
icon: Icon(Icons.search),
onPressed: () {
List<String> params = [];
params.add('search=true');
@@ -83,10 +84,7 @@ class _DevicesListPageState extends TbPageState<DevicesListPage> {
)
]);
}
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();
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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
),
if (tbClient.isSystemAdmin())
IconButton(
icon: Icon(Icons.search),
onPressed: () {
navigateTo('/tenants?search=true');
},
)
],
),
body: Builder(
builder: (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,
return dashboardUi.Dashboard(tbContext, home: true,
controllerCallback: (controller) {
controller.openDashboard(widget.dashboard.dashboardId!.id!,
hideToolbar: widget.dashboard.hideDashboardToolbar);
});
}
);
}
}

View File

@@ -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);
}
}

View File

@@ -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,
TbMainNavigationItem(
{required this.page,
required this.title,
required this.icon,
required this.path
});
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,10 +43,9 @@ 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:
@@ -57,27 +55,26 @@ class TbMainNavigationItem {
page: AlarmsPage(tbContext),
title: 'Alarms',
icon: Icon(Icons.notifications),
path: '/alarms'
),
path: '/alarms'),
TbMainNavigationItem(
page: DevicesMainPage(tbContext),
title: 'Devices',
icon: Icon(Icons.devices_other),
path: '/devices'
)
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,7 +139,9 @@ 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(),
),
@@ -151,15 +150,13 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
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()
),
)
)
);
onTap: (int index) =>
_setIndex(index) /*_currentIndex = index*/,
items: _tabItems
.map((item) => BottomNavigationBarItem(
icon: item.icon, label: item.title))
.toList()),
)));
}
int _indexFromPath(String path) {
@@ -185,5 +182,4 @@ class _MainPageState extends TbPageState<MainPage> with TbMainState, TickerProvi
_setIndex(int index) {
_tabController.index = index;
}
}

View File

@@ -4,32 +4,31 @@ 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(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.account_circle, size: 48, color: Color(0xFFAFAFAF)),
Icon(Icons.account_circle,
size: 48, color: Color(0xFFAFAFAF)),
Spacer(),
IconButton(icon: Icon(Icons.settings, color: Color(0xFFAFAFAF)), onPressed: () async {
IconButton(
icon: Icon(Icons.settings, color: Color(0xFFAFAFAF)),
onPressed: () async {
await navigateTo('/profile');
setState(() {});
})
@@ -41,18 +40,14 @@ class _MorePageState extends TbContextState<MorePage> {
color: Color(0xFF282828),
fontWeight: FontWeight.w500,
fontSize: 20,
height: 23 / 20
)
),
height: 23 / 20)),
SizedBox(height: 2),
Text(_getAuthorityName(),
style: TextStyle(
color: Color(0xFFAFAFAF),
fontWeight: FontWeight.normal,
fontSize: 14,
height: 16 / 14
)
),
height: 16 / 14)),
SizedBox(height: 24),
Divider(color: Color(0xFFEDEDED)),
SizedBox(height: 8),
@@ -65,10 +60,9 @@ class _MorePageState extends TbContextState<MorePage> {
child: Container(
height: 48,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
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',
@@ -77,21 +71,15 @@ class _MorePageState extends TbContextState<MorePage> {
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 14
))
]
)
)
),
height: 20 / 14))
]))),
onTap: () {
tbClient.logout(
requestConfig: RequestConfig(ignoreErrors: true));
}
)
})
],
),
)
);
));
}
Widget buildMoreMenuItems(BuildContext context) {
@@ -102,9 +90,7 @@ class _MorePageState extends TbContextState<MorePage> {
height: 48,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 18),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
child: Row(mainAxisSize: MainAxisSize.max, children: [
Icon(menuItem.icon, color: Color(0xFF282828)),
SizedBox(width: 34),
Text(menuItem.title,
@@ -113,20 +99,13 @@ class _MorePageState extends TbContextState<MorePage> {
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
fontSize: 14,
height: 20 / 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 {

View File

@@ -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);
@@ -48,87 +45,96 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
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) {
builder: (BuildContext context, bool showPassword,
child) {
return FormBuilderTextField(
name: 'newPassword',
obscureText: !showPassword,
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context, errorText: 'New password is required.')
FormBuilderValidators.required(
errorText: 'New password is required.')
]),
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
icon: Icon(showPassword
? Icons.visibility
: Icons.visibility_off),
onPressed: () {
_showNewPasswordNotifier.value = !_showNewPasswordNotifier.value;
_showNewPasswordNotifier.value =
!_showNewPasswordNotifier.value;
},
),
border: OutlineInputBorder(),
labelText: 'New password *'
),
labelText: 'New password *'),
);
}
),
}),
SizedBox(height: 24),
ValueListenableBuilder(
valueListenable: _showNewPassword2Notifier,
builder: (BuildContext context, bool showPassword, child) {
builder: (BuildContext context, bool showPassword,
child) {
return FormBuilderTextField(
name: 'newPassword2',
obscureText: !showPassword,
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context, errorText: 'New password again is required.')
FormBuilderValidators.required(
errorText:
'New password again is required.')
]),
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
icon: Icon(showPassword
? Icons.visibility
: Icons.visibility_off),
onPressed: () {
_showNewPassword2Notifier.value = !_showNewPassword2Notifier.value;
_showNewPassword2Notifier.value =
!_showNewPassword2Notifier.value;
},
),
border: OutlineInputBorder(),
labelText: 'New password again *'
),
labelText: 'New password again *'),
);
}
),
}),
SizedBox(height: 24),
ElevatedButton(
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(16),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.all(16),
alignment: Alignment.centerLeft),
onPressed: () {
_changePassword();
},
child: Center(child: Text('Change Password'))
)
]
),
)
)
),
child: Center(child: Text('Change Password')))
]),
))),
),
ValueListenableBuilder<bool>(
valueListenable: _isLoadingNotifier,
@@ -138,16 +144,13 @@ class _ChangePasswordPageState extends TbContextState<ChangePasswordPage> {
child: Container(
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;
}
}
}
}
}

View File

@@ -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
),
}),
if (widget._fullscreen)
IconButton(
icon: Icon(Icons.logout),
onPressed: () {
tbClient.logout();
}
)
})
],
),
body: Stack(
@@ -82,44 +75,40 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
FormBuilderTextField(
name: 'email',
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context, errorText: 'Email is required.'),
FormBuilderValidators.email(context, errorText: 'Invalid email format.')
FormBuilderValidators.required(
errorText: 'Email is required.'),
FormBuilderValidators.email(
errorText: 'Invalid email format.')
]),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email *'
),
labelText: 'Email *'),
),
SizedBox(height: 24),
FormBuilderTextField(
name: 'firstName',
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'First Name'
),
labelText: 'First Name'),
),
SizedBox(height: 24),
FormBuilderTextField(
name: 'lastName',
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Last Name'
),
labelText: 'Last Name'),
),
SizedBox(height: 24),
OutlinedButton(
style: OutlinedButton.styleFrom(padding: EdgeInsets.all(16),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.all(16),
alignment: Alignment.centerLeft),
onPressed: () {
_changePassword();
},
child: Center(child: Text('Change Password'))
)
]
),
)
)
),
child: Center(child: Text('Change Password')))
]),
))),
),
ValueListenableBuilder<bool>(
valueListenable: _isLoadingNotifier,
@@ -129,16 +118,13 @@ class _ProfilePageState extends TbPageState<ProfilePage> {
child: Container(
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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -2,7 +2,6 @@ import 'package:thingsboard_app/core/entity/entities_base.dart';
import 'package:thingsboard_client/thingsboard_client.dart';
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}');
}
}

View File

@@ -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);
}

View File

@@ -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: [
appBar = TbAppBar(tbContext, title: Text(tenantsList.title), actions: [
IconButton(
icon: Icon(
Icons.search
),
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();
}
}

View File

@@ -6,16 +6,13 @@ 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
@@ -28,5 +25,4 @@ class _TenantsWidgetState extends TbContextState<TenantsWidget> {
_pageLinkController.dispose();
super.dispose();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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)));
}
}

View File

@@ -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';

View File

@@ -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);
}
}

View File

@@ -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.
@@ -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;

View File

@@ -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');
@@ -56,25 +52,24 @@ class _QrCodeScannerPageState extends TbPageState<QrCodeScannerPage> {
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(
child: AppBar(
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
iconTheme: IconThemeData(
color: 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);
}
),
return Icon(snapshot.data == false
? Icons.flash_on
: Icons.flash_off);
}),
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
@@ -85,9 +80,10 @@ class _QrCodeScannerPageState extends TbPageState<QrCodeScannerPage> {
icon: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
return Icon(snapshot.data == CameraFacing.front ? Icons.camera_rear : Icons.camera_front);
}
),
return Icon(snapshot.data == CameraFacing.front
? Icons.camera_rear
: Icons.camera_front);
}),
onPressed: () async {
await controller?.flipCamera();
setState(() {});
@@ -98,8 +94,7 @@ class _QrCodeScannerPageState extends TbPageState<QrCodeScannerPage> {
),
)
],
)
);
));
}
Widget _buildQrView(BuildContext context) {

View File

@@ -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);
}
}

View File

@@ -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;
}
@@ -20,13 +20,12 @@ abstract class Utils {
}
var stateJson = json.encode(stateObj);
var encodedUri = Uri.encodeComponent(stateJson);
encodedUri = encodedUri.replaceAllMapped(RegExp(r'%([0-9A-F]{2})'), (match) {
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);
}
}
}

View File

@@ -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)),
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,8 +47,7 @@ class _TbAppBarState extends TbContextState<TbAppBar> {
List<Widget> children = <Widget>[];
children.add(buildDefaultBar());
if (widget.showLoadingIndicator) {
children.add(
ValueListenableBuilder(
children.add(ValueListenableBuilder(
valueListenable: loadingNotifier,
builder: (context, bool loading, child) {
if (loading) {
@@ -54,9 +55,7 @@ class _TbAppBarState extends TbContextState<TbAppBar> {
} 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,8 +126,7 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
List<Widget> children = <Widget>[];
children.add(buildSearchBar());
if (widget.showLoadingIndicator) {
children.add(
ValueListenableBuilder(
children.add(ValueListenableBuilder(
valueListenable: loadingNotifier,
builder: (context, bool loading, child) {
if (loading) {
@@ -129,9 +134,7 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
} else {
return Container(height: 4);
}
}
)
);
}));
}
return Column(
children: children,
@@ -152,18 +155,17 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
hintStyle: TextStyle(
color: Color(0xFF282828).withAlpha((255 * 0.38).ceil()),
),
contentPadding: EdgeInsets.only(left: 15, bottom: 11, top: 15, right: 15),
contentPadding:
EdgeInsets.only(left: 15, bottom: 11, top: 15, right: 15),
hintText: widget.searchHint ?? 'Search',
)
),
)),
actions: [
ValueListenableBuilder(valueListenable: _filter,
ValueListenableBuilder(
valueListenable: _filter,
builder: (context, value, child) {
if (_filter.text.isNotEmpty) {
return IconButton(
icon: Icon(
Icons.clear
),
icon: Icon(Icons.clear),
onPressed: () {
_filter.text = '';
},
@@ -171,9 +173,7 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar> {
} else {
return Container();
}
}
)
]
);
})
]);
}
}

View File

@@ -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({
@@ -26,12 +24,12 @@ class TbProgressIndicator extends ProgressIndicator {
@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;
@@ -40,7 +38,9 @@ class _TbProgressIndicatorState extends State<TbProgressIndicator> with SingleTi
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this, upperBound: 1, animationBehavior: AnimationBehavior.preserve);
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);
},
)
],
);
}
}

View File

@@ -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,
const TwoPageView(
{Key? key,
required this.first,
required this.second,
this.controller,
this.duration = const Duration(milliseconds: 250)
}) : super(key: key);
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,
);
}
}

View File

@@ -2,8 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class TwoValueListenableBuilder<A, B> extends StatelessWidget {
TwoValueListenableBuilder(
{
TwoValueListenableBuilder({
Key? key,
required this.firstValueListenable,
required this.secondValueListenable,

View File

@@ -7,21 +7,21 @@ packages:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.6"
version: "3.3.1"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
version: "2.3.1"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.1"
version: "2.8.2"
auto_size_text:
dependency: "direct main"
description:
@@ -42,7 +42,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
charcode:
dependency: transitive
description:
@@ -50,6 +50,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.5"
clock:
dependency: transitive
description:
@@ -63,42 +77,49 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
cross_file:
dependency: transitive
description:
name: cross_file
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.2"
version: "0.3.3+1"
crypto:
dependency: "direct main"
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.2"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
version: "1.0.5"
dart_jsonwebtoken:
dependency: "direct main"
description:
name: dart_jsonwebtoken
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
version: "2.4.2"
device_info:
dependency: "direct main"
description:
@@ -119,21 +140,21 @@ packages:
name: dio
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.3"
version: "4.0.6"
fading_edge_scrollview:
dependency: "direct main"
description:
name: fading_edge_scrollview
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "3.0.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
fluro:
dependency: "direct main"
description:
@@ -152,21 +173,23 @@ packages:
name: flutter_form_builder
url: "https://pub.dartlang.org"
source: hosted
version: "7.0.0"
version: "7.5.0"
flutter_inappwebview:
dependency: "direct main"
description:
name: flutter_inappwebview
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.2"
path: "."
ref: master
resolved-ref: a76ed7b1aafb4867517f2ed47dda9e9d15cf32d6
url: "https://github.com/rshrc/flutter_inappwebview"
source: git
version: "5.4.3+7"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.2"
version: "0.10.0"
flutter_localizations:
dependency: transitive
description: flutter
@@ -178,28 +201,28 @@ packages:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.0.7"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.2"
version: "5.1.0"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
@@ -227,14 +250,14 @@ packages:
name: flutter_speed_dial
url: "https://pub.dartlang.org"
source: hosted
version: "4.6.6"
version: "6.0.0"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "0.23.0+1"
version: "1.1.3"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -251,91 +274,119 @@ packages:
name: form_builder_validators
url: "https://pub.dartlang.org"
source: hosted
version: "7.2.0"
version: "8.3.0"
geolocator:
dependency: "direct main"
description:
name: geolocator
url: "https://pub.dartlang.org"
source: hosted
version: "7.7.1"
version: "9.0.1"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "4.0.2"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.2"
version: "2.2.1"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.6"
version: "4.0.6"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
version: "2.1.6"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.1"
html:
dependency: transitive
description:
name: html
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.0"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
version: "0.13.5"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
version: "4.0.1"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.8"
version: "3.2.0"
image_picker:
dependency: "direct main"
description:
name: image_picker
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.4+4"
version: "0.8.5+3"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.5+2"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
version: "2.1.8"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.5+6"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.1"
version: "2.6.1"
infinite_scroll_pagination:
dependency: "direct main"
description:
name: infinite_scroll_pagination
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
version: "3.2.0"
intl:
dependency: "direct main"
description:
@@ -349,7 +400,14 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
version: "0.6.4"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.6.0"
jwt_decoder:
dependency: transitive
description:
@@ -370,14 +428,21 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
material_design_icons_flutter:
dependency: "direct main"
description:
name: material_design_icons_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.6295"
version: "5.0.6996"
meta:
dependency: transitive
description:
@@ -391,7 +456,7 @@ packages:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.0.2"
package_info:
dependency: "direct main"
description:
@@ -405,42 +470,42 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1+1"
version: "1.0.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1"
version: "1.0.1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
version: "5.0.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.1.2"
pointycastle:
dependency: transitive
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "3.4.0"
version: "3.6.1"
preload_page_view:
dependency: "direct main"
description:
@@ -454,7 +519,7 @@ packages:
name: qr_code_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1"
version: "1.0.0"
sky_engine:
dependency: transitive
description: flutter
@@ -466,14 +531,14 @@ packages:
name: sliver_tools
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.5"
version: "0.2.7"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
stack_trace:
dependency: transitive
description:
@@ -515,21 +580,37 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
version: "0.4.9"
thingsboard_client:
dependency: "direct main"
description:
name: thingsboard_client
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
path: "."
ref: master
resolved-ref: d8dcad26dade6fca574d600ab88a6cc0ccaf1a7d
url: "git@github.com:thingsboard/dart_thingsboard_client.git"
source: git
version: "1.0.3"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.3.1"
universal_html:
dependency: "direct main"
description:
name: universal_html
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
universal_io:
dependency: transitive
description:
name: universal_io
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
universal_platform:
dependency: "direct main"
description:
@@ -543,70 +624,84 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.13"
version: "6.1.5"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.17"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.17"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "3.0.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "3.0.1"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.1.0"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.0.13"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "3.0.1"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.2"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.2.0"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.1"
version: "6.1.0"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
version: "3.1.1"
sdks:
dart: ">=2.14.0 <3.0.0"
flutter: ">=2.5.0"
dart: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0"

View File

@@ -11,18 +11,24 @@ environment:
dependencies:
flutter:
sdk: flutter
thingsboard_client: 1.0.2
thingsboard_client:
git:
url: git@github.com:thingsboard/dart_thingsboard_client.git
ref: master
intl: ^0.17.0
flutter_secure_storage: ^5.0.2
flutter_speed_dial: ^4.6.6
flutter_speed_dial: ^6.0.0
cupertino_icons: ^1.0.2
fluro: ^2.0.3
flutter_svg: ^0.23.0+1
flutter_svg: ^1.1.3
auto_size_text: ^3.0.0-nullsafety.0
infinite_scroll_pagination: ^3.0.1
fading_edge_scrollview: ^2.0.0
fading_edge_scrollview: ^3.0.0
stream_transform: ^2.0.0
flutter_inappwebview: ^5.3.2
flutter_inappwebview:
git:
url: https://github.com/rshrc/flutter_inappwebview
ref: master
# flutter_downloader: ^1.6.0
# permission_handler: ^8.0.0+2
# path_provider: ^2.0.2
@@ -30,22 +36,23 @@ dependencies:
image_picker: ^0.8.0
mime: ^1.0.0
logger: ^1.0.0
qr_code_scanner: ^0.6.1
qr_code_scanner: ^1.0.0
device_info: ^2.0.0
geolocator: ^7.0.3
geolocator: ^9.0.1
material_design_icons_flutter: ^5.0.5955-rc.1
package_info: ^2.0.2
dart_jsonwebtoken: ^2.2.0
crypto: ^3.0.1
flutter_form_builder: ^7.0.0
form_builder_validators: ^7.2.0
form_builder_validators: ^8.3.0
universal_html: ^2.0.8
universal_platform: ^1.0.0+1
preload_page_view: ^0.1.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_launcher_icons: ^0.9.0
flutter_launcher_icons: ^0.10.0
flutter:
uses-material-design: true