import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/msn_api.dart'; import '../../core/session_controller.dart'; import '../../models/profile_model.dart'; import '../../theme/toss_theme.dart'; /// Kakao-style profile sheet: large avatar, name, status, optional 1:1 chat. Future showUserProfileSheet( BuildContext parentContext, WidgetRef ref, { required String contextId, required String userId, }) async { await showModalBottomSheet( context: parentContext, isScrollControlled: true, showDragHandle: true, backgroundColor: TossColors.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (sheetContext) => _UserProfileSheetBody( parentContext: parentContext, contextId: contextId, userId: userId, ), ); } class _UserProfileSheetBody extends ConsumerStatefulWidget { const _UserProfileSheetBody({ required this.parentContext, required this.contextId, required this.userId, }); final BuildContext parentContext; final String contextId; final String userId; @override ConsumerState<_UserProfileSheetBody> createState() => _UserProfileSheetBodyState(); } class _UserProfileSheetBodyState extends ConsumerState<_UserProfileSheetBody> { late Future _future; @override void initState() { super.initState(); final myId = ref.read(sessionProvider).value?.userId; final isSelf = myId != null && widget.userId == myId; _future = isSelf ? ref.read(msnApiProvider).getMyProfile(widget.contextId) : ref.read(msnApiProvider).getUserProfile(widget.contextId, widget.userId); } Future _openDirect() async { final roomId = await ref.read(msnApiProvider).openDirectRoom(widget.contextId, widget.userId); if (!mounted) return; Navigator.of(context).pop(); if (widget.parentContext.mounted) { await widget.parentContext.push('/chat?roomId=$roomId&contextId=${widget.contextId}'); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final myId = ref.watch(sessionProvider).value?.userId; final isSelf = myId != null && widget.userId == myId; return SafeArea( child: Padding( padding: EdgeInsets.only( left: 24, right: 24, top: 8, bottom: 24 + MediaQuery.paddingOf(context).bottom, ), child: FutureBuilder( future: _future, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const SizedBox( height: 220, child: Center(child: CircularProgressIndicator(color: TossColors.blue)), ); } if (snapshot.hasError) { return SizedBox( height: 120, child: Center(child: Text('${snapshot.error}')), ); } final p = snapshot.data!; return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: _LargeAvatar( displayName: p.displayName, avatarUrl: p.avatarUrl, ), ), const SizedBox(height: 20), Text( p.displayName, style: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w700), textAlign: TextAlign.center, ), if (p.statusMessage != null && p.statusMessage!.isNotEmpty) ...[ const SizedBox(height: 12), Text( p.statusMessage!, style: theme.textTheme.bodyLarge, textAlign: TextAlign.center, ), ], const SizedBox(height: 16), Text( '이 맥락에서만 보이는 프로필이에요.', style: theme.textTheme.bodySmall, textAlign: TextAlign.center, ), if (!isSelf) ...[ const SizedBox(height: 24), FilledButton.icon( onPressed: _openDirect, icon: const Icon(Icons.chat_bubble_outline), label: const Text('1:1 채팅'), ), ], ], ), ); }, ), ), ); } } class _LargeAvatar extends StatelessWidget { const _LargeAvatar({ required this.displayName, this.avatarUrl, }); final String displayName; final String? avatarUrl; @override Widget build(BuildContext context) { final hasUrl = avatarUrl != null && avatarUrl!.isNotEmpty; return CircleAvatar( radius: 56, backgroundColor: TossColors.blue.withValues(alpha: 0.12), backgroundImage: hasUrl ? NetworkImage(avatarUrl!) : null, child: hasUrl ? null : Text( displayName.isNotEmpty ? displayName[0].toUpperCase() : '?', style: const TextStyle( fontSize: 40, fontWeight: FontWeight.w600, color: TossColors.blue, ), ), ); } }