aboutsummaryrefslogtreecommitdiff
path: root/lib/views/recipe_subview.dart
diff options
context:
space:
mode:
Diffstat (limited to 'lib/views/recipe_subview.dart')
-rw-r--r--lib/views/recipe_subview.dart392
1 files changed, 392 insertions, 0 deletions
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<RecipeSubview> {
+ final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+ final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
+ 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<Widget> _buildAppBarActions(BuildContext context, Recipe recipe) {
+ final actionList = [_shareData, _addToShoppingList, _uploadRecipe, _removeRecipe];
+
+ List<Widget> localActions = [
+ _buildAppBarCheckActions(),
+ PopupMenuButton(
+ onSelected: (int value) => actionList.elementAt(value)(context),
+ itemBuilder: (BuildContext context) => [
+ PopupMenuItem<int>(value: 0, child: Text(AppLocalizations.of(context)!.menu1)),
+ PopupMenuItem<int>(value: 1, child: Text(AppLocalizations.of(context)!.menu2)),
+ PopupMenuItem<int>(value: 2, child: Text(AppLocalizations.of(context)!.menu3)),
+ PopupMenuItem<int>(value: 3, child: Text(AppLocalizations.of(context)!.menu4)),
+ ],
+ ),
+ ];
+
+ List<Widget> remoteDownloadAction = [
+ IconButton(
+ icon: const Icon(Icons.save_alt_rounded),
+ onPressed: () {
+ downloadRecipe(context, recipe);
+ },
+ ),
+ ];
+
+ List<Widget> 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(() {});
+ },
+ );
+ }
+ }
+}