Files
iykyk_msn/mobile/lib/features/home/my_profile_tab.dart
2026-04-07 16:17:03 +09:00

191 lines
6.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/msn_api.dart';
import '../../core/session_controller.dart';
import '../../models/context_model.dart';
import '../../models/profile_model.dart';
import '../../theme/toss_theme.dart';
import 'home_providers.dart';
final myProfileForContextProvider =
FutureProvider.autoDispose.family<ProfileModel, String>((ref, contextId) async {
return ref.watch(msnApiProvider).getMyProfile(contextId);
});
/// Edit display name and status per messenger space, with 일상 / 직장 segment synced to top chips.
class MyProfileTab extends ConsumerStatefulWidget {
const MyProfileTab({super.key});
@override
ConsumerState<MyProfileTab> createState() => _MyProfileTabState();
}
class _MyProfileTabState extends ConsumerState<MyProfileTab> {
final _nameCtrl = TextEditingController();
final _statusCtrl = TextEditingController();
bool _dirty = false;
bool _saving = false;
@override
void dispose() {
_nameCtrl.dispose();
_statusCtrl.dispose();
super.dispose();
}
Future<void> _save(String contextId) async {
setState(() => _saving = true);
try {
await ref.read(msnApiProvider).updateMyProfile(
contextId,
displayName: _nameCtrl.text.trim(),
statusMessage: _statusCtrl.text.trim(),
);
ref.invalidate(myProfileForContextProvider(contextId));
ref.invalidate(membersForContextProvider(contextId));
if (!mounted) return;
setState(() {
_dirty = false;
_saving = false;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('저장했습니다')),
);
} catch (e) {
if (!mounted) return;
setState(() => _saving = false);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e')));
}
}
Future<void> _onSegmentChanged(String contextId) async {
await ref.read(sessionProvider.notifier).setSelectedContext(contextId);
setState(() => _dirty = false);
}
@override
Widget build(BuildContext context) {
final session = ref.watch(sessionProvider).value;
final cid = session?.effectiveContextId;
final contextsAsync = ref.watch(contextsListProvider);
return contextsAsync.when(
loading: () => const Center(child: CircularProgressIndicator(color: TossColors.blue)),
error: (e, _) => Center(child: Text('$e')),
data: (contexts) {
final personal = _firstOfKind(contexts, 'personal');
final work = _firstOfKind(contexts, 'work');
if (cid == null) {
return const Center(child: Text('맥락이 없습니다'));
}
final async = ref.watch(myProfileForContextProvider(cid));
ref.listen<AsyncValue<ProfileModel>>(myProfileForContextProvider(cid), (prev, next) {
next.whenData((p) {
if (!_dirty && mounted) {
_nameCtrl.text = p.displayName;
_statusCtrl.text = p.statusMessage ?? '';
}
});
});
return async.when(
loading: () => const Center(child: CircularProgressIndicator(color: TossColors.blue)),
error: (e, _) => Center(child: Text('$e')),
data: (_) => RefreshIndicator(
color: TossColors.blue,
onRefresh: () async {
ref.invalidate(myProfileForContextProvider(cid));
await ref.read(myProfileForContextProvider(cid).future);
},
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 100),
children: [
if (personal != null && work != null) ...[
SegmentedButton<String>(
segments: [
ButtonSegment<String>(
value: personal.id,
label: Text(personal.name),
icon: const Icon(Icons.home_outlined, size: 18),
),
ButtonSegment<String>(
value: work.id,
label: Text(work.name),
icon: const Icon(Icons.business_outlined, size: 18),
),
],
selected: {cid},
onSelectionChanged: (s) {
if (s.isEmpty) return;
_onSegmentChanged(s.first);
},
),
const SizedBox(height: 12),
],
Text(
'지금 편집: ${_contextName(contexts, cid)}',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
Text(
'이 맥락에서만 보이는 이름과 상태입니다.',
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 20),
TextField(
controller: _nameCtrl,
decoration: const InputDecoration(
labelText: '표시 이름',
border: OutlineInputBorder(),
),
onChanged: (_) => setState(() => _dirty = true),
),
const SizedBox(height: 16),
TextField(
controller: _statusCtrl,
decoration: const InputDecoration(
labelText: '상태 메시지',
border: OutlineInputBorder(),
),
maxLines: 3,
onChanged: (_) => setState(() => _dirty = true),
),
const SizedBox(height: 24),
FilledButton(
onPressed: _saving ? null : () => _save(cid),
child: _saving
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
)
: const Text('저장'),
),
],
),
),
);
},
);
}
ContextModel? _firstOfKind(List<ContextModel> list, String kind) {
for (final c in list) {
if (c.kind == kind) return c;
}
return null;
}
String _contextName(List<ContextModel> list, String id) {
for (final c in list) {
if (c.id == id) return c.name;
}
return id;
}
}