Skip to content

Commit 7140ee6

Browse files
authored
AZ index bar for recipe grid view (#714)
1 parent 55243cd commit 7140ee6

File tree

6 files changed

+636
-118
lines changed

6 files changed

+636
-118
lines changed

kitchenowl/lib/cubits/recipe_list_cubit.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class RecipeListCubit extends Cubit<RecipeListState> {
8989
forceOffline: true,
9090
);
9191

92-
if (state is LoadingRecipeListState) {
92+
if (state is LoadingRecipeListState && recipeList.isNotEmpty) {
9393
emit(ListRecipeListState(
9494
recipes: recipeList,
9595
tags: await tags,

kitchenowl/lib/models/recipe.dart

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import 'package:azlistview_plus/azlistview_plus.dart';
21
import 'package:fraction/fraction.dart';
32
import 'package:kitchenowl/models/item.dart';
43
import 'package:kitchenowl/models/model.dart';
54
import 'package:kitchenowl/models/tag.dart';
65

76
import 'household.dart';
87

9-
class Recipe extends Model implements ISuspensionBean {
8+
class Recipe extends Model {
109
final int? id;
1110
final String name;
1211
final String description;
@@ -178,15 +177,6 @@ class Recipe extends Model implements ISuspensionBean {
178177
"household_id": householdId,
179178
});
180179

181-
@override
182-
bool get isShowSuspension => true;
183-
184-
@override
185-
String getSuspensionTag() => name[0].toUpperCase();
186-
187-
@override
188-
set isShowSuspension(bool isShowSuspension) {}
189-
190180
List<RecipeItem> get optionalItems => items.where((e) => e.optional).toList();
191181
List<RecipeItem> get mandatoryItems =>
192182
items.where((e) => !e.optional).toList();

kitchenowl/lib/pages/household_page/recipe_list.dart

Lines changed: 85 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import 'package:azlistview_plus/azlistview_plus.dart';
21
import 'package:collection/collection.dart';
32
import 'package:flutter/material.dart';
43
import 'package:flutter_bloc/flutter_bloc.dart';
54
import 'package:kitchenowl/cubits/recipe_list_cubit.dart';
65
import 'package:kitchenowl/kitchenowl.dart';
6+
import 'package:kitchenowl/widgets/index_bar.dart';
77
import 'package:kitchenowl/widgets/choice_scroll.dart';
88
import 'package:kitchenowl/widgets/recipe_card.dart';
99
import 'package:kitchenowl/widgets/recipe_item.dart';
@@ -17,16 +17,42 @@ class RecipeListPage extends StatefulWidget {
1717

1818
class _RecipeListPageState extends State<RecipeListPage> {
1919
final TextEditingController searchController = TextEditingController();
20+
late final IndexScrollController scrollController;
21+
final headerKey = GlobalKey();
22+
23+
double getRowHeight() {
24+
if (!mounted) return 1;
25+
if (BlocProvider.of<RecipeListCubit>(context).state.listView) return 56;
26+
return ((headerKey.currentContext!.size!.width - 16 - 32) / getRowCount()) /
27+
0.8;
28+
}
29+
30+
double getHeaderHeight() {
31+
return headerKey.currentContext!.size!.height;
32+
}
33+
34+
int getRowCount() {
35+
if (!mounted) return 1;
36+
if (BlocProvider.of<RecipeListCubit>(context).state.listView) return 1;
37+
// header width - list padding
38+
return ((headerKey.currentContext!.size!.width - 16 - 32) / 350).ceil();
39+
}
2040

2141
@override
2242
void initState() {
2343
super.initState();
2444
searchController.text = BlocProvider.of<RecipeListCubit>(context).query;
45+
scrollController = IndexScrollController(
46+
getRowHeight: getRowHeight,
47+
getHeaderHeight: getHeaderHeight,
48+
getItemRowCount: getRowCount,
49+
);
2550
}
2651

2752
@override
2853
void dispose() {
2954
searchController.dispose();
55+
scrollController.dispose();
3056
super.dispose();
3157
}
3258

@@ -79,11 +105,13 @@ class _RecipeListPageState extends State<RecipeListPage> {
79105
state.tags.isEmpty ||
80106
state is SearchRecipeListState) {
81107
header = Align(
108+
key: headerKey,
82109
alignment: Alignment.topRight,
83110
child: header,
84111
);
85112
} else {
86113
header = Align(
114+
key: headerKey,
87115
alignment: Alignment.centerLeft,
88116
child: Padding(
89117
padding: const EdgeInsets.only(bottom: 6),
@@ -173,87 +201,70 @@ class _RecipeListPageState extends State<RecipeListPage> {
173201
);
174202
}
175203

176-
Widget child;
177-
if (state.listView) {
178-
child = AzListView(
179-
itemCount: recipes.length + 1,
180-
data: <ISuspensionBean>[
181-
_HeaderBean(recipes.first.getSuspensionTag())
182-
] +
183-
recipes,
184-
indexBarData: SuspensionUtil.getTagIndexList(recipes),
185-
indexBarAlignment: Alignment.centerLeft,
186-
indexBarOptions: IndexBarOptions(
187-
needRebuild: true,
188-
selectTextStyle: const TextStyle(
189-
fontWeight: FontWeight.bold,
190-
),
191-
selectItemDecoration: const BoxDecoration(),
192-
indexHintWidth: 50,
193-
indexHintHeight: 50,
194-
indexHintDecoration: BoxDecoration(
195-
shape: BoxShape.circle,
196-
color: Theme.of(context)
197-
.colorScheme
198-
.primary
199-
.withAlpha(0xFF),
200-
boxShadow: const [
201-
BoxShadow(
202-
color: Colors.black54,
203-
offset: Offset(1, 1),
204-
blurRadius: 6,
205-
spreadRadius: -2,
204+
return RefreshIndicator(
205+
onRefresh: cubit.refresh,
206+
child: Stack(
207+
children: [
208+
CustomScrollView(
209+
controller: scrollController,
210+
slivers: [
211+
SliverToBoxAdapter(child: header),
212+
SliverPadding(
213+
padding: const EdgeInsets.only(left: 32, right: 16),
214+
sliver: state.listView
215+
? SliverList(
216+
delegate: SliverChildBuilderDelegate(
217+
(context, i) => RecipeItemWidget(
218+
recipe: recipes[i],
219+
onUpdated: cubit.refresh,
220+
),
221+
childCount: recipes.length,
222+
),
223+
)
224+
: SliverGrid.builder(
225+
itemCount: recipes.length,
226+
gridDelegate:
227+
const SliverGridDelegateWithMaxCrossAxisExtent(
228+
maxCrossAxisExtent: 350,
229+
childAspectRatio: 0.8,
230+
),
231+
itemBuilder: (context, i) => RecipeCard(
232+
key: Key(recipes[i].name),
233+
recipe: recipes[i],
234+
onUpdated: cubit.refresh,
235+
),
236+
),
206237
),
207238
],
208239
),
209-
indexHintTextStyle: TextStyle(
210-
fontSize: 20,
211-
color: Theme.of(context).colorScheme.onPrimary,
212-
),
213-
indexHintAlignment: Alignment.centerLeft,
214-
),
215-
hapticFeedback: true,
216-
itemBuilder: (context, i) {
217-
if (i == 0) return header;
218-
i--;
219-
return Padding(
220-
key: Key(recipes[i].name),
221-
padding: const EdgeInsets.only(left: 32, right: 16),
222-
child: RecipeItemWidget(
223-
recipe: recipes[i],
224-
onUpdated: cubit.refresh,
225-
),
226-
);
227-
},
228-
);
229-
} else {
230-
child = CustomScrollView(
231-
primary: true,
232-
slivers: [
233-
SliverToBoxAdapter(child: header),
234-
SliverPadding(
235-
padding: const EdgeInsets.symmetric(horizontal: 16),
236-
sliver: SliverGrid.builder(
237-
itemCount: recipes.length,
238-
gridDelegate:
239-
const SliverGridDelegateWithMaxCrossAxisExtent(
240-
maxCrossAxisExtent: 350,
241-
childAspectRatio: 0.8,
240+
Align(
241+
alignment: Alignment.centerLeft,
242+
child: IndexBar(
243+
controller: scrollController,
244+
names: recipes.map((r) => r.name).toList(),
245+
indexHintDecoration: BoxDecoration(
246+
shape: BoxShape.circle,
247+
color: Theme.of(context)
248+
.colorScheme
249+
.primary
250+
.withAlpha(0xFF),
251+
boxShadow: const [
252+
BoxShadow(
253+
color: Colors.black54,
254+
offset: Offset(1, 1),
255+
blurRadius: 6,
256+
spreadRadius: -2,
257+
),
258+
],
242259
),
243-
itemBuilder: (context, i) => RecipeCard(
244-
key: Key(recipes[i].name),
245-
recipe: recipes[i],
246-
onUpdated: cubit.refresh,
260+
indexHintTextStyle: TextStyle(
261+
fontSize: 20,
262+
color: Theme.of(context).colorScheme.onPrimary,
247263
),
248264
),
249265
),
250266
],
251-
);
252-
}
253-
254-
return RefreshIndicator(
255-
onRefresh: cubit.refresh,
256-
child: child,
267+
),
257268
);
258269
},
259270
),
@@ -263,18 +274,3 @@ class _RecipeListPageState extends State<RecipeListPage> {
263274
);
264275
}
265276
}
266-
267-
class _HeaderBean implements ISuspensionBean {
268-
String suspensionTag;
269-
270-
_HeaderBean(this.suspensionTag);
271-
272-
@override
273-
bool get isShowSuspension => true;
274-
275-
@override
276-
String getSuspensionTag() => suspensionTag;
277-
278-
@override
279-
set isShowSuspension(bool _isShowSuspension) {}
280-
}

0 commit comments

Comments
 (0)