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

247 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/sake_list_provider.dart';
import '../providers/menu_providers.dart';
import '../providers/display_mode_provider.dart';
import '../providers/filter_providers.dart';
import '../models/sake_item.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/step_indicator.dart';
import '../theme/app_colors.dart';
import 'menu_pricing_screen.dart';
import '../widgets/sake_search_delegate.dart';
import '../widgets/prefecture_filter_sheet.dart';
import '../widgets/common/error_retry_widget.dart';
class MenuCreationScreen extends ConsumerWidget {
const MenuCreationScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appColors = Theme.of(context).extension<AppColors>()!;
// Force MenuMode provider for compatibility with child widgets (like SakeListItem)
// We should probably just pass `isMenuMode: true` to children, but SakeFilterChips might rely on it?
// SakeFilterChips doesn't use isMenuMode.
// SakeListItem/GridItem take `isMenuMode` as param.
// So we don't necessarily need to set the global provider, but it's safer if other widgets rely on it.
// Actually, `HomeScreen` toggles it. Here we are always in "Creation Mode".
final displayMode = ref.watch(displayModeProvider);
final sakeListAsync = ref.watch(sakeListProvider);
final showSelectedOnly = ref.watch(menuShowSelectedOnlyProvider);
final selectedPrefecture = ref.watch(sakeFilterPrefectureProvider);
return Scaffold(
appBar: AppBar(
title: const StepIndicator(currentStep: 1, totalSteps: 3),
centerTitle: true,
automaticallyImplyLeading: false, // ヘッダー戻るボタンを無効化
actions: [
// Search (最も使用頻度が高い)
IconButton(
icon: const Icon(Icons.search),
onPressed: () => showSearch(context: context, delegate: SakeSearchDelegate(ref)),
tooltip: '検索',
),
// Prefecture Filter
IconButton(
icon: const Icon(Icons.location_on),
onPressed: () => PrefectureFilterSheet.show(context),
tooltip: '都道府県で絞り込み',
color: selectedPrefecture != null ? appColors.brandPrimary : null,
),
// Show Selected Only Toggle (フィルターアイコンに変更)
IconButton(
icon: Icon(showSelectedOnly ? Icons.filter_list : Icons.filter_list_off),
color: showSelectedOnly ? appColors.brandPrimary : null,
tooltip: showSelectedOnly ? '全て表示' : '選択中のみ表示',
onPressed: () {
if (!showSelectedOnly) {
// Initialize order when switching to "Selected Only"
final selected = ref.read(selectedMenuSakeIdsProvider);
if (selected.isNotEmpty) {
ref.read(menuOrderedIdsProvider.notifier).initialize(selected.toList());
}
}
ref.read(menuShowSelectedOnlyProvider.notifier).toggle();
},
),
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(2),
child: LinearProgressIndicator(
value: 1 / 3, // Step 1 of 3 = 33%
backgroundColor: appColors.surfaceSubtle,
valueColor: AlwaysStoppedAnimation<Color>(appColors.brandPrimary),
minHeight: 2,
),
),
),
body: Column(
children: [
// Guide Banner (条件付き表示: 未選択時のみ)
() {
final selectedIds = ref.watch(selectedMenuSakeIdsProvider);
if (selectedIds.isEmpty && !showSelectedOnly) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: appColors.brandAccent.withValues(alpha: 0.1),
border: Border(
bottom: BorderSide(color: appColors.brandAccent.withValues(alpha: 0.3)),
),
),
child: Row(
children: [
Icon(Icons.touch_app, size: 20, color: appColors.brandAccent),
const SizedBox(width: 8),
Expanded(
child: Text(
'カードをタップして選択',
style: TextStyle(
color: appColors.brandPrimary,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
return const SizedBox.shrink();
}(),
// Main Content
Expanded(
child: sakeListAsync.when(
data: (sakeList) {
List<SakeItem> displayList;
if (showSelectedOnly) {
final orderedIds = ref.watch(menuOrderedIdsProvider);
final sakeMap = {for (var s in sakeList) s.id: s};
displayList = orderedIds
.map((id) => sakeMap[id])
.where((s) => s != null)
.cast<SakeItem>()
.toList();
} else {
displayList = sakeList;
}
// Phase D6: フィルタリング後のリストで空状態判定
final filteredAsync = ref.watch(filteredByModeProvider);
final isListActuallyEmpty = filteredAsync.asData?.value.isEmpty ?? true;
// Empty State Logic
if (displayList.isEmpty) {
if (showSelectedOnly) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.checklist, size: 60, color: appColors.iconSubtle),
const SizedBox(height: 16),
const Text('お品書きに追加されたお酒はありません', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text('「全て表示」に切り替えて\n掲載したいお酒を選択してください', textAlign: TextAlign.center, style: TextStyle(color: appColors.textSecondary)),
],
),
);
} else if (isListActuallyEmpty) {
return const HomeEmptyState(isMenuMode: true);
} else {
return const SakeNoMatchState();
}
}
return displayMode == 'list'
? SakeListView(sakeList: displayList, isMenuMode: true, enableReorder: false)
: SakeGridView(sakeList: displayList, isMenuMode: true, enableReorder: false);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, st) => ErrorRetryWidget(
message: 'お品書き作成の読み込みに失敗しました',
details: e.toString(),
onRetry: () => ref.refresh(sakeListProvider),
),
),
),
],
),
bottomNavigationBar: SafeArea(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: Row(
children: [
// 戻るボタン (左端)
SizedBox(
width: 56,
height: 56,
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
side: BorderSide(color: appColors.divider),
padding: EdgeInsets.zero,
),
child: Icon(Icons.arrow_back, color: appColors.iconDefault),
),
),
const SizedBox(width: 12),
// 次へボタン (右側いっぱいに広がる)
Expanded(
child: SizedBox(
height: 56,
child: ElevatedButton.icon(
onPressed: () {
final selectedIds = ref.read(selectedMenuSakeIdsProvider);
if (selectedIds.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('お酒を選択してください'))
);
return;
}
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const MenuPricingScreen()),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: appColors.brandPrimary,
foregroundColor: appColors.surfaceSubtle,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
icon: const Icon(Icons.arrow_forward),
label: const Text('価格設定', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
),
),
],
),
),
),
);
}
}