304 lines
11 KiB
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 '未設定';
|
|
}
|
|
}
|
|
}
|