Refactor mobile to use OpenApi generated SDK (#336)

This commit is contained in:
Alex
2022-07-13 07:23:48 -05:00
committed by GitHub
parent d69470e207
commit ae7e582ec8
276 changed files with 14513 additions and 3003 deletions

View File

@@ -1,10 +1,8 @@
import 'dart:convert';
import 'package:immich_mobile/shared/models/device_info.model.dart';
import 'package:openapi/api.dart';
class AuthenticationState {
final String deviceId;
final String deviceType;
final DeviceTypeEnum deviceType;
final String userId;
final String userEmail;
final bool isAuthenticated;
@@ -13,8 +11,7 @@ class AuthenticationState {
final bool isAdmin;
final bool shouldChangePassword;
final String profileImagePath;
final DeviceInfoRemote deviceInfo;
final DeviceInfoResponseDto deviceInfo;
AuthenticationState({
required this.deviceId,
required this.deviceType,
@@ -31,7 +28,7 @@ class AuthenticationState {
AuthenticationState copyWith({
String? deviceId,
String? deviceType,
DeviceTypeEnum? deviceType,
String? userId,
String? userEmail,
bool? isAuthenticated,
@@ -40,7 +37,7 @@ class AuthenticationState {
bool? isAdmin,
bool? shouldChangePassword,
String? profileImagePath,
DeviceInfoRemote? deviceInfo,
DeviceInfoResponseDto? deviceInfo,
}) {
return AuthenticationState(
deviceId: deviceId ?? this.deviceId,
@@ -57,45 +54,6 @@ class AuthenticationState {
);
}
Map<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'deviceId': deviceId});
result.addAll({'deviceType': deviceType});
result.addAll({'userId': userId});
result.addAll({'userEmail': userEmail});
result.addAll({'isAuthenticated': isAuthenticated});
result.addAll({'firstName': firstName});
result.addAll({'lastName': lastName});
result.addAll({'isAdmin': isAdmin});
result.addAll({'shouldChangePassword': shouldChangePassword});
result.addAll({'profileImagePath': profileImagePath});
result.addAll({'deviceInfo': deviceInfo.toMap()});
return result;
}
factory AuthenticationState.fromMap(Map<String, dynamic> map) {
return AuthenticationState(
deviceId: map['deviceId'] ?? '',
deviceType: map['deviceType'] ?? '',
userId: map['userId'] ?? '',
userEmail: map['userEmail'] ?? '',
isAuthenticated: map['isAuthenticated'] ?? false,
firstName: map['firstName'] ?? '',
lastName: map['lastName'] ?? '',
isAdmin: map['isAdmin'] ?? false,
shouldChangePassword: map['shouldChangePassword'] ?? false,
profileImagePath: map['profileImagePath'] ?? '',
deviceInfo: DeviceInfoRemote.fromMap(map['deviceInfo']),
);
}
String toJson() => json.encode(toMap());
factory AuthenticationState.fromJson(String source) =>
AuthenticationState.fromMap(json.decode(source));
@override
String toString() {
return 'AuthenticationState(deviceId: $deviceId, deviceType: $deviceType, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, firstName: $firstName, lastName: $lastName, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword, profileImagePath: $profileImagePath, deviceInfo: $deviceInfo)';

View File

@@ -16,9 +16,10 @@ class HiveSavedLoginInfo {
@HiveField(3)
bool isSaveLogin;
HiveSavedLoginInfo(
{required this.email,
required this.password,
required this.serverUrl,
required this.isSaveLogin});
HiveSavedLoginInfo({
required this.email,
required this.password,
required this.serverUrl,
required this.isSaveLogin,
});
}

View File

@@ -1,23 +1,23 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.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/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/modules/backup/services/backup.service.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/shared/services/device_info.service.dart';
import 'package:immich_mobile/shared/services/network.service.dart';
import 'package:immich_mobile/shared/models/device_info.model.dart';
import 'package:openapi/api.dart';
class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
AuthenticationNotifier(
this._deviceInfoService, this._backupService, this._networkService)
: super(
this._deviceInfoService,
this._backupService,
this._apiService,
) : super(
AuthenticationState(
deviceId: "",
deviceType: "",
deviceType: DeviceTypeEnum.ANDROID,
userId: "",
userEmail: "",
firstName: '',
@@ -26,12 +26,11 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
isAdmin: false,
shouldChangePassword: false,
isAuthenticated: false,
deviceInfo: DeviceInfoRemote(
deviceInfo: DeviceInfoResponseDto(
id: 0,
userId: "",
deviceId: "",
deviceType: "",
notificationToken: "",
deviceType: DeviceTypeEnum.ANDROID,
createdAt: "",
isAutoBackup: false,
),
@@ -40,10 +39,14 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
final DeviceInfoService _deviceInfoService;
final BackupService _backupService;
final NetworkService _networkService;
final ApiService _apiService;
Future<bool> login(String email, String password, String serverEndpoint,
bool isSavedLoginInfo) async {
Future<bool> 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);
@@ -52,12 +55,12 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
Hive.box(userInfoBox).put(serverEndpointKey, serverEndpoint);
}
// Check Server URL validity
try {
bool isServerEndpointVerified = await _networkService.pingServer();
if (!isServerEndpointVerified) {
return false;
}
_apiService.setEndpoint(Hive.box(userInfoBox).get(serverEndpointKey));
await _apiService.serverInfoApi.pingServer();
} catch (e) {
debugPrint('Invalid Server Endpoint Url $e');
return false;
}
@@ -72,56 +75,73 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
// Make sign-in request
try {
Response res = await _networkService.postRequest(
url: 'auth/login', data: {'email': email, 'password': password});
var loginResponse = await _apiService.authenticationApi.login(
LoginCredentialDto(
email: email,
password: password,
),
);
var payload = LogInReponse.fromJson(res.toString());
if (loginResponse == null) {
debugPrint('Login Response is null');
return false;
}
Hive.box(userInfoBox).put(accessTokenKey, payload.accessToken);
Hive.box(userInfoBox).put(accessTokenKey, loginResponse.accessToken);
state = state.copyWith(
isAuthenticated: true,
userId: payload.userId,
userEmail: payload.userEmail,
firstName: payload.firstName,
lastName: payload.lastName,
profileImagePath: payload.profileImagePath,
isAdmin: payload.isAdmin,
shouldChangePassword: payload.shouldChangePassword,
userId: loginResponse.userId,
userEmail: loginResponse.userEmail,
firstName: loginResponse.firstName,
lastName: loginResponse.lastName,
profileImagePath: loginResponse.profileImagePath,
isAdmin: loginResponse.isAdmin,
shouldChangePassword: loginResponse.shouldChangePassword,
);
// Login Success - Set Access Token to API Client
_apiService.setAccessToken(loginResponse.accessToken);
if (isSavedLoginInfo) {
// Save login info to local storage
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
savedLoginInfoKey,
HiveSavedLoginInfo(
email: email,
password: password,
isSaveLogin: true,
serverUrl: Hive.box(userInfoBox).get(serverEndpointKey)),
email: email,
password: password,
isSaveLogin: true,
serverUrl: Hive.box(userInfoBox).get(serverEndpointKey),
),
);
} else {
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox)
.delete(savedLoginInfoKey);
}
} catch (e) {
debugPrint("Error logging in $e");
return false;
}
// Register device info
try {
Response res = await _networkService.postRequest(
url: 'device-info',
data: {
'deviceId': state.deviceId,
'deviceType': state.deviceType,
},
DeviceInfoResponseDto? deviceInfo =
await _apiService.deviceInfoApi.createDeviceInfo(
CreateDeviceInfoDto(
deviceId: state.deviceId,
deviceType: state.deviceType,
),
);
DeviceInfoRemote deviceInfo = DeviceInfoRemote.fromJson(res.toString());
if (deviceInfo == null) {
debugPrint('Device Info Response is null');
return false;
}
state = state.copyWith(deviceInfo: deviceInfo);
} catch (e) {
debugPrint("ERROR Register Device Info: $e");
return false;
}
return true;
@@ -129,27 +149,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
Future<bool> logout() async {
Hive.box(userInfoBox).delete(accessTokenKey);
state = AuthenticationState(
deviceId: "",
deviceType: "",
userId: "",
userEmail: "",
firstName: '',
lastName: '',
profileImagePath: '',
shouldChangePassword: false,
isAuthenticated: false,
isAdmin: false,
deviceInfo: DeviceInfoRemote(
id: 0,
userId: "",
deviceId: "",
deviceType: "",
notificationToken: "",
createdAt: "",
isAutoBackup: false,
),
);
state = state.copyWith(isAuthenticated: false);
return true;
}
@@ -157,11 +157,13 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
setAutoBackup(bool backupState) async {
var deviceInfo = await _deviceInfoService.getDeviceInfo();
var deviceId = deviceInfo["deviceId"];
var deviceType = deviceInfo["deviceType"];
DeviceInfoRemote deviceInfoRemote =
DeviceTypeEnum deviceType = deviceInfo["deviceType"];
DeviceInfoResponseDto updatedDeviceInfo =
await _backupService.setAutoBackup(backupState, deviceId, deviceType);
state = state.copyWith(deviceInfo: deviceInfoRemote);
state = state.copyWith(deviceInfo: updatedDeviceInfo);
}
updateUserProfileImagePath(String path) {
@@ -169,19 +171,20 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
}
Future<bool> changePassword(String newPassword) async {
Response res = await _networkService.putRequest(
url: 'user',
data: {
'id': state.userId,
'password': newPassword,
'shouldChangePassword': false,
},
);
try {
await _apiService.userApi.updateUser(
UpdateUserDto(
id: state.userId,
password: newPassword,
shouldChangePassword: false,
),
);
if (res.statusCode == 200) {
state = state.copyWith(shouldChangePassword: false);
return true;
} else {
} catch (e) {
debugPrint("Error changing password $e");
return false;
}
}
@@ -192,6 +195,6 @@ final authenticationProvider =
return AuthenticationNotifier(
ref.watch(deviceInfoServiceProvider),
ref.watch(backupServiceProvider),
ref.watch(networkServiceProvider),
ref.watch(apiServiceProvider),
);
});

View File

@@ -140,35 +140,36 @@ class ChangePasswordButton extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.standard,
primary: Theme.of(context).primaryColor,
onPrimary: Colors.grey[50],
elevation: 2,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
),
onPressed: () async {
if (formKey.currentState!.validate()) {
var isSuccess = await ref
.watch(authenticationProvider.notifier)
.changePassword(passwordController.value.text);
style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.standard,
primary: Theme.of(context).primaryColor,
onPrimary: Colors.grey[50],
elevation: 2,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
),
onPressed: () async {
if (formKey.currentState!.validate()) {
var isSuccess = await ref
.watch(authenticationProvider.notifier)
.changePassword(passwordController.value.text);
if (isSuccess) {
bool res =
await ref.watch(authenticationProvider.notifier).logout();
if (isSuccess) {
bool res =
await ref.watch(authenticationProvider.notifier).logout();
if (res) {
ref.watch(backupProvider.notifier).cancelBackup();
ref.watch(assetProvider.notifier).clearAllAsset();
ref.watch(websocketProvider.notifier).disconnect();
AutoRouter.of(context).replace(const LoginRoute());
}
if (res) {
ref.watch(backupProvider.notifier).cancelBackup();
ref.watch(assetProvider.notifier).clearAllAsset();
ref.watch(websocketProvider.notifier).disconnect();
AutoRouter.of(context).replace(const LoginRoute());
}
}
},
child: const Text(
"Change Password",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
));
}
},
child: const Text(
"Change Password",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
);
}
}

View File

@@ -22,22 +22,25 @@ class LoginForm extends HookConsumerWidget {
final passwordController =
useTextEditingController.fromValue(TextEditingValue.empty);
final serverEndpointController =
useTextEditingController(text: 'login_endpoint_hint'.tr());
useTextEditingController(text: 'login_form_endpoint_hint'.tr());
final isSaveLoginInfo = useState<bool>(false);
useEffect(() {
var loginInfo =
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
useEffect(
() {
var loginInfo = Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox)
.get(savedLoginInfoKey);
if (loginInfo != null) {
usernameController.text = loginInfo.email;
passwordController.text = loginInfo.password;
serverEndpointController.text = loginInfo.serverUrl;
isSaveLoginInfo.value = loginInfo.isSaveLogin;
}
if (loginInfo != null) {
usernameController.text = loginInfo.email;
passwordController.text = loginInfo.password;
serverEndpointController.text = loginInfo.serverUrl;
isSaveLoginInfo.value = loginInfo.isSaveLogin;
}
return null;
}, []);
return null;
},
[],
);
return Center(
child: ConstrainedBox(
@@ -71,14 +74,16 @@ class LoginForm extends HookConsumerWidget {
dense: true,
side: const BorderSide(color: Colors.grey, width: 1.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
borderRadius: BorderRadius.circular(5),
),
enableFeedback: true,
title: const Text(
"login_form_save_login",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey),
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
).tr(),
value: isSaveLoginInfo.value,
onChanged: (switchValue) {
@@ -108,7 +113,6 @@ class ServerEndpointInput extends StatelessWidget {
: super(key: key);
String? _validateInput(String? url) {
if (url?.startsWith(RegExp(r'https?://')) == true) {
return null;
} else {
@@ -122,7 +126,7 @@ class ServerEndpointInput extends StatelessWidget {
controller: controller,
decoration: InputDecoration(
labelText: 'login_form_endpoint_url'.tr(),
border: OutlineInputBorder(),
border: const OutlineInputBorder(),
hintText: 'login_form_endpoint_hint'.tr(),
),
validator: _validateInput,
@@ -140,8 +144,9 @@ class EmailInput extends StatelessWidget {
if (email == null || email == '') return null;
if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr();
if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr();
if (email.contains(' ') || !email.contains('@'))
if (email.contains(' ') || !email.contains('@')) {
return 'login_form_err_invalid_email'.tr();
}
return null;
}
@@ -151,7 +156,7 @@ class EmailInput extends StatelessWidget {
controller: controller,
decoration: InputDecoration(
labelText: 'login_form_label_email'.tr(),
border: OutlineInputBorder(),
border: const OutlineInputBorder(),
hintText: 'login_form_email_hint'.tr(),
),
validator: _validateInput,
@@ -171,9 +176,10 @@ class PasswordInput extends StatelessWidget {
obscureText: true,
controller: controller,
decoration: InputDecoration(
labelText: 'login_form_label_password'.tr(),
border: OutlineInputBorder(),
hintText: 'login_form_password_hint'.tr()),
labelText: 'login_form_label_password'.tr(),
border: const OutlineInputBorder(),
hintText: 'login_form_password_hint'.tr(),
),
);
}
}
@@ -195,43 +201,47 @@ class LoginButton extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.standard,
primary: Theme.of(context).primaryColor,
onPrimary: Colors.grey[50],
elevation: 2,
padding: 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();
style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.standard,
primary: Theme.of(context).primaryColor,
onPrimary: Colors.grey[50],
elevation: 2,
padding: 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
.watch(authenticationProvider.notifier)
.login(emailController.text, passwordController.text,
serverEndpointController.text, isSavedLoginInfo);
var isAuthenticated =
await ref.watch(authenticationProvider.notifier).login(
emailController.text,
passwordController.text,
serverEndpointController.text,
isSavedLoginInfo,
);
if (isAuthenticated) {
// Resume backup (if enable) then navigate
if (isAuthenticated) {
// Resume backup (if enable) then navigate
if (ref.watch(authenticationProvider).shouldChangePassword &&
!ref.watch(authenticationProvider).isAdmin) {
AutoRouter.of(context).push(const ChangePasswordRoute());
} else {
ref.watch(backupProvider.notifier).resumeBackup();
AutoRouter.of(context).pushNamed("/tab-controller-page");
}
if (ref.watch(authenticationProvider).shouldChangePassword &&
!ref.watch(authenticationProvider).isAdmin) {
AutoRouter.of(context).push(const ChangePasswordRoute());
} else {
ImmichToast.show(
context: context,
msg: "login_failed".tr(),
toastType: ToastType.error,
);
ref.watch(backupProvider.notifier).resumeBackup();
AutoRouter.of(context).pushNamed("/tab-controller-page");
}
},
child: const Text(
"login_form_button_text",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
).tr());
} else {
ImmichToast.show(
context: context,
msg: "login_form_failed_login".tr(),
toastType: ToastType.error,
);
}
},
child: const Text(
"login_form_button_text",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
).tr(),
);
}
}