implemented backup feature and working on restore
This commit is contained in:
parent
efc254425d
commit
43a1811f91
12 changed files with 309 additions and 166 deletions
|
@ -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()
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
24
app/app.scss
24
app/app.scss
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
36
app/store.js
36
app/store.js
|
@ -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
10
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
Loading…
Reference in a new issue