import 'package:auto_route/auto_route.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:line_icons/line_icons.dart'; import 'package:loader_overlay/loader_overlay.dart'; import '../../../application/auth/auth_bloc.dart'; import '../../../application/auth/logout_form/logout_form_bloc.dart'; import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../../common/utils/fcm_service.dart'; import '../../../injection.dart'; import '../../components/button/button.dart'; import '../../components/spacer/spacer.dart'; import '../../components/toast/flushbar.dart'; import '../../router/app_router.gr.dart'; import 'widgets/account_info.dart'; import 'widgets/app_setting.dart'; import 'widgets/business_setting.dart'; import 'widgets/danger_zone.dart'; import 'widgets/header.dart'; import 'widgets/support.dart'; @RoutePage() class ProfilePage extends StatelessWidget implements AutoRouteWrapper { const ProfilePage({super.key}); @override Widget build(BuildContext context) { return MultiBlocListener( listeners: [ BlocListener( listener: (context, state) { state.failureOrAuthOption.fold( () => null, (either) => either.fold( (f) => AppFlushbar.showAuthFailureToast(context, f), (_) => context.router.replace(const LoginRoute()), ), ); }, ), BlocListener( listenWhen: (previous, current) => previous.isSubmitting != current.isSubmitting, listener: (context, state) { if (state.isSubmitting) { context.loaderOverlay.show(); } else { context.loaderOverlay.hide(); } }, ), ], child: BlocBuilder( builder: (context, state) { return Scaffold( backgroundColor: AppColor.background, body: CustomScrollView( slivers: [ SliverAppBar( backgroundColor: AppColor.primary, elevation: 0, pinned: true, expandedHeight: 270.0, flexibleSpace: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { // Calculate the collapse ratio final double top = constraints.biggest.height; final double collapsedHeight = MediaQuery.of(context).padding.top + kToolbarHeight; final double expandedHeight = 270.0; final double shrinkRatio = ((expandedHeight - top) / (expandedHeight - collapsedHeight)) .clamp(0.0, 1.0); return FlexibleSpaceBar( background: ProfileHeader(user: state.user), titlePadding: const EdgeInsets.only( left: 20, right: 12, bottom: 16, ), title: Opacity( opacity: shrinkRatio, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( context.lang.profile, style: AppStyle.xl.copyWith( fontWeight: FontWeight.w700, fontSize: 18, letterSpacing: -0.5, color: AppColor.white, ), ), ActionIconButton( onTap: () => context.router.push( ProfileEditRoute(user: state.user), ), icon: LineIcons.userEdit, ), ], ), ), ); }, ), ), SliverToBoxAdapter( child: Column( children: [ const SpaceHeight(20), ProfileAccountInfo(user: state.user), const SpaceHeight(12), ProfileBusinessSetting(), const SpaceHeight(12), ProfileAppSetting(), const SpaceHeight(12), ProfileSupport(), const SpaceHeight(12), ProfileDangerZone(), const SpaceHeight(30), // Debug only: FCM Token if (kDebugMode) const _FcmTokenDebugWidget(), const SpaceHeight(30), ], ), ), ], ), ); }, ), ); } @override Widget wrappedRoute(BuildContext context) => BlocProvider(create: (_) => getIt(), child: this); } class _FcmTokenDebugWidget extends StatefulWidget { const _FcmTokenDebugWidget(); @override State<_FcmTokenDebugWidget> createState() => _FcmTokenDebugWidgetState(); } class _FcmTokenDebugWidgetState extends State<_FcmTokenDebugWidget> { String? _token; @override void initState() { super.initState(); _loadToken(); } Future _loadToken() async { final token = await getIt().getToken(); if (mounted) setState(() => _token = token); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColor.black.withOpacity(0.05), borderRadius: BorderRadius.circular(8), border: Border.all(color: AppColor.black.withOpacity(0.1)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.bug_report, size: 14), const SizedBox(width: 4), Text( 'FCM Token (debug only)', style: AppStyle.sm.copyWith(fontWeight: FontWeight.w700), ), const Spacer(), GestureDetector( onTap: () async { if (_token == null) return; await Clipboard.setData(ClipboardData(text: _token!)); debugPrint('[FCM] Token copied: $_token'); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('FCM token copied!'), duration: Duration(seconds: 2), ), ); } }, child: Text( 'Copy', style: AppStyle.sm.copyWith( color: AppColor.primary, fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 6), Text( _token ?? 'Loading...', style: AppStyle.xs.copyWith(color: AppColor.black.withOpacity(0.6)), ), ], ), ), ); } }