2026-01-11 08:17:29 +00:00
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
|
|
import '../providers/theme_provider.dart';
|
|
|
|
|
|
import '../providers/display_mode_provider.dart';
|
2026-01-29 15:54:22 +00:00
|
|
|
|
import '../utils/translations.dart'; // Translation helper
|
2026-01-11 08:17:29 +00:00
|
|
|
|
import 'camera_screen.dart';
|
|
|
|
|
|
import 'menu_creation_screen.dart';
|
2026-01-29 15:54:22 +00:00
|
|
|
|
import '../theme/app_colors.dart';
|
2026-01-11 08:17:29 +00:00
|
|
|
|
import '../providers/sake_list_provider.dart';
|
|
|
|
|
|
import '../providers/filter_providers.dart';
|
|
|
|
|
|
import '../providers/menu_providers.dart'; // Phase 2-1
|
|
|
|
|
|
import '../models/sake_item.dart';
|
|
|
|
|
|
import '../widgets/sake_search_delegate.dart';
|
|
|
|
|
|
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';
|
2026-01-13 09:36:31 +00:00
|
|
|
|
import '../widgets/home/sake_list_view.dart';
|
|
|
|
|
|
import '../widgets/home/sake_grid_view.dart';
|
|
|
|
|
|
import '../widgets/add_set_item_dialog.dart';
|
2026-01-13 09:33:47 +00:00
|
|
|
|
import '../providers/ui_experiment_provider.dart'; // A/B Test
|
2026-01-11 08:17:29 +00:00
|
|
|
|
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
|
|
|
|
|
import 'package:lucide_icons/lucide_icons.dart';
|
|
|
|
|
|
import '../widgets/prefecture_filter_sheet.dart';
|
2026-02-15 15:13:12 +00:00
|
|
|
|
import '../widgets/pending_analysis_banner.dart'; // Phase 1: Banner widget
|
|
|
|
|
|
import '../widgets/common/error_retry_widget.dart';
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
// CR-006: NotifierProviderでオンボーディングチェック状態を管理(グローバル変数を削除)
|
|
|
|
|
|
class HasCheckedOnboardingNotifier extends Notifier<bool> {
|
|
|
|
|
|
@override
|
|
|
|
|
|
bool build() => false;
|
|
|
|
|
|
|
|
|
|
|
|
void setChecked() => state = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final hasCheckedOnboardingProvider = NotifierProvider<HasCheckedOnboardingNotifier, bool>(
|
|
|
|
|
|
HasCheckedOnboardingNotifier.new,
|
|
|
|
|
|
);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
|
|
|
|
|
class HomeScreen extends ConsumerWidget {
|
|
|
|
|
|
const HomeScreen({super.key});
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
|
|
|
|
final displayMode = ref.watch(displayModeProvider);
|
|
|
|
|
|
final sakeListAsync = ref.watch(sakeListProvider);
|
2026-01-29 15:54:22 +00:00
|
|
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
|
|
|
|
|
|
|
|
|
|
|
// CR-006: Onboarding Check (Run once per session) - NotifierProviderで管理
|
|
|
|
|
|
final hasChecked = ref.watch(hasCheckedOnboardingProvider);
|
|
|
|
|
|
if (!hasChecked) {
|
2026-01-11 08:17:29 +00:00
|
|
|
|
Future.microtask(() {
|
|
|
|
|
|
final profile = ref.read(userProfileProvider);
|
2026-01-13 01:33:13 +00:00
|
|
|
|
if (!profile.hasCompletedOnboarding && context.mounted) {
|
2026-01-11 08:17:29 +00:00
|
|
|
|
_showOnboardingDialog(context, ref);
|
|
|
|
|
|
}
|
2026-01-29 15:54:22 +00:00
|
|
|
|
ref.read(hasCheckedOnboardingProvider.notifier).setChecked();
|
2026-01-11 08:17:29 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Filter States
|
|
|
|
|
|
final isSearching = ref.watch(sakeSearchQueryProvider).isNotEmpty;
|
|
|
|
|
|
final showFavorites = ref.watch(sakeFilterFavoriteProvider);
|
|
|
|
|
|
final selectedPrefecture = ref.watch(sakeFilterPrefectureProvider);
|
|
|
|
|
|
|
|
|
|
|
|
final isMenuMode = ref.watch(menuModeProvider);
|
|
|
|
|
|
final userProfile = ref.watch(userProfileProvider);
|
|
|
|
|
|
final isBusinessMode = userProfile.isBusinessMode;
|
2026-01-29 15:54:22 +00:00
|
|
|
|
final t = Translations(userProfile.locale); // Translation helper
|
|
|
|
|
|
|
2026-02-15 15:13:12 +00:00
|
|
|
|
// Phase D6: フィルタリングされたリストでアイテム有無を判定
|
|
|
|
|
|
final hasItems = ref.watch(allSakeItemsProvider).asData?.value.isNotEmpty ?? false;
|
2026-01-15 15:53:44 +00:00
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
return Scaffold(
|
|
|
|
|
|
appBar: AppBar(
|
2026-01-29 15:54:22 +00:00
|
|
|
|
title: isMenuMode
|
|
|
|
|
|
? Text(t['menuCreation'], style: const TextStyle(fontWeight: FontWeight.bold))
|
|
|
|
|
|
: (isSearching
|
2026-01-11 08:17:29 +00:00
|
|
|
|
? Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: TextField(
|
|
|
|
|
|
autofocus: true,
|
2026-01-29 15:54:22 +00:00
|
|
|
|
decoration: InputDecoration(
|
|
|
|
|
|
hintText: t['searchPlaceholder'],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
border: InputBorder.none,
|
2026-01-29 15:54:22 +00:00
|
|
|
|
hintStyle: const TextStyle(color: Colors.white70),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
style: const TextStyle(color: Colors.white),
|
|
|
|
|
|
onChanged: (value) => ref.read(sakeSearchQueryProvider.notifier).set(value),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
// Sort Button (Searching State)
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
icon: const Icon(LucideIcons.arrowUpDown),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
tooltip: t['sort'],
|
|
|
|
|
|
onPressed: () => _showSortMenu(context, ref, t),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
|
|
|
|
|
: null),
|
|
|
|
|
|
actions: [
|
2026-01-13 01:33:13 +00:00
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
// Normal Actions
|
2026-01-13 02:56:17 +00:00
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
if (!isSearching) // Show Sort button here if not searching
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
icon: const Icon(LucideIcons.arrowUpDown),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
tooltip: t['sort'],
|
|
|
|
|
|
onPressed: () => _showSortMenu(context, ref, t),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
icon: const Icon(LucideIcons.search),
|
|
|
|
|
|
onPressed: () => showSearch(context: context, delegate: SakeSearchDelegate(ref)),
|
|
|
|
|
|
),
|
|
|
|
|
|
// ... rest of icons (Location, Favorite, DisplayMode, Guide)
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
icon: const Icon(LucideIcons.mapPin),
|
|
|
|
|
|
onPressed: () => PrefectureFilterSheet.show(context),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
tooltip: t['filterByPrefecture'],
|
|
|
|
|
|
color: selectedPrefecture != null ? appColors.brandPrimary : null,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
icon: Icon(showFavorites ? Icons.favorite : Icons.favorite_border),
|
|
|
|
|
|
color: showFavorites ? Colors.pink : null,
|
|
|
|
|
|
onPressed: () => ref.read(sakeFilterFavoriteProvider.notifier).toggle(),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
tooltip: t['favoritesOnly'],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
icon: Icon(displayMode == 'list' ? LucideIcons.layoutGrid : LucideIcons.list),
|
|
|
|
|
|
onPressed: () => ref.read(displayModeProvider.notifier).toggle(),
|
|
|
|
|
|
),
|
|
|
|
|
|
IconButton(
|
2026-01-29 15:54:22 +00:00
|
|
|
|
icon: const Icon(LucideIcons.helpCircle),
|
|
|
|
|
|
onPressed: () => _showUsageGuide(context, ref, t),
|
|
|
|
|
|
tooltip: t['helpGuide'],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
2026-01-13 01:33:13 +00:00
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
body: SafeArea(
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
2026-02-15 15:13:12 +00:00
|
|
|
|
// Phase 1: 未解析Draft通知バナー
|
|
|
|
|
|
const PendingAnalysisBanner(),
|
|
|
|
|
|
|
2026-01-15 15:53:44 +00:00
|
|
|
|
if (!isMenuMode && hasItems)
|
2026-01-11 08:17:29 +00:00
|
|
|
|
SakeFilterChips(
|
|
|
|
|
|
mode: isBusinessMode ? FilterChipMode.business : FilterChipMode.personal
|
|
|
|
|
|
),
|
2026-02-15 15:13:12 +00:00
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
// Menu Info Banner Removed
|
|
|
|
|
|
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: sakeListAsync.when(
|
|
|
|
|
|
data: (sakeList) {
|
|
|
|
|
|
final showSelected = isMenuMode && ref.watch(menuShowSelectedOnlyProvider);
|
|
|
|
|
|
|
|
|
|
|
|
List<SakeItem> displayList;
|
|
|
|
|
|
if (showSelected) {
|
|
|
|
|
|
final orderedIds = ref.watch(menuOrderedIdsProvider);
|
|
|
|
|
|
// Map Ordered Ids to Objects.
|
|
|
|
|
|
// Note: O(N*M) if naive. Use Map for O(N).
|
|
|
|
|
|
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;
|
2026-02-15 15:13:12 +00:00
|
|
|
|
}
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
2026-02-15 15:13:12 +00:00
|
|
|
|
// Phase D6: フィルタリング後のリストで空状態判定
|
|
|
|
|
|
// Personal Modeではセット商品除外後の状態で判定
|
|
|
|
|
|
final filteredAsync = ref.watch(filteredByModeProvider);
|
|
|
|
|
|
final isListActuallyEmpty = filteredAsync.asData?.value.isEmpty ?? true;
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
2026-02-15 15:13:12 +00:00
|
|
|
|
if (displayList.isEmpty) {
|
2026-01-11 08:17:29 +00:00
|
|
|
|
if (showSelected) {
|
|
|
|
|
|
return Center(
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Icon(LucideIcons.clipboardCheck, size: 60, color: Colors.grey[400]),
|
|
|
|
|
|
const SizedBox(height: 16),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
Text(t['noMenuItems'], style: const TextStyle(fontWeight: FontWeight.bold)),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
const SizedBox(height: 8),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
Text(t['goBackToList'], textAlign: TextAlign.center, style: const TextStyle(color: Colors.grey)),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
} else if (isListActuallyEmpty) {
|
2026-01-15 15:53:44 +00:00
|
|
|
|
return HomeEmptyState(isMenuMode: isMenuMode);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return const SakeNoMatchState();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Logic: Reorder only if Custom Sort is active (and not searching)
|
|
|
|
|
|
final sortMode = ref.watch(sakeSortModeProvider);
|
|
|
|
|
|
final isCustomSort = sortMode == SortMode.custom;
|
|
|
|
|
|
final canReorder = isCustomSort && !isSearching; // Menu mode doesn't support reorder
|
|
|
|
|
|
|
|
|
|
|
|
return displayMode == 'list'
|
|
|
|
|
|
? SakeListView(sakeList: displayList, isMenuMode: false, enableReorder: canReorder)
|
|
|
|
|
|
: SakeGridView(sakeList: displayList, isMenuMode: false, enableReorder: canReorder);
|
|
|
|
|
|
},
|
|
|
|
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
2026-02-15 15:13:12 +00:00
|
|
|
|
error: (e, st) => ErrorRetryWidget(
|
|
|
|
|
|
message: '日本酒リストの読み込みに失敗しました',
|
|
|
|
|
|
details: e.toString(),
|
|
|
|
|
|
onRetry: () => ref.refresh(sakeListProvider),
|
|
|
|
|
|
),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
floatingActionButton: isBusinessMode
|
2026-01-11 08:17:29 +00:00
|
|
|
|
? SpeedDial(
|
|
|
|
|
|
icon: LucideIcons.plus,
|
|
|
|
|
|
activeIcon: LucideIcons.x,
|
2026-01-29 15:54:22 +00:00
|
|
|
|
backgroundColor: appColors.brandPrimary,
|
|
|
|
|
|
foregroundColor: appColors.surfaceSubtle,
|
|
|
|
|
|
activeBackgroundColor: appColors.surfaceElevated,
|
|
|
|
|
|
shape: const CircleBorder(), // Fix white line artifact
|
2026-01-11 08:17:29 +00:00
|
|
|
|
overlayColor: Colors.black,
|
|
|
|
|
|
overlayOpacity: 0.5,
|
2026-01-13 09:33:47 +00:00
|
|
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
spacing: 12,
|
|
|
|
|
|
spaceBetweenChildren: 12,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
SpeedDialChild(
|
|
|
|
|
|
child: const Text('🍶', style: TextStyle(fontSize: 24)),
|
|
|
|
|
|
backgroundColor: Colors.white,
|
2026-01-29 15:54:22 +00:00
|
|
|
|
label: t['createMenu'],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
labelStyle: const TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
|
|
onTap: () {
|
|
|
|
|
|
Navigator.push(
|
|
|
|
|
|
context,
|
|
|
|
|
|
MaterialPageRoute(builder: (context) => const MenuCreationScreen()),
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
SpeedDialChild(
|
2026-01-29 15:54:22 +00:00
|
|
|
|
child: Icon(LucideIcons.packagePlus, color: appColors.brandAccent),
|
|
|
|
|
|
backgroundColor: appColors.surfaceSubtle,
|
|
|
|
|
|
label: t['createSet'],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
labelStyle: const TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
|
|
onTap: () {
|
|
|
|
|
|
showDialog(
|
|
|
|
|
|
context: context,
|
|
|
|
|
|
builder: (context) => const AddSetItemDialog(),
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
SpeedDialChild(
|
2026-01-29 15:54:22 +00:00
|
|
|
|
child: Icon(LucideIcons.image, color: appColors.brandPrimary),
|
|
|
|
|
|
backgroundColor: appColors.surfaceSubtle,
|
|
|
|
|
|
label: t['selectFromGallery'],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
labelStyle: const TextStyle(fontWeight: FontWeight.bold),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
Navigator.of(context).push(
|
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
|
builder: (context) => const CameraScreen(
|
|
|
|
|
|
mode: CameraMode.createItem,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
SpeedDialChild(
|
2026-01-29 15:54:22 +00:00
|
|
|
|
child: Icon(LucideIcons.camera, color: appColors.brandPrimary),
|
|
|
|
|
|
backgroundColor: appColors.surfaceSubtle,
|
|
|
|
|
|
label: t['takePhoto'],
|
2026-01-11 08:17:29 +00:00
|
|
|
|
labelStyle: const TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
|
|
onTap: () {
|
2026-01-29 15:54:22 +00:00
|
|
|
|
// ✅ 遅延を削除してサクッと感を復元
|
|
|
|
|
|
// MaterialPageRouteのアニメーション中にカメラが初期化されるため、
|
|
|
|
|
|
// ユーザーは待たされている感じがしない
|
|
|
|
|
|
Navigator.push(
|
|
|
|
|
|
context,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
MaterialPageRoute(builder: (context) => const CameraScreen()),
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
2026-01-29 15:54:22 +00:00
|
|
|
|
: FloatingActionButton(
|
|
|
|
|
|
onPressed: () {
|
|
|
|
|
|
// ✅ 遅延を削除してサクッと感を復元
|
|
|
|
|
|
Navigator.push(
|
|
|
|
|
|
context,
|
|
|
|
|
|
MaterialPageRoute(builder: (context) => const CameraScreen()),
|
|
|
|
|
|
);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
2026-01-29 15:54:22 +00:00
|
|
|
|
backgroundColor: appColors.brandPrimary,
|
|
|
|
|
|
foregroundColor: appColors.surfaceSubtle,
|
|
|
|
|
|
child: const Icon(LucideIcons.camera),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _showOnboardingDialog(BuildContext context, WidgetRef ref) {
|
|
|
|
|
|
showDialog(
|
|
|
|
|
|
context: context,
|
|
|
|
|
|
barrierDismissible: false,
|
|
|
|
|
|
builder: (ctx) => OnboardingDialog(
|
|
|
|
|
|
onFinish: () {
|
|
|
|
|
|
Navigator.pop(ctx);
|
|
|
|
|
|
ref.read(userProfileProvider.notifier).completeOnboarding();
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
void _showUsageGuide(BuildContext context, WidgetRef ref, Translations t) {
|
2026-01-11 08:17:29 +00:00
|
|
|
|
final userProfile = ref.read(userProfileProvider);
|
|
|
|
|
|
final isBusinessMode = userProfile.isBusinessMode;
|
|
|
|
|
|
|
2026-01-13 02:56:17 +00:00
|
|
|
|
List<Map<String, dynamic>>? pages;
|
2026-01-11 08:17:29 +00:00
|
|
|
|
|
|
|
|
|
|
if (isBusinessMode) {
|
|
|
|
|
|
pages = [
|
|
|
|
|
|
{
|
2026-01-29 15:54:22 +00:00
|
|
|
|
'title': t['welcomeBusinessMode'],
|
|
|
|
|
|
'description': t['businessModeDesc'],
|
2026-01-13 03:13:10 +00:00
|
|
|
|
'icon': LucideIcons.store,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-01-29 15:54:22 +00:00
|
|
|
|
'title': t['setProductCreation'],
|
|
|
|
|
|
'description': t['setProductDesc'],
|
2026-01-13 02:56:17 +00:00
|
|
|
|
'icon': LucideIcons.packagePlus,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-01-29 15:54:22 +00:00
|
|
|
|
'title': t['instaPromo'],
|
|
|
|
|
|
'description': t['instaPromoDesc'],
|
2026-01-13 02:56:17 +00:00
|
|
|
|
'icon': LucideIcons.instagram,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-01-29 15:54:22 +00:00
|
|
|
|
'title': t['salesAnalytics'],
|
|
|
|
|
|
'description': t['salesAnalyticsDesc'],
|
2026-01-13 02:56:17 +00:00
|
|
|
|
'icon': LucideIcons.barChartBig,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
showDialog(
|
|
|
|
|
|
context: context,
|
|
|
|
|
|
barrierDismissible: true,
|
|
|
|
|
|
builder: (ctx) => OnboardingDialog(
|
|
|
|
|
|
pages: pages,
|
|
|
|
|
|
onFinish: () => Navigator.pop(ctx),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-29 15:54:22 +00:00
|
|
|
|
void _showSortMenu(BuildContext context, WidgetRef ref, Translations t) {
|
2026-01-11 08:17:29 +00:00
|
|
|
|
final currentSort = ref.read(sakeSortModeProvider);
|
2026-01-29 15:54:22 +00:00
|
|
|
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
|
|
|
|
|
|
2026-01-11 08:17:29 +00:00
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
|
context: context,
|
|
|
|
|
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
builder: (dialogContext) => SafeArea(
|
2026-01-11 08:17:29 +00:00
|
|
|
|
child: Column(
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
|
children: [
|
2026-01-29 15:54:22 +00:00
|
|
|
|
Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
|
child: Text(t['sortTitle'], style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
|
2026-01-11 08:17:29 +00:00
|
|
|
|
),
|
|
|
|
|
|
ListTile(
|
|
|
|
|
|
leading: const Icon(LucideIcons.clock),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
title: Text(t['sortNewest']),
|
|
|
|
|
|
trailing: currentSort == SortMode.newest ? Icon(LucideIcons.check, color: appColors.brandPrimary) : null,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
ref.read(sakeSortModeProvider.notifier).set(SortMode.newest);
|
2026-01-29 15:54:22 +00:00
|
|
|
|
Navigator.pop(dialogContext);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
ListTile(
|
|
|
|
|
|
leading: const Icon(LucideIcons.history),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
title: Text(t['sortOldest']),
|
|
|
|
|
|
trailing: currentSort == SortMode.oldest ? Icon(LucideIcons.check, color: appColors.brandPrimary) : null,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
ref.read(sakeSortModeProvider.notifier).set(SortMode.oldest);
|
2026-01-29 15:54:22 +00:00
|
|
|
|
Navigator.pop(dialogContext);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
ListTile(
|
|
|
|
|
|
leading: const Icon(LucideIcons.arrowDownAZ),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
title: Text(t['sortName']),
|
|
|
|
|
|
trailing: currentSort == SortMode.name ? Icon(LucideIcons.check, color: appColors.brandPrimary) : null,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
ref.read(sakeSortModeProvider.notifier).set(SortMode.name);
|
2026-01-29 15:54:22 +00:00
|
|
|
|
Navigator.pop(dialogContext);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
ListTile(
|
|
|
|
|
|
leading: const Icon(LucideIcons.gripHorizontal),
|
2026-01-29 15:54:22 +00:00
|
|
|
|
title: Text(t['sortCustom']),
|
|
|
|
|
|
trailing: currentSort == SortMode.custom ? Icon(LucideIcons.check, color: appColors.brandPrimary) : null,
|
2026-01-11 08:17:29 +00:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
ref.read(sakeSortModeProvider.notifier).set(SortMode.custom);
|
2026-01-29 15:54:22 +00:00
|
|
|
|
Navigator.pop(dialogContext);
|
2026-01-11 08:17:29 +00:00
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Method _buildBusinessQuickFilters removed (Using SakeFilterChips instead)
|
|
|
|
|
|
} // End of HomeScreen class
|