aboutsummaryrefslogtreecommitdiff
path: root/lib/views/recipe_view.dart
diff options
context:
space:
mode:
Diffstat (limited to 'lib/views/recipe_view.dart')
-rw-r--r--lib/views/recipe_view.dart379
1 files changed, 379 insertions, 0 deletions
diff --git a/lib/views/recipe_view.dart b/lib/views/recipe_view.dart
new file mode 100644
index 0000000..960ba84
--- /dev/null
+++ b/lib/views/recipe_view.dart
@@ -0,0 +1,379 @@
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:http/http.dart';
+
+import 'package:kulinar_app/constants.dart';
+import 'package:kulinar_app/views/image_view.dart';
+import 'package:kulinar_app/models/recipe_class.dart';
+import 'package:kulinar_app/widgets/toastbar_widget.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/settings_data_class.dart';
+
+import 'package:image_picker/image_picker.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:share_plus/share_plus.dart';
+
+class RecipeView extends StatefulWidget {
+ const RecipeView({Key? key, this.remote = false, required this.readonly, this.recipe, this.redrawCallback, this.showToastCallback}) : super(key: key);
+
+ final bool remote;
+ final bool readonly;
+ final Recipe? recipe;
+ final Function? redrawCallback;
+ final Function? showToastCallback;
+
+ @override
+ _RecipeViewState createState() => _RecipeViewState();
+}
+
+class _RecipeViewState extends State<RecipeView> {
+ 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 _readonlyOverride = 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();
+ } 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 (!widget.readonly) {
+ RecipeData.recipeList.add(_unsavedRecipe);
+ RecipeData.save();
+ }
+
+ if (widget.recipe == null) {
+ Navigator.pop(context);
+ } else {
+ _readonlyOverride = false;
+
+ setState(() {});
+ }
+ }
+
+ void _removeRecipe() {
+ 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;
+
+ // FIXME: This might throw due to "!"
+ widget.showToastCallback!(_content, _actionLabel, () {
+ _addRecipe(passedRecipe: _removedRecipe);
+ widget.redrawCallback!();
+ });
+
+ setState(() {});
+ }
+
+ bool _isEditingAllowed() {
+ if (_readonlyOverride) return true;
+ if (!widget.readonly) return true;
+
+ return false;
+ }
+
+ // TODO: FIXME: This might introduce bugs later (sanetize); maybe use FileHandler?
+ void downloadRecipe(BuildContext context, Recipe recipe) async {
+ RecipeData.recipeList.add(recipe);
+ ToastBar.showToastBar(context, AppLocalizations.of(context)!.downloadSuccess);
+
+ setState(() {});
+ }
+
+ // FIXME: doesnt refresh, should be passed from parent
+ void _pickImage() async {
+ XFile? _pickedFile;
+
+ if (SettingsData.settings["photoSource"] == "0") {
+ // TODO: Maybe pick multi image?
+ _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(() {});
+ }
+
+ // FIXME: doesnt refresh, should be passed from parent
+ void _removeImage() async {
+ _unsavedRecipe.image = null;
+ await RecipeData.save();
+
+ setState(() {});
+ }
+
+ Widget _buildUtilityRow() {
+ if (widget.remote) {
+ return SizedBox(
+ height: 10.0,
+ width: double.infinity,
+ );
+ } else {
+ // _buildAppBarPhotoActions(recipe),
+ return UtilityIconRow(
+ remote: widget.remote,
+ readonly: !_isEditingAllowed(),
+ unsavedRecipe: _unsavedRecipe,
+ pickImageCallback: _pickImage,
+ removeImageCallback: _removeImage,
+ removeRecipeCallback: _removeRecipe,
+ downloadRecipeCallback: downloadRecipe,
+ isEditingAllowedCallback: _isEditingAllowed,
+ cacheUnsavedRecipeCallback: _cacheUnsavedRecipe,
+ );
+ }
+ }
+
+ Widget _buildTitleInput() {
+ return Padding(
+ padding: const EdgeInsets.only(right: 20.0, left: 20.0),
+ child: TextFormField(
+ controller: _controller1,
+ style: cRecipeTextStyle,
+ cursorColor: cPrimaryColor,
+ enabled: _isEditingAllowed(),
+ 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) {
+ // TODO: Use https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html
+ 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: ImageView(image: _unsavedRecipe.image!)),
+ );
+ },
+ ),
+ );
+ }
+
+ return Container();
+ }
+
+ Widget _buildDescriptionInput() {
+ return Padding(
+ padding: const EdgeInsets.all(18.0),
+ child: TextField(
+ expands: true,
+ maxLines: null,
+ minLines: null,
+ decoration: null,
+ controller: _controller2,
+ cursorColor: cPrimaryColor,
+ focusNode: _detailsTextFocus,
+ style: cRecipeDescriptionStyle,
+ readOnly: !_isEditingAllowed(),
+ enableInteractiveSelection: true,
+ toolbarOptions: ToolbarOptions(
+ copy: true,
+ cut: true,
+ paste: true,
+ selectAll: true,
+ ),
+ ),
+ );
+ }
+
+ void _shareData() {
+ Share.share("${_unsavedRecipe.title}\n\n${_unsavedRecipe.description}", subject: _unsavedRecipe.title);
+ }
+
+ void _addToShoppingList() {}
+
+ void _uploadRecipe(BuildContext context) async {
+ Map<String, String> _headers = {"Content-Type": "application/json; charset=UTF-8"};
+ String _body = _unsavedRecipe.toJsonString();
+ Response? res;
+
+ try {
+ res = await post(Uri.https(SettingsData.settings["serverURL"]!, "/"), headers: _headers, body: _body);
+ } catch (e) {
+ print(e);
+ } finally {
+ if (res != null && res.statusCode == 200) {
+ ToastBar.showToastBar(context, AppLocalizations.of(context)!.uploadSuccess);
+ } else {
+ ToastBar.showToastBar(context, AppLocalizations.of(context)!.uploadError);
+ }
+
+ setState(() {});
+ }
+ }
+
+ List<Widget> _buildAppBarActions(BuildContext context, Recipe recipe) {
+ final _actionList = [_shareData, _addToShoppingList, _uploadRecipe, _removeRecipe];
+
+ List<Widget> _localActions = [
+ _buildAppBarCheckActions(),
+ PopupMenuButton(
+ onSelected: (int value) => _actionList.elementAt(value)(),
+ itemBuilder: (BuildContext context) => [
+ // TODO: Translations
+ PopupMenuItem<int>(value: 0, child: Text("Teilen")),
+ PopupMenuItem<int>(value: 1, child: Text("Einkaufen")),
+ PopupMenuItem<int>(value: 2, child: Text("Hochladen")),
+ PopupMenuItem<int>(value: 3, child: Text("Löschen")),
+ ],
+ ),
+ ];
+
+ List<Widget> _remoteDownloadAction = [
+ IconButton(
+ icon: Icon(Icons.save_alt_rounded),
+ onPressed: () {
+ downloadRecipe(context, recipe);
+ },
+ ),
+ ];
+
+ List<Widget> _remoteLookupAction = [
+ IconButton(
+ icon: Icon(Icons.menu_open_rounded),
+ onPressed: null, // TODO: IMPLEMENT: Show local recipe
+ ),
+ ];
+
+ 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 (_isEditingAllowed()) _title = AppLocalizations.of(context)!.mode2;
+
+ return AppBar(
+ title: Text(_title),
+ leading: IconButton(
+ icon: Icon(Icons.close),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ actions: _buildAppBarActions(context, recipe),
+ );
+ }
+
+ Widget _buildAppBarCheckActions() {
+ if (_isEditingAllowed()) {
+ return IconButton(
+ icon: Icon(Icons.check),
+ onPressed: _addRecipe,
+ );
+ } else {
+ return IconButton(
+ icon: Icon(Icons.edit),
+ onPressed: () {
+ _readonlyOverride = true;
+
+ setState(() {});
+ },
+ );
+ }
+ }
+}