couchbase and vuex integrated

This commit is contained in:
Vishnu Raghav B 2020-10-27 02:19:54 +05:30
parent 1d7bca959e
commit 52446b3cd7
13 changed files with 437 additions and 371 deletions

View file

@ -15,7 +15,7 @@
android { android {
defaultConfig { defaultConfig {
minSdkVersion 23 minSdkVersion 25
generatedDensities = [] generatedDensities = []
ndk { ndk {
abiFilters.clear() abiFilters.clear()

View file

@ -1,40 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="__PACKAGE__" android:versionCode="10000" android:versionName="1.0">
package="__PACKAGE__" <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" />
android:versionCode="10000" <!-- <uses-permission android:name="android.permission.CAMERA" /> -->
android:versionName="1.0"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<supports-screens <!-- <uses-permission android:name="android.permission.READ_USER_DICTIONARY" /> -->
android:smallScreens="true" <!-- <uses-permission android:name="android.permission.INTERNET"/> -->
android:normalScreens="true" <!-- <uses-feature android:name="android.hardware.camera" android:required="true" /> -->
android:largeScreens="true" <application android:usesCleartextTraffic="true" android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@style/AppTheme" android:requestLegacyExternalStorage="true">
android:xlargeScreens="true"/> <activity android:name="com.tns.NativeScriptActivity" android:label="@string/title_activity_kimera" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|uiMode" android:theme="@style/LaunchScreenTheme">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true"
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="com.tns.NativeScriptActivity"
android:label="@string/title_activity_kimera"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|uiMode"
android:theme="@style/LaunchScreenTheme">
<meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" /> <meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.tns.ErrorReportActivity"/> <activity android:name="com.tns.ErrorReportActivity" />
</application> </application>
</manifest> </manifest>

View file

@ -20,7 +20,7 @@
<!-- theme to use AFTER launch screen is loaded--> <!-- theme to use AFTER launch screen is loaded-->
<style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:forceDarkAllowed">true</item> <item name="android:forceDarkAllowed">false</item>
<item name="toolbarStyle">@style/NativeScriptToolbarStyle</item> <item name="toolbarStyle">@style/NativeScriptToolbarStyle</item>
<item name="colorPrimary">@color/ns_primary</item> <item name="colorPrimary">@color/ns_primary</item>

View file

@ -181,6 +181,10 @@ TabView {
background: $grayD4; background: $grayD4;
} }
} }
.ns-dark .date-time-picker-spinners {
color: $grayL4;
background: $grayD4;
}
// ActionBar // ActionBar
ActionBar { ActionBar {
width: 100%; width: 100%;

View file

@ -75,6 +75,14 @@
</StackLayout> </StackLayout>
<StackLayout row="1"> <StackLayout row="1">
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<StackLayout
orientation="horizontal"
class="sd-item orkm"
@tap="donate"
>
<Label class="bx" :text="icon.donate" margin="0 24 0 0" />
<Label text="Donate" />
</StackLayout>
<StackLayout <StackLayout
@tap="navigateTo(item.component, true, false)" @tap="navigateTo(item.component, true, false)"
v-for="(item, index) in bottommenu" v-for="(item, index) in bottommenu"
@ -88,14 +96,6 @@
<Label class="bx" :text="icon[item.icon]" margin="0 24 0 0" /> <Label class="bx" :text="icon[item.icon]" margin="0 24 0 0" />
<Label :text="item.title" /> <Label :text="item.title" />
</StackLayout> </StackLayout>
<StackLayout
orientation="horizontal"
class="sd-item orkm"
@tap="donate"
>
<Label class="bx" :text="icon.donate" margin="0 24 0 0" />
<Label text="Donate" />
</StackLayout>
</StackLayout> </StackLayout>
</GridLayout> </GridLayout>
@ -104,7 +104,6 @@
<!-- Home --> <!-- Home -->
<EnRecipes <EnRecipes
ref="enrecipes" ref="enrecipes"
:passedRecipes="recipes"
:filterFavorites="filterFavorites" :filterFavorites="filterFavorites"
:filterMustTry="filterMustTry" :filterMustTry="filterMustTry"
:selectedCategory="selectedCategory" :selectedCategory="selectedCategory"
@ -134,10 +133,6 @@ 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 } from "nativescript-couchbase-plugin"
const cb = new Couchbase("enrecipes")
const cbCat = new Couchbase("categories")
let page let page
export default { export default {
components: { components: {
@ -145,6 +140,7 @@ export default {
Settings, Settings,
About, About,
}, },
data() { data() {
return { return {
selectedCategory: null, selectedCategory: null,
@ -180,11 +176,10 @@ export default {
}, },
], ],
catEditMode: false, catEditMode: false,
recipes: null,
} }
}, },
computed: { computed: {
...mapState(["icon", "currentComponent"]), ...mapState(["icon", "recipes", "currentComponent"]),
categories() { categories() {
let arr = this.recipes.map((e) => { let arr = this.recipes.map((e) => {
return e.category return e.category
@ -193,7 +188,12 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction", "renameCategoryAction"]), ...mapActions([
"setCurrentComponentAction",
"initializeRecipes",
"initializeCategories",
"renameCategoryAction",
]),
toggleCatEdit() { toggleCatEdit() {
this.catEditMode = !this.catEditMode this.catEditMode = !this.catEditMode
this.setComponent("EnRecipes") this.setComponent("EnRecipes")
@ -204,37 +204,19 @@ export default {
setComponent(comp) { setComponent(comp) {
this.setCurrentComponentAction(comp) this.setCurrentComponentAction(comp)
}, },
editCategory(item) { editCategory(category) {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.$showModal(PromptDialog, { this.$showModal(PromptDialog, {
props: { props: {
title: `Rename category`, title: `Rename category`,
hint: item, hint: category,
action: "RENAME", action: "RENAME",
}, },
}).then((result) => { }).then((newCategory) => {
this.hijackGlobalBackEvent() this.hijackGlobalBackEvent()
if (result.length) { if (newCategory.length) {
if (this.categories.includes(result)) { this.renameCategoryAction({ current: category, updated: newCategory })
Toast.makeText("Category already exists!", "long").show() this.catEditMode = false
} else {
let categories = cbCat.getDocument("categories").categories
console.log(categories, categories.indexOf(item))
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
}
} }
}) })
}, },
@ -308,8 +290,7 @@ export default {
backstackVisible: false, backstackVisible: false,
}) })
this.closeDrawer() this.closeDrawer()
this.catEditMode = false } else if (!this.catEditMode || !isCategory) {
} else if (!this.catEditMode) {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.hijackGlobalBackEvent() this.hijackGlobalBackEvent()
this.setComponent(to) this.setComponent(to)
@ -320,6 +301,7 @@ export default {
this.$refs.enrecipes.updateFilter() this.$refs.enrecipes.updateFilter()
this.closeDrawer() this.closeDrawer()
} }
this.catEditMode = false
}, },
restartApp() { restartApp() {
// Code from nativescript-master-technology // Code from nativescript-master-technology
@ -358,10 +340,8 @@ 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: [] }) if (!this.recipes.length) this.initializeRecipes()
cb.addDatabaseChangeListener((e) => { if (!this.categories.length) this.initializeCategories()
this.recipes = cb.query({ select: [] })
})
}, },
} }
</script> </script>

View file

@ -1,5 +1,5 @@
<template> <template>
<Page @loaded="initializePage" @unloaded="releaseBackEvent"> <Page @unloaded="releaseBackEvent">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<GridLayout rows="*" columns="auto, *, auto," class="actionBarContainer"> <GridLayout rows="*" columns="auto, *, auto," class="actionBarContainer">
<Label <Label
@ -62,13 +62,22 @@
:text="icon.close" :text="icon.close"
androidElevation="8" androidElevation="8"
/> />
<Label <GridLayout v-else rows="auto" columns="*, auto, auto, *">
v-else <Label
@tap="takePicture" col="1"
class="bx fab-button" @tap="takePicture"
:text="icon.camera" class="bx fab-button"
androidElevation="8" :text="icon.camera"
/> androidElevation="8"
/>
<Label
col="2"
@tap="selectPicture"
class="bx fab-button"
:text="icon.image"
androidElevation="8"
/>
</GridLayout>
</StackLayout> </StackLayout>
</AbsoluteLayout> </AbsoluteLayout>
@ -268,7 +277,8 @@ import {
getFileAccess, getFileAccess,
knownFolders, knownFolders,
} from "@nativescript/core" } from "@nativescript/core"
import { Mediafilepicker } from "nativescript-mediafilepicker" import * as imagepicker from "nativescript-imagepicker"
import * as camera from "@nativescript/camera"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
@ -276,13 +286,8 @@ 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: ["recipeID", "selectedCategory"], props: ["recipeIndex", "recipeID", "selectedCategory"],
data() { data() {
return { return {
title: "New recipe", title: "New recipe",
@ -308,63 +313,33 @@ export default {
tried: false, tried: false,
lastModified: null, lastModified: null,
}, },
tempRecipeContent: {}, tempRecipeContent: {
units: [ imageSrc: null,
"unit", title: null,
"tsp", category: null,
"Tbsp", prepTime: "00:00",
"oz", cookTime: "00:00",
"cup", portionSize: 1,
"pt", ingredients: [
"qt", {
"lb", item: "",
"gal", quantity: null,
"ml", unit: "unit",
"L", },
"mg", ],
"g", instructions: [""],
"kg", notes: [""],
"mm", references: [""],
"cm", isFavorite: false,
"m", tried: false,
"in", lastModified: null,
"°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, newRecipeID: null,
} }
}, },
computed: { computed: {
...mapState(["icon", "currentComponent"]), ...mapState(["icon", "units", "recipes", "categories", "currentComponent"]),
screenWidth() { screenWidth() {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
@ -380,31 +355,12 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction"]), ...mapActions([
initializePage() { "setCurrentComponentAction",
setTimeout((e) => { "addRecipeAction",
this.setCurrentComponentAction("EditRecipe") "overwriteRecipeAction",
}, 500) "addCategoryAction",
this.title = this.recipeID ? "Edit recipe" : "New recipe" ]),
if (this.recipeID) {
let recipe = cb.getDocument(this.recipeID)
Object.assign(this.recipeContent, recipe)
Object.assign(this.tempRecipeContent, recipe)
} else {
if (this.selectedCategory)
this.recipeContent.category = this.selectedCategory
Object.assign(this.tempRecipeContent, this.recipeContent)
this.newRecipeID = this.getRandomID()
}
this.hijackBackEvent()
let isCategoriesStored = cbCat.query({ select: [] }).length
if (isCategoriesStored) {
this.categories = cbCat.getDocument("categories").categories
} else {
cbCat.createDocument({ categories: [...this.categories] }, "categories")
}
},
getRandomID() { getRandomID() {
let res = "" let res = ""
let chars = "abcdefghijklmnopqrstuvwxyz0123456789" let chars = "abcdefghijklmnopqrstuvwxyz0123456789"
@ -451,10 +407,20 @@ export default {
this.clearEmptyFields() this.clearEmptyFields()
this.recipeContent.lastModified = new Date() this.recipeContent.lastModified = new Date()
if (this.recipeID) { if (this.recipeID) {
cb.updateDocument(this.recipeID, this.recipeContent) this.overwriteRecipeAction({
index: this.recipeIndex,
id: this.recipeID,
recipe: this.recipeContent,
})
} else { } else {
this.recipeContent.id = this.newRecipeID this.recipeContent.id = this.newRecipeID
cb.createDocument(this.recipeContent, this.newRecipeID) this.addRecipeAction({
id: this.newRecipeID,
recipe: this.recipeContent,
})
}
if (this.tempRecipeContent.imageSrc && !this.recipeContent.imageSrc) {
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
} }
this.$navigateBack() this.$navigateBack()
}, },
@ -485,15 +451,11 @@ export default {
title: "New category", title: "New category",
action: "ADD", action: "ADD",
}, },
}).then((result) => { }).then((category) => {
this.hijackBackEvent() this.hijackBackEvent()
if (result.length) { if (category.length) {
this.recipeContent.category = result this.recipeContent.category = category
this.categories.push(result) this.addCategoryAction(category)
this.categories.sort()
cbCat.updateDocument("categories", {
categories: [...this.categories],
})
} }
}) })
} else if (action) { } else if (action) {
@ -546,56 +508,58 @@ export default {
} }
}, },
takePicture() { takePicture() {
let mediafilepicker = new Mediafilepicker() const vm = this
let vm = this camera.requestPermissions().then(
const options = { () => {
width: this.screenWidth, camera
height: this.screenWidth, .takePicture({
lockSquare: true, width: vm.screenWidth,
} height: vm.screenWidth,
const androidOptions = { keepAspectRatio: false,
isFreeStyleCropEnabled: true, saveToGallery: false,
statusBarColor: "black", })
setAspectRatioOptions: { .then((imageAsset) => {
defaultIndex: 0, let result = imageAsset._android
aspectRatios: [ ImageSource.fromFile(result).then((savedImg) => {
{ let savedImgPath = path.join(
aspectRatioTitle: "1:1", knownFolders.documents().getFolder("enrecipes").path,
aspectRatioX: 1, `${vm.getRandomID()}.jpg`
aspectRatioY: 1, )
}, savedImg.saveToFile(savedImgPath, "jpg")
{ vm.recipeContent.imageSrc = savedImgPath
aspectRatioTitle: "16:9", })
aspectRatioX: 16, })
aspectRatioY: 9, .catch((err) => {
}, console.log("Error -> " + err.message)
{ })
aspectRatioTitle: "18:9",
aspectRatioX: 18,
aspectRatioY: 9,
},
],
},
}
mediafilepicker.openImagePicker({
android: {
isCaptureMood: false, // if true then camera will open directly.
isNeedCamera: true,
maxNumberFiles: 1,
isNeedFolderList: false,
}, },
() => {
console.log("permission request rejected")
}
)
},
selectPicture() {
let context = imagepicker.create({
mode: "single",
mediaType: "Image",
}) })
mediafilepicker.on("getFiles", function(res) { context
let result = res.object.get("results")[0].file .authorize()
ImageSource.fromFile(result).then((savedImg) => { .then(() => context.present())
let savedImgPath = path.join( .then((selection) => {
knownFolders.documents().getFolder("enrecipes").path, let result = selection[0]._android
`${vm.getRandomID()}.jpg` ImageSource.fromFile(result).then((savedImg) => {
) let savedImgPath = path.join(
savedImg.saveToFile(savedImgPath, "jpg") knownFolders.documents().getFolder("enrecipes").path,
vm.recipeContent.imageSrc = savedImgPath `${this.getRandomID()}.jpg`
)
savedImg.saveToFile(savedImgPath, "jpg")
this.recipeContent.imageSrc = savedImgPath
})
})
.catch(function(e) {
console.log(e)
}) })
})
}, },
removePicture() { removePicture() {
confirm({ confirm({
@ -605,7 +569,6 @@ export default {
cancelButtonText: "Cancel", cancelButtonText: "Cancel",
}).then((e) => { }).then((e) => {
if (e) { if (e) {
getFileAccess().deleteFile(this.recipeContent.imageSrc)
this.recipeContent.imageSrc = null this.recipeContent.imageSrc = null
} }
}) })
@ -657,5 +620,21 @@ export default {
}) })
}, },
}, },
created() {
setTimeout((e) => {
this.setCurrentComponentAction("EditRecipe")
}, 500)
this.title = this.recipeID ? "Edit recipe" : "New recipe"
if (this.recipeID) {
let recipe = this.recipes.filter((e) => e.id === this.recipeID)[0]
Object.assign(this.recipeContent, recipe)
Object.assign(this.tempRecipeContent, recipe)
} else {
if (this.selectedCategory)
this.recipeContent.category = this.selectedCategory
this.newRecipeID = this.getRandomID()
}
this.hijackBackEvent()
},
} }
</script> </script>

