restores images, data. updated logo

This commit is contained in:
Vishnu Raghav B 2020-11-06 14:37:41 +05:30
parent 43a1811f91
commit 3c0d077af1
66 changed files with 345 additions and 165 deletions

View file

@ -15,7 +15,7 @@
android { android {
defaultConfig { defaultConfig {
versionCode 1 versionCode 2
versionName '1.0.0' versionName '1.0.0'
minSdkVersion 19 minSdkVersion 19
generatedDensities = [] generatedDensities = []

View file

@ -5,7 +5,7 @@
<uses-feature android:name="android.hardware.camera" android:required="false" /> <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" />
<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/ic_launcher" android:roundIcon="@drawable/ic_launcher_round" android:label="@string/app_name" android:hardwareAccelerated="true" android:largeHeap="true" 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" />
<intent-filter> <intent-filter>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -2,10 +2,10 @@
<item> <item>
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<!-- <solid android:color="@android:color/white" /> --> <!-- <solid android:color="@android:color/white" /> -->
<solid android:color="#ff7043" /> <solid android:color="#ff5722" />
</shape> </shape>
</item> </item>
<item> <item>
<bitmap android:gravity="center" android:src="@drawable/logo" /> <bitmap android:gravity="center" android:src="@drawable/logo_white" />
</item> </item>
</layer-list> </layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -13,8 +13,7 @@ $grayL1: #e0e0e0;
$grayL2: #eeeeee; $grayL2: #eeeeee;
$grayL3: #f5f5f5; $grayL3: #f5f5f5;
$grayL4: #fafafa; $grayL4: #fafafa;
$orange400: #ff7043; $orange: #ff5722;
$orange500: #ff5722;
// Global SCSS styling // Global SCSS styling
// @see https://docs.nativescript.org/ui/styling // @see https://docs.nativescript.org/ui/styling
@ -78,7 +77,7 @@ Page {
} }
.fab-button { .fab-button {
color: white; color: white;
background-color: $orange500; background-color: $orange;
} }
.option, .option,
.icon-option { .icon-option {
@ -97,13 +96,14 @@ Page {
} }
.instruction { .instruction {
border-color: $grayD4; border-color: $grayD4;
} } // prettier-ignore
.text-btn, .text-btn,
.group-header, .group-header,
.category, .category,
ActivityIndicator, ActivityIndicator,
.selected-sd-item { .selected-sd-item,
color: $orange500; Progress {
color: $orange;
} }
} }
.ns-dark { .ns-dark {
@ -130,8 +130,7 @@ Page {
.recipeText, .recipeText,
.overviewItem, .overviewItem,
.recipeItem, .recipeItem,
.option-highlight, .option-highlight {
.appIconContainer {
background: $grayD3; background: $grayD3;
} }
.sd-item, .sd-item,
@ -144,13 +143,13 @@ Page {
} }
.fab-button { .fab-button {
color: #111; color: #111;
background: $orange400; background: $orange;
} }
.option, .option,
.icon-option { .icon-option {
.bx, .bx,
.option-info { .option-info {
color: $grayL2; color: $gray;
} }
} }
.imageHolder { .imageHolder {
@ -164,12 +163,14 @@ Page {
.instruction { .instruction {
border-color: $grayL4; border-color: $grayL4;
} }
// prettier-ignore
.text-btn, .text-btn,
.group-header, .group-header,
.category, .category,
ActivityIndicator, ActivityIndicator,
.selected-sd-item { .selected-sd-item,
color: $orange400; Progress {
color: $orange;
} }
} }
// ----------------------------- // -----------------------------
@ -200,6 +201,10 @@ TabView {
margin-left: 8; margin-left: 8;
padding: 0 8; padding: 0 8;
} }
// prettier-ignore
.progressContainer{
width: 100%;
}
// ----------------------------- // -----------------------------
// ActionBar // ActionBar
ActionBar { ActionBar {
@ -260,6 +265,10 @@ ActionBar {
color: $gray; color: $gray;
margin-bottom: 16; margin-bottom: 16;
} }
.logo {
width: 64;
margin-bottom: 16;
}
.title { .title {
font-size: 20; font-size: 20;
text-align: center; text-align: center;
@ -280,7 +289,7 @@ RadListView {
} }
.recipeItem { .recipeItem {
margin: 8 16; margin: 8 16;
border-radius: 6; border-radius: 4;
.recipeInfo { .recipeInfo {
margin: 4; margin: 4;
.category, .category,
@ -304,10 +313,10 @@ RadListView {
.imageHolder { .imageHolder {
vertical-alignment: center; vertical-alignment: center;
&.card { &.card {
border-radius: 6 0 0 6; border-radius: 4 0 0 4;
// prettier-ignore // prettier-ignore
Image { Image {
border-radius: 6 0 0 6; border-radius: 4 0 0 4;
} }
} }
} }
@ -316,7 +325,7 @@ RadListView {
background: #c62828; background: #c62828;
color: #fff; color: #fff;
height: 128; height: 128;
border-radius: 6; border-radius: 4;
} }
// ----------------------------- // -----------------------------
// SETTINGS // SETTINGS
@ -333,22 +342,18 @@ RadListView {
} }
.option-info { .option-info {
font-size: 12; font-size: 12;
line-height: 4;
} }
} }
} }
// ----------------------------- // -----------------------------
// ABOUT // ABOUT
.appIconContainer { .appIconContainer {
padding: 32 0; background: $orange;
padding: 16 0;
width: 100%; width: 100%;
.appIcon { .appIcon {
width: 56; height: 128;
height: 56;
margin: 0 6 0 0;
padding: 0;
}
.appName {
font-size: 24;
} }
} }
// ----------------------------- // -----------------------------
@ -376,7 +381,7 @@ RadListView {
.overviewContainer { .overviewContainer {
margin-top: 12; margin-top: 12;
.overviewItem { .overviewItem {
border-radius: 6; border-radius: 4;
padding: 8; padding: 8;
margin: 8; margin: 8;
android-elevation: 1; android-elevation: 1;
@ -405,7 +410,7 @@ RadListView {
padding-top: 4%; padding-top: 4%;
margin: 0 0 0 8; margin: 0 0 0 8;
text-align: center; text-align: center;
border-radius: 100; border-radius: 99;
&.square { &.square {
clip-path: polygon( clip-path: polygon(
5% 0, 5% 0,
@ -437,7 +442,7 @@ RadListView {
.referenceItem { .referenceItem {
padding: 14 16; padding: 14 16;
margin: 8 16 8; margin: 8 16 8;
border-radius: 6; border-radius: 4;
font-size: 16; font-size: 16;
.bx { .bx {
font-size: 24; font-size: 24;
@ -452,7 +457,7 @@ RadListView {
line-height: 6; line-height: 6;
padding: 16; padding: 16;
margin: 8 16 8; margin: 8 16 8;
border-radius: 6; border-radius: 4;
} }
} }
// ----------------------------- // -----------------------------

View file

@ -19,12 +19,7 @@
orientation="horizontal" orientation="horizontal"
class="appIconContainer" class="appIconContainer"
> >
<Image src="res://icon" class="appIcon" stretch="fill" /> <Image src="res://logo_white" class="appIcon" stretch="aspectFit" />
<Label
text="EnRecipes"
verticalAlignment="center"
class="appName orkb"
/>
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" class="option"> <StackLayout orientation="horizontal" class="option">
<Label class="bx" :text="icon.info" /> <Label class="bx" :text="icon.info" />
@ -98,6 +93,7 @@ export default {
data() { data() {
return { return {
viewIsScrolled: false, viewIsScrolled: false,
appTheme: "Light",
} }
}, },
methods: { methods: {

View file

@ -105,6 +105,7 @@
:showDrawer="showDrawer" :showDrawer="showDrawer"
:hijackGlobalBackEvent="hijackGlobalBackEvent" :hijackGlobalBackEvent="hijackGlobalBackEvent"
:releaseGlobalBackEvent="releaseGlobalBackEvent" :releaseGlobalBackEvent="releaseGlobalBackEvent"
:openAppSettingsPage="openAppSettingsPage"
/> />
</Frame> </Frame>
</GridLayout> </GridLayout>
@ -117,6 +118,7 @@ import {
Utils, Utils,
ApplicationSettings, ApplicationSettings,
AndroidApplication, AndroidApplication,
Application,
} from "@nativescript/core" } from "@nativescript/core"
import Theme from "@nativescript/theme" import Theme from "@nativescript/theme"
@ -286,6 +288,7 @@ export default {
restartApp: this.restartApp, restartApp: this.restartApp,
hijackGlobalBackEvent: this.hijackGlobalBackEvent, hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent, releaseGlobalBackEvent: this.releaseGlobalBackEvent,
openAppSettingsPage: this.openAppSettingsPage,
}, },
backstackVisible: false, backstackVisible: false,
}) })
@ -326,6 +329,18 @@ export default {
) )
android.os.Process.killProcess(android.os.Process.myPid()) android.os.Process.killProcess(android.os.Process.myPid())
}, },
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)
},
showDrawer() { showDrawer() {
this.$refs.drawer.nativeView.showDrawer() this.$refs.drawer.nativeView.showDrawer()
}, },

View file

@ -28,7 +28,6 @@
scrollBarIndicatorVisible="false" scrollBarIndicatorVisible="false"
> >
<StackLayout width="100%" padding="0 0 128"> <StackLayout width="100%" padding="0 0 128">
<!-- Image and camera btn -->
<AbsoluteLayout> <AbsoluteLayout>
<StackLayout <StackLayout
width="100%" width="100%"
@ -65,7 +64,6 @@
</StackLayout> </StackLayout>
</AbsoluteLayout> </AbsoluteLayout>
<!-- Primary information -->
<StackLayout margin="0 16"> <StackLayout margin="0 16">
<AbsoluteLayout class="inputField"> <AbsoluteLayout class="inputField">
<TextField <TextField
@ -115,7 +113,6 @@
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
<!-- Ingredients section -->
<StackLayout margin="0 16"> <StackLayout margin="0 16">
<Label text="Ingredients" class="sectionTitle" /> <Label text="Ingredients" class="sectionTitle" />
<GridLayout <GridLayout
@ -161,7 +158,6 @@
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
<!-- Instructions section -->
<StackLayout margin="0 16"> <StackLayout margin="0 16">
<Label text="Instructions" class="sectionTitle" /> <Label text="Instructions" class="sectionTitle" />
<GridLayout <GridLayout
@ -191,7 +187,6 @@
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
<!-- Notes section -->
<StackLayout margin="0 16"> <StackLayout margin="0 16">
<Label text="Notes" class="sectionTitle" /> <Label text="Notes" class="sectionTitle" />
<GridLayout <GridLayout
@ -217,7 +212,6 @@
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
<!-- References section -->
<StackLayout margin="0 16"> <StackLayout margin="0 16">
<Label text="References" class="sectionTitle" /> <Label text="References" class="sectionTitle" />
<GridLayout <GridLayout
@ -265,6 +259,7 @@ import {
knownFolders, knownFolders,
Utils, Utils,
File, File,
ApplicationSettings,
} from "@nativescript/core" } from "@nativescript/core"
import { Mediafilepicker } from "nativescript-mediafilepicker" import { Mediafilepicker } from "nativescript-mediafilepicker"
@ -276,10 +271,11 @@ import ActionDialog from "./modal/ActionDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import PromptDialog from "./modal/PromptDialog.vue" import PromptDialog from "./modal/PromptDialog.vue"
import ListPicker from "./modal/ListPicker.vue" import ListPicker from "./modal/ListPicker.vue"
import { create } from "domain" import * as Permissions from "@nativescript-community/perms"
import * as Toast from "nativescript-toast"
export default { export default {
props: ["recipeIndex", "recipeID", "selectedCategory"], props: ["recipeIndex", "recipeID", "selectedCategory", "openAppSettingsPage"],
data() { data() {
return { return {
title: "New recipe", title: "New recipe",
@ -519,14 +515,64 @@ export default {
}).then((action) => { }).then((action) => {
this.blockModal = false this.blockModal = false
if (action) { if (action) {
this.imagePicker() this.permissionCheck(
this.imagePickerPermissionConfirmation,
this.imagePicker
)
} else if (action != null) { } else if (action != null) {
this.recipeContent.imageSrc = null this.recipeContent.imageSrc = null
this.releaseBackEvent() this.releaseBackEvent()
} }
}) })
} else { } else {
this.imagePicker() this.permissionCheck(
this.imagePickerPermissionConfirmation,
this.imagePicker
)
}
},
imagePickerPermissionConfirmation() {
return this.$showModal(ConfirmDialog, {
props: {
title: "Grant permission",
description:
"EnRecipes requires storage and camera permission in order to set recipe photo.",
cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE",
},
})
},
permissionCheck(confirmation, action) {
if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) {
confirmation().then((e) => {
if (e) {
Permissions.request("camera").then((res) => {
let status = res[Object.keys(res)[0]]
if (status === "authorized") action()
if (status === "never_ask_again")
ApplicationSettings.setBoolean("storagePermissionAsked", true)
if (status === "denied")
Toast.makeText("Permission denied").show()
})
}
})
} else {
Permissions.check("camera").then((res) => {
if (res[0] !== "authorized") {
confirmation().then((e) => {
e && this.openAppSettingsPage()
})
} else {
Permissions.request("storage").then((res) => {
let status = res[Object.keys(res)[0]]
if (status !== "authorized") {
confirmation().then((e) => {
e && this.openAppSettingsPage()
})
} else action()
})
}
})
} }
}, },
imagePicker() { imagePicker() {
@ -541,6 +587,7 @@ export default {
}, },
}) })
mediafilepicker.on("getFiles", (image) => { mediafilepicker.on("getFiles", (image) => {
ApplicationSettings.setBoolean("storagePermissionAsked", true)
vm.recipeContent.imageSrc = image.object.get("results")[0].file vm.recipeContent.imageSrc = image.object.get("results")[0].file
}) })
}, },
@ -611,7 +658,10 @@ export default {
}, },
imageSaveOperation() { imageSaveOperation() {
let imgSavedToPath = path.join( let imgSavedToPath = path.join(
knownFolders.documents().getFolder("EnRecipes").path, knownFolders
.documents()
.getFolder("EnRecipes")
.getFolder("Images").path,
`${this.getRandomID()}.jpg` `${this.getRandomID()}.jpg`
) )
let workerService = new WorkerService() let workerService = new WorkerService()

View file

@ -76,6 +76,8 @@
v-if="recipe.imageSrc" v-if="recipe.imageSrc"
:src="recipe.imageSrc" :src="recipe.imageSrc"
stretch="aspectFill" stretch="aspectFill"
decodeWidth="112"
decodeHeight="112"
/> />
<Label <Label
row="0" row="0"
@ -118,7 +120,7 @@
class="noResult" class="noResult"
v-if="!recipes.length && !filterFavorites && !filterTrylater" v-if="!recipes.length && !filterFavorites && !filterTrylater"
> >
<Label class="bx icon" :text="icon.plusCircle" textWrap="true" /> <Image class="logo" src="res://icon_gray" stretch="aspectFit" />
<Label <Label
class="title orkm" class="title orkm"
text="Start adding your recipes!" text="Start adding your recipes!"
@ -199,10 +201,7 @@
</template> </template>
<script> <script>
import { import { Utils, AndroidApplication } from "@nativescript/core"
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"
@ -218,6 +217,7 @@ export default {
"showDrawer", "showDrawer",
"hijackGlobalBackEvent", "hijackGlobalBackEvent",
"releaseGlobalBackEvent", "releaseGlobalBackEvent",
"openAppSettingsPage",
], ],
components: { components: {
EditRecipe, EditRecipe,
@ -434,6 +434,7 @@ export default {
// }, // },
props: { props: {
selectedCategory: this.selectedCategory, selectedCategory: this.selectedCategory,
openAppSettingsPage: this.openAppSettingsPage,
}, },
}) })
}, },

