191 lines
6.5 KiB
Dart
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;
|
|
}
|
|
}
|