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

View file

@ -227,7 +227,7 @@
<StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout>
<StackLayout margin="0 16 24">
<StackLayout margin="0 16">
<Label text="References" class="sectionTitle" />
<GridLayout
columns="*,8,auto"
@ -254,6 +254,36 @@
text="+ ADD REFERENCE"
@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>
</ScrollView>
@ -273,6 +303,7 @@ import {
path,
Screen,
Utils,
ObservableArray,
} from "@nativescript/core"
import * as Permissions from "@nativescript-community/perms"
import * as Toast from "nativescript-toast"
@ -281,13 +312,13 @@ import { ImageCropper } from "nativescript-imagecropper"
import { mapState, mapActions } from "vuex"
import ActionDialog from "./modal/ActionDialog.vue"
import ActionDialogWithSearch from "./modal/ActionDialogWithSearch.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue"
import PromptDialog from "./modal/PromptDialog.vue"
import ListPicker from "./modal/ListPicker.vue"
export default {
props: [
"recipeIndex",
"recipeID",
"selectedCategory",
"openAppSettingsPage",
@ -311,6 +342,7 @@ export default {
instructions: [],
notes: [],
references: [],
combinations: [],
isFavorite: false,
tried: false,
lastTried: null,
@ -323,6 +355,7 @@ export default {
showFab: false,
imageLoading: false,
cacheImagePath: null,
unSyncCombinations: [],
}
},
computed: {
@ -360,6 +393,7 @@ export default {
"overwriteRecipeAction",
"addCategoryAction",
"addYieldUnitAction",
"unSyncCombinationsAction",
]),
onPageLoad() {
this.showFab = true
@ -367,7 +401,7 @@ export default {
// HELPERS
focusField(args, type) {
this.setInputTypeText(args, type)
if (type) this.setInputTypeText(args, type)
if (!args.object.text) {
args.object.focus()
setTimeout((e) => Utils.ad.showSoftInput(args.object.android), 1)
@ -719,15 +753,46 @@ export default {
} 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() {
this.recipeContent.notes.push("")
},
removeNote(index) {
if (this.recipeContent.notes[index].length) {
this.fieldDeletionConfirm("note").then((res) => {
if (res) {
this.recipeContent.notes.splice(index, 1)
}
if (res) this.recipeContent.notes.splice(index, 1)
})
} else this.recipeContent.notes.splice(index, 1)
},
@ -789,12 +854,15 @@ export default {
} else if (this.tempRecipeContent.imageSrc) {
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
}
this.unSyncCombinationsAction({
id: this.recipeID,
combinations: this.unSyncCombinations,
})
this.saveRecipe()
},
saveRecipe() {
if (this.recipeID) {
this.overwriteRecipeAction({
index: this.recipeIndex,
id: this.recipeID,
recipe: this.recipeContent,
})

View file

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

View file

@ -13,8 +13,8 @@
/>
<ScrollView
row="1"
col="1"
colSpan="3"
col="0"
colSpan="4"
orientation="horizontal"
scrollBarIndicatorVisible="false"
>
@ -102,8 +102,8 @@
<Label :text="` ${formattedTime(recipe.timeRequired)}`" />
</StackLayout>
<GridLayout
rows="auto, auto"
columns="*, *"
rows="auto, auto, auto"
columns="*, *"
class="overviewContainer"
>
<GridLayout
@ -142,12 +142,15 @@
class="itemCount"
:text="
`${recipe.instructions.length} ${
recipe.instructions.length == 1 ? 'Step' : 'Steps'
recipe.instructions.length == 1
? 'Instruction'
: 'Instructions'
}`
"
textWrap="true"
/>
</GridLayout>
<GridLayout
class="overviewItem"
row="1"
@ -190,6 +193,28 @@
textWrap="true"
/>
</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>
</StackLayout>
</StackLayout>
@ -415,6 +440,44 @@
</StackLayout>
</ScrollView>
</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>
<GridLayout id="btnFabContainer" rows="*, auto" columns="*, auto">
<MDFloatingActionButton
@ -440,6 +503,7 @@
<script>
import {
ApplicationSettings,
Color,
Device,
File,
@ -457,6 +521,7 @@ import { Application } from "@nativescript/core"
import { mapActions, mapState } from "vuex"
import EditRecipe from "./EditRecipe.vue"
import ViewRecipe from "./ViewRecipe.vue"
import ShareChooser from "./modal/ShareChooser.vue"
let feedback = new Feedback()
@ -464,8 +529,8 @@ let feedback = new Feedback()
export default {
props: [
"filterTrylater",
"recipeIndex",
"recipeID",
"recipeIndex",
"hijackGlobalBackEvent",
"releaseGlobalBackEvent",
],
@ -476,6 +541,7 @@ export default {
recipe: null,
showFab: false,
selectedTabIndex: 0,
currentRecipeID: this.recipeID,
}
},
computed: {
@ -491,7 +557,11 @@ export default {
},
},
methods: {
...mapActions(["toggleStateAction", "setCurrentComponentAction"]),
...mapActions([
"toggleStateAction",
"setCurrentComponentAction",
"overwriteRecipeAction",
]),
onPageLoad() {
this.releaseGlobalBackEvent()
this.busy = false
@ -501,6 +571,7 @@ export default {
this.yieldMultiplier = this.recipe.yield.quantity
this.showFab = true
this.keepScreenOn(true)
this.syncCombinations()
},
onPageUnload() {
feedback.hide()
@ -569,24 +640,45 @@ export default {
let pattern = new RegExp("^https?|www", "ig")
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
editRecipe() {
this.showFab = false
this.busy = true
this.$navigateTo(EditRecipe, {
// transition: {
// name: "slide",
// duration: 250,
// curve: "easeOut",
// },
props: {
recipeIndex: this.recipeIndex,
recipeID: this.recipeID,
recipeID: this.currentRecipeID,
},
// 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
shareHandler() {
@ -598,20 +690,6 @@ export default {
}).then((result) => {
switch (result) {
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) => {
SocialShare.shareImage(res, "Share recipe photo using")
})
@ -653,6 +731,13 @@ export default {
})
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) {
let notes = `\nNotes:\n\n`
this.recipe.notes.forEach((e, i) => {
@ -677,8 +762,7 @@ export default {
// DATA HANDLERS
toggle(key, setDate) {
this.toggleStateAction({
index: this.recipeIndex,
id: this.recipeID,
id: this.currentRecipeID,
recipe: this.recipe,
key,
setDate,
@ -716,7 +800,7 @@ export default {
},
},
created() {
this.recipe = this.recipes.filter((e) => e.id === this.recipeID)[0]
this.recipe = this.recipes.filter((e) => e.id === this.currentRecipeID)[0]
},
mounted() {
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"
Vue.use(ProgressPlugin)
// import SpeedDialPlugin from "@nativescript-community/ui-material-speeddial/vue"
// Vue.use(SpeedDialPlugin)
Vue.registerElement(
"RadSideDrawer",
() => require("nativescript-ui-sidedrawer").RadSideDrawer

View file

@ -182,6 +182,7 @@ export default new Vuex.Store({
source: "\ueaa0",
export: "\ued07",
import: "\ued0c",
outline: "\ueb07",
},
currentComponent: "EnRecipes",
},
@ -306,7 +307,10 @@ export default new Vuex.Store({
})
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)
EnRecipesDB.updateDocument(id, recipe)
},
@ -314,8 +318,17 @@ export default new Vuex.Store({
getFileAccess().deleteFile(state.recipes[index].imageSrc)
state.recipes.splice(index, 1)
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]
if (setDate) state.recipes[index].lastTried = new Date()
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: {
initializeRecipes({ commit }) {
@ -393,5 +414,8 @@ export default new Vuex.Store({
renameCategoryAction({ commit }, category) {
commit("renameCategory", category)
},
unSyncCombinationsAction({ commit }, combinations) {
commit("unSyncCombinations", combinations)
},
},
})