added combinations feature

This commit is contained in:
Vishnu Raghav B 2020-11-15 16:21:10 +05:30
parent eaf5419e8c
commit 01c9c32162
7 changed files with 318 additions and 45 deletions

View file

@ -202,6 +202,7 @@ MDActivityIndicator {
MDProgress { MDProgress {
progress-color: $orange; progress-color: $orange;
} }
// ----------------------------- // -----------------------------
// ActionBar // ActionBar
ActionBar { ActionBar {
@ -480,6 +481,7 @@ RadListView {
.recipeLink { .recipeLink {
padding: 16; padding: 16;
margin: 0; margin: 0;
line-height: 6;
} }
} }
.recipeText { .recipeText {
@ -522,6 +524,10 @@ MDButton.closeBtn {
min-width: 0; min-width: 0;
vertical-alignment: top; vertical-alignment: top;
} }
TextField.combinationToken {
border-width: 0;
background: white;
}
// ----------------------------- // -----------------------------
// DIALOGS // DIALOGS
.dialogContainer { .dialogContainer {
@ -547,6 +553,7 @@ MDButton.closeBtn {
.actionItem { .actionItem {
letter-spacing: 0; letter-spacing: 0;
text-transform: none; text-transform: none;
line-height: 6;
padding: 16 24; padding: 16 24;
margin: 0; margin: 0;
} }

View file

@ -227,7 +227,7 @@
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
<StackLayout margin="0 16 24"> <StackLayout margin="0 16">
<Label text="References" class="sectionTitle" /> <Label text="References" class="sectionTitle" />
<GridLayout <GridLayout
columns="*,8,auto" columns="*,8,auto"
@ -254,6 +254,36 @@
text="+ ADD REFERENCE" text="+ ADD REFERENCE"
@tap="addReference" @tap="addReference"
/> />
<StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout>
<StackLayout margin="0 16 24" v-if="recipes.length">
<Label text="Combinations" class="sectionTitle" />
<GridLayout
columns="*,8,auto"
v-for="(combination, index) in recipeContent.combinations"
:key="index"
>
<TextField
class="combinationToken"
col="0"
:text="getCombinationTitle(combination)"
editable="false"
/>
<MDButton
variant="text"
col="2"
class="bx closeBtn"
:text="icon.close"
@tap="removeCombination(combination)"
/>
</GridLayout>
<MDButton
variant="text"
class="text-btn orkm"
text="+ ADD COMBINATION"
@tap="showCombinations"
/>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
@ -273,6 +303,7 @@ import {
path, path,
Screen, Screen,
Utils, Utils,
ObservableArray,
} from "@nativescript/core" } from "@nativescript/core"
import * as Permissions from "@nativescript-community/perms" import * as Permissions from "@nativescript-community/perms"
import * as Toast from "nativescript-toast" import * as Toast from "nativescript-toast"
@ -281,13 +312,13 @@ import { ImageCropper } from "nativescript-imagecropper"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
import ActionDialog from "./modal/ActionDialog.vue" import ActionDialog from "./modal/ActionDialog.vue"
import ActionDialogWithSearch from "./modal/ActionDialogWithSearch.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import PromptDialog from "./modal/PromptDialog.vue" import PromptDialog from "./modal/PromptDialog.vue"
import ListPicker from "./modal/ListPicker.vue" import ListPicker from "./modal/ListPicker.vue"
export default { export default {
props: [ props: [
"recipeIndex",
"recipeID", "recipeID",
"selectedCategory", "selectedCategory",
"openAppSettingsPage", "openAppSettingsPage",
@ -311,6 +342,7 @@ export default {
instructions: [], instructions: [],
notes: [], notes: [],
references: [], references: [],
combinations: [],
isFavorite: false, isFavorite: false,
tried: false, tried: false,
lastTried: null, lastTried: null,
@ -323,6 +355,7 @@ export default {
showFab: false, showFab: false,
imageLoading: false, imageLoading: false,
cacheImagePath: null, cacheImagePath: null,
unSyncCombinations: [],
} }
}, },
computed: { computed: {
@ -360,6 +393,7 @@ export default {
"overwriteRecipeAction", "overwriteRecipeAction",
"addCategoryAction", "addCategoryAction",
"addYieldUnitAction", "addYieldUnitAction",
"unSyncCombinationsAction",
]), ]),
onPageLoad() { onPageLoad() {
this.showFab = true this.showFab = true
@ -367,7 +401,7 @@ export default {
// HELPERS // HELPERS
focusField(args, type) { focusField(args, type) {
this.setInputTypeText(args, type) if (type) this.setInputTypeText(args, type)
if (!args.object.text) { if (!args.object.text) {
args.object.focus() args.object.focus()
setTimeout((e) => Utils.ad.showSoftInput(args.object.android), 1) setTimeout((e) => Utils.ad.showSoftInput(args.object.android), 1)
@ -719,15 +753,46 @@ export default {
} else this.recipeContent.instructions.splice(index, 1) } else this.recipeContent.instructions.splice(index, 1)
}, },
getCombinationTitle(id) {
return this.recipes.filter((e) => e.id === id)[0].title
},
showCombinations() {
let existingCombinations = [
...this.recipeContent.combinations,
this.recipeContent.id,
]
console.log(existingCombinations)
let filteredRecipes = this.recipes.filter(
(e) => !existingCombinations.includes(e.id)
)
this.$showModal(ActionDialogWithSearch, {
props: {
title: "Select a recipe",
recipes: filteredRecipes,
},
}).then((res) => {
if (res) {
this.recipeContent.combinations.push(res)
}
})
},
removeCombination(id) {
let index = this.recipeContent.combinations.indexOf(id)
this.fieldDeletionConfirm("combination").then((res) => {
if (res) {
this.recipeContent.combinations.splice(index, 1)
this.unSyncCombinations.push(id)
}
})
},
addNote() { addNote() {
this.recipeContent.notes.push("") this.recipeContent.notes.push("")
}, },
removeNote(index) { removeNote(index) {
if (this.recipeContent.notes[index].length) { if (this.recipeContent.notes[index].length) {
this.fieldDeletionConfirm("note").then((res) => { this.fieldDeletionConfirm("note").then((res) => {
if (res) { if (res) this.recipeContent.notes.splice(index, 1)
this.recipeContent.notes.splice(index, 1)
}
}) })
} else this.recipeContent.notes.splice(index, 1) } else this.recipeContent.notes.splice(index, 1)
}, },
@ -789,12 +854,15 @@ export default {
} else if (this.tempRecipeContent.imageSrc) { } else if (this.tempRecipeContent.imageSrc) {
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc) getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
} }
this.unSyncCombinationsAction({
id: this.recipeID,
combinations: this.unSyncCombinations,
})
this.saveRecipe() this.saveRecipe()
}, },
saveRecipe() { saveRecipe() {
if (this.recipeID) { if (this.recipeID) {
this.overwriteRecipeAction({ this.overwriteRecipeAction({
index: this.recipeIndex,
id: this.recipeID, id: this.recipeID,
recipe: this.recipeContent, recipe: this.recipeContent,
}) })

View file

@ -384,7 +384,6 @@ export default {
// }, // },
props: { props: {
filterTrylater: this.filterTrylater, filterTrylater: this.filterTrylater,
recipeIndex: index,
recipeID: item.id, recipeID: item.id,
hijackGlobalBackEvent: this.hijackGlobalBackEvent, hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent, releaseGlobalBackEvent: this.releaseGlobalBackEvent,

View file

@ -13,8 +13,8 @@
/> />
<ScrollView <ScrollView
row="1" row="1"
col="1" col="0"
colSpan="3" colSpan="4"
orientation="horizontal" orientation="horizontal"
scrollBarIndicatorVisible="false" scrollBarIndicatorVisible="false"
> >
@ -102,8 +102,8 @@
<Label :text="` ${formattedTime(recipe.timeRequired)}`" /> <Label :text="` ${formattedTime(recipe.timeRequired)}`" />
</StackLayout> </StackLayout>
<GridLayout <GridLayout
rows="auto, auto" rows="auto, auto, auto"
columns="*, *" columns="*, *"
class="overviewContainer" class="overviewContainer"
> >
<GridLayout <GridLayout
@ -142,12 +142,15 @@
class="itemCount" class="itemCount"
:text=" :text="
`${recipe.instructions.length} ${ `${recipe.instructions.length} ${
recipe.instructions.length == 1 ? 'Step' : 'Steps' recipe.instructions.length == 1
? 'Instruction'
: 'Instructions'
}` }`
" "
textWrap="true" textWrap="true"
/> />
</GridLayout> </GridLayout>
<GridLayout <GridLayout
class="overviewItem" class="overviewItem"
row="1" row="1"
@ -190,6 +193,28 @@
textWrap="true" textWrap="true"
/> />
</GridLayout> </GridLayout>
<GridLayout
class="overviewItem"
row="2"
col="0"
rows="auto, auto"
columns="*"
>
<MDRipple rowSpan="2" @tap="selectedTabIndex = 5" />
<Label row="0" class="bx" :text="icon.outline" />
<Label
row="1"
class="itemCount"
:text="
`${recipe.combinations.length} ${
recipe.combinations.length == 1
? 'Combination'
: 'Combinations'
}`
"
textWrap="true"
/>
</GridLayout>
</GridLayout> </GridLayout>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
@ -415,6 +440,44 @@
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
<TabViewItem title="Combinations">
<ScrollView scrollBarIndicatorVisible="false">
<GridLayout
v-if="!recipe.combinations.length"
rows="*, auto, *, 88"
columns="*"
class="emptyStateContainer"
>
<StackLayout col="0" row="1" class="emptyState">
<Label class="bx icon" :text="icon.outline" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " />
<Label class="bx" :text="icon.edit" />
<Label text=" button" />
</StackLayout>
<Label text="to add some combinations" textWrap="true" />
</StackLayout>
</GridLayout>
<StackLayout v-else padding="10 0 132">
<GridLayout
columns="*"
v-for="(combination, index) in recipe.combinations"
:key="index"
androidElevation="1"
class="referenceItem"
>
<MDRipple col="0" @tap="viewCombination(combination)" />
<Label
col="0"
verticalAlignment="center"
class="recipeLink"
:text="getCombinationTitle(combination)"
textWrap="true"
/>
</GridLayout>
</StackLayout>
</ScrollView>
</TabViewItem>
</TabView> </TabView>
<GridLayout id="btnFabContainer" rows="*, auto" columns="*, auto"> <GridLayout id="btnFabContainer" rows="*, auto" columns="*, auto">
<MDFloatingActionButton <MDFloatingActionButton
@ -440,6 +503,7 @@
<script> <script>
import { import {
ApplicationSettings,
Color, Color,
Device, Device,
File, File,
@ -457,6 +521,7 @@ import { Application } from "@nativescript/core"
import { mapActions, mapState } from "vuex" import { mapActions, mapState } from "vuex"
import EditRecipe from "./EditRecipe.vue" import EditRecipe from "./EditRecipe.vue"
import ViewRecipe from "./ViewRecipe.vue"
import ShareChooser from "./modal/ShareChooser.vue" import ShareChooser from "./modal/ShareChooser.vue"
let feedback = new Feedback() let feedback = new Feedback()
@ -464,8 +529,8 @@ let feedback = new Feedback()
export default { export default {
props: [ props: [
"filterTrylater", "filterTrylater",
"recipeIndex",
"recipeID", "recipeID",
"recipeIndex",
"hijackGlobalBackEvent", "hijackGlobalBackEvent",
"releaseGlobalBackEvent", "releaseGlobalBackEvent",
], ],
@ -476,6 +541,7 @@ export default {
recipe: null, recipe: null,
showFab: false, showFab: false,
selectedTabIndex: 0, selectedTabIndex: 0,
currentRecipeID: this.recipeID,
} }
}, },
computed: { computed: {
@ -491,7 +557,11 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(["toggleStateAction", "setCurrentComponentAction"]), ...mapActions([
"toggleStateAction",
"setCurrentComponentAction",
"overwriteRecipeAction",
]),
onPageLoad() { onPageLoad() {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.busy = false this.busy = false
@ -501,6 +571,7 @@ export default {
this.yieldMultiplier = this.recipe.yield.quantity this.yieldMultiplier = this.recipe.yield.quantity
this.showFab = true this.showFab = true
this.keepScreenOn(true) this.keepScreenOn(true)
this.syncCombinations()
}, },
onPageUnload() { onPageUnload() {
feedback.hide() feedback.hide()
@ -569,24 +640,45 @@ export default {
let pattern = new RegExp("^https?|www", "ig") let pattern = new RegExp("^https?|www", "ig")
return pattern.test(string) return pattern.test(string)
}, },
getCombinationTitle(id) {
return this.recipes.filter((e) => e.id === id)[0].title
},
syncCombinations() {
let combinationForOtherRecipes = this.recipes
.filter(
(e) =>
e.combinations.indexOf(this.currentRecipeID) >= 0 ||
this.recipe.combinations.includes(e.id)
)
.map((e) => e.id)
this.recipe.combinations = combinationForOtherRecipes
this.overwriteRecipeAction({
id: this.currentRecipeID,
recipe: this.recipe,
})
},
// NAVIGATION HANDLERS // NAVIGATION HANDLERS
editRecipe() { editRecipe() {
this.showFab = false this.showFab = false
this.busy = true this.busy = true
this.$navigateTo(EditRecipe, { this.$navigateTo(EditRecipe, {
// transition: {
// name: "slide",
// duration: 250,
// curve: "easeOut",
// },
props: { props: {
recipeIndex: this.recipeIndex, recipeID: this.currentRecipeID,
recipeID: this.recipeID,
}, },
// backstackVisible: false,
}) })
}, },
viewCombination(combination) {
this.recipe = this.recipes.filter((e) => e.id === combination)[0]
this.currentRecipeID = combination
this.syncCombinations()
this.selectedTabIndex = 0
setTimeout(
(e) =>
this.recipe.tried && this.recipe.lastTried && this.showLastTried(),
500
)
},
// SHARE ACTION // SHARE ACTION
shareHandler() { shareHandler() {
@ -598,20 +690,6 @@ export default {
}).then((result) => { }).then((result) => {
switch (result) { switch (result) {
case "photo": case "photo":
// let cacheFilePath = path.join(
// knownFolders.temp().path,
// `${this.recipe.title}.jpg`
// )
// if (!File.exists(cacheFilePath)) {
// File.fromPath(cacheFilePath).writeSync(
// File.fromPath(this.recipe.imageSrc).readSync()
// )
// }
// let shareFile = new ShareFile()
// shareFile.open({
// path: cacheFilePath,
// title: "Share recipe photo using",
// })
ImageSource.fromFile(this.recipe.imageSrc).then((res) => { ImageSource.fromFile(this.recipe.imageSrc).then((res) => {
SocialShare.shareImage(res, "Share recipe photo using") SocialShare.shareImage(res, "Share recipe photo using")
}) })
@ -653,6 +731,13 @@ export default {
}) })
shareContent += instructions shareContent += instructions
} }
if (this.recipe.combinations.length) {
let combinations = `\nCombinations:\n\n`
this.recipe.combinations.forEach((e, i) => {
combinations += `${i + 1}. ${this.getCombinationTitle(e)}\n\n`
})
shareContent += combinations
}
if (this.recipe.notes.length) { if (this.recipe.notes.length) {
let notes = `\nNotes:\n\n` let notes = `\nNotes:\n\n`
this.recipe.notes.forEach((e, i) => { this.recipe.notes.forEach((e, i) => {
@ -677,8 +762,7 @@ export default {
// DATA HANDLERS // DATA HANDLERS
toggle(key, setDate) { toggle(key, setDate) {
this.toggleStateAction({ this.toggleStateAction({
index: this.recipeIndex, id: this.currentRecipeID,
id: this.recipeID,
recipe: this.recipe, recipe: this.recipe,
key, key,
setDate, setDate,
@ -716,7 +800,7 @@ export default {
}, },
}, },
created() { created() {
this.recipe = this.recipes.filter((e) => e.id === this.recipeID)[0] this.recipe = this.recipes.filter((e) => e.id === this.currentRecipeID)[0]
}, },
mounted() { mounted() {
this.showFab = true this.showFab = true

View file

@ -0,0 +1,94 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" />
<StackLayout padding="0 24 24">
<TextField hint="Search" v-model="searchTerm" />
</StackLayout>
<ScrollView width="100%" :height="height ? height : screenHeight - 320">
<StackLayout>
<MDButton
v-for="(recipe, index) in filteredRecipes"
:key="index"
class="actionItem"
variant="text"
:rippleColor="rippleColor"
:text="recipe.title"
@loaded="onLabelLoaded"
@tap="tapAction(recipe)"
/>
<Label
padding="0 24"
lineHeight="6"
v-if="!filteredRecipes.length"
text="Nothing here! Add some recipes that goes well with this and try again."
horizontalAlignment="center"
textWrap="true"
/>
</StackLayout>
</ScrollView>
<GridLayout rows="auto" columns="auto, *, auto" class="actionsContainer">
<MDButton
:rippleColor="rippleColor"
variant="text"
v-if="action"
col="0"
class="action orkm pull-left"
:text="action"
@tap="$modal.close(action)"
/>
<MDButton
:rippleColor="rippleColor"
variant="text"
col="2"
class="action orkm pull-right"
text="CANCEL"
@tap="$modal.close(false)"
/>
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application, Screen } from "@nativescript/core"
export default {
props: ["title", "recipes", "height", "action"],
data() {
return {
searchTerm: "",
}
},
computed: {
appTheme() {
return Application.systemAppearance()
},
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
},
screenHeight() {
return Math.round(Screen.mainScreen.heightDIPs)
},
filteredRecipes() {
return this.recipes
.map((e, i) => {
return {
id: e.id,
title: e.title,
}
})
.filter((e) => e.title.includes(this.searchTerm))
},
},
methods: {
tapAction(recipe) {
this.$modal.close(recipe.id)
},
onLabelLoaded(args) {
args.object.android.setGravity(16)
},
},
}
</script>

View file

@ -20,9 +20,6 @@ Vue.use(FloatingActionButtonPlugin)
import ProgressPlugin from "@nativescript-community/ui-material-progress/vue" import ProgressPlugin from "@nativescript-community/ui-material-progress/vue"
Vue.use(ProgressPlugin) Vue.use(ProgressPlugin)
// import SpeedDialPlugin from "@nativescript-community/ui-material-speeddial/vue"
// Vue.use(SpeedDialPlugin)
Vue.registerElement( Vue.registerElement(
"RadSideDrawer", "RadSideDrawer",
() => require("nativescript-ui-sidedrawer").RadSideDrawer () => require("nativescript-ui-sidedrawer").RadSideDrawer

View file

@ -182,6 +182,7 @@ export default new Vuex.Store({
source: "\ueaa0", source: "\ueaa0",
export: "\ued07", export: "\ued07",
import: "\ued0c", import: "\ued0c",
outline: "\ueb07",
}, },
currentComponent: "EnRecipes", currentComponent: "EnRecipes",
}, },
@ -306,7 +307,10 @@ export default new Vuex.Store({
}) })
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits] state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
}, },
overwriteRecipe(state, { index, id, recipe }) { overwriteRecipe(state, { id, recipe }) {
let index = state.recipes.indexOf(
state.recipes.filter((e) => e.id === id)[0]
)
Object.assign(state.recipes[index], recipe) Object.assign(state.recipes[index], recipe)
EnRecipesDB.updateDocument(id, recipe) EnRecipesDB.updateDocument(id, recipe)
}, },
@ -314,8 +318,17 @@ export default new Vuex.Store({
getFileAccess().deleteFile(state.recipes[index].imageSrc) getFileAccess().deleteFile(state.recipes[index].imageSrc)
state.recipes.splice(index, 1) state.recipes.splice(index, 1)
EnRecipesDB.deleteDocument(id) EnRecipesDB.deleteDocument(id)
state.recipes.forEach((e, i) => {
if (e.combinations.includes(id)) {
state.recipes[i].combinations.splice(e.combinations.indexOf(id), 1)
EnRecipesDB.updateDocument(state.recipes[i].id, state.recipes[i])
}
})
}, },
toggleState(state, { index, id, recipe, key, setDate }) { toggleState(state, { id, recipe, key, setDate }) {
let index = state.recipes.indexOf(
state.recipes.filter((e) => e.id === id)[0]
)
state.recipes[index][key] = !state.recipes[index][key] state.recipes[index][key] = !state.recipes[index][key]
if (setDate) state.recipes[index].lastTried = new Date() if (setDate) state.recipes[index].lastTried = new Date()
EnRecipesDB.updateDocument(id, recipe) EnRecipesDB.updateDocument(id, recipe)
@ -346,6 +359,14 @@ export default new Vuex.Store({
} }
}) })
}, },
unSyncCombinations(state, { id, combinations }) {
state.recipes.forEach((e, i) => {
if (combinations.includes(e.id)) {
state.recipes[i].combinations.splice(e.combinations.indexOf(id), 1)
EnRecipesDB.updateDocument(state.recipes[i].id, state.recipes[i])
}
})
},
}, },
actions: { actions: {
initializeRecipes({ commit }) { initializeRecipes({ commit }) {
@ -393,5 +414,8 @@ export default new Vuex.Store({
renameCategoryAction({ commit }, category) { renameCategoryAction({ commit }, category) {
commit("renameCategory", category) commit("renameCategory", category)
}, },
unSyncCombinationsAction({ commit }, combinations) {
commit("unSyncCombinations", combinations)
},
}, },
}) })