feat(app): 完善播放协议搜索与空状态
This commit is contained in:
1 parent
671e0ce143
commit
ef29fc5f15
85 files changed
+2976
-768
No files matched your search
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user