Initial commit
This commit is contained in:
147
lib/core/auth/login/login_page.dart
Normal file
147
lib/core/auth/login/login_page.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
import 'dart:ui';
|
||||
|
||||
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_client/thingsboard_client.dart';
|
||||
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
class LoginPage extends TbPageWidget<LoginPage, _LoginPageState> {
|
||||
|
||||
LoginPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
|
||||
}
|
||||
|
||||
class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
|
||||
|
||||
final usernameController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
usernameController.dispose();
|
||||
passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Login to ThingsBoard'),
|
||||
),
|
||||
body: ValueListenableBuilder(
|
||||
valueListenable: loadingNotifier,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
List<Widget> children = [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 60.0),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 300,
|
||||
height: 150,
|
||||
child: SvgPicture.asset(ThingsboardImage.thingsBoardLogoBlue,
|
||||
semanticsLabel: 'ThingsBoard Logo')
|
||||
)
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
|
||||
padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextField(
|
||||
enabled: !loading,
|
||||
controller: usernameController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Username (email)',
|
||||
hintText: 'Enter valid email id as abc@gmail.com'),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
//padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextField(
|
||||
enabled: !loading,
|
||||
controller: passwordController,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Password',
|
||||
hintText: 'Enter secure password'),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: loading ? null : () {
|
||||
//TODO FORGOT PASSWORD SCREEN GOES HERE
|
||||
},
|
||||
child: Text(
|
||||
'Forgot Password?',
|
||||
style: TextStyle(color: loading ? Colors.black12 : Theme.of(context).colorScheme.primary, fontSize: 15),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 50,
|
||||
width: 250,
|
||||
decoration: BoxDecoration(
|
||||
color: loading ? Colors.black12 : Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(4)),
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () {
|
||||
tbContext.tbClient.login(
|
||||
LoginRequest(usernameController.text, passwordController.text));
|
||||
},
|
||||
child: Text(
|
||||
'Login',
|
||||
style: TextStyle(color: Colors.white, fontSize: 25),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 130,
|
||||
),
|
||||
Text('New User? Create Account')
|
||||
]
|
||||
)
|
||||
)
|
||||
];
|
||||
if (loading) {
|
||||
children.add(
|
||||
SizedBox.expand(
|
||||
child: ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
|
||||
child: Container(
|
||||
decoration: new BoxDecoration(
|
||||
color: Colors.grey.shade200.withOpacity(0.2)
|
||||
),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
//children.add(Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
return Stack(
|
||||
children: children,
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
202
lib/core/context/tb_context.dart
Normal file
202
lib/core/context/tb_context.dart
Normal file
@@ -0,0 +1,202 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
import 'package:thingsboard_app/utils/services/tb_secure_storage.dart';
|
||||
import 'package:thingsboard_app/constants/api_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
enum NotificationType {
|
||||
info,
|
||||
warn,
|
||||
success,
|
||||
error
|
||||
}
|
||||
|
||||
class TbContext {
|
||||
bool _initialized = false;
|
||||
bool isUserLoaded = false;
|
||||
bool isAuthenticated = false;
|
||||
final _isLoadingNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
GlobalKey<ScaffoldMessengerState> messengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
late ThingsboardClient tbClient;
|
||||
|
||||
final FluroRouter router;
|
||||
final RouteObserver<PageRoute> routeObserver;
|
||||
|
||||
TbContextState? currentState;
|
||||
|
||||
TbContext(this.router, this.routeObserver);
|
||||
|
||||
void init() {
|
||||
assert(() {
|
||||
if (_initialized) {
|
||||
throw StateError('TbContext already initialized!');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
_initialized = true;
|
||||
tbClient = ThingsboardClient(thingsBoardApiEndpoint,
|
||||
storage: TbSecureStorage(),
|
||||
onUserLoaded: onUserLoaded,
|
||||
onError: onError,
|
||||
onLoadStarted: onLoadStarted,
|
||||
onLoadFinished: onLoadFinished,
|
||||
computeFunc: <Q, R>(callback, message) => compute(callback, message));
|
||||
tbClient.init().onError((error, stackTrace) {
|
||||
print('Error: $error');
|
||||
print('Stack: $stackTrace');
|
||||
});
|
||||
}
|
||||
|
||||
void onError(ThingsboardError error) {
|
||||
print('onError: error=$error');
|
||||
showErrorNotification(error.message!);
|
||||
}
|
||||
|
||||
void showErrorNotification(String message, {Duration? duration}) {
|
||||
showNotification(message, NotificationType.error, duration: duration);
|
||||
}
|
||||
|
||||
void showInfoNotification(String message, {Duration? duration}) {
|
||||
showNotification(message, NotificationType.info, duration: duration);
|
||||
}
|
||||
|
||||
void showWarnNotification(String message, {Duration? duration}) {
|
||||
showNotification(message, NotificationType.warn, duration: duration);
|
||||
}
|
||||
|
||||
void showSuccessNotification(String message, {Duration? duration}) {
|
||||
showNotification(message, NotificationType.success, duration: duration);
|
||||
}
|
||||
|
||||
void showNotification(String message, NotificationType type, {Duration? duration}) {
|
||||
duration ??= const Duration(days: 1);
|
||||
Color backgroundColor;
|
||||
var textColor = Color(0xFFFFFFFF);
|
||||
switch(type) {
|
||||
case NotificationType.info:
|
||||
backgroundColor = Color(0xFF323232);
|
||||
break;
|
||||
case NotificationType.warn:
|
||||
backgroundColor = Color(0xFFdc6d1b);
|
||||
break;
|
||||
case NotificationType.success:
|
||||
backgroundColor = Color(0xFF008000);
|
||||
break;
|
||||
case NotificationType.error:
|
||||
backgroundColor = Color(0xFF800000);
|
||||
break;
|
||||
}
|
||||
final snackBar = SnackBar(
|
||||
duration: duration,
|
||||
backgroundColor: backgroundColor,
|
||||
content: Text(message,
|
||||
style: TextStyle(
|
||||
color: textColor
|
||||
),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Close',
|
||||
textColor: textColor,
|
||||
onPressed: () {
|
||||
messengerKey.currentState!.hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
|
||||
},
|
||||
),
|
||||
);
|
||||
messengerKey.currentState!.removeCurrentSnackBar();
|
||||
messengerKey.currentState!.showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
void hideNotification() {
|
||||
messengerKey.currentState!.removeCurrentSnackBar();
|
||||
}
|
||||
|
||||
void onLoadStarted() {
|
||||
print('ON LOAD STARTED!');
|
||||
_isLoadingNotifier.value = true;
|
||||
}
|
||||
|
||||
void onLoadFinished() {
|
||||
print('ON LOAD FINISHED!');
|
||||
_isLoadingNotifier.value = false;
|
||||
}
|
||||
|
||||
Future<void> onUserLoaded() async {
|
||||
try {
|
||||
print('onUserLoaded: isAuthenticated=${tbClient.isAuthenticated()}');
|
||||
isUserLoaded = true;
|
||||
isAuthenticated = tbClient.isAuthenticated();
|
||||
if (tbClient.isAuthenticated()) {
|
||||
print('authUser: ${tbClient.getAuthUser()}');
|
||||
}
|
||||
updateRouteState();
|
||||
} catch (e, s) {
|
||||
print('Error: $e');
|
||||
print('Stack: $s');
|
||||
}
|
||||
}
|
||||
|
||||
void updateRouteState() {
|
||||
if (currentState != null) {
|
||||
if (tbClient.isAuthenticated()) {
|
||||
navigateTo('/home', replace: true);
|
||||
} else {
|
||||
navigateTo('/login', replace: true, clearStack: true, transition: TransitionType.inFromTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void navigateTo(String path, {bool replace = false, bool clearStack = false, TransitionType? transition}) {
|
||||
if (currentState != null) {
|
||||
if (transition == null) {
|
||||
transition = TransitionType.inFromRight;
|
||||
}
|
||||
hideNotification();
|
||||
router.navigateTo(currentState!.context, path, transition: transition, replace: replace, clearStack: clearStack);
|
||||
}
|
||||
}
|
||||
|
||||
void pop() {
|
||||
if (currentState != null) {
|
||||
router.pop(currentState!.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixin HasTbContext {
|
||||
late final TbContext _tbContext;
|
||||
|
||||
void setTbContext(TbContext tbContext) {
|
||||
_tbContext = tbContext;
|
||||
}
|
||||
|
||||
void setupTbContext(TbContextState currentState) {
|
||||
_tbContext = currentState.widget.tbContext;
|
||||
}
|
||||
|
||||
void setupCurrentState(TbContextState currentState) {
|
||||
_tbContext.currentState = currentState;
|
||||
}
|
||||
|
||||
ValueNotifier<bool> get loadingNotifier => _tbContext._isLoadingNotifier;
|
||||
TbContext get tbContext => _tbContext;
|
||||
|
||||
void navigateTo(String path, {bool replace = false}) => _tbContext.navigateTo(path, replace: replace);
|
||||
|
||||
void pop() => _tbContext.pop();
|
||||
|
||||
void hideNotification() => _tbContext.hideNotification();
|
||||
|
||||
void showErrorNotification(String message, {Duration? duration}) => _tbContext.showErrorNotification(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 showSuccessNotification(String message, {Duration? duration}) => _tbContext.showSuccessNotification(message, duration: duration);
|
||||
|
||||
}
|
||||
68
lib/core/context/tb_context_widget.dart
Normal file
68
lib/core/context/tb_context_widget.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
|
||||
abstract class TbContextStatelessWidget extends StatelessWidget with HasTbContext {
|
||||
TbContextStatelessWidget(TbContext tbContext, {Key? key}) : super(key: key) {
|
||||
setTbContext(tbContext);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TbContextWidget<W extends TbContextWidget<W,S>, S extends TbContextState<W,S>> extends StatefulWidget with HasTbContext {
|
||||
TbContextWidget(TbContext tbContext, {Key? key}) : super(key: key) {
|
||||
setTbContext(tbContext);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TbContextState<W extends TbContextWidget<W,S>, S extends TbContextState<W,S>> extends State<W> with HasTbContext {
|
||||
|
||||
final bool handleLoading;
|
||||
|
||||
TbContextState({this.handleLoading = false});
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setupTbContext(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void updateState() {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TbPageWidget<W extends TbPageWidget<W,S>, S extends TbPageState<W,S>> extends TbContextWidget<W,S> {
|
||||
TbPageWidget(TbContext tbContext, {Key? key}) : super(tbContext, key: key);
|
||||
}
|
||||
|
||||
abstract class TbPageState<W extends TbPageWidget<W,S>, S extends TbPageState<W,S>> extends TbContextState<W,S> with RouteAware {
|
||||
TbPageState({bool handleUserLoaded = false}): super(handleLoading: true);
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
tbContext.routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
tbContext.routeObserver.unsubscribe(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didPush() {
|
||||
setupCurrentState(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didPopNext() {
|
||||
tbContext.hideNotification();
|
||||
setupCurrentState(this);
|
||||
}
|
||||
|
||||
}
|
||||
34
lib/core/init/init_app.dart
Normal file
34
lib/core/init/init_app.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
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 ThingsboardInitApp extends TbPageWidget<ThingsboardInitApp, _ThingsboardInitAppState> {
|
||||
|
||||
ThingsboardInitApp(TbContext tbContext, {Key? key}) : super(tbContext, key: key);
|
||||
|
||||
@override
|
||||
_ThingsboardInitAppState createState() => _ThingsboardInitAppState();
|
||||
|
||||
}
|
||||
|
||||
class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp, _ThingsboardInitAppState> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
tbContext.init();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('ThingsBoard Init'),
|
||||
),
|
||||
body: Center(
|
||||
child: CircularProgressIndicator()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user