From f08babbe0da6161188193b2d350083b369b4f7f7 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 24 Apr 2022 21:28:24 -0500 Subject: [PATCH] Implemented remembering login data --- mobile/lib/constants/hive_box.dart | 8 +- mobile/lib/main.dart | 3 + .../models/hive_saved_login_info.model.dart | 20 +++++ .../models/hive_saved_login_info.model.g.dart | 50 +++++++++++++ .../providers/authentication.provider.dart | 17 ++++- mobile/lib/modules/login/ui/login_form.dart | 74 +++++++++++++++---- 6 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 mobile/lib/modules/login/models/hive_saved_login_info.model.dart create mode 100644 mobile/lib/modules/login/models/hive_saved_login_info.model.g.dart diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart index 61b6fb93ae176..4c000524a8fd0 100644 --- a/mobile/lib/constants/hive_box.dart +++ b/mobile/lib/constants/hive_box.dart @@ -3,9 +3,9 @@ const String userInfoBox = "immichBoxUserInfo"; // Box const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1 const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2 -// SERVER ENDPOINT +// Server endpoint const String serverEndpointKey = 'immichBoxServerEndpoint'; -// KEY -const String hiveAllAsssetKey = "allAssets"; -const String hiveBackupProgressKey = "backupProgressAssets"; +// Login Info +const String hiveLoginInfoBox = "immichLoginInfoBox"; +const String savedLoginInfoKey = "immichSavedLoginInfoKey"; diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 3d2d71e584841..836571913b558 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/tab_navigation_observer.dart'; @@ -15,7 +16,9 @@ import 'constants/hive_box.dart'; void main() async { await Hive.initFlutter(); + Hive.registerAdapter(HiveSavedLoginInfoAdapter()); await Hive.openBox(userInfoBox); + await Hive.openBox(hiveLoginInfoBox); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( diff --git a/mobile/lib/modules/login/models/hive_saved_login_info.model.dart b/mobile/lib/modules/login/models/hive_saved_login_info.model.dart new file mode 100644 index 0000000000000..d77923d4b18b8 --- /dev/null +++ b/mobile/lib/modules/login/models/hive_saved_login_info.model.dart @@ -0,0 +1,20 @@ +import 'package:hive/hive.dart'; + +part 'hive_saved_login_info.model.g.dart'; + +@HiveType(typeId: 0) +class HiveSavedLoginInfo { + @HiveField(0) + String email; + + @HiveField(1) + String password; + + @HiveField(2) + String serverUrl; + + @HiveField(3) + bool isSaveLogin; + + HiveSavedLoginInfo({required this.email, required this.password, required this.serverUrl, required this.isSaveLogin}); +} diff --git a/mobile/lib/modules/login/models/hive_saved_login_info.model.g.dart b/mobile/lib/modules/login/models/hive_saved_login_info.model.g.dart new file mode 100644 index 0000000000000..80e6f30a9d626 --- /dev/null +++ b/mobile/lib/modules/login/models/hive_saved_login_info.model.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'hive_saved_login_info.model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class HiveSavedLoginInfoAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + HiveSavedLoginInfo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return HiveSavedLoginInfo( + email: fields[0] as String, + password: fields[1] as String, + serverUrl: fields[2] as String, + isSaveLogin: fields[3] as bool, + ); + } + + @override + void write(BinaryWriter writer, HiveSavedLoginInfo obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.email) + ..writeByte(1) + ..write(obj.password) + ..writeByte(2) + ..write(obj.serverUrl) + ..writeByte(3) + ..write(obj.isSaveLogin); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is HiveSavedLoginInfoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index cb6fcf71d1f41..b7c22cae35dbe 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -4,6 +4,7 @@ import 'package:hive/hive.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; +import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; import 'package:immich_mobile/modules/login/models/login_response.model.dart'; import 'package:immich_mobile/shared/services/backup.service.dart'; import 'package:immich_mobile/shared/services/device_info.service.dart'; @@ -36,7 +37,7 @@ class AuthenticationNotifier extends StateNotifier { final BackupService _backupService = BackupService(); final NetworkService _networkService = NetworkService(); - Future login(String email, String password, String serverEndpoint) async { + Future login(String email, String password, String serverEndpoint, bool isSavedLoginInfo) async { // Store server endpoint to Hive and test endpoint if (serverEndpoint[serverEndpoint.length - 1] == "/") { var validUrl = serverEndpoint.substring(0, serverEndpoint.length - 1); @@ -76,6 +77,20 @@ class AuthenticationNotifier extends StateNotifier { userId: payload.userId, userEmail: payload.userEmail, ); + + if (isSavedLoginInfo) { + // Save login info to local storage + Hive.box(hiveLoginInfoBox).put( + savedLoginInfoKey, + HiveSavedLoginInfo( + email: email, + password: password, + isSaveLogin: true, + serverUrl: Hive.box(userInfoBox).get(serverEndpointKey)), + ); + } else { + Hive.box(hiveLoginInfoBox).delete(savedLoginInfoKey); + } } catch (e) { return false; } diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart index 9ea9adfbc7bd4..bb3dde2b77f44 100644 --- a/mobile/lib/modules/login/ui/login_form.dart +++ b/mobile/lib/modules/login/ui/login_form.dart @@ -1,7 +1,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hive/hive.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/hive_box.dart'; +import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/shared/providers/backup.provider.dart'; @@ -12,22 +15,36 @@ class LoginForm extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final usernameController = useTextEditingController(text: 'testuser@email.com'); - final passwordController = useTextEditingController(text: 'password'); - final serverEndpointController = useTextEditingController(text: 'http://192.168.1.216:2283'); + final usernameController = useTextEditingController.fromValue(TextEditingValue.empty); + final passwordController = useTextEditingController.fromValue(TextEditingValue.empty); + final serverEndpointController = useTextEditingController(text: 'http://your-server-ip:2283'); + final isSaveLoginInfo = useState(false); + + useEffect(() { + var loginInfo = Hive.box(hiveLoginInfoBox).get(savedLoginInfoKey); + + if (loginInfo != null) { + usernameController.text = loginInfo.email; + passwordController.text = loginInfo.password; + serverEndpointController.text = loginInfo.serverUrl; + isSaveLoginInfo.value = loginInfo.isSaveLogin; + } + + return null; + }, []); return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 300), child: SingleChildScrollView( child: Wrap( - spacing: 32, - runSpacing: 32, + spacing: 16, + runSpacing: 16, alignment: WrapAlignment.center, children: [ const Image( image: AssetImage('assets/immich-logo-no-outline.png'), - width: 128, + width: 100, filterQuality: FilterQuality.high, ), Text( @@ -42,10 +59,29 @@ class LoginForm extends HookConsumerWidget { EmailInput(controller: usernameController), PasswordInput(controller: passwordController), ServerEndpointInput(controller: serverEndpointController), + CheckboxListTile( + activeColor: Theme.of(context).primaryColor, + contentPadding: const EdgeInsets.symmetric(horizontal: 8), + dense: true, + side: const BorderSide(color: Colors.grey, width: 1.5), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + enableFeedback: true, + title: const Text( + "Save login", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey), + ), + value: isSaveLoginInfo.value, + onChanged: (switchValue) { + if (switchValue != null) { + isSaveLoginInfo.value = switchValue; + } + }, + ), LoginButton( emailController: usernameController, passwordController: passwordController, serverEndpointController: serverEndpointController, + isSavedLoginInfo: isSaveLoginInfo.value, ), ], ), @@ -104,29 +140,34 @@ class LoginButton extends ConsumerWidget { final TextEditingController emailController; final TextEditingController passwordController; final TextEditingController serverEndpointController; + final bool isSavedLoginInfo; - const LoginButton( - {Key? key, - required this.emailController, - required this.passwordController, - required this.serverEndpointController}) - : super(key: key); + const LoginButton({ + Key? key, + required this.emailController, + required this.passwordController, + required this.serverEndpointController, + required this.isSavedLoginInfo, + }) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { return ElevatedButton( + style: ButtonStyle( + visualDensity: VisualDensity.standard, + padding: MaterialStateProperty.all(const EdgeInsets.symmetric(vertical: 10, horizontal: 25)), + ), onPressed: () async { // This will remove current cache asset state of previous user login. ref.watch(assetProvider.notifier).clearAllAsset(); var isAuthenticated = await ref .read(authenticationProvider.notifier) - .login(emailController.text, passwordController.text, serverEndpointController.text); + .login(emailController.text, passwordController.text, serverEndpointController.text, isSavedLoginInfo); if (isAuthenticated) { // Resume backup (if enable) then navigate ref.watch(backupProvider.notifier).resumeBackup(); - // AutoRouter.of(context).pushNamed("/home-page"); AutoRouter.of(context).pushNamed("/tab-controller-page"); } else { ImmichToast.show( @@ -136,6 +177,9 @@ class LoginButton extends ConsumerWidget { ); } }, - child: const Text("Login")); + child: const Text( + "Login", + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + )); } }