From 0570147b3104eb329207ff374541d9d6797fe427 Mon Sep 17 00:00:00 2001 From: davidpkj Date: Sat, 11 Mar 2023 17:54:46 +0100 Subject: Updated code to dart analysis recommendations --- lib/views/recipe_subview.dart | 392 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 lib/views/recipe_subview.dart (limited to 'lib/views/recipe_subview.dart') diff --git a/lib/views/recipe_subview.dart b/lib/views/recipe_subview.dart new file mode 100644 index 0000000..9c7d91d --- /dev/null +++ b/lib/views/recipe_subview.dart @@ -0,0 +1,392 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import 'package:kulinar_app/constants.dart'; +import 'package:kulinar_app/views/image_subview.dart'; +import 'package:kulinar_app/models/recipe_class.dart'; +import 'package:kulinar_app/widgets/toastbar_widget.dart'; +import 'package:kulinar_app/widgets/custom_markdown_style.dart'; +import 'package:kulinar_app/models/data/recipe_data_class.dart'; +import 'package:kulinar_app/widgets/page_route_transitions.dart'; +import 'package:kulinar_app/widgets/utility_icon_row_widget.dart'; +import 'package:kulinar_app/models/data/shoplist_data_class.dart'; +import 'package:kulinar_app/models/data/settings_data_class.dart'; + +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:share_plus/share_plus.dart'; + +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class RecipeSubview extends StatefulWidget { + const RecipeSubview({Key? key, this.remote = false, this.recipe, this.redrawCallback, this.showToastCallback}) : super(key: key); + + final bool remote; + final Recipe? recipe; + final Function? redrawCallback; + final Function? showToastCallback; + + @override + RecipeSubviewState createState() => RecipeSubviewState(); +} + +class RecipeSubviewState extends State { + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + final TextEditingController _controller1 = TextEditingController(); + final TextEditingController _controller2 = TextEditingController(); + final FocusNode _detailsTitleFocus = FocusNode(); + final FocusNode _detailsTextFocus = FocusNode(); + Recipe _unsavedRecipe = Recipe(); + bool _isEditModeEnabled = false; + Recipe? _removedRecipe; + + @override + Widget build(BuildContext context) { + // TODO: Refactor to easier code + if (widget.recipe != null && _unsavedRecipe.isDefault()) _unsavedRecipe = widget.recipe!; + + if (_unsavedRecipe.isDefault()) { + _controller1.clear(); + _controller2.clear(); + _isEditModeEnabled = true; + } else { + _controller1.text = _unsavedRecipe.title ?? ""; + _controller2.text = _unsavedRecipe.description ?? ""; + } + + return Scaffold( + key: _scaffoldKey, + appBar: _buildAppBar(context, _unsavedRecipe), + body: GestureDetector( + onTap: () { + _detailsTextFocus.requestFocus(); + _controller2.selection = TextSelection.fromPosition(TextPosition(offset: _controller2.text.length)); + }, + child: Form( + key: _formKey, + child: ListView( + children: [ + _buildUtilityRow(), + _buildTitleInput(), + _buildImage(), + _buildDescriptionInput(), + ], + ), + ), + ), + ); + } + + void _cacheUnsavedRecipe() { + _unsavedRecipe.title = _controller1.text; + _unsavedRecipe.description = _controller2.text; + } + + void _addRecipe({Recipe? passedRecipe}) { + if (passedRecipe != null) { + RecipeData.recipeList.add(passedRecipe); + RecipeData.save(); + return; + } + + if (!_formKey.currentState!.validate()) return; + + _unsavedRecipe.title = _controller1.text; + _unsavedRecipe.description = _controller2.text; + + if (_isEditModeEnabled) { + if (!RecipeData.recipeList.contains(_unsavedRecipe)) RecipeData.recipeList.add(_unsavedRecipe); + RecipeData.save(); + } + + _isEditModeEnabled = false; + + if (widget.recipe == null) { + Navigator.pop(context); + } else { + setState(() {}); + } + } + + void _removeRecipe(BuildContext _) { + Navigator.pop(context); + RecipeData.recipeList.remove(widget.recipe); + RecipeData.save(); + + _removedRecipe = widget.recipe; + + String content = AppLocalizations.of(context)!.removed; + String actionLabel = AppLocalizations.of(context)!.undo; + + if (widget.showToastCallback != null) { + widget.showToastCallback!(content, actionLabel, () { + _addRecipe(passedRecipe: _removedRecipe); + widget.redrawCallback!(); + }); + } + + setState(() {}); + } + + // TODO: This might introduce bugs later (sanitize); maybe use FileHandler? + void downloadRecipe(BuildContext context, Recipe recipe) async { + RecipeData.recipeList.add(recipe); + ToastBar.showToastBar(context, AppLocalizations.of(context)!.downloadSuccess); + + setState(() {}); + } + + void _pickImage() async { + XFile? pickedFile; + + if (SettingsData.settings["photoSource"] == "0") { + pickedFile = await ImagePicker().pickImage(source: ImageSource.camera); + } else { + pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery); + } + + if (pickedFile != null) { + Directory directory = await getApplicationDocumentsDirectory(); + String name = DateTime.now().millisecondsSinceEpoch.toString(); + File file = File("${directory.path}/$name"); + + file.writeAsBytes(await pickedFile.readAsBytes()); + _unsavedRecipe.image = file.path; + + RecipeData.save(); + } + + setState(() {}); + } + + void _removeImage() async { + _unsavedRecipe.image = null; + await RecipeData.save(); + + setState(() {}); + } + + Widget _buildUtilityRow() { + if (widget.remote) { + return const SizedBox( + height: 10.0, + width: double.infinity, + ); + } else { + // _buildAppBarPhotoActions(recipe), + return UtilityIconRow( + remote: widget.remote, + isEditModeEnabled: _isEditModeEnabled, + unsavedRecipe: _unsavedRecipe, + pickImageCallback: _pickImage, + removeImageCallback: _removeImage, + removeRecipeCallback: _removeRecipe, + downloadRecipeCallback: downloadRecipe, + cacheUnsavedRecipeCallback: _cacheUnsavedRecipe, + ); + } + } + + Widget _buildTitleInput() { + return Padding( + padding: const EdgeInsets.only(right: 20.0, left: 20.0), + child: TextFormField( + controller: _controller1, + style: cRecipeTitleStyle, + cursorColor: cPrimaryColor, + enabled: _isEditModeEnabled, + focusNode: _detailsTitleFocus, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)!.inputHint, + hintStyle: cInputHintStyle, + ), + onFieldSubmitted: (_) { + _detailsTitleFocus.unfocus(); + FocusScope.of(context).requestFocus(_detailsTextFocus); + }, + validator: (input) { + if (input == "" || input!.trim().isEmpty) return AppLocalizations.of(context)!.inputError; + + return null; + }, + ), + ); + } + + Widget _buildImage() { + if (_unsavedRecipe.image != null) { + return Padding( + padding: const EdgeInsets.only(top: 12.0, left: 20.0, right: 20.0), + child: GestureDetector( + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Hero( + tag: "image", + child: Image( + image: FileImage( + File(_unsavedRecipe.image!), + ), + ), + ), + ), + onTap: () { + Navigator.push( + context, + FadeRoute(child: ImageSubview(image: _unsavedRecipe.image!)), + ); + }, + ), + ); + } + + return Container(); + } + + Widget _buildDescriptionInput() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: _isEditModeEnabled ? _buildInputDescription() : _buildMarkdownDescription(), + ); + } + + TextField _buildInputDescription() { + return TextField( + expands: true, + maxLines: null, + minLines: null, + decoration: null, + controller: _controller2, + cursorColor: cPrimaryColor, + focusNode: _detailsTextFocus, + style: cRecipeDescriptionStyle, + enableInteractiveSelection: true, + // TODO: only replace with code with same function + // ignore: deprecated_member_use + toolbarOptions: const ToolbarOptions( + copy: true, + cut: true, + paste: true, + selectAll: true, + ), + ); + } + + MarkdownBody _buildMarkdownDescription() { + return MarkdownBody( + styleSheet: CustomMarkdownStyle.sheet(), + data: _controller2.text, + selectable: true, + onTapLink: (text, href, title) => {}, + imageBuilder: (uri, title, alt) => Container(), + // styleSheet: CustomMarkdownStyle(), + ); + } + + void _shareData(BuildContext _) { + Share.share("${_unsavedRecipe.title}\n\n${_unsavedRecipe.description}", subject: _unsavedRecipe.title); + } + + void _addToShoppingList(BuildContext _) { + final ingredients = _unsavedRecipe.description!.split("\n")..retainWhere((element) => element.startsWith("- ")); + + if (ingredients.isEmpty) { + widget.showToastCallback!(AppLocalizations.of(context)!.ingredientsError, "", () {}); + return; + } + + for (String ingredient in ingredients) { + String item = ingredient.substring(2).trim(); + + if (!ShoplistData.shoplist.contains(item)) { + ShoplistData.shoplist.add(item); + } + } + + ShoplistData.save(); + + if (widget.showToastCallback != null) { + widget.showToastCallback!(AppLocalizations.of(context)!.ingredientsAdded, "", () {}); + } + } + + void _uploadRecipe(BuildContext context) async { + return; + } + + List _buildAppBarActions(BuildContext context, Recipe recipe) { + final actionList = [_shareData, _addToShoppingList, _uploadRecipe, _removeRecipe]; + + List localActions = [ + _buildAppBarCheckActions(), + PopupMenuButton( + onSelected: (int value) => actionList.elementAt(value)(context), + itemBuilder: (BuildContext context) => [ + PopupMenuItem(value: 0, child: Text(AppLocalizations.of(context)!.menu1)), + PopupMenuItem(value: 1, child: Text(AppLocalizations.of(context)!.menu2)), + PopupMenuItem(value: 2, child: Text(AppLocalizations.of(context)!.menu3)), + PopupMenuItem(value: 3, child: Text(AppLocalizations.of(context)!.menu4)), + ], + ), + ]; + + List remoteDownloadAction = [ + IconButton( + icon: const Icon(Icons.save_alt_rounded), + onPressed: () { + downloadRecipe(context, recipe); + }, + ), + ]; + + List remoteLookupAction = [ + const IconButton( + icon: Icon(Icons.menu_open_rounded), + onPressed: null, + ), + ]; + + if (widget.remote && RecipeData.recipeList.contains(recipe)) return remoteLookupAction; + if (widget.remote) return remoteDownloadAction; + + return localActions; + } + + PreferredSizeWidget _buildAppBar(BuildContext context, Recipe recipe) { + String title = AppLocalizations.of(context)!.mode1; + + if (_isEditModeEnabled) title = AppLocalizations.of(context)!.mode2; + + return AppBar( + title: Text(title), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + actions: _buildAppBarActions(context, recipe), + ); + } + + Widget _buildAppBarCheckActions() { + if (_isEditModeEnabled) { + return IconButton( + icon: const Icon(Icons.check), + onPressed: _addRecipe, + ); + } else { + return IconButton( + icon: const Icon(Icons.edit), + onPressed: () { + _isEditModeEnabled = true; + + setState(() {}); + }, + ); + } + } +} -- cgit v1.2.3