View file

@ -22,23 +22,46 @@
> >
<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" />
<Label :text="appTheme" 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="Database" class="group-header" />
<StackLayout orientation="horizontal" class="option" @tap="backupCheck"> <StackLayout orientation="horizontal" class="option" @tap="backupCheck">
<Label class="bx" :text="icon.save" /> <Label class="bx" :text="icon.export" />
<Label text="Backup data" /> <StackLayout>
<Label text="Export a full backup" />
<GridLayout
class="progressContainer"
v-if="backupInProgress"
columns="*, 64"
>
<Progress col="0" :value="backupProgress" />
<Label col="1" :text="` ${backupProgress}%`" />
</GridLayout>
<Label
v-else
text="Generates a zip file that contains all your data. This file can be imported back."
class="option-info"
textWrap="true"
/>
</StackLayout>
</StackLayout> </StackLayout>
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="option" class="option"
@tap="restoreCheck" @tap="restoreCheck"
> >
<Label class="bx" :text="icon.restore" /> <Label class="bx" :text="icon.import" />
<Label text="Restore data" /> <StackLayout>
<Label text="Import from backup" />
<Label
text="Supports full backups exported by this app."
class="option-info"
textWrap="true"
/>
</StackLayout>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
@ -64,7 +87,6 @@ import ActionDialog from "./modal/ActionDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import { Couchbase } from "nativescript-couchbase-plugin" 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: [
@ -73,11 +95,14 @@ export default {
"restartApp", "restartApp",
"hijackGlobalBackEvent", "hijackGlobalBackEvent",
"releaseGlobalBackEvent", "releaseGlobalBackEvent",
"openAppSettingsPage",
], ],
data() { data() {
return { return {
viewIsScrolled: false, viewIsScrolled: false,
appTheme: "Light", appTheme: "Light",
backupProgress: 0,
backupInProgress: false,
} }
}, },
computed: { computed: {
@ -90,7 +115,12 @@ export default {
]), ]),
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction"]), ...mapActions([
"setCurrentComponentAction",
"importCategoriesAction",
"importYieldUnitsAction",
"importRecipesAction",
]),
initializePage() { initializePage() {
this.setCurrentComponentAction("Settings") this.setCurrentComponentAction("Settings")
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
@ -112,9 +142,9 @@ export default {
if (action && action !== "Cancel" && this.appTheme !== action) { if (action && action !== "Cancel" && this.appTheme !== action) {
this.$showModal(ConfirmDialog, { this.$showModal(ConfirmDialog, {
props: { props: {
title: "App Reload Required", title: "Reload required",
description: description:
"The app needs to be reloaded for the theme change to take effect.", "EnRecipes needs to be reloaded for the theme change to take effect.",
cancelButtonText: "CANCEL", cancelButtonText: "CANCEL",
okButtonText: "RELOAD", okButtonText: "RELOAD",
}, },
@ -130,26 +160,22 @@ export default {
}, },
writeFile(file, data) { writeFile(file, data) {
file file.writeText(JSON.stringify(data))
.writeText(JSON.stringify(data))
.then((res) => {
file.readText().then((res) => {
// console.log("Data: ", res)
})
})
.catch((err) => {
console.log(err)
})
}, },
BackupDataFiles(option) { BackupDataFiles(option) {
const folder = path.join(knownFolders.documents().path, "EnRecipes") const folder = path.join(knownFolders.documents().path, "EnRecipes")
const EnRecipesFile = File.fromPath(path.join(folder, "EnRecipes.json")) const EnRecipesFile = File.fromPath(path.join(folder, "EnRecipes.json"))
const userCategoriesFile = File.fromPath( let userCategoriesFile, userYieldUnitsFile
path.join(folder, "userCategories.json") if (this.userCategories.length) {
) userCategoriesFile = File.fromPath(
const userYieldUnitsFile = File.fromPath( path.join(folder, "userCategories.json")
path.join(folder, "userYieldUnits.json") )
) }
if (this.userYieldUnits.length) {
userYieldUnitsFile = File.fromPath(
path.join(folder, "userYieldUnits.json")
)
}
switch (option) { switch (option) {
case "create": case "create":
this.writeFile(EnRecipesFile, this.recipes) this.writeFile(EnRecipesFile, this.recipes)
@ -199,28 +225,34 @@ export default {
sdDownloadPath, sdDownloadPath,
`EnRecipes-Backup_${formattedDate}.zip` `EnRecipes-Backup_${formattedDate}.zip`
) )
this.backupInProgress = true
Zip.zip({ Zip.zip({
directory: fromPath, directory: fromPath,
archive: destPath, archive: destPath,
onProgress: (progress) => {
this.backupProgress = progress
if (progress == 100) {
setTimeout((e) => {
this.backupInProgress = false
}, 2000)
}
},
}).then((success) => {
Toast.makeText(
"Backup file successfully saved to Downloads",
"long"
).show()
this.BackupDataFiles("delete")
}) })
.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) { backupCheck(args) {
let btn = args.object let btn = args.object
this.highlight(args) this.highlight(args)
if (!this.recipes.length) { if (!this.recipes.length) {
Toast.makeText("To perform a backup, add at least one recipe").show() Toast.makeText(
"Add at least one recipe to perform a backup",
"long"
).show()
} else { } else {
this.permissionCheck(this.backupPermissionConfirmation, this.backupData) this.permissionCheck(this.backupPermissionConfirmation, this.backupData)
} }
@ -237,7 +269,6 @@ export default {
}, },
}) })
}, },
restoreData() {},
restoreCheck(args) { restoreCheck(args) {
let btn = args.object let btn = args.object
this.highlight(args) this.highlight(args)
@ -258,58 +289,77 @@ export default {
let zipPath = result let zipPath = result
let dest = knownFolders.documents().path let dest = knownFolders.documents().path
this.validateZipContent(zipPath) this.validateZipContent(zipPath)
// Zip.unzip({ })
// archive: zipPath, },
// directory: dest, importDataToDB(data, db, zipPath) {
// overwrite: true, switch (db) {
// }) case "EnRecipesDB":
// .then((success) => { this.copyImages(zipPath)
// this.restoreDataInDB() this.importRecipesAction(data)
// Toast.makeText("Restore successful!").show() break
// }) case "userCategoriesDB":
// .catch((err) => { this.importCategoriesAction(data)
// console.log(err) break
// }) case "userYieldUnitsDB":
this.importYieldUnitsAction(data)
break
default:
break
}
},
isImportedDataValid(file) {
file.forEach((file, i) => {
if (File.exists(file.path)) {
File.fromPath(file.path)
.readText()
.then((data) => {
Array.isArray(JSON.parse(data)) &&
this.importDataToDB(JSON.parse(data), file.db, file.zipPath)
})
}
}) })
}, },
validateZipContent(zipPath) { validateZipContent(zipPath) {
Zip.unzip({ Zip.unzip({
archive: zipPath, archive: zipPath,
overwrite: true, overwrite: true,
}).then((success) => { }).then((extractedFolderPath) => {
let cacheFolderPath = success + "/EnRecipes" let cacheFolderPath = extractedFolderPath + "/EnRecipes"
const EnRecipesFilePath = cacheFolderPath + "/EnRecipes.json" const EnRecipesFilePath = cacheFolderPath + "/EnRecipes.json"
const userCategoriesFilePath = cacheFolderPath + "/userCategories.json" const userCategoriesFilePath = cacheFolderPath + "/userCategories.json"
const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json" const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json"
if ( if (Folder.exists(cacheFolderPath)) {
Folder.exists(cacheFolderPath) && this.isImportedDataValid([
File.exists(EnRecipesFilePath) && {
File.exists(userCategoriesFilePath) && zipPath,
File.exists(userCategoriesFilePath) path: EnRecipesFilePath,
) { db: "EnRecipesDB",
console.log("Zip intact") },
// Check if EnRecipes.json is of type array { zipPath, path: userCategoriesFilePath, db: "userCategoriesDB" },
File.fromPath(EnRecipesFilePath) { zipPath, path: userYieldUnitsFilePath, db: "userYieldUnitsDB" },
.readText() ])
.then((data) => {
let EnRecipesData = JSON.parse(data)
console.log(Array.isArray(EnRecipesData))
EnRecipesData.forEach(recipe => {
})
console.log(EnRecipesData)
})
} else { } else {
Folder.fromPath(success).remove() Folder.fromPath(extractedFolderPath).remove()
console.log("Zip modified externally or incorrect file") Toast.makeText(
"Zip modified externally or incorrect file",
"long"
).show()
}
if (Folder.exists(cacheFolderPath + "/Images")) {
this.copyImages(cacheFolderPath + "/Images")
} }
}) })
}, },
restoreDataInDB() { copyImages(sourcePath) {
// recipesDB.android let dest = knownFolders.documents().path
// recipesDB.android.destroyDatabase() Zip.unzip({
archive: sourcePath,
directory: dest,
overwrite: true,
}).then((res) => {
Toast.makeText("Import successful!", "long").show()
})
}, },
permissionCheck(confirmation, action) { permissionCheck(confirmation, action) {
if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) { if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) {
confirmation().then((e) => { confirmation().then((e) => {
@ -334,18 +384,6 @@ export default {
}) })
} }
}, },
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.appTheme = ApplicationSettings.getString("appTheme", "Light") this.appTheme = ApplicationSettings.getString("appTheme", "Light")

