Skip to content

Commit c6b821c

Browse files
committed
fix ingredients tab update/add element
1 parent 8c73e70 commit c6b821c

File tree

19 files changed

+441
-352
lines changed

19 files changed

+441
-352
lines changed

apps/weekly_menu_app/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,4 @@ SPEC CHECKSUMS:
191191

192192
PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d
193193

194-
COCOAPODS: 1.12.1
194+
COCOAPODS: 1.14.2

apps/weekly_menu_app/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
97C146E61CF9000F007C117D /* Project object */ = {
168168
isa = PBXProject;
169169
attributes = {
170-
LastUpgradeCheck = 1300;
170+
LastUpgradeCheck = 1430;
171171
ORGANIZATIONNAME = "The Chromium Authors";
172172
TargetAttributes = {
173173
97C146ED1CF9000F007C117D = {

apps/weekly_menu_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1300"
3+
LastUpgradeVersion = "1430"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

apps/weekly_menu_app/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ class App extends StatelessWidget {
6666
// To use the playground font, add GoogleFonts package and uncomment
6767
// fontFamily: GoogleFonts.notoSans().fontFamily,
6868
),
69+
6970
theme: ThemeData(
71+
useMaterial3: false,
7072
// Define the default brightness and colors.
7173
brightness: Brightness.light,
7274
//backgroundColor: Colors.white,

apps/weekly_menu_app/lib/widgets/screens/recipe_screen/notifier.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,18 @@ class RecipeScreenStateNotifier extends StateNotifier<RecipeScreenState> {
7373

7474
Recipe revertRecipe() => state.recipeOriginator.revert();
7575

76-
bool get editEnabled => state.editEnabled;
7776
set editEnabled(bool newValue) {
7877
state = state.copyWith(editEnabled: newValue);
7978
}
8079

81-
bool get newIngredientMode => state.editEnabled;
8280
set newIngredientMode(bool newValue) {
8381
state = state.copyWith(newIngredientMode: newValue);
8482
}
8583

86-
bool get newStepMode => state.editEnabled;
8784
set newStepMode(bool newValue) {
8885
state = state.copyWith(newStepMode: newValue);
8986
}
9087

91-
bool get edited => state.recipeOriginator.isEdited;
9288

9389
void updateRecipeName(String name) {
9490
state.recipeOriginator

apps/weekly_menu_app/lib/widgets/screens/recipe_screen/screen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class _RecipeScreen extends HookConsumerWidget {
182182
}
183183

184184
Future<void> _handleBackButton() async {
185-
if (notifier.edited) {
185+
if (ref.read(recipeScreenNotifierProvider).recipeOriginator.isEdited) {
186186
final wannaSave = await showWannaSaveDialog(context);
187187

188188
if (wannaSave ?? false) {

apps/weekly_menu_app/lib/widgets/screens/recipe_screen/tabs/ingredients_tab.dart

Lines changed: 53 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'package:collection/collection.dart';
88
import 'package:model/ingredient.dart';
99
import 'package:model/recipe.dart';
1010
import 'package:common/extensions.dart';
11-
import 'package:weekly_menu_app/widgets/shared/flutter_data_state_builder.dart';
1211

1312
import '../../../shared/base_dialog.dart';
1413
import '../../../shared/empty_page_placeholder.dart';
@@ -102,7 +101,7 @@ class RecipeIngredientsTab extends HookConsumerWidget {
102101
}
103102
}
104103

105-
class _RecipeIngredientListTileWrapper extends HookConsumerWidget {
104+
class _RecipeIngredientListTileWrapper extends StatelessWidget {
106105
final RecipeIngredient? recipeIngredient;
107106
final bool editEnabled;
108107
final bool autofocus;
@@ -123,7 +122,7 @@ class _RecipeIngredientListTileWrapper extends HookConsumerWidget {
123122
: super(key: key);
124123

125124
@override
126-
Widget build(BuildContext context, WidgetRef ref) {
125+
Widget build(BuildContext context) {
127126
if (recipeIngredient != null) {
128127
return _RecipeIngredientListTile(
129128
key: ValueKey(recipeIngredient!.ingredientName),
@@ -357,63 +356,57 @@ class _IngredientSuggestionTextField extends HookConsumerWidget {
357356
final hasFocus = useState(false);
358357
final hasText = useState(false);
359358

360-
final ingredientsStream = ref.read(ingredientsRepositoryProvider).stream();
361-
362-
return RepositoryStreamBuilder<List<Ingredient>>(
363-
stream: ingredientsStream,
364-
builder: (context, model) {
365-
return Autocomplete<Ingredient>(
366-
initialValue:
367-
TextEditingValue(text: recipeIngredient?.ingredientName ?? ''),
368-
optionsMaxHeight: 100,
369-
optionsBuilder: (textEditingValue) async {
370-
if (textEditingValue.text.length < suggestAfter ||
371-
!enabled ||
372-
textEditingValue.text == recipeIngredient?.ingredientName)
373-
return const <Ingredient>[];
374-
375-
return model.where((i) => i.name
376-
.toLowerCase()
377-
.contains(textEditingValue.text.toLowerCase()));
378-
},
379-
displayStringForOption: (option) => option.name,
380-
fieldViewBuilder:
381-
(context, textEditingController, focusNode, onFieldSubmitted) {
382-
focusNode.addListener(() {
383-
hasFocus.value = focusNode.hasPrimaryFocus;
384-
385-
if (!focusNode.hasPrimaryFocus) {
386-
textEditingController.text =
387-
recipeIngredient?.ingredientName ?? '';
388-
textEditingController.selection = TextSelection.fromPosition(
389-
TextPosition(offset: textEditingController.text.length));
390-
}
391-
392-
onFocusChanged?.call(focusNode.hasPrimaryFocus);
393-
});
394-
395-
return AutoSizeTextField(
396-
scrollController: scrollController,
397-
autofocus: autofocus,
398-
focusNode: enabled ? focusNode : null,
399-
textCapitalization: TextCapitalization.sentences,
400-
controller: textEditingController,
401-
onChanged: (text) {
402-
hasText.value = text.isNotBlank;
403-
},
404-
readOnly: !enabled,
405-
decoration: InputDecoration(
406-
border: InputBorder.none,
407-
suffixIcon: _buildSuffixIcon(
408-
ref,
409-
editEnabled: enabled,
410-
hasFocus: hasFocus.value,
411-
hasText: hasText.value,
412-
controller: textEditingController,
413-
)),
414-
onSubmitted: (text) => _submit(ref, text));
415-
},
416-
);
359+
return Autocomplete<Ingredient>(
360+
initialValue:
361+
TextEditingValue(text: recipeIngredient?.ingredientName ?? ''),
362+
optionsMaxHeight: 100,
363+
optionsBuilder: (textEditingValue) async {
364+
if (textEditingValue.text.length < suggestAfter ||
365+
!enabled ||
366+
textEditingValue.text == recipeIngredient?.ingredientName)
367+
return const <Ingredient>[];
368+
369+
final ingredientsStream = await ref.read(ingredientsRepositoryProvider).loadAll();
370+
return ingredientsStream.where((i) => i.name
371+
.toLowerCase()
372+
.contains(textEditingValue.text.toLowerCase()));
373+
},
374+
displayStringForOption: (option) => option.name,
375+
fieldViewBuilder:
376+
(context, textEditingController, focusNode, onFieldSubmitted) {
377+
focusNode.addListener(() {
378+
hasFocus.value = focusNode.hasPrimaryFocus;
379+
380+
if (!focusNode.hasPrimaryFocus) {
381+
textEditingController.text =
382+
recipeIngredient?.ingredientName ?? '';
383+
textEditingController.selection = TextSelection.fromPosition(
384+
TextPosition(offset: textEditingController.text.length));
385+
}
386+
387+
onFocusChanged?.call(focusNode.hasPrimaryFocus);
388+
});
389+
390+
return AutoSizeTextField(
391+
scrollController: scrollController,
392+
autofocus: autofocus,
393+
focusNode: enabled ? focusNode : null,
394+
textCapitalization: TextCapitalization.sentences,
395+
controller: textEditingController,
396+
onChanged: (text) {
397+
hasText.value = text.isNotBlank;
398+
},
399+
readOnly: !enabled,
400+
decoration: InputDecoration(
401+
border: InputBorder.none,
402+
suffixIcon: _buildSuffixIcon(
403+
ref,
404+
editEnabled: enabled,
405+
hasFocus: hasFocus.value,
406+
hasText: hasText.value,
407+
controller: textEditingController,
408+
)),
409+
onSubmitted: (text) => _submit(ref, text));
417410
},
418411
);
419412
}

apps/weekly_menu_app/lib/widgets/shared/flutter_data_state_builder.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:async';
12
import 'dart:developer';
23

34
import 'package:common/log.dart';
@@ -76,6 +77,66 @@ class RepositoryStreamBuilder<T> extends HookConsumerWidget {
7677
}
7778
}
7879

80+
class RepositoryFutureBuilder<T> extends HookConsumerWidget {
81+
final Future<T?> future;
82+
final Widget? notFound;
83+
final Widget loading;
84+
final Widget error;
85+
final Widget Function(BuildContext context, T model) builder;
86+
87+
RepositoryFutureBuilder(
88+
{required this.future,
89+
required this.builder,
90+
this.notFound = const Text('not found'),
91+
this.error = const Text('error'),
92+
this.loading = const Center(child: CircularProgressIndicator())});
93+
94+
@override
95+
Widget build(BuildContext context, WidgetRef ref) {
96+
return FutureBuilder<T?>(
97+
future: future,
98+
builder: ((context, snapshot) {
99+
if (snapshot.error != null) {
100+
//early catch the forbidden/unauthorized to redirect user to login page
101+
if (snapshot.error is DataException) {
102+
final ex = snapshot.error as DataException;
103+
if (ex.statusCode == 403 || ex.statusCode == 401) {
104+
Future.delayed(Duration.zero, () => goToLoginPage(context));
105+
return loading;
106+
}
107+
}
108+
109+
// for other errors shows popup ?
110+
log(
111+
"RepositoryStreamBuilder caught an error: " +
112+
(snapshot.error.toString()),
113+
level: Level.SEVERE.value,
114+
error: snapshot.error);
115+
116+
return error;
117+
}
118+
119+
if (snapshot.connectionState == ConnectionState.waiting ||
120+
!snapshot.hasData) {
121+
return loading;
122+
}
123+
124+
final emptyModel = snapshot.data == null ||
125+
((snapshot.data is List) && (snapshot.data as List).isEmpty);
126+
127+
final baseWidget =
128+
emptyModel ? notFound : builder(context, snapshot.data!);
129+
130+
return baseWidget!;
131+
}));
132+
}
133+
134+
static void goToLoginPage(BuildContext context) {
135+
Navigator.of(context)
136+
.pushReplacement(MaterialPageRoute(builder: (_) => LoginScreen()));
137+
}
138+
}
139+
79140
class DataRepositoryStreamBuilder<T extends Data<T>>
80141
extends HookConsumerWidget {
81142
final Stream<Data<T?>> stream;

0 commit comments

Comments
 (0)