View file

@ -40,14 +40,14 @@
/> />
<Label class="title orkm" :text="currentComponent" col="1" /> <Label class="title orkm" :text="currentComponent" col="1" />
<Label <Label
v-if="passedRecipes.length" v-if="recipes.length"
class="bx" class="bx"
:text="icon.search" :text="icon.search"
col="2" col="2"
@tap="openSearch" @tap="openSearch"
/> />
<Label <Label
v-if="passedRecipes.length" v-if="recipes.length"
class="bx" class="bx"
:text="icon.sort" :text="icon.sort"
col="3" col="3"
@ -59,7 +59,7 @@
<RadListView <RadListView
ref="listView" ref="listView"
itemHeight="112" itemHeight="112"
for="recipe in passedRecipes" for="recipe in recipes"
swipeActions="true" swipeActions="true"
@itemSwipeProgressChanged="onSwiping" @itemSwipeProgressChanged="onSwiping"
@itemSwipeProgressEnded="onSwipeEnded" @itemSwipeProgressEnded="onSwipeEnded"
@ -115,7 +115,7 @@
</v-template> </v-template>
</RadListView> </RadListView>
<Label <Label
v-if="!passedRecipes.length && !filterFavorites && !filterMustTry" v-if="!recipes.length && !filterFavorites && !filterMustTry"
class="noResults" class="noResults"
text='Click the "+" icon to add a new recipe.' text='Click the "+" icon to add a new recipe.'
textWrap="true" textWrap="true"
@ -156,7 +156,6 @@
<script> <script>
import { Utils, AndroidApplication } from "@nativescript/core" import { Utils, AndroidApplication } from "@nativescript/core"
import * as Toast from "nativescript-toast"
import EditRecipe from "./EditRecipe.vue" import EditRecipe from "./EditRecipe.vue"
import ViewRecipe from "./ViewRecipe.vue" import ViewRecipe from "./ViewRecipe.vue"
@ -164,12 +163,8 @@ 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",
@ -192,25 +187,25 @@ export default {
} }
}, },
computed: { computed: {
...mapState(["icon", "currentComponent"]), ...mapState(["icon", "recipes", "currentComponent"]),
filteredRecipes() { filteredRecipes() {
if (this.filterFavorites) { if (this.filterFavorites) {
return this.passedRecipes.filter( return this.recipes.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.passedRecipes.filter( return this.recipes.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.passedRecipes.filter( return this.recipes.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.passedRecipes.filter((e) => return this.recipes.filter((e) =>
e.title.toLowerCase().includes(this.searchQuery) e.title.toLowerCase().includes(this.searchQuery)
) )
} }
@ -343,7 +338,7 @@ export default {
} }
}, },
onSwipeEnded({ index }) { onSwipeEnded({ index }) {
let recipeID = this.passedRecipes[index].id let recipeID = this.recipes[index].id
if (this.rightAction && !this.deletionDialogActive) if (this.rightAction && !this.deletionDialogActive)
this.deleteRecipe(index, recipeID) this.deleteRecipe(index, recipeID)
this.rightAction = false this.rightAction = false
@ -353,13 +348,13 @@ export default {
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.passedRecipes[index].title}"?`, description: `Are you sure you want to delete the recipe "${this.recipes[index].title}"?`,
cancelButtonText: "CANCEL", cancelButtonText: "CANCEL",
okButtonText: "DELETE", okButtonText: "DELETE",
}, },
}).then((action) => { }).then((action) => {
if (action) { if (action) {
cb.deleteDocument(recipeID) this.deleteRecipeAction({ index, id: recipeID })
} }
this.deletionDialogActive = false this.deletionDialogActive = false
}) })
@ -405,7 +400,7 @@ export default {
}, },
}) })
}, },
viewRecipe({ item }) { viewRecipe({ item, index }) {
this.$navigateTo(ViewRecipe, { this.$navigateTo(ViewRecipe, {
transition: { transition: {
name: "fade", name: "fade",
@ -413,6 +408,7 @@ export default {
curve: "easeIn", curve: "easeIn",
}, },
props: { props: {
recipeIndex: index,
recipeID: item.id, recipeID: item.id,
hijackGlobalBackEvent: this.hijackGlobalBackEvent, hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent, releaseGlobalBackEvent: this.releaseGlobalBackEvent,

View file

@ -67,6 +67,9 @@ import {
} from "@nativescript/core" } from "@nativescript/core"
import * as permissions from "nativescript-permissions" import * as permissions from "nativescript-permissions"
import { Zip } from "nativescript-zip" import { Zip } from "nativescript-zip"
import * as Toast from "nativescript-toast"
import * as filepicker from "nativescript-plugin-filepicker"
import Theme from "@nativescript/theme" import Theme from "@nativescript/theme"
import ActionDialog from "./modal/ActionDialog.vue" import ActionDialog from "./modal/ActionDialog.vue"
@ -174,14 +177,22 @@ export default {
const sdDownloadPath = android.os.Environment.getExternalStoragePublicDirectory( const sdDownloadPath = android.os.Environment.getExternalStoragePublicDirectory(
android.os.Environment.DIRECTORY_DOWNLOADS android.os.Environment.DIRECTORY_DOWNLOADS
).toString() ).toString()
let date = new Date()
let fromPath = path.join(knownFolders.documents().path, "enrecipes") let fromPath = path.join(knownFolders.documents().path, "enrecipes")
let destPath = path.join(sdDownloadPath, "enrecipes.zip") let destPath = path.join(
sdDownloadPath,
`enrecipes_${date.toString()}.zip`
)
console.log(fromPath, destPath, sdDownloadPath) console.log(fromPath, destPath, sdDownloadPath)
Zip.zip({ Zip.zip({
directory: fromPath, directory: fromPath,
archive: destPath, archive: destPath,
}) })
.then((success) => { .then((success) => {
Toast.makeText(
"Backup file successfully saved to Downloads",
"long"
).show()
console.log("success:" + success) console.log("success:" + success)
}) })
.catch((err) => { .catch((err) => {
@ -197,13 +208,22 @@ export default {
restoreData(args) { restoreData(args) {
let btn = args.object let btn = args.object
this.highlight(args) this.highlight(args)
permissions let vm = this
.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) let context = filepicker.create({
.then(() => { mode: "single", // use "multiple" for multiple selection
alert("Restore successful!") extensions: ["zip"],
})
context
.authorize()
.then(function() {
return context.present()
}) })
.catch(() => { .then(function(selection) {
console.log("Uh oh, no permissions - plan B time!") let result = selection
console.log(result)
})
.catch(function(e) {
console.log(e)
}) })
}, },
}, },

View file

@ -27,7 +27,13 @@
verticalAlignment="bottom" verticalAlignment="bottom"
/> />
</ScrollView> </ScrollView>
<Label row="0" col="2" class="bx" :text="icon.share" @tap="" /> <Label
row="0"
col="2"
class="bx"
:text="icon.share"
@tap="shareRecipe"
/>
<Label <Label
row="0" row="0"
col="3" col="3"
@ -260,18 +266,21 @@
</template> </template>
<script> <script>
import { Screen, Utils } from "@nativescript/core" import { Screen, Utils, ImageSource, Device } from "@nativescript/core"
import * as Toast from "nativescript-toast" import * as Toast from "nativescript-toast"
import * as SocialShare from "nativescript-social-share"
import { mapState, mapActions } from "vuex" 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: ["recipeID", "hijackGlobalBackEvent", "releaseGlobalBackEvent"], props: [
"recipeIndex",
"recipeID",
"hijackGlobalBackEvent",
"releaseGlobalBackEvent",
],
data() { data() {
return { return {
busy: false, busy: false,
@ -280,7 +289,7 @@ export default {
} }
}, },
computed: { computed: {
...mapState(["icon"]), ...mapState(["icon", "recipes"]),
screenWidth() { screenWidth() {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
@ -291,17 +300,23 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(["toggleMustTryAction", "setCurrentComponentAction"]), ...mapActions(["toggleStateAction", "setCurrentComponentAction"]),
initializePage() { initializePage() {
this.recipe = cb.getDocument(this.recipeID)
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.busy = false this.busy = false
setTimeout((e) => { setTimeout((e) => {
this.setCurrentComponentAction("ViewRecipe") this.setCurrentComponentAction("ViewRecipe")
}, 500) }, 500)
this.portionScale = this.recipe.portionSize
}, },
roundedQuantity(quantity, unit) { roundedQuantity(quantity) {
return Math.round(quantity * this.isPortionScalePositive * 100) / 100 return (
Math.round(
(quantity / this.recipe.portionSize) *
this.isPortionScalePositive *
100
) / 100
)
}, },
editRecipe() { editRecipe() {
this.busy = true this.busy = true
@ -312,15 +327,28 @@ export default {
curve: "easeIn", curve: "easeIn",
}, },
props: { props: {
recipeIndex: this.recipeIndex,
recipeID: this.recipeID, recipeID: this.recipeID,
}, },
// backstackVisible: false, // backstackVisible: false,
}) })
}, },
toggle(key) { toggle(key) {
this.recipe[key] = !this.recipe[key] this.toggleStateAction({
cb.updateDocument(this.recipeID, this.recipe) index: this.recipeIndex,
this.recipe = cb.getDocument(this.recipeID) id: this.recipeID,
recipe: this.recipe,
key,
})
},
shareRecipe() {
// if (this.recipe.imageSrc) {
// let image = ImageSource.fromFile(this.recipe.imageSrc)
// SocialShare.shareImage(image)
// } else {
// SocialShare.shareText("Text only")
// }
alert(Device.sdkVersion)
}, },
toggleFavorite() { toggleFavorite() {
this.recipe.isFavorite this.recipe.isFavorite
@ -345,7 +373,7 @@ export default {
}, },
}, },
created() { created() {
this.recipe = cb.getDocument(this.recipeID) this.recipe = this.recipes.filter((e) => e.id === this.recipeID)[0]
}, },
} }
</script> </script>

View file

@ -13,10 +13,10 @@ Vue.registerElement(
() => require("nativescript-ui-sidedrawer").RadSideDrawer () => require("nativescript-ui-sidedrawer").RadSideDrawer
) )
Vue.registerElement( // Vue.registerElement(
"Fab", // "Fab",
() => require("@nstudio/nativescript-floatingactionbutton").Fab // () => require("@nstudio/nativescript-floatingactionbutton").Fab
) // )
if (TNS_ENV !== "production") { if (TNS_ENV !== "production") {
// Vue.use(VueDevtools) // Vue.use(VueDevtools)

View file

@ -1,11 +1,37 @@
import Vue from "vue" import Vue from "vue"
import Vuex from "vuex" import Vuex from "vuex"
import { Couchbase } from "nativescript-couchbase-plugin"
const recipesDB = new Couchbase("enrecipes")
const categoriesDB = new Couchbase("categories")
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
// recipes: [], recipes: [],
categories: [],
units: [
"unit",
"tsp",
"Tbsp",
"oz",
"cup",
"pt",
"qt",
"lb",
"gal",
"ml",
"L",
"mg",
"g",
"kg",
"mm",
"cm",
"m",
"in",
"°C",
"°F",
],
icon: { icon: {
home: "\ued99", home: "\ued99",
heart: "\ued94", heart: "\ued94",
@ -39,71 +65,133 @@ export default new Vuex.Store({
currentComponent: "EnRecipes", currentComponent: "EnRecipes",
}, },
mutations: { mutations: {
// addRecipe(state, recipe) { initializeRecipes(state) {
// state.recipes.push(recipe) let a = recipesDB.query({ select: [] })
// }, a.forEach((e) => {
// addCategory(state, category) { state.recipes.push(e)
// let a = state.categories.filter((e) => e === category).length })
// if (a == 0) { },
// state.categories.push(category) initializeCategories(state) {
// state.categories.sort() let isCategoriesStored = categoriesDB.query({ select: [] }).length
// } let cats
// }, if (isCategoriesStored) {
// overwriteRecipe(state, { index, recipe }) { cats = categoriesDB.getDocument("categories").categories
// Object.assign(state.recipes[index], recipe) } else {
// }, categoriesDB.createDocument(
// deleteRecipe(state, index) { {
// state.recipes.splice(index, 1) categories: [
// }, "Appetizers",
// toggleFavorite(state, index) { "BBQ",
// state.recipes[index].isFavorite = !state.recipes[index].isFavorite "Beverages",
// }, "Breads",
// toggleMustTry(state, index) { "Breakfast",
// state.recipes[index].tried = !state.recipes[index].tried "Desserts",
// }, "Dinner",
"Drinks",
"Healthy",
"Lunch",
"Main dishes",
"Meat",
"Noodles",
"Pasta",
"Poultry",
"Rice",
"Salads",
"Sauces",
"Seafood",
"Side dishes",
"Snacks",
"Soups",
"Undefined",
"Vegan",
"Vegetarian",
],
},
"categories"
)
cats = categoriesDB.getDocument("categories").categories
}
cats.forEach((e) => state.categories.push(e))
},
addRecipe(state, { id, recipe }) {
state.recipes.push(recipe)
recipesDB.createDocument(recipe, id)
},
addCategory(state, category) {
let a = state.categories.filter((e) => e === category).length
if (a == 0) {
state.categories.push(category)
state.categories.sort()
categoriesDB.updateDocument("categories", {
categories: [...state.categories],
})
}
},
overwriteRecipe(state, { index, id, recipe }) {
Object.assign(state.recipes[index], recipe)
recipesDB.updateDocument(id, recipe)
},
deleteRecipe(state, { index, id }) {
state.recipes.splice(index, 1)
recipesDB.deleteDocument(id)
},
toggleState(state, { index, id, recipe, key }) {
state.recipes[index][key] = !state.recipes[index][key]
recipesDB.updateDocument(id, recipe)
},
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 exists = state.categories.filter((e) => e === updated).length
// if (a == 0) {
// // 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) // update recipes with updated category
// state.categories.sort() if (!exists) {
// // rename all occurences state.categories.push(updated)
// state.recipes.forEach((e, i) => { state.categories.sort()
// if (e.category == current) { categoriesDB.updateDocument("categories", {
// state.recipes[i].category = updated categories: [...state.categories],
// } })
// }) }
// } state.recipes.forEach((e, i) => {
// }, if (e.category == current) {
state.recipes[i].category = updated
recipesDB.inBatch(() => {
recipesDB.updateDocument(state.recipes[i].id, state.recipes[i])
})
}
})
},
}, },
actions: { actions: {
// addRecipeAction({ commit }, recipe) { initializeRecipes({ commit }) {
// commit("addRecipe", recipe) commit("initializeRecipes")
// }, },
// addCategoryAction({ commit }, category) { initializeCategories({ commit }) {
// commit("addCategory", category) commit("initializeCategories")
// }, },
// overwriteRecipeAction({ commit }, updatedRecipe) { addRecipeAction({ commit }, recipe) {
// commit("overwriteRecipe", updatedRecipe) commit("addRecipe", recipe)
// }, },
// deleteRecipeAction({ commit }, index) { addCategoryAction({ commit }, category) {
// commit("deleteRecipe", index) commit("addCategory", category)
// }, },
// toggleFavoriteAction({ commit }, index) { overwriteRecipeAction({ commit }, updatedRecipe) {
// commit("toggleFavorite", index) commit("overwriteRecipe", updatedRecipe)
// }, },
// toggleMustTryAction({ commit }, index) { deleteRecipeAction({ commit }, recipe) {
// commit("toggleMustTry", index) commit("deleteRecipe", recipe)
// }, },
toggleStateAction({ commit }, toggledRecipe) {
commit("toggleState", toggledRecipe)
},
setCurrentComponentAction({ commit }, comp) { setCurrentComponentAction({ commit }, comp) {
commit("setCurrentComponent", comp) commit("setCurrentComponent", comp)
}, },
// renameCategoryAction({ commit }, category) { renameCategoryAction({ commit }, category) {
// commit("renameCategory", category) commit("renameCategory", category)
// }, },
}, },
}) })

