ponshu-room-lite/lib/screens/soul_screen.dart

304 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
import '../providers/theme_provider.dart';
import '../widgets/settings/app_settings_section.dart';
import '../widgets/settings/other_settings_section.dart';
import '../widgets/settings/backup_settings_section.dart';
import '../widgets/gamification/level_title_card.dart';
import '../widgets/gamification/activity_stats.dart';
import '../widgets/gamification/badge_case.dart';
class SoulScreen extends ConsumerStatefulWidget {
const SoulScreen({super.key});
@override
ConsumerState<SoulScreen> createState() => _SoulScreenState();
}
class _SoulScreenState extends ConsumerState<SoulScreen> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final userProfile = ref.watch(userProfileProvider);
return Scaffold(
appBar: AppBar(
title: const Text('マイページ'),
centerTitle: true,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Gamification Section (v1.3)
const LevelTitleCard(),
const SizedBox(height: 16),
const ActivityStats(),
const SizedBox(height: 16),
const BadgeCase(),
const SizedBox(height: 32),
// Identity Section
_buildSectionHeader('プロフィール (ID)', LucideIcons.fingerprint),
Card(
child: Column(
children: [
ListTile(
leading: Icon(LucideIcons.user, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[400] : null),
title: const Text('ニックネーム'),
subtitle: Text(userProfile.nickname ?? '未設定'),
trailing: Icon(LucideIcons.chevronRight, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[600] : null),
onTap: () => _showNicknameDialog(context, userProfile.nickname),
),
const Divider(height: 1),
ListTile(
leading: Icon(LucideIcons.personStanding, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[400] : null),
title: const Text('性別'),
subtitle: Text(_getGenderLabel(userProfile.gender)),
trailing: Icon(LucideIcons.chevronRight, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[600] : null),
onTap: () => _showGenderDialog(context, userProfile.gender),
),
const Divider(height: 1),
ListTile(
leading: Icon(LucideIcons.calendar, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[400] : null),
title: const Text('生年月日'),
subtitle: Text(userProfile.birthdate != null
? DateFormat('yyyy/MM/dd').format(userProfile.birthdate!)
: '未設定 (四柱推命用)'),
trailing: Icon(LucideIcons.chevronRight, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[600] : null),
onTap: () => _pickBirthDate(context, userProfile.birthdate),
),
const Divider(height: 1),
ListTile(
leading: Icon(LucideIcons.brainCircuit, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[400] : null),
title: const Text('MBTI診断'),
subtitle: Text(userProfile.mbti ?? '未設定'),
trailing: Icon(LucideIcons.chevronRight, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[600] : null),
onTap: () => _showMbtiDialog(context, userProfile.mbti),
),
],
),
),
const SizedBox(height: 24),
// App Settings
const AppearanceSettingsSection(),
const SizedBox(height: 24),
// other Settings
const OtherSettingsSection(
title: 'データ・その他',
),
const SizedBox(height: 24),
BackupSettingsSection(),
// Future Update (Couple Sharing) - Hidden for now
// const SizedBox(height: 40),
],
),
);
}
Widget _buildSectionHeader(String title, IconData icon) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
child: Row(
children: [
Icon(icon, size: 20, color: isDark ? Colors.orange[300] : Theme.of(context).primaryColor),
const SizedBox(width: 8),
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: isDark ? Colors.grey[300] : Theme.of(context).primaryColor,
),
),
],
),
);
}
void _showMbtiDialog(BuildContext context, String? current) {
const typesWithLabels = {
'INTJ': '建築家',
'INTP': '論理学者',
'ENTJ': '指揮官',
'ENTP': '討論者',
'INFJ': '提唱者',
'INFP': '仲介者',
'ENFJ': '主人公',
'ENFP': '広報運動家',
'ISTJ': '管理者',
'ISFJ': '擁護者',
'ESTJ': '幹部',
'ESFJ': '領事官',
'ISTP': '巨匠',
'ISFP': '冒険家',
'ESTP': '起業家',
'ESFP': 'エンターテイナー',
};
showDialog(
context: context,
builder: (context) => SimpleDialog(
title: const Text('MBTI タイプ選択'),
children: [
SizedBox(
width: double.maxFinite,
height: 400,
child: ListView(
children: typesWithLabels.entries.map((entry) => SimpleDialogOption(
onPressed: () {
ref.read(userProfileProvider.notifier).setIdentity(mbti: entry.key);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Icon(
entry.key == current ? Icons.check : Icons.circle_outlined,
size: 20,
color: entry.key == current ? Theme.of(context).primaryColor : Colors.grey[400],
),
const SizedBox(width: 16),
Expanded(
child: RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style.copyWith(fontSize: 16),
children: [
TextSpan(
text: entry.key,
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: ' (${entry.value})',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).brightness == Brightness.dark
? Colors.grey[500]
: Colors.grey[600],
),
),
],
),
),
),
],
),
),
)).toList(),
),
),
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'※AIによる独自の相性診断です。遊び心としてお楽しみください',
style: TextStyle(fontSize: 10, color: Colors.grey),
textAlign: TextAlign.center,
),
),
],
),
);
}
Future<void> _pickBirthDate(BuildContext context, DateTime? current) async {
final picked = await showDatePicker(
context: context,
initialDate: current ?? DateTime(2000),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
locale: const Locale('ja'), // Ensure Japanese locale if initialized
);
if (picked != null) {
ref.read(userProfileProvider.notifier).setIdentity(birthdate: picked);
}
}
void _showNicknameDialog(BuildContext context, String? current) {
final controller = TextEditingController(text: current);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('ニックネーム変更'),
content: TextField(
controller: controller,
decoration: const InputDecoration(hintText: '呼び名を入力'),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('キャンセル'),
),
TextButton(
onPressed: () {
ref.read(userProfileProvider.notifier).setIdentity(nickname: controller.text);
Navigator.pop(context);
},
child: const Text('保存'),
),
],
),
);
}
void _showGenderDialog(BuildContext context, String? current) {
showDialog(
context: context,
builder: (context) => SimpleDialog(
title: const Text('性別を選択'),
children: [
_buildGenderOption(context, 'male', '男性', current),
_buildGenderOption(context, 'female', '女性', current),
_buildGenderOption(context, 'other', 'その他', current),
_buildGenderOption(context, '', '回答しない', current),
],
),
);
}
Widget _buildGenderOption(BuildContext context, String? value, String label, String? current) {
return SimpleDialogOption(
onPressed: () {
ref.read(userProfileProvider.notifier).setIdentity(gender: value);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Icon(
value == current ? Icons.check_circle : Icons.circle_outlined,
color: value == current ? Theme.of(context).primaryColor : Colors.grey,
),
const SizedBox(width: 16),
Text(label),
],
),
),
);
}
String _getGenderLabel(String? gender) {
switch (gender) {
case 'male': return '男性';
case 'female': return '女性';
case 'other': return 'その他';
default: return '未設定';
}
}
}