feat(app): 完善播放协议搜索与空状态

This commit is contained in:
Zhou Weidong committed 2026-10-23 12:38:47 +08:00
1 parent 671e0ce143
commit ef29fc5f15
85 files changed
+2976 -768

No files matched your search

-1
View File
@@ -336,7 +336,6 @@ class _AuthBrand extends StatelessWidget {
key: const Key('auth-brand-mark'),
size: compact ? _compactBrandMarkSize : _brandMarkSize,
radius: compact ? _compactBrandMarkRadius : _brandMarkRadius,
iconSize: compact ? _compactBrandIconSize : _brandIconSize,
);
Widget _brandCopy(BuildContext context, {required bool centered}) => Column(
+75
View File
@@ -0,0 +1,75 @@
// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../../core/legal/legal_opener.dart';
import '../../core/legal/legal_page.dart';
import '../../core/theme/app_colors.dart';
import '../../l10n/app_localizations.dart';
const double _legalFontSize = 12;
/// 登录页底部的协议入口。点用户协议或隐私政策会打开对应语言的页面。
class AuthLegalLinks extends StatefulWidget {
const AuthLegalLinks({super.key, this.open});
/// 测试时可替换真正的跳转。
final Future<void> Function(String url)? open;
@override
State<AuthLegalLinks> createState() => _AuthLegalLinksState();
}
class _AuthLegalLinksState extends State<AuthLegalLinks> {
late final TapGestureRecognizer _termsTap = TapGestureRecognizer()
..onTap = () => _open(legalTermsPath);
late final TapGestureRecognizer _privacyTap = TapGestureRecognizer()
..onTap = () => _open(legalPrivacyPath);
@override
void dispose() {
_termsTap.dispose();
_privacyTap.dispose();
super.dispose();
}
void _open(String path) {
final url = legalPageUrl(path, Localizations.localeOf(context));
final custom = widget.open;
if (custom != null) {
unawaited(custom(url));
return;
}
unawaited(openLegalUrlOrNotify(context, url));
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final muted = TextStyle(color: AppColors.muted, fontSize: _legalFontSize, height: 1.6);
final link = muted.copyWith(color: AppColors.blue);
return Text.rich(
TextSpan(
style: muted,
children: [
TextSpan(text: l10n.authLegalLead),
const TextSpan(text: ' '),
TextSpan(text: l10n.authTerms, style: link, recognizer: _termsTap),
const TextSpan(text: ' '),
TextSpan(text: l10n.authLegalJoin),
const TextSpan(text: ' '),
TextSpan(
text: l10n.authPrivacy,
style: link,
recognizer: _privacyTap,
),
],
),
key: const Key('auth-legal'),
textAlign: TextAlign.center,
);
}
}
+14 -37
View File
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/formatting/error_text.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/platform/haptics.dart';
import '../../core/platform/keyboard_inset.dart';
@@ -15,6 +16,7 @@ import '../../core/theme/app_theme.dart';
import '../../core/widgets/centered_toast.dart';
import '../../core/widgets/subpage_header.dart';
import '../../l10n/app_localizations.dart';
import 'auth_legal_links.dart';
import 'auth_logic.dart';
import 'phone_auth_sheet.dart';
@@ -26,8 +28,8 @@ const double _brandMarkSize = 72;
const double _compactBrandMarkSize = 48;
const double _brandMarkRadius = 20;
const double _compactBrandMarkRadius = 14;
const double _brandIconSize = 36;
const double _compactBrandIconSize = 26;
const double _landingLogoTopGap = 28;
const String _appLogoAsset = 'assets/icon/fruitshorts.png';
const double _contentMaxWidth = 440;
// Auth keeps its approved 20px card silhouette.
const double _cardRadius = AppDimens.authCardRadius;
@@ -58,31 +60,15 @@ Widget _brandMarkWidget({
Key? key,
required double size,
required double radius,
required double iconSize,
}) {
return Container(
return ClipRRect(
key: key,
width: size,
height: size,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.blue, AppColors.secondary],
),
boxShadow: [
BoxShadow(
color: AppColors.blue.withValues(alpha: 0.24),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Icon(
Icons.play_arrow_rounded,
color: AppColors.onPrimary,
size: iconSize,
borderRadius: BorderRadius.circular(radius),
child: Image.asset(
_appLogoAsset,
width: size,
height: size,
fit: BoxFit.cover,
),
);
}
@@ -257,7 +243,7 @@ class _AuthPageState extends State<AuthPage> {
if (!mounted) return;
Navigator.of(context).maybePop();
} on ApiException catch (error) {
failureMessage = error.message;
failureMessage = userFacingApiMessage(l10n, error.code, error.message);
} catch (_) {
failureMessage = l10n.authNetworkError;
} finally {
@@ -431,12 +417,12 @@ class _AuthPageState extends State<AuthPage> {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: _landingLogoTopGap),
Center(
child: _brandMarkWidget(
key: const Key('auth-brand-logo'),
size: _brandMarkSize,
radius: _brandMarkRadius,
iconSize: _brandIconSize,
),
),
const SizedBox(height: 16),
@@ -473,16 +459,7 @@ class _AuthPageState extends State<AuthPage> {
onTap: () => setState(() => _emailEntryOpen = true),
),
const SizedBox(height: 18),
Text(
l10n.authLegal,
key: const Key('auth-legal'),
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.muted,
fontSize: 9,
height: 1.6,
),
),
const AuthLegalLinks(),
],
);