implemented couchbase, fs image import, zip export

This commit is contained in:
Vishnu Raghav B 2020-10-24 23:32:35 +05:30
parent 7c102fe518
commit 1d7bca959e
13 changed files with 2503 additions and 2892 deletions

View file

@ -9,10 +9,18 @@
// e.g. project.ext.googlePlayServicesVersion = "15.0.1" // e.g. project.ext.googlePlayServicesVersion = "15.0.1"
// create a file named before-plugins.gradle in the current directory and place it there // create a file named before-plugins.gradle in the current directory and place it there
// abiFilter "arm64-v8a" "armeabi-v7a" "x86" "x86_64"
android { android {
defaultConfig { defaultConfig {
minSdkVersion 23 minSdkVersion 23
generatedDensities = [] generatedDensities = []
ndk {
abiFilters.clear()
abiFilters.addAll(['arm64-v8a','x86'])
}
} }
aaptOptions { aaptOptions {
additionalParameters "--no-version-vectors" additionalParameters "--no-version-vectors"

View file

@ -71,7 +71,8 @@ Page {
color: $grayD4; color: $grayD4;
} }
} }
.view-imageHolder { .view-imageHolder,
.recipeImgContainer {
color: $grayL1; color: $grayL1;
background: $grayL2; background: $grayL2;
} }
@ -126,7 +127,8 @@ Page {
color: $grayL4; color: $grayL4;
} }
} }
.view-imageHolder { .view-imageHolder,
.recipeImgContainer {
color: $grayD4; color: $grayD4;
background: #111; background: #111;
} }
@ -239,7 +241,7 @@ RadListView {
margin: 8 16; margin: 8 16;
border-radius: 6; border-radius: 6;
.recipe-info { .recipe-info {
margin: 4 0; margin: 4;
} }
.recipe-cat { .recipe-cat {
font-size: 12; font-size: 12;
@ -255,15 +257,31 @@ RadListView {
.recipe-time { .recipe-time {
padding: 0; padding: 0;
} }
.recipe-favorite { .recipe-cat {
font-size: 12;
padding: 14 8 0 0;
}
.recipe-cat,
.recipe-favorite {
color: $orange; color: $orange;
} }
} }
.noResults {
width: 100%;
font-size: 16;
line-height: 8;
padding: 32 16;
text-align: center;
}
.swipe-item {
margin: 0 8;
background: #c62828;
color: #fff;
height: 128;
border-radius: 6;
}
.recipeImgContainer {
vertical-alignment: center;
// prettier-ignore
Image {
border-radius: 6 0 0 6;
}
}
// Settings // Settings
.group-header { .group-header {
@ -372,13 +390,13 @@ RadListView {
} }
// Edit Recipe // Edit Recipe
.fab-button { .fab-button {
color: white; color: #fff;
height: 56; height: 56;
width: 56; width: 56;
background-color: #ff7043; background-color: #ff7043;
horizontal-align: center; horizontal-align: center;
vertical-align: center; vertical-align: center;
border-radius: 100; border-radius: 28;
padding: 16; padding: 16;
margin: 16; margin: 16;
} }
@ -411,6 +429,9 @@ RadListView {
padding: 24 24 12; padding: 24 24 12;
font-size: 20; font-size: 20;
} }
.dialogInputField {
padding: 0 24 16;
}
.dialogDescription { .dialogDescription {
font-size: 16; font-size: 16;
padding: 0 24 16; padding: 0 24 16;

View file

@ -34,6 +34,7 @@
class="sd-group-header orkm" class="sd-group-header orkm"
rows="auto" rows="auto"
columns="*, auto" columns="*, auto"
v-if="categories.length"
> >
<Label col="0" text="Categories" /> <Label col="0" text="Categories" />
<Label <Label
@ -103,6 +104,7 @@
<!-- Home --> <!-- Home -->
<EnRecipes <EnRecipes
ref="enrecipes" ref="enrecipes"
:passedRecipes="recipes"
:filterFavorites="filterFavorites" :filterFavorites="filterFavorites"
:filterMustTry="filterMustTry" :filterMustTry="filterMustTry"
:selectedCategory="selectedCategory" :selectedCategory="selectedCategory"
@ -132,8 +134,9 @@ import About from "./About.vue"
import PromptDialog from "./modal/PromptDialog.vue" import PromptDialog from "./modal/PromptDialog.vue"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
// import { Couchbase, ConcurrencyMode } from "nativescript-couchbase-plugin" import { Couchbase } from "nativescript-couchbase-plugin"
// const cb = new Couchbase("enrecipes") const cb = new Couchbase("enrecipes")
const cbCat = new Couchbase("categories")
let page let page
export default { export default {
@ -177,10 +180,11 @@ export default {
}, },
], ],
catEditMode: false, catEditMode: false,
recipes: null,
} }
}, },
computed: { computed: {
...mapState(["recipes", "categories", "icon", "currentComponent"]), ...mapState(["icon", "currentComponent"]),
categories() { categories() {
let arr = this.recipes.map((e) => { let arr = this.recipes.map((e) => {
return e.category return e.category
@ -214,9 +218,20 @@ export default {
if (this.categories.includes(result)) { if (this.categories.includes(result)) {
Toast.makeText("Category already exists!", "long").show() Toast.makeText("Category already exists!", "long").show()
} else { } else {
this.renameCategoryAction({ let categories = cbCat.getDocument("categories").categories
current: item, console.log(categories, categories.indexOf(item))
updated: result, categories.splice(categories.indexOf(item), 1)
categories.push(result)
categories.sort()
categories = [...new Set(categories)]
cbCat.updateDocument("categories", {
categories: [...categories],
})
this.recipes.forEach((e, i) => {
if (e.category == item) {
e.category = result
cb.updateDocument(e.id, e)
}
}) })
this.catEditMode = false this.catEditMode = false
} }
@ -293,6 +308,7 @@ export default {
backstackVisible: false, backstackVisible: false,
}) })
this.closeDrawer() this.closeDrawer()
this.catEditMode = false
} else if (!this.catEditMode) { } else if (!this.catEditMode) {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.hijackGlobalBackEvent() this.hijackGlobalBackEvent()
@ -342,40 +358,10 @@ export default {
created() { created() {
let themeName = ApplicationSettings.getString("application-theme", "Light") let themeName = ApplicationSettings.getString("application-theme", "Light")
setTimeout((e) => Theme.setMode(Theme[themeName]), 50) setTimeout((e) => Theme.setMode(Theme[themeName]), 50)
this.recipes = cb.query({ select: [] })
cb.addDatabaseChangeListener((e) => {
this.recipes = cb.query({ select: [] })
})
}, },
} }
</script> </script>
<style lang="scss">
.noResults {
width: 100%;
padding: 16;
font-size: 16;
line-height: 8;
}
.swipe-item {
margin: 0 8;
background: #ff7043;
color: #fff;
height: 128;
border-radius: 6;
}
#btnFabContainer {
width: 100%;
height: 100%;
}
.btnFab {
width: 56;
height: 56;
padding: 16;
background-color: #ff7043;
color: #fff;
border-radius: 28;
text-align: center;
}
// prettier-ignore
Button {
color: #ff7043;
}
</style>

View file

@ -260,7 +260,14 @@
</template> </template>
<script> <script>
import { Screen, AndroidApplication } from "@nativescript/core" import {
Screen,
AndroidApplication,
ImageSource,
path,
getFileAccess,
knownFolders,
} from "@nativescript/core"
import { Mediafilepicker } from "nativescript-mediafilepicker" import { Mediafilepicker } from "nativescript-mediafilepicker"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
@ -269,8 +276,13 @@ import ActionDialog from "./modal/ActionDialog.vue"
import PromptDialog from "./modal/PromptDialog.vue" import PromptDialog from "./modal/PromptDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import { Couchbase } from "nativescript-couchbase-plugin"
import { load } from "@nativescript/core/ui/builder"
const cb = new Couchbase("enrecipes")
const cbCat = new Couchbase("categories")
export default { export default {
props: ["recipeIndex", "selectedCategory"], props: ["recipeID", "selectedCategory"],
data() { data() {
return { return {
title: "New recipe", title: "New recipe",
@ -297,16 +309,67 @@ export default {
lastModified: null, lastModified: null,
}, },
tempRecipeContent: {}, tempRecipeContent: {},
units: [
"unit",
"tsp",
"Tbsp",
"oz",
"cup",
"pt",
"qt",
"lb",
"gal",
"ml",
"L",
"mg",
"g",
"kg",
"mm",
"cm",
"m",
"in",
"°C",
"°F",
],
categories: [
"Appetizers",
"BBQ",
"Beverages",
"Breads",
"Breakfast",
"Desserts",
"Dinner",
"Drinks",
"Healthy",
"Lunch",
"Main dishes",
"Meat",
"Noodles",
"Pasta",
"Poultry",
"Rice",
"Salads",
"Sauces",
"Seafood",
"Side dishes",
"Snacks",
"Soups",
"Undefined",
"Vegan",
"Vegetarian",
],
blockModal: false, blockModal: false,
cbCat: [],
newRecipeID: null,
} }
}, },
computed: { computed: {
...mapState(["icon", "units", "categories", "currentComponent", "recipes"]), ...mapState(["icon", "currentComponent"]),
screenWidth() { screenWidth() {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
hasEnoughDetails() { hasEnoughDetails() {
if (this.recipeIndex) { if (this.recipeID) {
return ( return (
JSON.stringify(this.recipeContent) !== JSON.stringify(this.recipeContent) !==
JSON.stringify(this.tempRecipeContent) JSON.stringify(this.tempRecipeContent)
@ -317,26 +380,38 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions([ ...mapActions(["setCurrentComponentAction"]),
"setCurrentComponentAction",
"overwriteRecipeAction",
"addRecipeAction",
"addCategoryAction",
]),
initializePage() { initializePage() {
setTimeout((e) => { setTimeout((e) => {
this.setCurrentComponentAction("EditRecipe") this.setCurrentComponentAction("EditRecipe")
}, 500) }, 500)
this.title = this.recipeIndex >= 0 ? "Edit recipe" : "New recipe" this.title = this.recipeID ? "Edit recipe" : "New recipe"
if (this.recipeIndex >= 0) { if (this.recipeID) {
Object.assign(this.recipeContent, this.recipes[this.recipeIndex]) let recipe = cb.getDocument(this.recipeID)
Object.assign(this.tempRecipeContent, this.recipes[this.recipeIndex]) Object.assign(this.recipeContent, recipe)
Object.assign(this.tempRecipeContent, recipe)
} else { } else {
if (this.selectedCategory) if (this.selectedCategory)
this.recipeContent.category = this.selectedCategory this.recipeContent.category = this.selectedCategory
Object.assign(this.tempRecipeContent, this.recipeContent) Object.assign(this.tempRecipeContent, this.recipeContent)
this.newRecipeID = this.getRandomID()
} }
this.hijackBackEvent() this.hijackBackEvent()
let isCategoriesStored = cbCat.query({ select: [] }).length
if (isCategoriesStored) {
this.categories = cbCat.getDocument("categories").categories
} else {
cbCat.createDocument({ categories: [...this.categories] }, "categories")
}
},
getRandomID() {
let res = ""
let chars = "abcdefghijklmnopqrstuvwxyz0123456789"
for (let i = 0; i < 10; i++) {
res += chars.charAt(Math.floor(Math.random() * chars.length))
}
return res
}, },
setTime(key, time) { setTime(key, time) {
if (Date.parse(time)) { if (Date.parse(time)) {
@ -375,13 +450,11 @@ export default {
saveRecipe() { saveRecipe() {
this.clearEmptyFields() this.clearEmptyFields()
this.recipeContent.lastModified = new Date() this.recipeContent.lastModified = new Date()
if (this.recipeIndex >= 0) { if (this.recipeID) {
this.overwriteRecipeAction({ cb.updateDocument(this.recipeID, this.recipeContent)
index: this.recipeIndex,
recipe: this.recipeContent,
})
} else { } else {
this.addRecipeAction(this.recipeContent) this.recipeContent.id = this.newRecipeID
cb.createDocument(this.recipeContent, this.newRecipeID)
} }
this.$navigateBack() this.$navigateBack()
}, },
@ -416,7 +489,11 @@ export default {
this.hijackBackEvent() this.hijackBackEvent()
if (result.length) { if (result.length) {
this.recipeContent.category = result this.recipeContent.category = result
this.addCategoryAction(result) this.categories.push(result)
this.categories.sort()
cbCat.updateDocument("categories", {
categories: [...this.categories],
})
} }
}) })
} else if (action) { } else if (action) {
@ -471,7 +548,11 @@ export default {
takePicture() { takePicture() {
let mediafilepicker = new Mediafilepicker() let mediafilepicker = new Mediafilepicker()
let vm = this let vm = this
const options = { width: 800, height: 800, lockSquare: true } const options = {
width: this.screenWidth,
height: this.screenWidth,
lockSquare: true,
}
const androidOptions = { const androidOptions = {
isFreeStyleCropEnabled: true, isFreeStyleCropEnabled: true,
statusBarColor: "black", statusBarColor: "black",
@ -505,8 +586,15 @@ export default {
}, },
}) })
mediafilepicker.on("getFiles", function(res) { mediafilepicker.on("getFiles", function(res) {
let result = res.object.get("results") let result = res.object.get("results")[0].file
vm.recipeContent.imageSrc = result[0].file ImageSource.fromFile(result).then((savedImg) => {
let savedImgPath = path.join(
knownFolders.documents().getFolder("enrecipes").path,
`${vm.getRandomID()}.jpg`
)
savedImg.saveToFile(savedImgPath, "jpg")
vm.recipeContent.imageSrc = savedImgPath
})
}) })
}, },
removePicture() { removePicture() {
@ -516,7 +604,10 @@ export default {
okButtonText: "Delete", okButtonText: "Delete",
cancelButtonText: "Cancel", cancelButtonText: "Cancel",
}).then((e) => { }).then((e) => {
if (e) this.recipeContent.imageSrc = null if (e) {
getFileAccess().deleteFile(this.recipeContent.imageSrc)
this.recipeContent.imageSrc = null
}
}) })
}, },

View file

@ -39,15 +39,27 @@
col="0" col="0"
/> />
<Label class="title orkm" :text="currentComponent" col="1" /> <Label class="title orkm" :text="currentComponent" col="1" />
<Label class="bx" :text="icon.search" col="2" @tap="openSearch" /> <Label
<Label class="bx" :text="icon.sort" col="3" @tap="sortDialog" /> v-if="passedRecipes.length"
class="bx"
:text="icon.search"
col="2"
@tap="openSearch"
/>
<Label
v-if="passedRecipes.length"
class="bx"
:text="icon.sort"
col="3"
@tap="sortDialog"
/>
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<AbsoluteLayout> <AbsoluteLayout>
<RadListView <RadListView
ref="listView" ref="listView"
itemHeight="112" itemHeight="112"
for="recipe in recipes" for="recipe in passedRecipes"
swipeActions="true" swipeActions="true"
@itemSwipeProgressChanged="onSwiping" @itemSwipeProgressChanged="onSwiping"
@itemSwipeProgressEnded="onSwipeEnded" @itemSwipeProgressEnded="onSwipeEnded"
@ -63,7 +75,24 @@
columns="112, *" columns="112, *"
androidElevation="1" androidElevation="1"
> >
<Image col="0" src="res://icon" stretch="fill" /> <GridLayout class="recipeImgContainer" rows="112" columns="112">
<Image
row="0"
col="0"
v-if="recipe.imageSrc"
:src="recipe.imageSrc"
stretch="aspectFill"
/>
<Label
row="0"
col="0"
v-else
horizontalAlignment="center"
class="bx"
fontSize="56"
:text="icon.image"
/>
</GridLayout>
<StackLayout class="recipe-info" col="1"> <StackLayout class="recipe-info" col="1">
<Label :text="recipe.category" class="orkm recipe-cat" /> <Label :text="recipe.category" class="orkm recipe-cat" />
<Label :text="recipe.title" class="orkm recipe-title" /> <Label :text="recipe.title" class="orkm recipe-title" />
@ -86,37 +115,29 @@
</v-template> </v-template>
</RadListView> </RadListView>
<Label <Label
v-if="!recipes.length && !filterFavorites && !filterMustTry" v-if="!passedRecipes.length && !filterFavorites && !filterMustTry"
class="noResults" class="noResults"
horizontalAlignment="center"
text='Click the "+" icon to add a new recipe.' text='Click the "+" icon to add a new recipe.'
textAlignment="center"
textWrap="true" textWrap="true"
/> />
<Label <Label
v-if="!filteredRecipes.length && searchQuery" v-if="!filteredRecipes.length && searchQuery"
class="noResults" class="noResults"
horizontalAlignment="center"
:text=" :text="
`Your search &quot;${searchQuery}&quot; did not match any recipes in this category.` `Your search &quot;${searchQuery}&quot; did not match any recipes in this category.`
" "
textAlignment="center"
textWrap="true" textWrap="true"
/> />
<Label <Label
v-if="!filteredRecipes.length && filterFavorites && !searchQuery" v-if="!filteredRecipes.length && filterFavorites && !searchQuery"
class="noResults" class="noResults"
horizontalAlignment="center"
text="Your favorite recipes will be listed here." text="Your favorite recipes will be listed here."
textAlignment="center"
textWrap="true" textWrap="true"
/> />
<Label <Label
v-if="!filteredRecipes.length && filterMustTry && !searchQuery" v-if="!filteredRecipes.length && filterMustTry && !searchQuery"
class="noResults" class="noResults"
horizontalAlignment="center" text="Your must-try recipes will be listed here."
text="Your Must-Try recipes will be listed here."
textAlignment="center"
textWrap="true" textWrap="true"
/> />
<GridLayout id="btnFabContainer" rows="*,88" columns="*,88"> <GridLayout id="btnFabContainer" rows="*,88" columns="*,88">
@ -143,8 +164,12 @@ import ActionDialog from "./modal/ActionDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
import { Couchbase } from "nativescript-couchbase-plugin"
const cb = new Couchbase("enrecipes")
export default { export default {
props: [ props: [
"passedRecipes",
"filterFavorites", "filterFavorites",
"filterMustTry", "filterMustTry",
"selectedCategory", "selectedCategory",
@ -161,31 +186,31 @@ export default {
searchQuery: "", searchQuery: "",
viewIsScrolled: false, viewIsScrolled: false,
showSearch: false, showSearch: false,
// leftAction: false,
rightAction: false, rightAction: false,
sortType: "Natural order", sortType: "Natural order",
deletionDialogActive: false,
} }
}, },
computed: { computed: {
...mapState(["recipes", "icon", "currentComponent"]), ...mapState(["icon", "currentComponent"]),
filteredRecipes() { filteredRecipes() {
if (this.filterFavorites) { if (this.filterFavorites) {
return this.recipes.filter( return this.passedRecipes.filter(
(e) => (e) =>
e.isFavorite && e.title.toLowerCase().includes(this.searchQuery) e.isFavorite && e.title.toLowerCase().includes(this.searchQuery)
) )
} else if (this.filterMustTry) { } else if (this.filterMustTry) {
return this.recipes.filter( return this.passedRecipes.filter(
(e) => !e.tried && e.title.toLowerCase().includes(this.searchQuery) (e) => !e.tried && e.title.toLowerCase().includes(this.searchQuery)
) )
} else if (this.selectedCategory) { } else if (this.selectedCategory) {
return this.recipes.filter( return this.passedRecipes.filter(
(e) => (e) =>
e.category === this.selectedCategory && e.category === this.selectedCategory &&
e.title.toLowerCase().includes(this.searchQuery) e.title.toLowerCase().includes(this.searchQuery)
) )
} else { } else {
return this.recipes.filter((e) => return this.passedRecipes.filter((e) =>
e.title.toLowerCase().includes(this.searchQuery) e.title.toLowerCase().includes(this.searchQuery)
) )
} }
@ -193,6 +218,15 @@ export default {
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction", "deleteRecipeAction"]), ...mapActions(["setCurrentComponentAction", "deleteRecipeAction"]),
initializePage() {
this.filterFavorites
? this.setComponent("Favorites")
: this.filterMustTry
? this.setComponent("Must-Try")
: this.selectedCategory
? this.setComponent(this.selectedCategory)
: this.setComponent("EnRecipes")
},
openSearch() { openSearch() {
this.showSearch = true this.showSearch = true
this.hijackLocalBackEvent() this.hijackLocalBackEvent()
@ -297,15 +331,6 @@ export default {
} }
}, },
initializePage() {
this.filterFavorites
? this.setComponent("Favorites")
: this.filterMustTry
? this.setComponent("Must-Try")
: this.selectedCategory
? this.setComponent(this.selectedCategory)
: this.setComponent("EnRecipes")
},
onSwiping({ data, object }) { onSwiping({ data, object }) {
const swipeLimits = data.swipeLimits const swipeLimits = data.swipeLimits
const swipeView = object const swipeView = object
@ -318,21 +343,25 @@ export default {
} }
}, },
onSwipeEnded({ index }) { onSwipeEnded({ index }) {
if (this.rightAction) this.deleteRecipe(index) let recipeID = this.passedRecipes[index].id
if (this.rightAction && !this.deletionDialogActive)
this.deleteRecipe(index, recipeID)
this.rightAction = false this.rightAction = false
}, },
deleteRecipe(index) { deleteRecipe(index, recipeID) {
this.deletionDialogActive = true
this.$showModal(ConfirmDialog, { this.$showModal(ConfirmDialog, {
props: { props: {
title: "Delete recipe", title: "Delete recipe",
description: `Are you sure you want to delete the recipe "${this.recipes[index].title}"?`, description: `Are you sure you want to delete the recipe "${this.passedRecipes[index].title}"?`,
cancelButtonText: "CANCEL", cancelButtonText: "CANCEL",
okButtonText: "DELETE", okButtonText: "DELETE",
}, },
}).then((action) => { }).then((action) => {
if (action) { if (action) {
this.deleteRecipeAction(index) cb.deleteDocument(recipeID)
} }
this.deletionDialogActive = false
}) })
}, },
getTotalTime(prepTime, cookTime) { getTotalTime(prepTime, cookTime) {
@ -384,7 +413,7 @@ export default {
curve: "easeIn", curve: "easeIn",
}, },
props: { props: {
recipeIndex: this.recipes.indexOf(item), recipeID: item.id,
hijackGlobalBackEvent: this.hijackGlobalBackEvent, hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent, releaseGlobalBackEvent: this.releaseGlobalBackEvent,
}, },

View file

@ -58,8 +58,15 @@
</template> </template>
<script> <script>
import { ApplicationSettings } from "@nativescript/core" import {
ApplicationSettings,
path,
getFileAccess,
knownFolders,
Application,
} from "@nativescript/core"
import * as permissions from "nativescript-permissions" import * as permissions from "nativescript-permissions"
import { Zip } from "nativescript-zip"
import Theme from "@nativescript/theme" import Theme from "@nativescript/theme"
import ActionDialog from "./modal/ActionDialog.vue" import ActionDialog from "./modal/ActionDialog.vue"
@ -164,7 +171,24 @@ export default {
permissions permissions
.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) .requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.then(() => { .then(() => {
alert("Backup successful!") const sdDownloadPath = android.os.Environment.getExternalStoragePublicDirectory(
android.os.Environment.DIRECTORY_DOWNLOADS
).toString()
let fromPath = path.join(knownFolders.documents().path, "enrecipes")
let destPath = path.join(sdDownloadPath, "enrecipes.zip")
console.log(fromPath, destPath, sdDownloadPath)
Zip.zip({
directory: fromPath,
archive: destPath,
})
.then((success) => {
console.log("success:" + success)
})
.catch((err) => {
console.log(err)
})
// console.log(fromPath, destPath, sdDownloadPath)
// alert("Backup successful!")
}) })
.catch(() => { .catch(() => {
console.log("Uh oh, no permissions - plan B time!") console.log("Uh oh, no permissions - plan B time!")

View file

@ -95,9 +95,15 @@
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
<TabViewItem title="Ingredients" v-if="recipe.ingredients.length"> <TabViewItem title="Ingredients">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<StackLayout padding="16 16 124"> <Label
v-if="!recipe.ingredients.length"
class="noResults"
text="Click the edit button to add ingredients to this recipe"
textWrap="true"
/>
<StackLayout v-else padding="16 16 124">
<AbsoluteLayout class="inputField"> <AbsoluteLayout class="inputField">
<TextField <TextField
width="50%" width="50%"
@ -135,9 +141,15 @@
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
<TabViewItem title="Instructions" v-if="recipe.instructions.length"> <TabViewItem title="Instructions">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<StackLayout padding="32 16 132"> <Label
v-if="!recipe.instructions.length"
class="noResults"
text="Click the edit button to add instructions to this recipe"
textWrap="true"
/>
<StackLayout v-else padding="32 16 132">
<GridLayout <GridLayout
columns="auto ,*" columns="auto ,*"
v-for="(instruction, index) in recipe.instructions" v-for="(instruction, index) in recipe.instructions"
@ -165,9 +177,15 @@
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
<TabViewItem title="Notes" v-if="recipe.notes.length"> <TabViewItem title="Notes">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<StackLayout padding="32 16 132"> <Label
v-if="!recipe.notes.length"
class="noResults"
text="Click the edit button to add notes to this recipe"
textWrap="true"
/>
<StackLayout v-else padding="32 16 132">
<GridLayout <GridLayout
columns="auto ,*" columns="auto ,*"
v-for="(note, index) in recipe.notes" v-for="(note, index) in recipe.notes"
@ -191,9 +209,15 @@
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
<TabViewItem title="References" v-if="recipe.references.length"> <TabViewItem title="References">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<StackLayout padding="32 16 132"> <Label
v-if="!recipe.references.length"
class="noResults"
text="Click the edit button to add references to this recipe"
textWrap="true"
/>
<StackLayout v-else padding="32 16 132">
<GridLayout <GridLayout
columns="auto ,*" columns="auto ,*"
v-for="(reference, index) in recipe.references" v-for="(reference, index) in recipe.references"
@ -243,19 +267,20 @@ import { mapState, mapActions } from "vuex"
import EditRecipe from "./EditRecipe.vue" import EditRecipe from "./EditRecipe.vue"
import { Couchbase } from "nativescript-couchbase-plugin"
const cb = new Couchbase("enrecipes")
export default { export default {
props: ["recipeIndex", "hijackGlobalBackEvent", "releaseGlobalBackEvent"], props: ["recipeID", "hijackGlobalBackEvent", "releaseGlobalBackEvent"],
data() { data() {
return { return {
busy: false, busy: false,
portionScale: 1, portionScale: 1,
recipe: null,
} }
}, },
computed: { computed: {
...mapState(["icon", "recipes"]), ...mapState(["icon"]),
recipe() {
return this.recipes[this.recipeIndex]
},
screenWidth() { screenWidth() {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
@ -266,11 +291,15 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions([ ...mapActions(["toggleMustTryAction", "setCurrentComponentAction"]),
"toggleFavoriteAction", initializePage() {
"toggleMustTryAction", this.recipe = cb.getDocument(this.recipeID)
"setCurrentComponentAction", this.releaseGlobalBackEvent()
]), this.busy = false
setTimeout((e) => {
this.setCurrentComponentAction("ViewRecipe")
}, 500)
},
roundedQuantity(quantity, unit) { roundedQuantity(quantity, unit) {
return Math.round(quantity * this.isPortionScalePositive * 100) / 100 return Math.round(quantity * this.isPortionScalePositive * 100) / 100
}, },
@ -283,24 +312,27 @@ export default {
curve: "easeIn", curve: "easeIn",
}, },
props: { props: {
recipeIndex: this.recipeIndex, recipeID: this.recipeID,
}, },
// backstackVisible: false, // backstackVisible: false,
}) })
}, },
toggle(key) {
this.recipe[key] = !this.recipe[key]
cb.updateDocument(this.recipeID, this.recipe)
this.recipe = cb.getDocument(this.recipeID)
},
toggleFavorite() { toggleFavorite() {
this.recipe.isFavorite this.recipe.isFavorite
? Toast.makeText("Removed from Favorites").show() ? Toast.makeText("Removed from Favorites").show()
: Toast.makeText("Added to Favorites").show() : Toast.makeText("Added to Favorites").show()
this.toggle("isFavorite")
this.toggleFavoriteAction(this.recipeIndex)
}, },
toggleMustTry() { toggleMustTry() {
this.recipe.tried this.recipe.tried
? Toast.makeText("Added to Must-Try").show() ? Toast.makeText("Added to Must-Try").show()
: Toast.makeText("Removed from Must-Try").show() : Toast.makeText("Removed from Must-Try").show()
this.toggle("tried")
this.toggleMustTryAction(this.recipeIndex)
}, },
getTime(time) { getTime(time) {
let t = time.split(":") let t = time.split(":")
@ -311,13 +343,9 @@ export default {
openURL(args, url) { openURL(args, url) {
Utils.openUrl(url) Utils.openUrl(url)
}, },
initializePage() {
this.releaseGlobalBackEvent()
this.busy = false
setTimeout((e) => {
this.setCurrentComponentAction("ViewRecipe")
}, 500)
}, },
created() {
this.recipe = cb.getDocument(this.recipeID)
}, },
} }
</script> </script>

View file

@ -1,9 +1,7 @@
<template> <template>
<Page> <Page>
<StackLayout <StackLayout class="dialogContainer" :class="isLightMode">
class="dialogContainer" <!-- :class="isLightTheme ? 'light' : 'dark'" -->
:class="isLightTheme ? 'light' : 'dark'"
>
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<ListView <ListView
width="100%" width="100%"
@ -38,21 +36,18 @@
</template> </template>
<script> <script>
import Theme from "@nativescript/theme" import { Application } from "@nativescript/core"
export default { export default {
props: ["title", "list", "height", "action"], props: ["title", "list", "height", "action"],
data() { computed: {
return { isLightMode() {
isLightTheme: true, return Application.systemAppearance()
} },
}, },
methods: { methods: {
tapAction({ item }) { tapAction({ item }) {
this.$modal.close(item) this.$modal.close(item)
}, },
}, },
created() {
this.isLightTheme = Theme.getMode() == "ns-light" ? true : false
},
} }
</script> </script>

View file

@ -1,9 +1,6 @@
<template> <template>
<Page> <Page>
<StackLayout <StackLayout class="dialogContainer" :class="isLightMode">
class="dialogContainer"
:class="isLightTheme ? 'light' : 'dark'"
>
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<Label class="dialogDescription" :text="description" textWrap="true" /> <Label class="dialogDescription" :text="description" textWrap="true" />
<StackLayout <StackLayout
@ -27,16 +24,13 @@
</template> </template>
<script> <script>
import Theme from "@nativescript/theme" import { Application } from "@nativescript/core"
export default { export default {
props: ["title", "description", "cancelButtonText", "okButtonText"], props: ["title", "description", "cancelButtonText", "okButtonText"],
data() { computed: {
return { isLightMode() {
isLightTheme: true, return Application.systemAppearance()
}
}, },
created() {
this.isLightTheme = Theme.getMode() == "ns-light" ? true : false
}, },
} }
</script> </script>

View file

@ -1,16 +1,14 @@
<template> <template>
<Page> <Page>
<StackLayout <StackLayout class="dialogContainer" :class="isLightMode">
class="dialogContainer"
:class="isLightTheme ? 'light' : 'dark'"
>
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<StackLayout class="dialogInputField">
<TextField <TextField
width="100%"
:hint="hint" :hint="hint"
v-model="category" v-model="category"
autocapitalizationType="words" autocapitalizationType="words"
/> />
</StackLayout>
<StackLayout orientation="horizontal" horizontalAlignment="right"> <StackLayout orientation="horizontal" horizontalAlignment="right">
<Label class="action orkm" text="CANCEL" @tap="$modal.close(false)" /> <Label class="action orkm" text="CANCEL" @tap="$modal.close(false)" />
<Label <Label
@ -24,34 +22,18 @@
</template> </template>
<script> <script>
import Theme from "@nativescript/theme" import { Application } from "@nativescript/core"
export default { export default {
props: ["title", "hint", "action"], props: ["title", "hint", "action"],
data() { data() {
return { return {
category: null, category: null,
isLightTheme: true,
} }
}, },
created() { computed: {
this.isLightTheme = Theme.getMode() == "ns-light" ? true : false isLightMode() {
return Application.systemAppearance()
},
}, },
} }
</script> </script>
<style lang="scss" scoped>
TextField {
margin: 0 24 16;
}
.dialogContainer {
padding: 0 24;
}
.dialogTitle {
padding: 24 0 12;
font-size: 20;
}
.action {
padding: 24 0 24 32;
font-size: 12;
color: #ff7043;
}
</style>

View file

@ -5,181 +5,7 @@ Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
recipes: [ // recipes: [],
{
imageSrc: null,
title: "Mediterranean Salad",
category: "Salads",
prepTime: "12:25",
cookTime: "00:30",
portionSize: 1,
ingredients: [
{
item: "Cucumbers, Seeded And Sliced",
quantity: 3,
unit: "unit",
},
{
item: "Crumbled Feta Cheese",
quantity: 1.5,
unit: "cup",
},
{
item: "Black Olives, Pitted And Sliced",
quantity: 1,
unit: "cup",
},
{
item: "Diced Roma Tomatoes",
quantity: 3,
unit: "cup",
},
{
item: "Diced Oil Packed Sun Dried Tomatoes, Drained, Oil Reserved",
quantity: 0.3,
unit: "cup",
},
{
item: "Onion, Sliced",
quantity: 1.5,
unit: "unit",
},
{
item: "Cucumbers, Seeded And Sliced",
quantity: 3,
unit: "unit",
},
{
item: "Crumbled Feta Cheese",
quantity: 1.5,
unit: "cup",
},
{
item: "Black Olives, Pitted And Sliced",
quantity: 1,
unit: "cup",
},
{
item: "Diced Roma Tomatoes",
quantity: 3,
unit: "cup",
},
{
item: "Diced Oil Packed Sun Dried Tomatoes, Drained, Oil Reserved",
quantity: 0.3,
unit: "cup",
},
{
item: "Onion, Sliced",
quantity: 1.5,
unit: "unit",
},
{
item: "Cucumbers, Seeded And Sliced",
quantity: 3,
unit: "unit",
},
{
item: "Crumbled Feta Cheese",
quantity: 1.5,
unit: "cup",
},
{
item: "Black Olives, Pitted And Sliced",
quantity: 1,
unit: "cup",
},
{
item: "Diced Roma Tomatoes",
quantity: 3,
unit: "cup",
},
{
item: "Diced Oil Packed Sun Dried Tomatoes, Drained, Oil Reserved",
quantity: 0.3,
unit: "cup",
},
{
item: "Onion, Sliced",
quantity: 1.5,
unit: "unit",
},
],
instructions: [
"In a large salad bowl, toss together the cucumbers, feta cheese, olives, roma tomatoes, sun-dried tomatoes, 2 tablespoons reserved sun-dried tomato oil, and red onion.",
"Chill until serving.",
"In a large salad bowl, toss together the cucumbers, feta cheese, olives, roma tomatoes, sun-dried tomatoes, 2 tablespoons reserved sun-dried tomato oil, and red onion. In a large salad bowl, toss together the cucumbers, feta cheese, olives, roma tomatoes, sun-dried tomatoes, 2 tablespoons reserved sun-dried tomato oil, and red onion.",
"Chill until serving.",
"Chill until serving.",
"In a large salad bowl, toss together the cucumbers, feta cheese, olives, roma tomatoes, sun-dried tomatoes, 2 tablespoons reserved sun-dried tomato oil, and red onion.",
"Chill until serving.",
"Chill until serving.",
"Chill until serving.",
"Chill until serving.",
"Chill until serving.",
],
notes: [
"Per Serving: 130.6 calories; protein 5.5g 11% DV; carbohydrates 9.3g 3% DV; fat 8.8g 14% DV; cholesterol 25mg 8% DV; sodium 486.4mg 20% DV.",
"Per Serving: 130.6 calories; protein 5.5g 11% DV; carbohydrates 9.3g 3% DV; fat 8.8g 14% DV; cholesterol 25mg 8% DV; sodium 486.4mg 20% DV.",
"Per Serving: 130.6 calories; protein 5.5g 11% DV; carbohydrates 9.3g 3% DV; fat 8.8g 14% DV; cholesterol 25mg 8% DV; sodium 486.4mg 20% DV.",
],
references: [
"https://www.allrecipes.com/recipe/14403/mediterranean-greek-salad/",
"https://www.allrecipes.com/recipe/14403/mediterranean-greek-salad/",
"https://www.allrecipes.com/recipe/14403/mediterranean-greek-salad/",
"https://www.allrecipes.com/recipe/14403/mediterranean-greek-salad/",
],
isFavorite: true,
tried: false,
lastModified: "2020-10-18T17:37:51.798Z",
},
{
imageSrc: null,
title: "Fresh Tomato Sauce",
category: "Sauces",
prepTime: "00:45",
cookTime: "00:35",
portionSize: 1,
ingredients: [],
instructions: [],
notes: [],
references: [],
isFavorite: true,
tried: true,
lastModified: "2020-10-15T17:37:51.798Z",
},
{
imageSrc: null,
title: "Creamy Mushroom Herb Pasta",
category: "Lunch",
prepTime: "00:10",
cookTime: "00:15",
portionSize: 1,
ingredients: [],
instructions: [],
notes: [],
references: [],
isFavorite: false,
tried: false,
lastModified: "2020-10-12T17:37:51.798Z",
},
{
imageSrc: null,
title: "Grilled Cheese Sandwich",
category: "Lunch",
prepTime: "00:50",
cookTime: "00:12",
portionSize: 1,
ingredients: [],
instructions: [],
notes: [],
references: [],
isFavorite: false,
tried: true,
lastModified: "2020-10-03T17:37:51.798Z",
},
],
viewIsScrolled: false,
icon: { icon: {
home: "\ued99", home: "\ued99",
heart: "\ued94", heart: "\ued94",
@ -210,123 +36,74 @@ export default new Vuex.Store({
musttry: "\uec96", musttry: "\uec96",
musttryOutline: "\ue9bb", musttryOutline: "\ue9bb",
}, },
units: [
"unit",
"tsp",
"Tbsp",
"oz",
"cup",
"pt",
"qt",
"lb",
"gal",
"ml",
"L",
"mg",
"g",
"kg",
"mm",
"cm",
"m",
"in",
"°C",
"°F",
],
categories: [
"Appetizers",
"BBQ",
"Beverages",
"Breads",
"Breakfast",
"Desserts",
"Dinner",
"Drinks",
"Healthy",
"Lunch",
"Main dishes",
"Meat",
"Noodles",
"Pasta",
"Poultry",
"Rice",
"Salads",
"Sauces",
"Seafood",
"Side dishes",
"Snacks",
"Soups",
"Undefined",
"Vegan",
"Vegetarian",
],
currentComponent: "EnRecipes", currentComponent: "EnRecipes",
}, },
mutations: { mutations: {
addRecipe(state, recipe) { // addRecipe(state, recipe) {
state.recipes.push(recipe) // state.recipes.push(recipe)
}, // },
addCategory(state, category) { // addCategory(state, category) {
let a = state.categories.filter((e) => e === category).length // let a = state.categories.filter((e) => e === category).length
if (a == 0) { // if (a == 0) {
state.categories.push(category) // state.categories.push(category)
state.categories.sort() // state.categories.sort()
} // }
}, // },
overwriteRecipe(state, { index, recipe }) { // overwriteRecipe(state, { index, recipe }) {
Object.assign(state.recipes[index], recipe) // Object.assign(state.recipes[index], recipe)
}, // },
deleteRecipe(state, index) { // deleteRecipe(state, index) {
state.recipes.splice(index, 1) // state.recipes.splice(index, 1)
}, // },
toggleFavorite(state, index) { // toggleFavorite(state, index) {
state.recipes[index].isFavorite = !state.recipes[index].isFavorite // state.recipes[index].isFavorite = !state.recipes[index].isFavorite
}, // },
toggleMustTry(state, index) { // toggleMustTry(state, index) {
state.recipes[index].tried = !state.recipes[index].tried // state.recipes[index].tried = !state.recipes[index].tried
}, // },
setCurrentComponent(state, comp) { setCurrentComponent(state, comp) {
state.currentComponent = comp state.currentComponent = comp
}, },
renameCategory(state, { current, updated }) { // renameCategory(state, { current, updated }) {
let a = state.categories.filter((e) => e === updated).length // let a = state.categories.filter((e) => e === updated).length
if (a == 0) { // if (a == 0) {
// add updated category to categories // // add updated category to categories
state.categories.splice(state.categories.indexOf(current), 1) // state.categories.splice(state.categories.indexOf(current), 1)
state.categories.push(updated) // state.categories.push(updated)
state.categories.sort() // state.categories.sort()
// rename all occurences // // rename all occurences
state.recipes.forEach((e, i) => { // state.recipes.forEach((e, i) => {
if (e.category == current) { // if (e.category == current) {
state.recipes[i].category = updated // state.recipes[i].category = updated
} // }
}) // })
} // }
}, // },
}, },
actions: { actions: {
addRecipeAction({ commit }, recipe) { // addRecipeAction({ commit }, recipe) {
commit("addRecipe", recipe) // commit("addRecipe", recipe)
}, // },
addCategoryAction({ commit }, category) { // addCategoryAction({ commit }, category) {
commit("addCategory", category) // commit("addCategory", category)
}, // },
overwriteRecipeAction({ commit }, updatedRecipe) { // overwriteRecipeAction({ commit }, updatedRecipe) {
commit("overwriteRecipe", updatedRecipe) // commit("overwriteRecipe", updatedRecipe)
}, // },
deleteRecipeAction({ commit }, index) { // deleteRecipeAction({ commit }, index) {
commit("deleteRecipe", index) // commit("deleteRecipe", index)
}, // },
toggleFavoriteAction({ commit }, index) { // toggleFavoriteAction({ commit }, index) {
commit("toggleFavorite", index) // commit("toggleFavorite", index)
}, // },
toggleMustTryAction({ commit }, index) { // toggleMustTryAction({ commit }, index) {
commit("toggleMustTry", index) // commit("toggleMustTry", index)
}, // },
setCurrentComponentAction({ commit }, comp) { setCurrentComponentAction({ commit }, comp) {
commit("setCurrentComponent", comp) commit("setCurrentComponent", comp)
}, },
renameCategoryAction({ commit }, category) { // renameCategoryAction({ commit }, category) {
commit("renameCategory", category) // commit("renameCategory", category)
}, // },
}, },
}) })

4449
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@
"@nativescript/core": "~7.0.0", "@nativescript/core": "~7.0.0",
"@nativescript/datetimepicker": "^2.0.4", "@nativescript/datetimepicker": "^2.0.4",
"@nativescript/theme": "^3.0.0", "@nativescript/theme": "^3.0.0",
"@nativescript/webpack": "3.0.0",
"@nstudio/nativescript-floatingactionbutton": "^3.0.3", "@nstudio/nativescript-floatingactionbutton": "^3.0.3",
"nativescript-camera": "^4.5.0", "nativescript-camera": "^4.5.0",
"nativescript-couchbase-plugin": "^0.9.6", "nativescript-couchbase-plugin": "^0.9.6",
@ -20,6 +21,7 @@
"nativescript-ui-listview": "^9.0.4", "nativescript-ui-listview": "^9.0.4",
"nativescript-ui-sidedrawer": "^9.0.3", "nativescript-ui-sidedrawer": "^9.0.3",
"nativescript-vue": "^2.6.1", "nativescript-vue": "^2.6.1",
"nativescript-zip": "^4.0.2",
"vuex": "^3.3.0" "vuex": "^3.3.0"
}, },
"devDependencies": { "devDependencies": {
@ -28,7 +30,6 @@
"@nativescript/android": "7.0.1", "@nativescript/android": "7.0.1",
"@types/node": "^14.0.27", "@types/node": "^14.0.27",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"nativescript-dev-webpack": "^1.5.1",
"nativescript-vue-template-compiler": "^2.6.0", "nativescript-vue-template-compiler": "^2.6.0",
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
"vue-loader": "^15.9.1" "vue-loader": "^15.9.1"