Account settings auth devices

This commit is contained in:
2025-06-07 22:56:25 +08:00
parent d832729278
commit 3dd7c8a5b2
9 changed files with 861 additions and 48 deletions

View File

@ -1,8 +1,11 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'package:animations/animations.dart';
import 'package:auto_route/auto_route.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -63,6 +66,19 @@ class LoginScreen extends HookConsumerWidget {
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
trackGap: 0,
stopIndicatorRadius: 0,
)
else if (currentTicket.value != null)
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
trackGap: 0,
stopIndicatorRadius: 0,
value:
1 -
(currentTicket.value!.stepRemain /
currentTicket.value!.stepTotal),
)
else
const Gap(4),
@ -121,17 +137,8 @@ class LoginScreen extends HookConsumerWidget {
).padding(all: 24),
).center(),
),
if (currentTicket.value != null)
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
value:
1 -
(currentTicket.value!.stepRemain /
currentTicket.value!.stepTotal),
)
else
const Gap(4),
const Gap(4),
],
),
),
@ -170,6 +177,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
if (pwd.isEmpty) return;
isBusy.value = true;
try {
// Pass challenge
final client = ref.watch(apiClientProvider);
final resp = await client.patch(
'/auth/challenge/${challenge!.id}',
@ -181,6 +189,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
onNext();
return;
}
// Get token if challenge is completed
final tokenResp = await client.post(
'/auth/token',
data: {'grant_type': 'authorization_code', 'code': result.id},
@ -189,6 +199,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
setToken(ref.watch(sharedPreferencesProvider), token);
ref.invalidate(tokenProvider);
if (!context.mounted) return;
// Do post login tasks
final userNotifier = ref.read(userInfoProvider.notifier);
userNotifier.fetchUser().then((_) {
final apiClient = ref.read(apiClientProvider);
@ -197,6 +209,31 @@ class _LoginCheckScreen extends HookConsumerWidget {
wsNotifier.connect();
if (context.mounted) Navigator.pop(context, true);
});
// Update the sessions' device name is available
if (!kIsWeb) {
String? name;
if (Platform.isIOS) {
return;
// TODO waiting for apple to respond to grant my access to com.apple.developer.device-information.user-assigned-device-name
// ignore: dead_code
final deviceInfo = await DeviceInfoPlugin().iosInfo;
name = deviceInfo.name;
} else if (Platform.isAndroid) {
final deviceInfo = await DeviceInfoPlugin().androidInfo;
name = deviceInfo.name;
} else if (Platform.isWindows) {
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
name = deviceInfo.computerName;
}
if (name != null) {
final client = ref.watch(apiClientProvider);
await client.patch(
'/accounts/me/sessions/current/label',
data: jsonEncode(name),
);
}
}
} catch (err) {
showErrorAlert(err);
return;
@ -336,6 +373,14 @@ class _LoginPickerScreen extends HookConsumerWidget {
onPickFactor(factors!.where((x) => x == factorPicked.value).first);
onNext();
} catch (err) {
if (err is DioException && err.response?.statusCode == 400) {
onPickFactor(factors!.where((x) => x == factorPicked.value).first);
onNext();
if (context.mounted) {
showSnackBar(context, err.response!.data.toString());
}
return;
}
showErrorAlert(err);
return;
} finally {
@ -404,6 +449,7 @@ class _LoginPickerScreen extends HookConsumerWidget {
TextField(
controller: hintController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'authFactorHint'.tr(),
helperText: 'authFactorHintHelper'.tr(),