296 lines
11 KiB
Dart
296 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:google_fonts/google_fonts.dart';
|
||
|
|
import '../../providers/theme_provider.dart';
|
||
|
|
import '../../providers/ui_experiment_provider.dart';
|
||
|
|
import '../../theme/app_colors.dart';
|
||
|
|
|
||
|
|
class DisplaySettingsSection extends ConsumerWidget {
|
||
|
|
const DisplaySettingsSection({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
||
|
|
final userProfile = ref.watch(userProfileProvider);
|
||
|
|
final experiment = ref.watch(uiExperimentProvider);
|
||
|
|
final colorVariant = userProfile.colorVariant;
|
||
|
|
final themeMode = userProfile.themeMode;
|
||
|
|
final fontPref = userProfile.fontPreference;
|
||
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
|
|
||
|
|
return Column(
|
||
|
|
children: [
|
||
|
|
_buildSectionHeader(context, '表示設定', LucideIcons.monitor),
|
||
|
|
Card(
|
||
|
|
color: appColors.surfaceSubtle,
|
||
|
|
child: Column(
|
||
|
|
children: [
|
||
|
|
// 1. カラーテーマ
|
||
|
|
ListTile(
|
||
|
|
leading: Icon(LucideIcons.palette, color: appColors.iconDefault),
|
||
|
|
title: Text('カラーテーマ', style: TextStyle(color: appColors.textPrimary)),
|
||
|
|
subtitle: Text(
|
||
|
|
colorVariant == 'washi_sumi_kohaku' ? '和紙×墨×琥珀' : 'Posimai Blue',
|
||
|
|
style: TextStyle(color: appColors.textSecondary)
|
||
|
|
),
|
||
|
|
trailing: Icon(LucideIcons.chevronRight, color: appColors.iconSubtle),
|
||
|
|
onTap: () => _showColorThemeDialog(context, ref, colorVariant),
|
||
|
|
),
|
||
|
|
Divider(height: 1, color: appColors.divider),
|
||
|
|
|
||
|
|
// 2. グリッド列数
|
||
|
|
ListTile(
|
||
|
|
leading: Icon(LucideIcons.grid, color: appColors.iconDefault),
|
||
|
|
title: Text('グリッド表示', style: TextStyle(color: appColors.textPrimary)),
|
||
|
|
subtitle: Text('${experiment.gridColumns}列表示', style: TextStyle(color: appColors.textSecondary)),
|
||
|
|
trailing: Icon(LucideIcons.chevronRight, color: appColors.iconSubtle),
|
||
|
|
onTap: () => _showGridColumnsDialog(context, ref, experiment.gridColumns),
|
||
|
|
),
|
||
|
|
Divider(height: 1, color: appColors.divider),
|
||
|
|
|
||
|
|
// 3. フォント
|
||
|
|
ListTile(
|
||
|
|
leading: Icon(LucideIcons.type, color: appColors.iconDefault),
|
||
|
|
title: Text('フォント', style: TextStyle(color: appColors.textPrimary)),
|
||
|
|
subtitle: Text(_getFontName(fontPref), style: TextStyle(color: appColors.textSecondary)),
|
||
|
|
trailing: Icon(LucideIcons.chevronRight, color: appColors.iconSubtle),
|
||
|
|
onTap: () => _showFontSelectionDialog(context, ref, fontPref),
|
||
|
|
),
|
||
|
|
Divider(height: 1, color: appColors.divider),
|
||
|
|
|
||
|
|
// 4. テーマモード
|
||
|
|
ListTile(
|
||
|
|
leading: Icon(LucideIcons.sunMoon, color: appColors.iconDefault),
|
||
|
|
title: Text('明るさ', style: TextStyle(color: appColors.textPrimary)),
|
||
|
|
subtitle: Text(_getThemeModeName(themeMode, context), style: TextStyle(color: appColors.textSecondary)),
|
||
|
|
trailing: Icon(LucideIcons.chevronRight, color: appColors.iconSubtle),
|
||
|
|
onTap: () => _showThemeDialog(context, ref, themeMode),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildSectionHeader(BuildContext context, String title, IconData icon) {
|
||
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
|
return Padding(
|
||
|
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Icon(icon, size: 20, color: appColors.iconDefault),
|
||
|
|
const SizedBox(width: 8),
|
||
|
|
Text(
|
||
|
|
title,
|
||
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||
|
|
fontWeight: FontWeight.bold,
|
||
|
|
color: appColors.textPrimary,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// カラーテーマダイアログ
|
||
|
|
void _showColorThemeDialog(BuildContext context, WidgetRef ref, String current) {
|
||
|
|
showDialog(
|
||
|
|
context: context,
|
||
|
|
builder: (context) => SimpleDialog(
|
||
|
|
title: const Text('カラーテーマ'),
|
||
|
|
children: [
|
||
|
|
_buildColorThemeOption(context, ref, 'washi_sumi_kohaku', '和紙×墨×琥珀', '日本酒の世界観', current),
|
||
|
|
_buildColorThemeOption(context, ref, 'current', 'Posimai Blue', '既存のテーマ', current),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildColorThemeOption(BuildContext context, WidgetRef ref, String value, String title, String subtitle, String current) {
|
||
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
|
return SimpleDialogOption(
|
||
|
|
onPressed: () {
|
||
|
|
ref.read(userProfileProvider.notifier).setColorVariant(value);
|
||
|
|
Navigator.pop(context);
|
||
|
|
},
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Icon(
|
||
|
|
value == current ? Icons.check_circle : Icons.circle_outlined,
|
||
|
|
size: 20,
|
||
|
|
color: value == current ? appColors.brandPrimary : appColors.iconSubtle,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 16),
|
||
|
|
Expanded(
|
||
|
|
child: Column(
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
Text(title, style: TextStyle(fontWeight: FontWeight.bold, color: appColors.textPrimary)),
|
||
|
|
Text(subtitle, style: TextStyle(fontSize: 12, color: appColors.textSecondary)),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// グリッド列数ダイアログ
|
||
|
|
void _showGridColumnsDialog(BuildContext context, WidgetRef ref, int current) {
|
||
|
|
showDialog(
|
||
|
|
context: context,
|
||
|
|
builder: (context) => SimpleDialog(
|
||
|
|
title: const Text('グリッド表示'),
|
||
|
|
children: [
|
||
|
|
_buildGridOption(context, ref, 2, '2列表示', '標準サイズ(推奨)', current),
|
||
|
|
_buildGridOption(context, ref, 3, '3列表示', 'コンパクト表示', current),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildGridOption(BuildContext context, WidgetRef ref, int value, String title, String subtitle, int current) {
|
||
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
|
return SimpleDialogOption(
|
||
|
|
onPressed: () {
|
||
|
|
ref.read(uiExperimentProvider.notifier).setGridColumns(value);
|
||
|
|
Navigator.pop(context);
|
||
|
|
},
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Icon(
|
||
|
|
value == current ? Icons.check_circle : Icons.circle_outlined,
|
||
|
|
size: 20,
|
||
|
|
color: value == current ? appColors.brandPrimary : appColors.iconSubtle,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 16),
|
||
|
|
Expanded(
|
||
|
|
child: Column(
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
Text(title, style: TextStyle(fontWeight: FontWeight.bold, color: appColors.textPrimary)),
|
||
|
|
Text(subtitle, style: TextStyle(fontSize: 12, color: appColors.textSecondary)),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// フォントダイアログ
|
||
|
|
void _showFontSelectionDialog(BuildContext context, WidgetRef ref, String current) {
|
||
|
|
showDialog(
|
||
|
|
context: context,
|
||
|
|
builder: (context) => SimpleDialog(
|
||
|
|
title: Text('フォント設定', style: _getFontStyleForPreview(current, context)),
|
||
|
|
children: [
|
||
|
|
_buildFontOption(context, ref, 'sans', 'ゴシック (標準)', current),
|
||
|
|
_buildFontOption(context, ref, 'pottaOne', '髭文字 (和風)', current),
|
||
|
|
_buildFontOption(context, ref, 'serif', '明朝 (上品)', current),
|
||
|
|
_buildFontOption(context, ref, 'digital', 'ドット (レトロ)', current),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildFontOption(BuildContext context, WidgetRef ref, String value, String label, String current) {
|
||
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
|
return SimpleDialogOption(
|
||
|
|
onPressed: () {
|
||
|
|
ref.read(userProfileProvider.notifier).setFontPreference(value);
|
||
|
|
Navigator.pop(context);
|
||
|
|
},
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Icon(
|
||
|
|
value == current ? Icons.check_circle : Icons.circle_outlined,
|
||
|
|
size: 20,
|
||
|
|
color: value == current ? appColors.brandPrimary : appColors.iconSubtle,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 16),
|
||
|
|
Text(
|
||
|
|
label,
|
||
|
|
style: _getFontStyleForPreview(value, context),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
TextStyle _getFontStyleForPreview(String fontValue, BuildContext context) {
|
||
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
|
final baseStyle = TextStyle(color: appColors.textPrimary, fontSize: 16);
|
||
|
|
|
||
|
|
switch (fontValue) {
|
||
|
|
case 'pottaOne':
|
||
|
|
return GoogleFonts.pottaOne(textStyle: baseStyle);
|
||
|
|
case 'serif':
|
||
|
|
return GoogleFonts.notoSerifJp(textStyle: baseStyle);
|
||
|
|
case 'digital':
|
||
|
|
return GoogleFonts.dotGothic16(textStyle: baseStyle);
|
||
|
|
case 'sans':
|
||
|
|
default:
|
||
|
|
return GoogleFonts.notoSansJp(textStyle: baseStyle);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
String _getFontName(String pref) {
|
||
|
|
switch (pref) {
|
||
|
|
case 'pottaOne': return '髭文字 (和風)';
|
||
|
|
case 'serif': return '明朝 (上品)';
|
||
|
|
case 'digital': return 'ドット (レトロ)';
|
||
|
|
default: return 'ゴシック (標準)';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// テーマモードダイアログ
|
||
|
|
void _showThemeDialog(BuildContext context, WidgetRef ref, String current) {
|
||
|
|
showDialog(
|
||
|
|
context: context,
|
||
|
|
builder: (context) => SimpleDialog(
|
||
|
|
title: const Text('テーマ設定'),
|
||
|
|
children: [
|
||
|
|
_buildThemeOption(context, ref, 'system', 'システム設定', current),
|
||
|
|
_buildThemeOption(context, ref, 'light', 'ライトモード', current),
|
||
|
|
_buildThemeOption(context, ref, 'dark', 'ダークモード', current),
|
||
|
|
_buildThemeOption(context, ref, 'auto_time', '時間連動 (20:00〜06:00)', current),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildThemeOption(BuildContext context, WidgetRef ref, String value, String label, String current) {
|
||
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
|
return SimpleDialogOption(
|
||
|
|
onPressed: () {
|
||
|
|
ref.read(userProfileProvider.notifier).setThemeMode(value);
|
||
|
|
Navigator.pop(context);
|
||
|
|
},
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Icon(
|
||
|
|
value == current ? Icons.check_circle : Icons.circle_outlined,
|
||
|
|
size: 20,
|
||
|
|
color: value == current ? appColors.brandPrimary : appColors.iconSubtle,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 16),
|
||
|
|
Text(label, style: TextStyle(color: appColors.textPrimary)),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
String _getThemeModeName(String mode, BuildContext context) {
|
||
|
|
switch (mode) {
|
||
|
|
case 'light': return 'ライト';
|
||
|
|
case 'dark': return 'ダーク';
|
||
|
|
case 'auto_time':
|
||
|
|
final isCurrentlyDark = Theme.of(context).brightness == Brightness.dark;
|
||
|
|
return '時間連動 (${isCurrentlyDark ? '現在: ダーク' : '現在: ライト'})';
|
||
|
|
default: return 'システム設定';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|