diff --git a/lib/backend/login_repository.dart b/lib/backend/login_repository.dart index 51f55f5..42036eb 100644 --- a/lib/backend/login_repository.dart +++ b/lib/backend/login_repository.dart @@ -1,42 +1,15 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import '../login_config.dart'; import '../model/login_confirmation_result.dart'; import '../model/login_user.dart'; -abstract class LoginRepository with ChangeNotifier { - String? _loggedIn = ''; +abstract class LoginRepository { + bool loggedIn = false; String loginError = ''; - bool _initialized = false; - - bool isInitialized() => _initialized; - - /// This function returns true if the user is logged in. - bool isLoggedIn() => _loggedIn != null && _loggedIn != ''; - String get user => _loggedIn!; - - /// This function sets the logged in user. - void setLoggedIn(String user) => _loggedIn = user; String getLoginError() => loginError; - Future login(String username, String password); - - Future logout() async { - _loggedIn = null; - } - - /// This function returns a map with the username. - Map getUser() => { - 'username': _loggedIn, - }; - - @mustCallSuper - Future init() async { - // Auto login here - _initialized = true; - } - Future signInWithSocial(SocialLoginBundle bundle); Future userprofileExists(); Future sendLoginEmail(String input); @@ -59,6 +32,6 @@ abstract class LoginRepository with ChangeNotifier { }); Future forgotPassword(String email); Future isRegistrationRequired(LoginUser user); - Future reLogin(); + Future reLogin({required VoidCallback onLoggedIn}); Future signInAnonymous(); } diff --git a/lib/flutter_login_sdk.dart b/lib/flutter_login_sdk.dart deleted file mode 100644 index 3870576..0000000 --- a/lib/flutter_login_sdk.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'sdk/screen.dart'; -import 'sdk/user.dart'; - -mixin FlutterLoginSdk { - static final UserService _userService = UserService(); - static final ScreenService _screenService = ScreenService(); - - UserService get users => _userService; - ScreenService get screens => _screenService; - - static UserService get userService => _userService; - static ScreenService get screenService => _screenService; - - void dispose() { - _userService.dispose(); - } -} diff --git a/lib/flutter_login_view.dart b/lib/flutter_login_view.dart index 0145641..e65733e 100644 --- a/lib/flutter_login_view.dart +++ b/lib/flutter_login_view.dart @@ -2,10 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_login/backend/login_repository.dart'; import '../default_translation.dart'; import '../plugins/login/login_email_password.dart'; -import 'flutter_login_sdk.dart'; import 'login_config.dart'; import 'plugins/login/choose_login.dart'; -import 'widgets/custom_navigator.dart'; export '../plugins/form/form.dart'; export '../plugins/login/email_password_form.dart'; export '../plugins/login/login_email_password.dart'; @@ -15,8 +13,8 @@ export 'model/login_confirmation_result.dart'; export 'model/login_user.dart'; export 'plugins/settings/control.dart' show Control; -class FlutterLogin extends InheritedWidget with FlutterLoginSdk { - FlutterLogin({ +class FlutterLogin extends InheritedWidget { + const FlutterLogin({ required this.config, required this.repository, required Widget child, @@ -24,14 +22,6 @@ class FlutterLogin extends InheritedWidget with FlutterLoginSdk { Key? key, }) : super(key: key, child: child); - FlutterLogin.from({ - required FlutterLogin appShell, - required Widget child, - Key? key, - }) : config = appShell.config, - repository = appShell.repository, - app = appShell.app, - super(child: child, key: key); static Function(Object?) logError = (error) {}; static Map> get defaultTranslations => defaultTranslation; @@ -41,21 +31,21 @@ class FlutterLogin extends InheritedWidget with FlutterLoginSdk { final Widget app; static FlutterLogin of(BuildContext context) { - var inheritedAppshell = + var inheritedLogin = context.dependOnInheritedWidgetOfExactType(); - if (inheritedAppshell == null) { + if (inheritedLogin == null) { throw FlutterError( 'You are retrieving an flutter login from a context that does not contain an flutter login. Make sure you keep the flutter login in your inheritence tree', ); } - return inheritedAppshell; + return inheritedLogin; } @override bool updateShouldNotify(FlutterLogin oldWidget) => config != oldWidget.config; } -extension AppShellRetrieval on BuildContext { +extension LoginRetrieval on BuildContext { static LoginRepository? _cachedBackend; FlutterLogin login() => FlutterLogin.of(this); LoginRepository loginRepository() { @@ -77,7 +67,7 @@ extension StringFormat on String { String format(List params) => _interpolate(this, params); } -extension AppShellTranslate on BuildContext { +extension LoginTranslate on BuildContext { String? _getDefaultTranslation(String key, List arguments) { var locale = Localizations.localeOf(this); var code = locale.countryCode ?? 'nl'; @@ -86,8 +76,11 @@ extension AppShellTranslate on BuildContext { return translationMap?[key]?.toString().format(arguments); } - String translate(String key, - {String? defaultValue, List arguments = const []}) { + String translate( + String key, { + String? defaultValue, + List arguments = const [], + }) { dynamic translateFunction = login().config.translate; if (translateFunction == null) { return _getDefaultTranslation(key, arguments) ?? defaultValue ?? key; @@ -100,53 +93,64 @@ extension AppShellTranslate on BuildContext { } } -class LoginMain extends StatelessWidget { - LoginMain({ +class LoginMain extends StatefulWidget { + const LoginMain({ required this.child, super.key, }); final Widget child; - Widget _login(context) { - return Builder( - builder: (context) { - if (context.login().users.isLoggedIn(context)) { - return child; - } - - return FlutterLogin.of(context) - .config - .loginOptions - .loginMethod - .contains(LoginMethod.LoginInteractiveWithSocial) || - FlutterLogin.of(context) - .config - .loginOptions - .loginMethod - .contains(LoginMethod.LoginInteractiveWithPhoneNumber) - ? ChooseLogin( - child: child, - ) - : EmailPasswordLogin( - onPressedForgotPassword: FlutterLogin.of(context) - .config - .loginOptions - .onPressForgotPassword, - child: child, - ); - }, - ); - } - @override - CustomNavigator build(BuildContext context) => CustomNavigator( - pageRoute: PageRoutes.materialPageRoute, - home: _login(context), - ); + State createState() => _LoginMainState(); } -class AppShellException implements Exception { - AppShellException( +class _LoginMainState extends State { + bool _checkedIfLoggedIn = false; + bool _isLoggedIn = false; + + @override + Widget build(BuildContext context) { + if (!_checkedIfLoggedIn) { + context.loginRepository().reLogin( + onLoggedIn: () => setState( + () { + _isLoggedIn = true; + }, + ), + ); + + _checkedIfLoggedIn = true; + } + + return _isLoggedIn + ? widget.child + : Builder( + builder: (context) => FlutterLogin.of(context) + .config + .loginOptions + .loginMethod + .contains(LoginMethod.LoginInteractiveWithSocial) || + FlutterLogin.of(context) + .config + .loginOptions + .loginMethod + .contains(LoginMethod.LoginInteractiveWithPhoneNumber) + ? ChooseLogin( + child: widget.child, + ) + : EmailPasswordLogin( + onPressedForgotPassword: FlutterLogin.of(context) + .config + .loginOptions + .onPressForgotPassword, + child: widget.child, + ), + ); + } +} + +class LoginException implements Exception { + LoginException( this.error, [ this.stackTrace = StackTrace.empty, ]); @@ -158,7 +162,7 @@ class AppShellException implements Exception { @override String toString() { - return 'Unhandled error occurred in Appshell: $error\n' + return 'Unhandled error occurred in login: $error\n' '$stackTrace'; } } diff --git a/lib/login_config.dart b/lib/login_config.dart index cf072df..f0b4554 100644 --- a/lib/login_config.dart +++ b/lib/login_config.dart @@ -337,7 +337,7 @@ class LoginConfig extends StatefulWidget { } class LoginConfigState extends State with WidgetsBindingObserver { - FlutterLogin? appShell; + FlutterLogin? login; late final ConfigData configData; late LoginRepository repository; @@ -352,7 +352,6 @@ class LoginConfigState extends State with WidgetsBindingObserver { if (widget.repository != null) { repository = widget.repository!; - repository.init(); } WidgetsBinding.instance.addObserver(this); @@ -383,7 +382,7 @@ class LoginConfigState extends State with WidgetsBindingObserver { !configData.loginOptions.socialOptions.forceAppleSignin))) { // check if apple login is removed by developer if (configData.loginOptions.socialOptions.socialLogins.isEmpty) { - throw AppShellException( + throw LoginException( 'If you enable LoginMethod.LoginInteractiveWithSocial you must provide atleast 1 social login option!'); } } @@ -391,7 +390,7 @@ class LoginConfigState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - appShell = FlutterLogin( + login = FlutterLogin( config: widget.config ?? ConfigData.example(), repository: repository, app: widget.child, @@ -406,21 +405,10 @@ class LoginConfigState extends State with WidgetsBindingObserver { if (isFlutterDefaultTheme(context)) { return Theme( data: defaultTheme, - child: appShell!, + child: login!, ); } - return appShell!; - } - - @override - void dispose() { - super.dispose(); - repository.dispose(); + return login!; } } - -class AppshellNoDisplayError { - AppshellNoDisplayError(this.error); - Object error; -} diff --git a/lib/plugins/login/choose_login.dart b/lib/plugins/login/choose_login.dart index 3ec879c..f4fc336 100644 --- a/lib/plugins/login/choose_login.dart +++ b/lib/plugins/login/choose_login.dart @@ -14,7 +14,8 @@ class ChooseLogin extends Login { static String? finalEmail; static String? finalPassword; - createState() => ChooseLoginState(); + @override + ChooseLoginState createState() => ChooseLoginState(); } class ChooseLoginState extends LoginState { diff --git a/lib/plugins/login/forgot_password.dart b/lib/plugins/login/forgot_password.dart index dd3f1cc..15b928d 100644 --- a/lib/plugins/login/forgot_password.dart +++ b/lib/plugins/login/forgot_password.dart @@ -47,139 +47,122 @@ class ForgotPasswordState extends State { context.translate('forgot_password.text.title'), ), ), - body: context.login().screens.getAppshellScreenWrapper( - context, - backgroundImg: - context.login().config.loginOptions.backgroundImage, - child: Column( + body: Column( + children: [ + Expanded( + child: ListView( children: [ - Expanded( - child: ListView( + Container( + alignment: Alignment.topLeft, + padding: const EdgeInsets.only( + top: 27, + left: 5, + ), + child: context.login().config.appTheme.buttons.backButton( + context: context, + ), + ), + Padding( + padding: const EdgeInsets.all(30.0), + child: Text( + context.translate('forgot_password.text.body'), + style: Theme.of(context).textTheme.subtitle1, + textAlign: TextAlign.left, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 30, right: 30), + child: Column( children: [ - Container( - alignment: Alignment.topLeft, - padding: const EdgeInsets.only( - top: 27, - left: 5, - ), - child: context - .login() - .config - .appTheme - .buttons - .backButton( - context: context, - ), + const Padding( + padding: EdgeInsets.only(bottom: 20), ), - Padding( - padding: const EdgeInsets.all(30.0), - child: Text( - context.translate('forgot_password.text.body'), - style: Theme.of(context).textTheme.subtitle1, - textAlign: TextAlign.left, - ), - ), - Padding( - padding: const EdgeInsets.only(left: 30, right: 30), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(bottom: 20), + context.login().config.appTheme.inputs.textField( + title: context.translate( + 'forgot_password.input.email', ), - context.login().config.appTheme.inputs.textField( - title: context.translate( - 'forgot_password.input.email', - ), - validators: [ - EmailValidator( - errorMessage: context.translate( - 'forgot_password.error.invalid_email', - ), - ) - ], - onChange: (value, valid) { - setState(() { - showError = false; - }); - if (valid) { - email = value; - } - }, + validators: [ + EmailValidator( + errorMessage: context.translate( + 'forgot_password.error.invalid_email', ), - if (showError) ...[ - Text( - context.translate( - 'forgot_password.error.email_does_not_exist', - ), - style: Theme.of(context) - .textTheme - .bodyText2! - .copyWith( - color: Theme.of(context).errorColor, - ), - ), + ) ], - const Padding( - padding: EdgeInsets.only(bottom: 30), - ), - ], + onChange: (value, valid) { + setState(() { + showError = false; + }); + if (valid) { + email = value; + } + }, + ), + if (showError) ...[ + Text( + context.translate( + 'forgot_password.error.email_does_not_exist', + ), + style: + Theme.of(context).textTheme.bodyText2!.copyWith( + color: Theme.of(context).errorColor, + ), ), + ], + const Padding( + padding: EdgeInsets.only(bottom: 30), ), ], ), ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 30), - padding: const EdgeInsets.only(bottom: 40.0), - child: context - .login() - .config - .appTheme - .buttons - .primaryButton( - context: context, - child: Text( - context.translate('forgot_password.button.submit'), - style: Theme.of(context).textTheme.button, - ), - onPressed: () async { - if (email != null) { - setState(() { - showError = false; - }); - - var result = await context - .loginRepository() - .forgotPassword(email!); - - if (result) { - showAlert( - title: context.translate( - 'forgot_password.dialog.text.title', - ), - text: context.translate( - 'forgot_password.dialog.text.body', - arguments: [email], - ), - buttonTitle: context.translate( - 'forgot_password.dialog.text.button', - ), - buttonAction: () { - Navigator.pop(context); - }, - ); - } else { - setState(() { - showError = true; - }); - } - } - }, - ), - ), ], ), ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 30), + padding: const EdgeInsets.only(bottom: 40.0), + child: context.login().config.appTheme.buttons.primaryButton( + context: context, + child: Text( + context.translate('forgot_password.button.submit'), + style: Theme.of(context).textTheme.button, + ), + onPressed: () async { + if (email != null) { + setState(() { + showError = false; + }); + + var result = await context + .loginRepository() + .forgotPassword(email!); + + if (result) { + showAlert( + title: context.translate( + 'forgot_password.dialog.text.title', + ), + text: context.translate( + 'forgot_password.dialog.text.body', + arguments: [email], + ), + buttonTitle: context.translate( + 'forgot_password.dialog.text.button', + ), + buttonAction: () { + Navigator.pop(context); + }, + ); + } else { + setState(() { + showError = true; + }); + } + } + }, + ), + ), + ], + ), ), ); } diff --git a/lib/plugins/login/login_await_email.dart b/lib/plugins/login/login_await_email.dart index 05a4140..131b9b8 100644 --- a/lib/plugins/login/login_await_email.dart +++ b/lib/plugins/login/login_await_email.dart @@ -15,30 +15,16 @@ class LoginAwaitEmailScreen extends StatefulWidget { final Widget? child; @override - _LoginAwaitEmailScreenState createState() => _LoginAwaitEmailScreenState(); + LoginAwaitEmailScreenState createState() => LoginAwaitEmailScreenState(); } -class _LoginAwaitEmailScreenState extends State +class LoginAwaitEmailScreenState extends State with NavigateWidgetMixin { @override void initState() { - context.loginRepository().addListener(registrateOrMainScreen); super.initState(); } - Future registrateOrMainScreen() async { - var data = - await (context.loginRepository().userprofileExists() as FutureOr); - if (context.login().config.registrationOptions.registrationMode == - RegistrationMode.Disabled || - data) { - context.loginRepository().setLoggedIn(EmailPasswordLogin.finalEmail!); - widget.loginComplete!(); - } else { - debugPrint('Register'); - } - } - void navigateToEmailPage(BuildContext context) { navigateFadeTo( context, diff --git a/lib/plugins/login/login_email_password.dart b/lib/plugins/login/login_email_password.dart index b9561e7..73b7130 100644 --- a/lib/plugins/login/login_email_password.dart +++ b/lib/plugins/login/login_email_password.dart @@ -87,7 +87,7 @@ class EmailLoginState extends LoginState { _loading = false; }); } else { - context.loginRepository().setLoggedIn(EmailPasswordLogin.finalEmail!); + context.loginRepository().loggedIn = true; navigateFadeToReplace( context, (context) => widget.child, diff --git a/lib/plugins/login/login_image.dart b/lib/plugins/login/login_image.dart index a0fc18e..915951c 100644 --- a/lib/plugins/login/login_image.dart +++ b/lib/plugins/login/login_image.dart @@ -14,7 +14,6 @@ class LoginImage extends StatelessWidget { if (config.loginImage == '') { image = const AssetImage( 'assets/images/login.png', - package: 'appshell', ); } else if (split.length < 2) { image = AssetImage(config.loginImage); diff --git a/lib/plugins/login/login_widget.dart b/lib/plugins/login/login_widget.dart index bb3b59b..bef3b2e 100644 --- a/lib/plugins/login/login_widget.dart +++ b/lib/plugins/login/login_widget.dart @@ -36,43 +36,38 @@ abstract class LoginState extends State @override Widget build(BuildContext context) => Scaffold( backgroundColor: Theme.of(context).backgroundColor, - body: context.login().screens.getAppshellScreenWrapper( - context, - backgroundImg: - context.login().config.loginOptions.backgroundImage, - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Stack( - children: [ - SingleChildScrollView( - child: buildLoginPage(context), - ), - if (widget.allowExit) ...[ - Align( - alignment: Alignment.topRight, - child: Padding( - padding: const EdgeInsets.only(top: 30, right: 10), - child: SizedBox( - height: 48, - width: 48, - child: GestureDetector( - key: const Key('navigateToSettings'), - child: const Icon( - Icons.close, - size: 24, - ), - onTap: () { - Navigator.of(context).pop(); - }, - ), - ), - ), - ), - ], - ], - ), + body: SizedBox( + height: MediaQuery.of(context).size.height, + child: Stack( + children: [ + SingleChildScrollView( + child: buildLoginPage(context), ), - ), + if (widget.allowExit) ...[ + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.only(top: 30, right: 10), + child: SizedBox( + height: 48, + width: 48, + child: GestureDetector( + key: const Key('navigateToSettings'), + child: const Icon( + Icons.close, + size: 24, + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ), + ), + ), + ], + ], + ), + ), ); Widget buildLoginPage(BuildContext context); diff --git a/lib/plugins/login/resend.dart b/lib/plugins/login/resend.dart index ee0c7ad..93a43e4 100644 --- a/lib/plugins/login/resend.dart +++ b/lib/plugins/login/resend.dart @@ -5,10 +5,10 @@ class Resend extends StatefulWidget { const Resend({super.key}); @override - _ConfirmationState createState() => _ConfirmationState(); + ConfirmationState createState() => ConfirmationState(); } -class _ConfirmationState extends State { +class ConfirmationState extends State { @override void initState() { super.initState(); diff --git a/lib/sdk/screen.dart b/lib/sdk/screen.dart deleted file mode 100644 index 6f4af9c..0000000 --- a/lib/sdk/screen.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import '../flutter_login_view.dart'; - -class ScreenService { - late bool shouldShowIntroductionScreen; - late bool shouldShowPolicyPage; - - Widget getAppshellScreenWrapper( - BuildContext context, { - required Widget child, - String? backgroundImg, - }) { - var bgImage = - backgroundImg ?? context.login().config.appOptions.backgroundImage; - if (bgImage.isNotEmpty) { - late AssetImage image; - var split = bgImage.split(';'); - - image = split.length < 2 - ? AssetImage(bgImage) - : AssetImage( - split.first, - package: split.last, - ); - - return Container( - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - image: DecorationImage( - image: image, - fit: BoxFit.cover, - ), - ), - child: child, - ); - } else { - return child; - } - } -} diff --git a/lib/sdk/user.dart b/lib/sdk/user.dart deleted file mode 100644 index 20bb699..0000000 --- a/lib/sdk/user.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import '../flutter_login_view.dart'; - -class UserService extends ChangeNotifier { - late Map? _currentProfile = {}; - - set profile(Map profile) { - _currentProfile = profile; - notifyListeners(); - } - - Future checkAutoLogin(FlutterLogin login) async { - debugPrint('checking autologin'); - if (login.config.loginOptions.loginMode != LoginMode.NoLogin) { - await Future.delayed(const Duration(milliseconds: 100), () async { - if (login.config.loginOptions.loginMode == LoginMode.LoginAutomatic) { - if (login.config.loginOptions.loginEmail == null || - login.config.loginOptions.loginEmail == '') { - throw Exception('No login account for automatic login provided!'); - } - if (login.config.loginOptions.loginPassword == null || - login.config.loginOptions.loginPassword == '') { - throw Exception( - 'No login password for automatic login provided!', - ); - } - await login.repository.login(login.config.loginOptions.loginEmail!, - login.config.loginOptions.loginPassword!); - } else if (login.config.loginOptions.loginMode == - LoginMode.LoginAnonymous) { - await login.repository.signInAnonymous(); - } else { - var prefs = await SharedPreferences.getInstance(); - var autoLoginMode = login.config.loginOptions.autoLoginMode; - if ((autoLoginMode != AutoLoginMode.alwaysOff && - (prefs.getBool('autoLogin') ?? false) == true) || - autoLoginMode == AutoLoginMode.alwaysOn) { - await login.repository.reLogin(); - } - } - }); - } - } - - void addProfileListener( - void Function(Map) onProfileChanged, - ) { - addListener(() { - onProfileChanged.call(_currentProfile!); - }); - } - - bool isLoggedIn(BuildContext context) => - context.loginRepository().isLoggedIn(); - - Future logout(BuildContext context) => - SharedPreferences.getInstance().then( - (value) { - value - .setBool('autoLogin', false) - .then((value) => context.loginRepository().logout()); - }, - ); -} - -class UserProfile { - late Map rawFields; - String? photoUrl; - - @mustCallSuper - void init(Map raw) { - rawFields = raw; - photoUrl = raw['photo']; - } - - bool isProfileComplete({List requiredFields = const []}) { - return !requiredFields.any((element) => rawFields[element] == null); - } - - dynamic getValue(String key) { - return rawFields[key]; - } -} diff --git a/lib/widgets/custom_navigator.dart b/lib/widgets/custom_navigator.dart index b17ba74..6993f54 100644 --- a/lib/widgets/custom_navigator.dart +++ b/lib/widgets/custom_navigator.dart @@ -24,10 +24,10 @@ class CustomNavigator extends StatefulWidget { final List navigatorObservers; @override - _CustomNavigatorState createState() => _CustomNavigatorState(); + CustomNavigatorState createState() => CustomNavigatorState(); } -class _CustomNavigatorState extends State +class CustomNavigatorState extends State implements WidgetsBindingObserver { GlobalKey? _navigator; @@ -134,9 +134,11 @@ class _CustomNavigatorState extends State return result; } - didChangeAppLifecycleState(AppLifecycleState state) {} + @override + void didChangeAppLifecycleState(AppLifecycleState state) {} - noSuchMethod(Invocation invocation) { + @override + void noSuchMethod(Invocation invocation) { var name = invocation.memberName.toString(); debugPrint( 'Expected a method to be called with name $name, ' @@ -147,9 +149,9 @@ class _CustomNavigatorState extends State class PageRoutes { static final materialPageRoute = - ((RouteSettings settings, WidgetBuilder builder) => - MaterialPageRoute(settings: settings, builder: builder)); + (RouteSettings settings, WidgetBuilder builder) => + MaterialPageRoute(settings: settings, builder: builder); static final cupertinoPageRoute = - ((RouteSettings settings, WidgetBuilder builder) => - CupertinoPageRoute(settings: settings, builder: builder)); + (RouteSettings settings, WidgetBuilder builder) => + CupertinoPageRoute(settings: settings, builder: builder); }