View file

@ -474,7 +474,6 @@ export default {
: 1 : 1
}, },
isLightMode() { isLightMode() {
console.log(Application.systemAppearance())
return Application.systemAppearance() === "light" return Application.systemAppearance() === "light"
}, },
}, },
@ -555,9 +554,7 @@ export default {
shareRecipe() { shareRecipe() {
let overview = `${ let overview = `${
this.recipe.title this.recipe.title
}\n\nTime required: ${this.formattedTime( }\n\nTime required: ${this.formattedTime(this.recipe.timeRequired)}\n`
this.recipe.timeRequired
)}\n`
let shareContent = overview let shareContent = overview
if (this.recipe.ingredients.length) { if (this.recipe.ingredients.length) {
let ingredients = `\n\nIngredients for ${ let ingredients = `\n\nIngredients for ${

View file

@ -2,7 +2,7 @@ import Vue from "vue"
import Vuex from "vuex" import Vuex from "vuex"
import { Couchbase } from "nativescript-couchbase-plugin" import { Couchbase } from "nativescript-couchbase-plugin"
import { getFileAccess } from "@nativescript/core" import { getFileAccess } from "@nativescript/core"
const recipesDB = new Couchbase("EnRecipes") const EnRecipesDB = new Couchbase("EnRecipes")
const userCategoriesDB = new Couchbase("userCategories") const userCategoriesDB = new Couchbase("userCategories")
const userYieldUnitsDB = new Couchbase("userYieldUnits") const userYieldUnitsDB = new Couchbase("userYieldUnits")
@ -163,7 +163,6 @@ export default new Vuex.Store({
share: "\uedf3", share: "\uedf3",
edit: "\uedba", edit: "\uedba",
theme: "\uecaa", theme: "\uecaa",
restore: "\uea72",
link: "\ueaa0", link: "\ueaa0",
file: "\ued02", file: "\ued02",
user: "\uee33", user: "\uee33",
@ -179,12 +178,14 @@ export default new Vuex.Store({
item: "\ue99d", item: "\ue99d",
step: "\ue948", step: "\ue948",
source: "\ueaa0", source: "\ueaa0",
export: "\ued07",
import: "\ued0c",
}, },
currentComponent: "EnRecipes", currentComponent: "EnRecipes",
}, },
mutations: { mutations: {
initializeRecipes(state) { initializeRecipes(state) {
let a = recipesDB.query({ select: [] }) let a = EnRecipesDB.query({ select: [] })
a.forEach((e) => { a.forEach((e) => {
state.recipes.push(e) state.recipes.push(e)
}) })
@ -228,7 +229,45 @@ export default new Vuex.Store({
}, },
addRecipe(state, { id, recipe }) { addRecipe(state, { id, recipe }) {
state.recipes.push(recipe) state.recipes.push(recipe)
recipesDB.createDocument(recipe, id) EnRecipesDB.createDocument(recipe, id)
},
importRecipes(state, recipes) {
console.log("hello")
let localRecipesIDs, partition
if (state.recipes.length) {
localRecipesIDs = state.recipes.map((e) => e.id)
partition = recipes.reduce(
(result, recipe, i) => {
localRecipesIDs.indexOf(recipe.id) < 0
? result[0].push(recipe) // create candidates
: result[1].push(recipe) // update candidates
return result
},
[[], []]
)
if (partition[0].length) createDocuments(partition[0])
if (partition[1].length) updateDocuments(partition[1])
} else {
createDocuments(recipes)
}
function createDocuments(data) {
console.log("creating")
state.recipes = [...state.recipes, ...data]
data.forEach((recipe) => {
EnRecipesDB.createDocument(recipe, recipe.id)
})
}
function updateDocuments(data) {
console.log("updating")
data.forEach((recipe) => {
let recipeIndex = state.recipes
.map((e, i) => (e.id === recipe.id ? i : -1))
.filter((e) => e >= 0)[0]
console.log(recipeIndex)
Object.assign(state.recipes[recipeIndex], recipe)
EnRecipesDB.updateDocument(recipe.id, recipe)
})
}
}, },
addCategory(state, category) { addCategory(state, category) {
let lowercase = state.categories.map((e) => e.toLowerCase()) let lowercase = state.categories.map((e) => e.toLowerCase())
@ -241,33 +280,48 @@ export default new Vuex.Store({
state.categories.sort() state.categories.sort()
} }
}, },
addYieldUnit(state, unit) { importCategories(state, categories) {
state.userCategories = new Set([...state.userCategories, ...categories])
userCategoriesDB.updateDocument("userCategories", {
userCategories: [...state.userCategories],
})
state.categories = [...defaultCategories, ...state.userCategories]
state.categories.sort()
},
addYieldUnit(state, yieldUnit) {
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(yieldUnit.toLowerCase()) == -1) {
state.userYieldUnits.push(unit) state.userYieldUnits.push(yieldUnit)
userYieldUnitsDB.updateDocument("userYieldUnits", { userYieldUnitsDB.updateDocument("userYieldUnits", {
userYieldUnits: [...state.userYieldUnits], userYieldUnits: [...state.userYieldUnits],
}) })
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits] state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
} }
}, },
importYieldUnits(state, yieldUnits) {
state.userYieldUnits = new Set([...state.userYieldUnits, ...yieldUnits])
userYieldUnitsDB.updateDocument("userYieldUnits", {
userYieldUnits: [...state.userYieldUnits],
})
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
},
overwriteRecipe(state, { index, id, recipe }) { overwriteRecipe(state, { index, id, recipe }) {
Object.assign(state.recipes[index], recipe) Object.assign(state.recipes[index], recipe)
recipesDB.updateDocument(id, recipe) EnRecipesDB.updateDocument(id, recipe)
}, },
deleteRecipe(state, { index, id }) { deleteRecipe(state, { index, id }) {
getFileAccess().deleteFile(state.recipes[index].imageSrc) getFileAccess().deleteFile(state.recipes[index].imageSrc)
state.recipes.splice(index, 1) state.recipes.splice(index, 1)
recipesDB.deleteDocument(id) EnRecipesDB.deleteDocument(id)
}, },
toggleState(state, { index, id, recipe, key, setDate }) { toggleState(state, { index, id, recipe, key, setDate }) {
state.recipes[index][key] = !state.recipes[index][key] state.recipes[index][key] = !state.recipes[index][key]
if (setDate) state.recipes[index].lastTried = new Date() if (setDate) state.recipes[index].lastTried = new Date()
recipesDB.updateDocument(id, recipe) EnRecipesDB.updateDocument(id, recipe)
}, },
setLastTriedDate(state, index) { setLastTriedDate(state, index) {
state.recipes[index].lastTried = new Date() state.recipes[index].lastTried = new Date()
recipesDB.updateDocument(state.recipes[index].id, state.recipes[index]) EnRecipesDB.updateDocument(state.recipes[index].id, state.recipes[index])
}, },
setCurrentComponent(state, comp) { setCurrentComponent(state, comp) {
state.currentComponent = comp state.currentComponent = comp
@ -285,8 +339,8 @@ export default new Vuex.Store({
state.recipes.forEach((e, i) => { state.recipes.forEach((e, i) => {
if (e.category == current) { if (e.category == current) {
state.recipes[i].category = updated state.recipes[i].category = updated
recipesDB.inBatch(() => { EnRecipesDB.inBatch(() => {
recipesDB.updateDocument(state.recipes[i].id, state.recipes[i]) EnRecipesDB.updateDocument(state.recipes[i].id, state.recipes[i])
}) })
} }
}) })
@ -305,12 +359,21 @@ export default new Vuex.Store({
addRecipeAction({ commit }, recipe) { addRecipeAction({ commit }, recipe) {
commit("addRecipe", recipe) commit("addRecipe", recipe)
}, },
importRecipesAction({ commit }, recipes) {
commit("importRecipes", recipes)
},
addCategoryAction({ commit }, category) { addCategoryAction({ commit }, category) {
commit("addCategory", category) commit("addCategory", category)
}, },
importCategoriesAction({ commit }, categories) {
commit("importCategories", categories)
},
addYieldUnitAction({ commit }, yieldUnit) { addYieldUnitAction({ commit }, yieldUnit) {
commit("addYieldUnit", yieldUnit) commit("addYieldUnit", yieldUnit)
}, },
importYieldUnitsAction({ commit }, yieldUnits) {
commit("importYieldUnits", yieldUnits)
},
overwriteRecipeAction({ commit }, updatedRecipe) { overwriteRecipeAction({ commit }, updatedRecipe) {
commit("overwriteRecipe", updatedRecipe) commit("overwriteRecipe", updatedRecipe)
}, },

View file

@ -1,11 +1,16 @@
require("tns-core-modules/globals") require("tns-core-modules/globals")
import { ImageSource } from "@nativescript/core" import { ImageSource, ImageAsset } from "@nativescript/core"
global.onmessage = function({ data }) { global.onmessage = function({ data }) {
let imgFile = data.imgFile
let imgSavedToPath = data.imgSavedToPath let imgSavedToPath = data.imgSavedToPath
ImageSource.fromFile(imgFile).then((imgData) => { let imgAsset = new ImageAsset(data.imgFile)
if (imgData.saveToFile(imgSavedToPath, "jpg")) { imgAsset.options = {
width: 1200,
height: 1200,
keepAspectRatio: true,
}
ImageSource.fromAsset(imgAsset).then((imgData) => {
if (imgData.saveToFile(imgSavedToPath, "jpg", 75)) {
global.postMessage("savedToFile") global.postMessage("savedToFile")
} }
}) })