import 'package:client_proxy_framework/client_proxy_framework.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../../core/app_env.dart'; import '../../core/open_purchase_store.dart'; import '../../core/ext_config_document_urls.dart'; import '../../core/user/user_state.dart'; import '../../design/pencil_theme.dart'; import '../../widgets/pencil_chrome.dart'; import '../purchase/purchase_screen.dart'; import '../web/app_web_view_screen.dart'; import 'delete_account_flow.dart'; /// `5J8Po` 个人中心。 class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key, this.isRootTab = false}); /// When true (e.g. bottom tab), hide close; when pushed, show close. final bool isRootTab; @override State createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { String _version = '…'; @override void initState() { super.initState(); PackageInfo.fromPlatform().then((p) { if (mounted) setState(() => _version = p.version); }); } @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( gradient: PencilTheme.yellowWhitePageGradient, ), child: Scaffold( backgroundColor: Colors.transparent, body: SafeArea( bottom: false, child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(2, 0, 14, 10), child: SizedBox( height: 56, child: widget.isRootTab ? Center( child: Text( 'Profile', style: GoogleFonts.inter( fontSize: 18, fontWeight: FontWeight.w600, color: PencilTheme.ink, ), ), ) : Align( alignment: Alignment.centerRight, child: PencilRoundCloseButton( onPressed: () => Navigator.of(context).pop(), ), ), ), ), Expanded( child: ListView( padding: EdgeInsets.only( bottom: 28 + (widget.isRootTab ? PencilTheme.mainTabBottomChromeReserve(context) : 0), ), children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), child: Column( children: [ Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white, border: Border.all( color: PencilTheme.profileAvatarRing, width: 2, ), ), child: ValueListenableBuilder( valueListenable: UserState.avatar, builder: (context, url, _) { if (url != null && url.isNotEmpty) { return ClipOval( child: Image.network( url, fit: BoxFit.cover, width: 100, height: 100, errorBuilder: (_, _, _) => _avatarFallback(), ), ); } return _avatarFallback(); }, ), ), const SizedBox(height: 12), ValueListenableBuilder( valueListenable: UserState.userId, builder: (context, id, _) { return Text( 'ID:${id ?? '—'}', style: GoogleFonts.inter( fontSize: 17, fontWeight: FontWeight.w700, color: PencilTheme.stone900, ), ); }, ), const SizedBox(height: 4), ValueListenableBuilder( valueListenable: UserState.credits, builder: (context, c, _) { return InkWell( onTap: () => openPurchaseStore(context), child: Text( 'Credits · ${_formatCredits(c)}', style: GoogleFonts.inter( fontSize: 15, fontWeight: FontWeight.w600, color: PencilTheme.profileCredits, decoration: TextDecoration.underline, decorationColor: PencilTheme.profileCredits .withValues(alpha: 0.45), ), ), ); }, ), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(20, 20, 20, 12), child: _menuCard(context), ), const SizedBox(height: 24), ], ), ), ], ), ), ), ); } Widget _avatarFallback() { return Icon( Icons.person_rounded, size: 44, color: PencilTheme.profileAvatarIcon, ); } String _formatCredits(int c) { final s = c.toString(); if (s.length <= 3) return s; return '${s.substring(0, s.length - 3)} ${s.substring(s.length - 3)}'; } void _openAppWebView( BuildContext context, { required String title, required String? url, }) { if (url == null || url.trim().isEmpty) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Link not configured'))); return; } Navigator.of(context).push( MaterialPageRoute( builder: (_) => AppWebViewScreen(title: title, initialUrl: url.trim()), ), ); } Widget _menuCard(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: PencilTheme.genHintBorder), boxShadow: [ BoxShadow( color: const Color(0x30CA8A04), blurRadius: 20, offset: const Offset(0, 6), ), ], ), child: Column( children: [ _row( 'Terms of Service', trailing: Icons.chevron_right_rounded, onTap: () => _openAppWebView( context, title: 'Terms of Service', url: ExtConfigDocumentUrls.agreementUrl, ), ), _divider(), _row( 'Privacy Policy', trailing: Icons.chevron_right_rounded, onTap: () => _openAppWebView( context, title: 'Privacy Policy', url: ExtConfigDocumentUrls.privacyUrl, ), ), _divider(), _row('Version', value: 'v$_version'), _divider(), _row( 'Delete account', danger: true, trailing: Icons.chevron_right_rounded, onTap: () => _delete(context), ), ], ), ); } Widget _row( String title, { IconData? trailing, String? value, bool danger = false, VoidCallback? onTap, }) { return ListTile( onTap: onTap, title: Text( title, style: GoogleFonts.inter( fontSize: 15, fontWeight: FontWeight.w600, color: danger ? const Color(0xFFDC2626) : PencilTheme.stone700, ), ), trailing: value != null ? Text( value, style: GoogleFonts.inter( fontSize: 14, color: const Color(0xFF78716C), ), ) : Icon( trailing, color: danger ? const Color(0xFFFCA292) : const Color(0xFFA8A29E), ), ); } Widget _divider() => Container(height: 1, color: const Color(0xFFF5F5F4)); Future _delete(BuildContext context) async { final ok = await showDeleteAccountConfirmationFlow(context); if (ok != true || !context.mounted) return; final res = await UserApi.deleteAccount( app: currentBackendAppType(), userId: UserState.userId.value, ); if (!context.mounted) return; if (res.isSuccess) { ApiClient.instance.setUserToken(null); UserState.clear(); Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Account deleted. Please restart the app and sign in again.', ), ), ); } else { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(res.msg))); } } }