62
package-lock.json generated
View file

@ -1095,17 +1095,20 @@
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
"@nativescript-community/perms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@nativescript-community/perms/-/perms-2.1.1.tgz",
"integrity": "sha512-Ay4v1lEGTQ5rYYlYA8CKcCXuxOuyU4633r/JXi9aRG8MgxfOT+rDuQLgSz+LLCYmBK1ndfHHfyUTilkaUj1H8Q=="
},
"@nativescript/android": { "@nativescript/android": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@nativescript/android/-/android-7.0.1.tgz", "resolved": "https://registry.npmjs.org/@nativescript/android/-/android-7.0.1.tgz",
"integrity": "sha512-VsZCJ5zfZo0+/lFwKz+S7iFb7MA2jgACB7y8dNje3/cnZl+moKPNjFqitoEP0DY4gLz9LJNbFIIaUt84tMdUSQ==", "integrity": "sha512-VsZCJ5zfZo0+/lFwKz+S7iFb7MA2jgACB7y8dNje3/cnZl+moKPNjFqitoEP0DY4gLz9LJNbFIIaUt84tMdUSQ==",
"dev": true "dev": true
}, },
"@nativescript/camera": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@nativescript/camera/-/camera-5.0.2.tgz",
"integrity": "sha512-frNeCLhdQ+W6oXIv05pALdmZDcwilw/NopLtQILtUwuLS7xhE+UMx6CqQxxCxMYzWKsvET2k9VLAo3mJGAoSeg==",
"requires": {
"nativescript-permissions": "~1.3.0"
}
},
"@nativescript/core": { "@nativescript/core": {
"version": "7.0.12", "version": "7.0.12",
"resolved": "https://registry.npmjs.org/@nativescript/core/-/core-7.0.12.tgz", "resolved": "https://registry.npmjs.org/@nativescript/core/-/core-7.0.12.tgz",
@ -1203,11 +1206,6 @@
"mkdirp": "^1.0.4" "mkdirp": "^1.0.4"
} }
}, },
"@nstudio/nativescript-floatingactionbutton": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@nstudio/nativescript-floatingactionbutton/-/nativescript-floatingactionbutton-3.0.3.tgz",
"integrity": "sha512-xA7a/CKQ+kkuFLfgqFClEu+Hl2stMo5BS0qnpYW0gt//MHPNJf/OLlWQ5r5g2rAtw/AKZJGnCewMx2SfWaengQ=="
},
"@types/anymatch": { "@types/anymatch": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@ -5098,26 +5096,17 @@
"to-regex": "^3.0.1" "to-regex": "^3.0.1"
} }
}, },
"nativescript-camera": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/nativescript-camera/-/nativescript-camera-4.5.0.tgz",
"integrity": "sha512-Ecw9JH0CNgnGKXfqD1oifMoIOSnKBgWkecwVpdKfrChkQaCdKDFWdfdJ6X8+YTSwJH4n43e74JmrPzLN2eitwQ==",
"requires": {
"nativescript-permissions": "~1.3.0"
}
},
"nativescript-couchbase-plugin": { "nativescript-couchbase-plugin": {
"version": "0.9.6", "version": "0.9.6",
"resolved": "https://registry.npmjs.org/nativescript-couchbase-plugin/-/nativescript-couchbase-plugin-0.9.6.tgz", "resolved": "https://registry.npmjs.org/nativescript-couchbase-plugin/-/nativescript-couchbase-plugin-0.9.6.tgz",
"integrity": "sha512-kMA9KHQX82TFaGnGUhY94KLOLss4pb5QmghgoEdu1sLwd94I/f1MQ+kHWbuBOdFmdQJw5oCK+Sey+A22Nd5jgA==" "integrity": "sha512-kMA9KHQX82TFaGnGUhY94KLOLss4pb5QmghgoEdu1sLwd94I/f1MQ+kHWbuBOdFmdQJw5oCK+Sey+A22Nd5jgA=="
}, },
"nativescript-mediafilepicker": { "nativescript-imagepicker": {
"version": "4.0.1", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/nativescript-mediafilepicker/-/nativescript-mediafilepicker-4.0.1.tgz", "resolved": "https://registry.npmjs.org/nativescript-imagepicker/-/nativescript-imagepicker-7.1.0.tgz",
"integrity": "sha512-rBrZQR+46dCypIyLrzIlzmHgpmTSMGFR5a6snq8uUhtIqLlc674/nwWlNM1kFOxMh1kKxA+qyk74Of+NCKYoqQ==", "integrity": "sha512-YFVwmPz7mv7mNXA7vmnIXmqPZiWxH4RoJPDL3m34egV8Ae9mKJCXZxl2LyPraOP+T4v6iXsxV9NSbjg0kMDuNQ==",
"requires": { "requires": {
"@nativescript-community/perms": "^2.1.1", "nativescript-permissions": "~1.3.0"
"ts-node": "^9.0.0"
} }
}, },
"nativescript-permissions": { "nativescript-permissions": {
@ -5125,6 +5114,19 @@
"resolved": "https://registry.npmjs.org/nativescript-permissions/-/nativescript-permissions-1.3.11.tgz", "resolved": "https://registry.npmjs.org/nativescript-permissions/-/nativescript-permissions-1.3.11.tgz",
"integrity": "sha512-4ox9WpVJPLfepPauqECvPfbxVE1hVPVVBLZxOs3d9+2Yrr0mSkJO7D7BQ4OUS90hHfRdPhf70aJKWxzJoqi63g==" "integrity": "sha512-4ox9WpVJPLfepPauqECvPfbxVE1hVPVVBLZxOs3d9+2Yrr0mSkJO7D7BQ4OUS90hHfRdPhf70aJKWxzJoqi63g=="
}, },
"nativescript-plugin-filepicker": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/nativescript-plugin-filepicker/-/nativescript-plugin-filepicker-1.0.0.tgz",
"integrity": "sha512-BOf7ycOQJGcg7ayzfbyFooMjngQgBAg6HAaws4fKQBRnI39aw7VVqXruuHSCDPqLW681/7GuECwWxknmAalOnQ==",
"requires": {
"nativescript-permissions": "~1.3.0"
}
},
"nativescript-social-share": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/nativescript-social-share/-/nativescript-social-share-1.6.0.tgz",
"integrity": "sha512-PjSMseCWPGJbW0KPMgQBiTQke6I8cYxf0CGXtuJ0BnRhXrEjF3d+3kAnI8E3O8PeW/BFwNIqLYG4fkoQF4obyA=="
},
"nativescript-toast": { "nativescript-toast": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/nativescript-toast/-/nativescript-toast-2.0.0.tgz", "resolved": "https://registry.npmjs.org/nativescript-toast/-/nativescript-toast-2.0.0.tgz",
@ -7354,18 +7356,6 @@
} }
} }
}, },
"ts-node": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz",
"integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==",
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
}
},
"tslib": { "tslib": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",

View file

@ -8,15 +8,16 @@
"run": "ns run android" "run": "ns run android"
}, },
"dependencies": { "dependencies": {
"@nativescript/camera": "^5.0.2",
"@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", "@nativescript/webpack": "3.0.0",
"@nstudio/nativescript-floatingactionbutton": "^3.0.3",
"nativescript-camera": "^4.5.0",
"nativescript-couchbase-plugin": "^0.9.6", "nativescript-couchbase-plugin": "^0.9.6",
"nativescript-mediafilepicker": "^4.0.0", "nativescript-imagepicker": "^7.1.0",
"nativescript-permissions": "^1.3.9", "nativescript-permissions": "^1.3.9",
"nativescript-plugin-filepicker": "^1.0.0",
"nativescript-social-share": "^1.6.0",
"nativescript-toast": "^2.0.0", "nativescript-toast": "^2.0.0",
"nativescript-ui-listview": "^9.0.4", "nativescript-ui-listview": "^9.0.4",
"nativescript-ui-sidedrawer": "^9.0.3", "nativescript-ui-sidedrawer": "^9.0.3",