feat(mine): 重排我的页并新增迷你播放器

This commit is contained in:
Zhou Weidong committed 2026-10-22 17:44:48 +08:00
1 parent 5aa8170951
commit cbc6d80cd4
21 files changed
+860 -531

No files matched your search

+120 -60
View File
@@ -1,19 +1,28 @@
// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../../../core/data/services/auth_controller.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/centered_toast.dart';
import '../../../core/widgets/remote_image.dart';
import '../../../l10n/app_localizations.dart';
/// 「我的」页顶部身份卡。
class MineHeader extends StatelessWidget {
const MineHeader({super.key, required this.onTap});
const double _buttonHeight = 34;
const double _buttonRadius = 20;
const double _copyIconSize = 15;
final VoidCallback onTap;
/// 「我的」页用户区。
class MineHeader extends StatelessWidget {
const MineHeader({super.key, required this.onLogin, required this.onEdit});
final VoidCallback onLogin;
final VoidCallback onEdit;
@override
Widget build(BuildContext context) {
@@ -21,41 +30,80 @@ class MineHeader extends StatelessWidget {
final auth = context.watch<AuthController>();
final user = auth.user;
final signedIn = auth.isSignedIn;
final name = signedIn && (user?.nickname.isNotEmpty ?? false)
? user!.nickname
: l10n.localViewer;
final subtitle = signedIn && (user?.uid.isNotEmpty ?? false)
? 'ID: ${user!.uid}'
: l10n.logInHint;
return Container(
final nickname = signedIn && (user?.nickname.trim().isNotEmpty ?? false)
? user!.nickname.trim()
: l10n.visitor;
final uid = signedIn ? user?.uid.trim() ?? '' : '';
return Padding(
key: const Key('mine-user-row'),
decoration: BoxDecoration(
color: AppColors.contentCard,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
border: Border.all(color: AppColors.contentBorder),
),
child: Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(AppDimens.cardPadding),
child: Row(
padding: const EdgeInsets.symmetric(vertical: AppDimens.spaceMd),
child: Row(
children: [
_avatar(user?.avatarUrl ?? ''),
const SizedBox(width: AppDimens.spaceMd),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_avatar(user?.avatarUrl ?? ''),
const SizedBox(width: AppDimens.spaceMd),
Expanded(child: _labels(name, subtitle)),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.textSecondary,
size: 20,
Text(
nickname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.textPrimary,
fontSize: AppDimens.mineUserName,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppDimens.spaceXs),
Row(
children: [
Flexible(
child: Text(
'${l10n.accountUid}: ${uid.isEmpty ? '--' : uid}',
key: const Key('mine-uid'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.mineUid,
),
),
),
const SizedBox(width: AppDimens.spaceXs),
IconButton(
key: const Key('mine-uid-copy'),
tooltip: l10n.copyUid,
onPressed: uid.isEmpty
? null
: () => unawaited(
_copyUid(context, uid, l10n.uidCopied),
),
visualDensity: VisualDensity.compact,
constraints: const BoxConstraints.tightFor(
width: AppDimens.tapTarget,
height: AppDimens.tapTarget,
),
padding: EdgeInsets.zero,
icon: const Icon(
Icons.copy_rounded,
color: AppColors.textSecondary,
size: _copyIconSize,
),
),
],
),
],
),
),
),
const SizedBox(width: AppDimens.spaceSm),
_accountButton(
key: signedIn ? const Key('mine-edit') : const Key('mine-login'),
label: signedIn ? l10n.edit : l10n.logIn,
onTap: signedIn ? onEdit : onLogin,
),
],
),
);
}
@@ -63,8 +111,8 @@ class MineHeader extends StatelessWidget {
Widget _avatar(String path) {
return Container(
key: const Key('mine-avatar'),
width: 64,
height: 64,
width: AppDimens.mineAvatar,
height: AppDimens.mineAvatar,
decoration: const BoxDecoration(
color: AppColors.contentSurface,
shape: BoxShape.circle,
@@ -74,38 +122,50 @@ class MineHeader extends StatelessWidget {
? const Icon(
Icons.person_rounded,
color: AppColors.textPrimary,
size: 30,
size: 28,
)
: RemoteImage(path, fit: BoxFit.cover),
);
}
Widget _labels(String name, String subtitle) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.textPrimary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
Widget _accountButton({
required Key key,
required String label,
required VoidCallback onTap,
}) {
return OutlinedButton(
key: key,
onPressed: onTap,
style: OutlinedButton.styleFrom(
minimumSize: const Size(0, _buttonHeight),
padding: const EdgeInsets.symmetric(horizontal: AppDimens.spaceMd),
foregroundColor: AppColors.textPrimary,
side: const BorderSide(color: AppColors.mineOutline),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_buttonRadius),
),
const SizedBox(height: AppDimens.spaceXs),
Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.caption,
),
textStyle: const TextStyle(
fontSize: AppDimens.body,
fontWeight: FontWeight.w500,
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(label),
const SizedBox(width: 2),
const Icon(Icons.chevron_right_rounded, size: 17),
],
),
);
}
Future<void> _copyUid(
BuildContext context,
String uid,
String message,
) async {
await Clipboard.setData(ClipboardData(text: uid));
if (context.mounted) showCenteredToast(context, message);
}
}