diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart index 61b6fb93ae..4c000524a8 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 3d2d71e584..836571913b 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 0000000000..d77923d4b1 --- /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 0000000000..80e6f30a9d Binary files /dev/null and b/mobile/lib/modules/login/models/hive_saved_login_info.model.g.dart differ diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index cb6fcf71d1..b7c22cae35 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 9ea9adfbc7..bb3dde2b77 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), + )); } }