diff --git a/lib/providers/ui_experiment_provider.dart b/lib/providers/ui_experiment_provider.dart new file mode 100644 index 0000000..dcdd849 --- /dev/null +++ b/lib/providers/ui_experiment_provider.dart @@ -0,0 +1,39 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +// UI実験設定クラス +class UiExperimentSettings { + final int gridColumns; // 2 or 3 + final String fabAnimation; // 'rotate' or 'bounce' + + const UiExperimentSettings({ + this.gridColumns = 2, + this.fabAnimation = 'rotate', + }); + + UiExperimentSettings copyWith({ + int? gridColumns, + String? fabAnimation, + }) { + return UiExperimentSettings( + gridColumns: gridColumns ?? this.gridColumns, + fabAnimation: fabAnimation ?? this.fabAnimation, + ); + } +} + +// Provider +final uiExperimentProvider = StateNotifierProvider( + (ref) => UiExperimentNotifier(), +); + +class UiExperimentNotifier extends StateNotifier { + UiExperimentNotifier() : super(const UiExperimentSettings()); + + void setGridColumns(int columns) { + state = state.copyWith(gridColumns: columns); + } + + void setFabAnimation(String animation) { + state = state.copyWith(fabAnimation: animation); + } +} diff --git a/lib/screens/dev_menu_screen.dart b/lib/screens/dev_menu_screen.dart new file mode 100644 index 0000000..e6b809f --- /dev/null +++ b/lib/screens/dev_menu_screen.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import '../providers/ui_experiment_provider.dart'; + +class DevMenuScreen extends ConsumerWidget { + const DevMenuScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final experiment = ref.watch(uiExperimentProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('🔬 開発者メニュー'), + ), + body: ListView( + children: [ + const ListTile( + leading: Icon(LucideIcons.flaskConical), + title: Text('UI実験'), + subtitle: Text('新しいデザインのテスト'), + ), + Card( + margin: const EdgeInsets.all(16), + child: Column( + children: [ + SwitchListTile( + secondary: const Text('📱', style: TextStyle(fontSize: 24)), + title: const Text('グリッド3列表示'), + subtitle: Text('瓶型の縦長カード (現在: ${experiment.gridColumns}列)'), + value: experiment.gridColumns == 3, + onChanged: (val) => ref.read(uiExperimentProvider.notifier) + .setGridColumns(val ? 3 : 2), + ), + const Divider(), + SwitchListTile( + secondary: const Text('✨', style: TextStyle(fontSize: 24)), + title: const Text('FABバウンス'), + subtitle: Text('ぷるんとした動き (現在: ${experiment.fabAnimation == 'bounce' ? 'ON' : 'OFF'})'), + value: experiment.fabAnimation == 'bounce', + onChanged: (val) => ref.read(uiExperimentProvider.notifier) + .setFabAnimation(val ? 'bounce' : 'rotate'), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 98d54a7..d639abc 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -24,9 +24,10 @@ import '../widgets/onboarding_dialog.dart'; import '../widgets/home/sake_filter_chips.dart'; import '../widgets/home/home_empty_state.dart'; import '../widgets/home/sake_no_match_state.dart'; -import '../widgets/home/sake_list_view.dart'; -import '../widgets/home/sake_grid_view.dart'; -import '../widgets/add_set_item_dialog.dart'; +import '../models/user_profile.dart'; // UserProfile +import '../widgets/analyzing_dialog.dart'; +import '../widgets/empty_state.dart'; // Generic empty state +import '../providers/ui_experiment_provider.dart'; // A/B Test import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../widgets/prefecture_filter_sheet.dart'; @@ -210,6 +211,15 @@ class HomeScreen extends ConsumerWidget { activeBackgroundColor: Colors.grey[800], overlayColor: Colors.black, overlayOpacity: 0.5, + + // A/B Test Animation + animationCurve: ref.watch(uiExperimentProvider).fabAnimation == 'bounce' + ? Curves.elasticOut + : Curves.linear, + animationDuration: ref.watch(uiExperimentProvider).fabAnimation == 'bounce' + ? const Duration(milliseconds: 400) + : const Duration(milliseconds: 250), + spacing: 12, spaceBetweenChildren: 12, children: [ diff --git a/lib/widgets/home/sake_grid_view.dart b/lib/widgets/home/sake_grid_view.dart index 695f3a8..1957322 100644 --- a/lib/widgets/home/sake_grid_view.dart +++ b/lib/widgets/home/sake_grid_view.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; // HapticFeedback import '../../models/sake_item.dart'; import '../../providers/sake_list_provider.dart'; // For sakeOrderControllerProvider +import '../../providers/ui_experiment_provider.dart'; // A/B Test import 'sake_grid_item.dart'; class SakeGridView extends ConsumerWidget { @@ -23,15 +24,17 @@ class SakeGridView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final list = sakeList.cast(); + final experiment = ref.watch(uiExperimentProvider); + // If reorder is disabled (Menu Creation Screen), use standard GridView if (!enableReorder || isMenuMode) { return GridView.builder( padding: const EdgeInsets.all(4), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: experiment.gridColumns, crossAxisSpacing: 4, mainAxisSpacing: 4, - childAspectRatio: 1.0, + childAspectRatio: experiment.gridColumns == 3 ? (1.0 / 1.5) : 1.0, ), itemCount: list.length, itemBuilder: (context, index) { @@ -47,11 +50,11 @@ class SakeGridView extends ConsumerWidget { // Standard ReorderableGridView for Home Screen return ReorderableGridView.builder( padding: const EdgeInsets.all(4), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: experiment.gridColumns, crossAxisSpacing: 4, mainAxisSpacing: 4, - childAspectRatio: 1.0, + childAspectRatio: experiment.gridColumns == 3 ? (1.0 / 1.5) : 1.0, ), itemCount: list.length, onReorder: (oldIndex, newIndex) { diff --git a/lib/widgets/settings/other_settings_section.dart b/lib/widgets/settings/other_settings_section.dart index 65ce22e..7983ca7 100644 --- a/lib/widgets/settings/other_settings_section.dart +++ b/lib/widgets/settings/other_settings_section.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:flutter/services.dart'; +import '../../screens/dev_menu_screen.dart'; import '../../providers/theme_provider.dart'; class OtherSettingsSection extends ConsumerStatefulWidget { @@ -20,6 +22,7 @@ class OtherSettingsSection extends ConsumerStatefulWidget { class _OtherSettingsSectionState extends ConsumerState { String _appVersion = 'Loading...'; + int _devTapCount = 0; @override void initState() { @@ -60,11 +63,27 @@ class _OtherSettingsSectionState extends ConsumerState { ), const Divider(height: 1), ], - ListTile( - leading: Icon(LucideIcons.info, color: isDark ? Colors.grey[400] : null), - title: const Text('アプリバージョン'), - subtitle: Text(_appVersion), - ), + ListTile( + leading: Icon(LucideIcons.info, color: isDark ? Colors.grey[400] : null), + title: const Text('アプリバージョン'), + subtitle: Text(_appVersion), + onTap: () { + setState(() { + _devTapCount++; + if (_devTapCount >= 5) { + _devTapCount = 0; + HapticFeedback.heavyImpact(); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const DevMenuScreen()), + ); + } else { + // Optional: Small feedback + HapticFeedback.lightImpact(); + } + }); + }, + ), ], ), ),