implemented backup feature and working on restore

This commit is contained in:
Vishnu Raghav B 2020-11-04 01:27:31 +05:30
parent efc254425d
commit 43a1811f91
12 changed files with 309 additions and 166 deletions

View file

@ -17,7 +17,7 @@ android {
defaultConfig { defaultConfig {
versionCode 1 versionCode 1
versionName '1.0.0' versionName '1.0.0'
minSdkVersion 25 minSdkVersion 19
generatedDensities = [] generatedDensities = []
ndk { ndk {
abiFilters.clear() abiFilters.clear()

View file

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

View file

@ -303,10 +303,12 @@ RadListView {
} }
.imageHolder { .imageHolder {
vertical-alignment: center; vertical-alignment: center;
border-radius: 6 0 0 6; &.card {
// prettier-ignore
Image {
border-radius: 6 0 0 6; border-radius: 6 0 0 6;
// prettier-ignore
Image {
border-radius: 6 0 0 6;
}
} }
} }
.swipe-item { .swipe-item {
@ -533,11 +535,9 @@ ActivityIndicator {
animation-timing-function: ease-in-out; animation-timing-function: ease-in-out;
} }
.bounce-leave-active { .bounce-leave-active {
animation-name: bounce-in; animation-name: bounce-out;
animation-duration: 0.1s; animation-duration: 0.25s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
animation-direction: reverse;
animation-timing-function: ease;
} }
@keyframes bounce-in { @keyframes bounce-in {
0% { 0% {
@ -559,6 +559,16 @@ ActivityIndicator {
transform: scale(1); transform: scale(1);
} }
} }
@keyframes bounce-out {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
opacity: 0;
}
}
.dolly-enter-active { .dolly-enter-active {
animation-name: dolly; animation-name: dolly;
animation-duration: 1s; animation-duration: 1s;

View file

@ -338,7 +338,7 @@ export default {
}, },
}, },
created() { created() {
let themeName = ApplicationSettings.getString("application-theme", "Light") let themeName = ApplicationSettings.getString("appTheme", "Light")
setTimeout((e) => Theme.setMode(Theme[themeName]), 50) setTimeout((e) => Theme.setMode(Theme[themeName]), 50)
if (!this.recipes.length) this.initializeRecipes() if (!this.recipes.length) this.initializeRecipes()
if (!this.categories.length) this.initializeCategories() if (!this.categories.length) this.initializeCategories()

View file

@ -611,7 +611,7 @@ export default {
}, },
imageSaveOperation() { imageSaveOperation() {
let imgSavedToPath = path.join( let imgSavedToPath = path.join(
knownFolders.documents().getFolder("enrecipes").path, knownFolders.documents().getFolder("EnRecipes").path,
`${this.getRandomID()}.jpg` `${this.getRandomID()}.jpg`
) )
let workerService = new WorkerService() let workerService = new WorkerService()

View file

@ -69,7 +69,7 @@
columns="112, *" columns="112, *"
androidElevation="2" androidElevation="2"
> >
<GridLayout class="imageHolder" rows="112" columns="112"> <GridLayout class="imageHolder card" rows="112" columns="112">
<Image <Image
row="0" row="0"
col="0" col="0"
@ -199,7 +199,10 @@
</template> </template>
<script> <script>
import { Utils, AndroidApplication } from "@nativescript/core" import {
Utils,
AndroidApplication,
} from "@nativescript/core"
import EditRecipe from "./EditRecipe.vue" import EditRecipe from "./EditRecipe.vue"
import ViewRecipe from "./ViewRecipe.vue" import ViewRecipe from "./ViewRecipe.vue"
@ -405,20 +408,7 @@ export default {
this.deletionDialogActive = false this.deletionDialogActive = false
}) })
}, },
// getTotalTime(prepTime, timeRequired) {
// let pT = prepTime.split(":")
// let cT = timeRequired.split(":")
// let hrs = parseInt(pT[0]) + parseInt(cT[0])
// let mins = parseInt(pT[1]) + parseInt(cT[1])
// if (mins > 60) {
// hrs += Math.floor(mins / 60)
// mins -= 60
// }
// return {
// hrs,
// mins,
// }
// },
formattedTime(time) { formattedTime(time) {
let t = time.split(":") let t = time.split(":")
let h = parseInt(t[0]) let h = parseInt(t[0])
@ -438,9 +428,9 @@ export default {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.$navigateTo(EditRecipe, { this.$navigateTo(EditRecipe, {
// transition: { // transition: {
// name: "slide", // name: "fade",
// duration: 250, // duration: 200,
// curve: "easeIn", // curve: "easeOut",
// }, // },
props: { props: {
selectedCategory: this.selectedCategory, selectedCategory: this.selectedCategory,
@ -452,8 +442,8 @@ export default {
this.$navigateTo(ViewRecipe, { this.$navigateTo(ViewRecipe, {
// transition: { // transition: {
// name: "fade", // name: "fade",
// duration: 250, // duration: 200,
// curve: "easeIn", // curve: "easeOut",
// }, // },
props: { props: {
filterTrylater: this.filterTrylater, filterTrylater: this.filterTrylater,

View file

@ -23,16 +23,20 @@
<Label verticalAlignment="center" class="bx" :text="icon.theme" /> <Label verticalAlignment="center" class="bx" :text="icon.theme" />
<StackLayout> <StackLayout>
<Label text="Theme" class="option-title" /> <Label text="Theme" class="option-title" />
<Label :text="themeName" class="option-info" textWrap="true" /> <Label :text="appTheme" class="option-info" textWrap="true" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<Label text="Backup/Restore" class="group-header" /> <Label text="Backup/Restore" class="group-header" />
<StackLayout orientation="horizontal" class="option" @tap="backupData"> <StackLayout orientation="horizontal" class="option" @tap="backupCheck">
<Label class="bx" :text="icon.save" /> <Label class="bx" :text="icon.save" />
<Label text="Backup data" /> <Label text="Backup data" />
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" class="option" @tap="restoreData"> <StackLayout
orientation="horizontal"
class="option"
@tap="restoreCheck"
>
<Label class="bx" :text="icon.restore" /> <Label class="bx" :text="icon.restore" />
<Label text="Restore data" /> <Label text="Restore data" />
</StackLayout> </StackLayout>
@ -47,17 +51,20 @@ import {
path, path,
getFileAccess, getFileAccess,
knownFolders, knownFolders,
Application,
File,
Folder,
} from "@nativescript/core" } from "@nativescript/core"
import * as permissions from "nativescript-permissions" import * as Permissions from "@nativescript-community/perms"
import { Zip } from "nativescript-zip" import { Zip } from "@nativescript/zip"
import * as Toast from "nativescript-toast" import * as Toast from "nativescript-toast"
import * as Filepicker from "nativescript-plugin-filepicker"
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"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import { Couchbase } from "nativescript-couchbase-plugin"
const recipesDB = new Couchbase("EnRecipes")
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
export default { export default {
props: [ props: [
@ -70,36 +77,17 @@ export default {
data() { data() {
return { return {
viewIsScrolled: false, viewIsScrolled: false,
interface: { appTheme: "Light",
theme: {
title: "Theme",
subTitle: "Light",
icon: "\ued09",
},
},
backupRestore: [
{
title: "EnRecipes Backup Directory",
subTitle: "/storage/emulated/0/EnRecipes",
icon: "\ued7c",
},
{
title: "Backup Data",
subTitle: null,
icon: "\uee48",
},
{
title: "Restore Data",
subTitle: null,
icon: "\ueadc",
},
],
themeName: "Light",
themesArray: ["Light", "Dark"],
} }
}, },
computed: { computed: {
...mapState(["icon", "currentComponent"]), ...mapState([
"icon",
"recipes",
"userCategories",
"userYieldUnits",
"currentComponent",
]),
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction"]), ...mapActions(["setCurrentComponentAction"]),
@ -121,7 +109,7 @@ export default {
height: "108", height: "108",
}, },
}).then((action) => { }).then((action) => {
if (action && action !== "Cancel" && this.themeName !== action) { if (action && action !== "Cancel" && this.appTheme !== action) {
this.$showModal(ConfirmDialog, { this.$showModal(ConfirmDialog, {
props: { props: {
title: "App Reload Required", title: "App Reload Required",
@ -132,91 +120,235 @@ export default {
}, },
}).then((result) => { }).then((result) => {
if (result) { if (result) {
this.interface.theme.subTitle = this.themeName = action this.appTheme = action
ApplicationSettings.setString("application-theme", action) ApplicationSettings.setString("appTheme", action)
setTimeout((e) => this.restartApp(), 250) setTimeout((e) => this.restartApp(), 250)
} }
}) })
} }
}) })
}, },
selectBackupDir(args) {
this.highlight(args) writeFile(file, data) {
let btn = args.object file
permissions .writeText(JSON.stringify(data))
.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) .then((res) => {
.then(() => { file.readText().then((res) => {
alert("select backup directory") // console.log("Data: ", res)
})
.catch(() => {
console.log("Uh oh, no permissions - plan B time!")
})
},
backupData(args) {
let btn = args.object
this.highlight(args)
permissions
.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.then(() => {
const sdDownloadPath = android.os.Environment.getExternalStoragePublicDirectory(
android.os.Environment.DIRECTORY_DOWNLOADS
).toString()
let date = new Date()
let formattedDate = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`
let fromPath = path.join(knownFolders.documents().path, "enrecipes")
let destPath = path.join(
sdDownloadPath,
`EnRecipes-${formattedDate}.zip`
)
console.log(fromPath, destPath, sdDownloadPath)
Zip.zip({
directory: fromPath,
archive: destPath,
}) })
.then((success) => {
Toast.makeText(
"Backup file successfully saved to Downloads",
"long"
).show()
console.log("success:" + success)
})
.catch((err) => {
console.log(err)
})
// console.log(fromPath, destPath, sdDownloadPath)
// alert("Backup successful!")
}) })
.catch(() => { .catch((err) => {
console.log("Uh oh, no permissions - plan B time!") console.log(err)
}) })
}, },
restoreData(args) { BackupDataFiles(option) {
const folder = path.join(knownFolders.documents().path, "EnRecipes")
const EnRecipesFile = File.fromPath(path.join(folder, "EnRecipes.json"))
const userCategoriesFile = File.fromPath(
path.join(folder, "userCategories.json")
)
const userYieldUnitsFile = File.fromPath(
path.join(folder, "userYieldUnits.json")
)
switch (option) {
case "create":
this.writeFile(EnRecipesFile, this.recipes)
this.userCategories.length &&
this.writeFile(userCategoriesFile, this.userCategories)
this.userYieldUnits.length &&
this.writeFile(userYieldUnitsFile, this.userYieldUnits)
break
case "delete":
EnRecipesFile.remove()
this.userCategories.length && userCategoriesFile.remove()
this.userYieldUnits.length && userYieldUnitsFile.remove()
break
default:
break
}
},
backupPermissionConfirmation() {
return this.$showModal(ConfirmDialog, {
props: {
title: "Grant permission",
description:
"EnRecipes requires storage permission in order to backup your data to this device",
cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE",
},
})
},
backupData() {
this.BackupDataFiles("create")
let date = new Date()
let formattedDate =
date.getFullYear() +
"-" +
("0" + (date.getMonth() + 1)).slice(-2) +
"-" +
("0" + date.getDate()).slice(-2) +
"_" +
("0" + date.getHours()).slice(-2) +
("0" + date.getMinutes()).slice(-2) +
("0" + date.getSeconds()).slice(-2)
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-Backup_${formattedDate}.zip`
)
Zip.zip({
directory: fromPath,
archive: destPath,
})
.then((success) => {
Toast.makeText(
"Backup file successfully saved to Downloads",
"long"
).show()
console.log("success:" + success)
this.BackupDataFiles("delete")
})
.catch((err) => {
console.log(err)
})
},
backupCheck(args) {
let btn = args.object let btn = args.object
this.highlight(args) this.highlight(args)
let vm = this
let context = filepicker.create({ if (!this.recipes.length) {
Toast.makeText("To perform a backup, add at least one recipe").show()
} else {
this.permissionCheck(this.backupPermissionConfirmation, this.backupData)
}
},
restorePermissionConfirmation() {
return this.$showModal(ConfirmDialog, {
props: {
title: "Grant permission",
description:
"EnRecipes requires storage permission in order to restore your data from a previous backup.",
cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE",
},
})
},
restoreData() {},
restoreCheck(args) {
let btn = args.object
this.highlight(args)
this.permissionCheck(
this.restorePermissionConfirmation,
this.openFilePicker
)
},
openFilePicker() {
let context = Filepicker.create({
mode: "single", // use "multiple" for multiple selection mode: "single", // use "multiple" for multiple selection
extensions: ["zip"], extensions: ["zip"],
}) })
context context.present().then((selection) => {
.authorize() Toast.makeText("Processing...").show()
.then(function() { let result = selection[0]
return context.present() let zipPath = result
let dest = knownFolders.documents().path
this.validateZipContent(zipPath)
// Zip.unzip({
// archive: zipPath,
// directory: dest,
// overwrite: true,
// })
// .then((success) => {
// this.restoreDataInDB()
// Toast.makeText("Restore successful!").show()
// })
// .catch((err) => {
// console.log(err)
// })
})
},
validateZipContent(zipPath) {
Zip.unzip({
archive: zipPath,
overwrite: true,
}).then((success) => {
let cacheFolderPath = success + "/EnRecipes"
const EnRecipesFilePath = cacheFolderPath + "/EnRecipes.json"
const userCategoriesFilePath = cacheFolderPath + "/userCategories.json"
const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json"
if (
Folder.exists(cacheFolderPath) &&
File.exists(EnRecipesFilePath) &&
File.exists(userCategoriesFilePath) &&
File.exists(userCategoriesFilePath)
) {
console.log("Zip intact")
// Check if EnRecipes.json is of type array
File.fromPath(EnRecipesFilePath)
.readText()
.then((data) => {
let EnRecipesData = JSON.parse(data)
console.log(Array.isArray(EnRecipesData))
EnRecipesData.forEach(recipe => {
})
console.log(EnRecipesData)
})
} else {
Folder.fromPath(success).remove()
console.log("Zip modified externally or incorrect file")
}
})
},
restoreDataInDB() {
// recipesDB.android
// recipesDB.android.destroyDatabase()
},
permissionCheck(confirmation, action) {
if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) {
confirmation().then((e) => {
if (e) {
Permissions.request("storage").then((res) => {
let status = res[Object.keys(res)[0]]
if (status === "authorized") action()
if (status !== "denied")
ApplicationSettings.setBoolean("storagePermissionAsked", true)
else Toast.makeText("Permission denied").show()
})
}
}) })
.then(function(selection) { } else {
let result = selection Permissions.request("storage").then((res) => {
console.log(result) let status = res[Object.keys(res)[0]]
}) if (status !== "authorized") {
.catch(function(e) { confirmation().then((e) => {
console.log(e) e && this.openAppSettingsPage()
})
} else action()
}) })
}
},
openAppSettingsPage() {
const intent = new android.content.Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
)
intent.addCategory(android.content.Intent.CATEGORY_DEFAULT)
intent.setData(
android.net.Uri.parse(
"package:" + Application.android.context.getPackageName()
)
)
Application.android.foregroundActivity.startActivity(intent)
}, },
}, },
created() { created() {
this.interface.theme.subTitle = this.themeName = ApplicationSettings.getString( this.appTheme = ApplicationSettings.getString("appTheme", "Light")
"application-theme",
"Light"
)
}, },
} }
</script> </script>

View file

@ -1,7 +1,7 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="initializePage" @unloaded="hideInfo">
<ActionBar height="152" margin="0" flat="true" visibility="collapse"> <ActionBar height="136" margin="0" flat="true">
<GridLayout rows="24, 64, 64" columns="auto, *, auto,auto, auto"> <GridLayout rows="24, 64, 48" columns="auto, *, auto,auto, auto">
<Label <Label
row="1" row="1"
col="0" col="0"
@ -444,6 +444,8 @@ import { mapState, mapActions } from "vuex"
import EditRecipe from "./EditRecipe.vue" import EditRecipe from "./EditRecipe.vue"
let feedback = new Feedback()
export default { export default {
props: [ props: [
"filterTrylater", "filterTrylater",
@ -507,7 +509,6 @@ export default {
this.selectedTabIndex = args.object.selectedIndex this.selectedTabIndex = args.object.selectedIndex
}, },
showInfo() { showInfo() {
let feedback = new Feedback()
feedback.show({ feedback.show({
title: `You tried this recipe ${this.niceDates( title: `You tried this recipe ${this.niceDates(
this.recipe.lastTried this.recipe.lastTried
@ -518,6 +519,9 @@ export default {
), ),
}) })
}, },
hideInfo() {
feedback.hide()
},
highlight(args) { highlight(args) {
let temp = args.object.className let temp = args.object.className
args.object.className = `${temp} option-highlight` args.object.className = `${temp} option-highlight`
@ -551,7 +555,7 @@ export default {
shareRecipe() { shareRecipe() {
let overview = `${ let overview = `${
this.recipe.title this.recipe.title
} Recipe\n\nApprox. cooking time: ${this.formattedTime( }\n\nTime required: ${this.formattedTime(
this.recipe.timeRequired this.recipe.timeRequired
)}\n` )}\n`
let shareContent = overview let shareContent = overview
@ -583,12 +587,12 @@ export default {
if (this.recipe.references.length) { if (this.recipe.references.length) {
let references = `\nReferences:\n\n` let references = `\nReferences:\n\n`
this.recipe.references.forEach((e, i) => { this.recipe.references.forEach((e, i) => {
references += `${e}\n\n` references += `${i + 1}. ${e}\n\n`
}) })
shareContent += references shareContent += references
} }
let sharenote = let sharenote =
"\nCreated and shared via EnRecipes.\n\nDownload the app on f-droid:\nhttps://www.vishnuraghav.com/" "\nCreated and shared via EnRecipes.\nDownload the app on f-droid: https://www.vishnuraghav.com/"
shareContent += sharenote shareContent += sharenote
@ -652,7 +656,7 @@ export default {
}, },
mounted() { mounted() {
this.showFab = true this.showFab = true
setTimeout((e) => this.recipe.tried && this.showInfo(), 2000) setTimeout((e) => this.recipe.tried && this.showInfo(), 500)
}, },
} }
</script> </script>

View file

@ -1,6 +1,6 @@
<template> <template>
<Page> <Page>
<StackLayout class="dialogContainer" :class="isLightMode"> <StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<ListView <ListView
width="100%" width="100%"
@ -37,7 +37,7 @@ import { Application } from "@nativescript/core"
export default { export default {
props: ["title", "list", "height", "action"], props: ["title", "list", "height", "action"],
computed: { computed: {
isLightMode() { appTheme() {
return Application.systemAppearance() return Application.systemAppearance()
}, },
}, },

View file

@ -1,9 +1,10 @@
import Vue from "vue" import Vue from "vue"
import Vuex from "vuex" import Vuex from "vuex"
import { Couchbase } from "nativescript-couchbase-plugin" import { Couchbase } from "nativescript-couchbase-plugin"
const recipesDB = new Couchbase("enrecipes") import { getFileAccess } from "@nativescript/core"
const categoriesDB = new Couchbase("userCategories") const recipesDB = new Couchbase("EnRecipes")
const yieldUnitsDB = new Couchbase("userYieldUnits") const userCategoriesDB = new Couchbase("userCategories")
const userYieldUnitsDB = new Couchbase("userYieldUnits")
Vue.use(Vuex) Vue.use(Vuex)
@ -49,7 +50,7 @@ let defaultYieldUnits = [
"Millilitre", "Millilitre",
"Litre", "Litre",
"Roll", "Roll",
"Pattie", "Patty",
"Loaf", "Loaf",
] ]
@ -98,7 +99,7 @@ export default new Vuex.Store({
// "Continue adding hot broth, 1/2 cup at a time, and stirring until all the broth has been absorbed and rice is tender but firm. Add the mushroom mixture, spinach, pepper, salt and grated Parmesan cheese; cook and stir until heated through. If desired, sprinkle with parsley and shaved Parmesan cheese. Serve immediately.", // "Continue adding hot broth, 1/2 cup at a time, and stirring until all the broth has been absorbed and rice is tender but firm. Add the mushroom mixture, spinach, pepper, salt and grated Parmesan cheese; cook and stir until heated through. If desired, sprinkle with parsley and shaved Parmesan cheese. Serve immediately.",
// ], // ],
// notes: [ // notes: [
// "Nutrition Facts\n3/4 cup: 409 calories, 22g fat (12g saturated fat), 61mg cholesterol, 667mg sodium, 41g carbohydrate (3g sugars, 2g fiber), 11g protein.", // "Nutrition Facts: 3/4 cup: 409 calories, 22g fat (12g saturated fat), 61mg cholesterol, 667mg sodium, 41g carbohydrate (3g sugars, 2g fiber), 11g protein.",
// ], // ],
// references: [ // references: [
// "https://www.tasteofhome.com/recipes/mushroom-spinach-risotto/", // "https://www.tasteofhome.com/recipes/mushroom-spinach-risotto/",
@ -189,9 +190,9 @@ export default new Vuex.Store({
}) })
}, },
initializeCategories(state) { initializeCategories(state) {
let isCategoriesStored = categoriesDB.query({ select: [] }).length let isCategoriesStored = userCategoriesDB.query({ select: [] }).length
if (isCategoriesStored) { if (isCategoriesStored) {
state.userCategories = categoriesDB.getDocument( state.userCategories = userCategoriesDB.getDocument(
"userCategories" "userCategories"
).userCategories ).userCategories
let categoriesWithRecipes = state.recipes.map((e) => e.category) let categoriesWithRecipes = state.recipes.map((e) => e.category)
@ -199,15 +200,18 @@ export default new Vuex.Store({
categoriesWithRecipes.includes(e) categoriesWithRecipes.includes(e)
) )
} else { } else {
categoriesDB.createDocument({ userCategories: [] }, "userCategories") userCategoriesDB.createDocument(
{ userCategories: [] },
"userCategories"
)
} }
state.categories = [...defaultCategories, ...state.userCategories] state.categories = [...defaultCategories, ...state.userCategories]
state.categories.sort() state.categories.sort()
}, },
initializeYieldUnits(state) { initializeYieldUnits(state) {
let isYieldUnitsStored = yieldUnitsDB.query({ select: [] }).length let isYieldUnitsStored = userYieldUnitsDB.query({ select: [] }).length
if (isYieldUnitsStored) { if (isYieldUnitsStored) {
state.userYieldUnits = yieldUnitsDB.getDocument( state.userYieldUnits = userYieldUnitsDB.getDocument(
"userYieldUnits" "userYieldUnits"
).userYieldUnits ).userYieldUnits
let yieldUnitsWithRecipes = state.recipes.map((e) => e.yield.unit) let yieldUnitsWithRecipes = state.recipes.map((e) => e.yield.unit)
@ -215,7 +219,10 @@ export default new Vuex.Store({
yieldUnitsWithRecipes.includes(e) yieldUnitsWithRecipes.includes(e)
) )
} else { } else {
yieldUnitsDB.createDocument({ userYieldUnits: [] }, "userYieldUnits") userYieldUnitsDB.createDocument(
{ userYieldUnits: [] },
"userYieldUnits"
)
} }
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits] state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
}, },
@ -227,7 +234,7 @@ export default new Vuex.Store({
let lowercase = state.categories.map((e) => e.toLowerCase()) let lowercase = state.categories.map((e) => e.toLowerCase())
if (lowercase.indexOf(category.toLowerCase()) == -1) { if (lowercase.indexOf(category.toLowerCase()) == -1) {
state.userCategories.push(category) state.userCategories.push(category)
categoriesDB.updateDocument("userCategories", { userCategoriesDB.updateDocument("userCategories", {
userCategories: [...state.userCategories], userCategories: [...state.userCategories],
}) })
state.categories = [...defaultCategories, ...state.userCategories] state.categories = [...defaultCategories, ...state.userCategories]
@ -238,7 +245,7 @@ export default new Vuex.Store({
let lowercase = state.yieldUnits.map((e) => e.toLowerCase()) let lowercase = state.yieldUnits.map((e) => e.toLowerCase())
if (lowercase.indexOf(unit.toLowerCase()) == -1) { if (lowercase.indexOf(unit.toLowerCase()) == -1) {
state.userYieldUnits.push(unit) state.userYieldUnits.push(unit)
yieldUnitsDB.updateDocument("userYieldUnits", { userYieldUnitsDB.updateDocument("userYieldUnits", {
userYieldUnits: [...state.userYieldUnits], userYieldUnits: [...state.userYieldUnits],
}) })
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits] state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
@ -249,6 +256,7 @@ export default new Vuex.Store({
recipesDB.updateDocument(id, recipe) recipesDB.updateDocument(id, recipe)
}, },
deleteRecipe(state, { index, id }) { deleteRecipe(state, { index, id }) {
getFileAccess().deleteFile(state.recipes[index].imageSrc)
state.recipes.splice(index, 1) state.recipes.splice(index, 1)
recipesDB.deleteDocument(id) recipesDB.deleteDocument(id)
}, },
@ -268,7 +276,7 @@ export default new Vuex.Store({
let lowercase = state.categories.map((e) => e.toLowerCase()) let lowercase = state.categories.map((e) => e.toLowerCase())
if (lowercase.indexOf(updated.toLowerCase()) == -1) { if (lowercase.indexOf(updated.toLowerCase()) == -1) {
state.userCategories.push(updated) state.userCategories.push(updated)
categoriesDB.updateDocument("userCategories", { userCategoriesDB.updateDocument("userCategories", {
userCategories: [...state.userCategories], userCategories: [...state.userCategories],
}) })
state.categories = [...defaultCategories, ...state.userCategories] state.categories = [...defaultCategories, ...state.userCategories]

10
package-lock.json generated
View file

@ -1172,6 +1172,11 @@
"webpack-sources": "~1.4.3" "webpack-sources": "~1.4.3"
} }
}, },
"@nativescript/zip": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@nativescript/zip/-/zip-5.0.0.tgz",
"integrity": "sha512-iM1ln1KPRqNEKebvXkZGe3899+Esbp9DMF+hwTwC7Gd+oqhc4Wh8Tas97ormzMHR6iPasiOXs6wKuu625TYdIQ=="
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
@ -5223,11 +5228,6 @@
"schema-utils": "^2.7.0" "schema-utils": "^2.7.0"
} }
}, },
"nativescript-zip": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/nativescript-zip/-/nativescript-zip-4.0.2.tgz",
"integrity": "sha512-CoJx97Pd49mOMIkpL2dJBBKFsy3XsnstWEQLL5tiV9ObfX3gWH42uG0HL4LjCP5yh5ovxT7CDYGlm68lnExP9A=="
},
"negotiator": { "negotiator": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",

View file

@ -8,10 +8,12 @@
"run": "ns run android" "run": "ns run android"
}, },
"dependencies": { "dependencies": {
"@nativescript-community/perms": "^2.1.1",
"@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",
"@nativescript/zip": "^5.0.0",
"@nstudio/nativescript-checkbox": "^2.0.4", "@nstudio/nativescript-checkbox": "^2.0.4",
"nativescript-clipboard": "^2.0.0", "nativescript-clipboard": "^2.0.0",
"nativescript-couchbase-plugin": "^0.9.6", "nativescript-couchbase-plugin": "^0.9.6",
@ -23,7 +25,6 @@
"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": {