Android Feature Optimization and Updates
Build Android Release / Build Android (push) Failing after 7s

This commit is contained in:
2026-05-21 10:31:43 +08:00
parent 31acfd4e13
commit 1d9e0a831f
19 changed files with 2506 additions and 562 deletions
+117 -44
View File
@@ -5,7 +5,11 @@ import 'package:lucide_icons/lucide_icons.dart';
import 'package:provider/provider.dart';
import '../../../core/exceptions/app_exception.dart';
import '../../../core/validators/string_validator.dart';
import '../../../data/models/login_config_model.dart';
import '../../providers/auth_provider.dart';
import '../../../services/api_service.dart';
import '../../../services/auth_service.dart';
import '../../../services/captcha_service.dart';
import '../../../services/server_service.dart';
import '../../../router/app_router.dart';
import 'forgot_password_page.dart';
@@ -27,11 +31,15 @@ class _LoginPageState extends State<LoginPage> {
bool _obscurePassword = true;
bool _rememberMe = false;
bool _isLoading = false;
LoginConfigModel _loginConfig = const LoginConfigModel();
@override
void initState() {
super.initState();
_loadRememberedInfo();
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadLoginConfig();
});
}
@override
@@ -57,23 +65,59 @@ class _LoginPageState extends State<LoginPage> {
}
}
Future<void> _loadLoginConfig() async {
final server = ServerService.instance.currentServer;
if (server == null) return;
try {
await ApiService.instance.setBaseUrl(server.baseUrl);
final config = await AuthService.instance.getLoginConfig().timeout(
const Duration(seconds: 10),
);
if (!mounted) return;
setState(() => _loginConfig = config);
if (config.loginCaptcha) {
await CaptchaService.instance.loadCaptcha(server.baseUrl);
if (mounted) setState(() {});
} else {
CaptchaService.instance.clearCaptcha();
}
} catch (_) {}
}
Future<void> _login() async {
if (!_formKey.currentState!.validate()) return;
final captcha = CaptchaService.instance;
if (_loginConfig.loginCaptcha && !captcha.isWebCaptchaVerified) {
ToastHelper.failure('请先完成人机验证');
return;
}
final navigator = Navigator.of(context);
final authProvider = Provider.of<AuthProvider>(context, listen: false);
setState(() => _isLoading = true);
try {
final success = await authProvider.passwordLogin(
email: _emailController.text.trim(),
password: _passwordController.text,
rememberMe: _rememberMe,
).timeout(
const Duration(seconds: 5),
onTimeout: () => throw Exception('请求超时'),
);
final captchaParams = _loginConfig.loginCaptcha
? captcha.getCaptchaParams()
: <String, String>{};
final success = await authProvider
.passwordLogin(
email: _emailController.text.trim(),
password: _passwordController.text,
rememberMe: _rememberMe,
captcha: captchaParams['captcha'],
ticket: captchaParams['ticket'],
)
.timeout(
const Duration(seconds: 15),
onTimeout: () => throw Exception('请求超时'),
);
if (mounted) setState(() => _isLoading = false);
@@ -83,6 +127,11 @@ class _LoginPageState extends State<LoginPage> {
await Future.delayed(const Duration(seconds: 1));
if (mounted) navigator.pushReplacementNamed(RouteNames.home);
} else if (mounted) {
if (_loginConfig.loginCaptcha) {
await captcha.refreshCaptcha();
if (mounted) setState(() {});
}
final errorMessage = authProvider.errorMessage;
if (errorMessage != null && errorMessage.isNotEmpty) {
final errorMsg = _parseErrorMessage(errorMessage);
@@ -99,6 +148,11 @@ class _LoginPageState extends State<LoginPage> {
} catch (e) {
if (mounted) {
setState(() => _isLoading = false);
if (_loginConfig.loginCaptcha) {
await captcha.refreshCaptcha();
if (mounted) setState(() {});
}
final errorMsg = _parseErrorMessage(e.toString());
ToastHelper.failure(errorMsg);
}
@@ -153,6 +207,7 @@ class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final captcha = CaptchaService.instance;
return Scaffold(
body: SafeArea(
@@ -168,10 +223,7 @@ class _LoginPageState extends State<LoginPage> {
Center(child: _buildLogo()),
const SizedBox(height: 32),
Center(
child: Text(
'公云存储',
style: theme.textTheme.headlineMedium,
),
child: Text('公云存储', style: theme.textTheme.headlineMedium),
),
const SizedBox(height: 32),
Card(
@@ -196,7 +248,8 @@ class _LoginPageState extends State<LoginPage> {
hintText: '请输入邮箱地址',
prefixIcon: Icon(LucideIcons.mail),
),
onFieldSubmitted: (_) => _focusNode.requestFocus(),
onFieldSubmitted: (_) =>
_focusNode.requestFocus(),
),
const SizedBox(height: 16),
@@ -219,18 +272,27 @@ class _LoginPageState extends State<LoginPage> {
size: 20,
),
onPressed: () {
setState(() => _obscurePassword = !_obscurePassword);
setState(
() =>
_obscurePassword = !_obscurePassword,
);
},
),
),
onFieldSubmitted: (_) => _login(),
),
if (_loginConfig.loginCaptcha) ...[
const SizedBox(height: 16),
captcha.buildCaptchaInput(context),
],
const SizedBox(height: 12),
// 记住我
InkWell(
onTap: () => setState(() => _rememberMe = !_rememberMe),
onTap: () =>
setState(() => _rememberMe = !_rememberMe),
borderRadius: BorderRadius.circular(8),
child: Row(
mainAxisSize: MainAxisSize.min,
@@ -240,7 +302,9 @@ class _LoginPageState extends State<LoginPage> {
height: 24,
child: Checkbox(
value: _rememberMe,
onChanged: (v) => setState(() => _rememberMe = v ?? false),
onChanged: (v) => setState(
() => _rememberMe = v ?? false,
),
),
),
const SizedBox(width: 8),
@@ -259,7 +323,10 @@ class _LoginPageState extends State<LoginPage> {
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ForgotPasswordPage(),
builder: (context) =>
ForgotPasswordPage(
loginConfig: _loginConfig,
),
),
);
},
@@ -269,7 +336,9 @@ class _LoginPageState extends State<LoginPage> {
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const RegisterPage(),
builder: (context) => RegisterPage(
loginConfig: _loginConfig,
),
),
);
},
@@ -293,7 +362,9 @@ class _LoginPageState extends State<LoginPage> {
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2),
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Text('登录'),
),
@@ -356,17 +427,17 @@ class _TwoFactorDialogState extends State<_TwoFactorDialog>
duration: const Duration(milliseconds: 400),
vsync: this,
);
_shakeAnimation = TweenSequence<double>([
TweenSequenceItem(tween: Tween(begin: 0, end: 10), weight: 1),
TweenSequenceItem(tween: Tween(begin: 10, end: -10), weight: 1),
TweenSequenceItem(tween: Tween(begin: -10, end: 8), weight: 1),
TweenSequenceItem(tween: Tween(begin: 8, end: -8), weight: 1),
TweenSequenceItem(tween: Tween(begin: -8, end: 4), weight: 1),
TweenSequenceItem(tween: Tween(begin: 4, end: 0), weight: 1),
]).animate(CurvedAnimation(
parent: _shakeController,
curve: Curves.easeInOut,
));
_shakeAnimation =
TweenSequence<double>([
TweenSequenceItem(tween: Tween(begin: 0, end: 10), weight: 1),
TweenSequenceItem(tween: Tween(begin: 10, end: -10), weight: 1),
TweenSequenceItem(tween: Tween(begin: -10, end: 8), weight: 1),
TweenSequenceItem(tween: Tween(begin: 8, end: -8), weight: 1),
TweenSequenceItem(tween: Tween(begin: -8, end: 4), weight: 1),
TweenSequenceItem(tween: Tween(begin: 4, end: 0), weight: 1),
]).animate(
CurvedAnimation(parent: _shakeController, curve: Curves.easeInOut),
);
_controller.addListener(_onTextChanged);
WidgetsBinding.instance.addPostFrameCallback((_) {
_focusNode.requestFocus();
@@ -396,16 +467,18 @@ class _TwoFactorDialogState extends State<_TwoFactorDialog>
final authProvider = Provider.of<AuthProvider>(context, listen: false);
try {
final success = await authProvider.twoFactorLogin(
otp: code,
sessionId: widget.sessionId,
email: widget.email,
password: widget.password,
rememberMe: widget.rememberMe,
).timeout(
const Duration(seconds: 5),
onTimeout: () => throw Exception('请求超时'),
);
final success = await authProvider
.twoFactorLogin(
otp: code,
sessionId: widget.sessionId,
email: widget.email,
password: widget.password,
rememberMe: widget.rememberMe,
)
.timeout(
const Duration(seconds: 5),
onTimeout: () => throw Exception('请求超时'),
);
if (!mounted) return;
@@ -493,9 +566,7 @@ class _TwoFactorDialogState extends State<_TwoFactorDialog>
borderRadius: BorderRadius.circular(12),
),
),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onSubmitted: (_) => _submit(),
),
),
@@ -503,7 +574,9 @@ class _TwoFactorDialogState extends State<_TwoFactorDialog>
),
actions: [
TextButton(
onPressed: _isSubmitting ? null : () => Navigator.of(context).pop(false),
onPressed: _isSubmitting
? null
: () => Navigator.of(context).pop(false),
child: const Text('取消'),
),
FilledButton(