179 lines
5.6 KiB
Dart
179 lines
5.6 KiB
Dart
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<void> showUserProfileSheet(
|
|
BuildContext parentContext,
|
|
WidgetRef ref, {
|
|
required String contextId,
|
|
required String userId,
|
|
}) async {
|
|
await showModalBottomSheet<void>(
|
|
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<ProfileModel> _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<void> _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<ProfileModel>(
|
|
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,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|