restores images, data. updated logo
|
@ -15,7 +15,7 @@
|
|||
|
||||
android {
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionCode 2
|
||||
versionName '1.0.0'
|
||||
minSdkVersion 19
|
||||
generatedDensities = []
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<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.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">
|
||||
<meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" />
|
||||
<intent-filter>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Before Width: | Height: | Size: 550 B |
BIN
App_Resources/Android/src/main/res/drawable-hdpi/ic_launcher.png
Executable file
After Width: | Height: | Size: 3.6 KiB |
BIN
App_Resources/Android/src/main/res/drawable-hdpi/ic_launcher_background.png
Executable file
After Width: | Height: | Size: 875 B |
BIN
App_Resources/Android/src/main/res/drawable-hdpi/ic_launcher_foreground.png
Executable file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.1 KiB |
BIN
App_Resources/Android/src/main/res/drawable-hdpi/icon_gray.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 9.8 KiB |
BIN
App_Resources/Android/src/main/res/drawable-hdpi/logo_dark.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
App_Resources/Android/src/main/res/drawable-hdpi/logo_white.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 461 B |
BIN
App_Resources/Android/src/main/res/drawable-ldpi/ic_launcher.png
Executable file
After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 664 B |
BIN
App_Resources/Android/src/main/res/drawable-ldpi/icon_gray.png
Normal file
After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 4.4 KiB |
BIN
App_Resources/Android/src/main/res/drawable-ldpi/logo_dark.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
App_Resources/Android/src/main/res/drawable-ldpi/logo_white.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 472 B |
BIN
App_Resources/Android/src/main/res/drawable-mdpi/ic_launcher.png
Executable file
After Width: | Height: | Size: 2.4 KiB |
BIN
App_Resources/Android/src/main/res/drawable-mdpi/ic_launcher_background.png
Executable file
After Width: | Height: | Size: 740 B |
BIN
App_Resources/Android/src/main/res/drawable-mdpi/ic_launcher_foreground.png
Executable file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 780 B |
BIN
App_Resources/Android/src/main/res/drawable-mdpi/icon_gray.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.6 KiB |
BIN
App_Resources/Android/src/main/res/drawable-mdpi/logo_dark.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
App_Resources/Android/src/main/res/drawable-mdpi/logo_white.png
Normal file
After Width: | Height: | Size: 11 KiB |
|
@ -2,10 +2,10 @@
|
|||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<!-- <solid android:color="@android:color/white" /> -->
|
||||
<solid android:color="#ff7043" />
|
||||
<solid android:color="#ff5722" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/logo" />
|
||||
<bitmap android:gravity="center" android:src="@drawable/logo_white" />
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
Before Width: | Height: | Size: 680 B |
BIN
App_Resources/Android/src/main/res/drawable-xhdpi/ic_launcher.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xhdpi/ic_launcher_background.png
Executable file
After Width: | Height: | Size: 1.1 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
Executable file
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xhdpi/icon_gray.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 12 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xhdpi/logo_dark.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xhdpi/logo_white.png
Normal file
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 861 B |
BIN
App_Resources/Android/src/main/res/drawable-xxhdpi/ic_launcher.png
Executable file
After Width: | Height: | Size: 7.1 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xxhdpi/ic_launcher_background.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
Executable file
After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xxhdpi/icon_gray.png
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 24 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xxhdpi/logo_dark.png
Normal file
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 1.1 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xxxhdpi/ic_launcher.png
Executable file
After Width: | Height: | Size: 9.3 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xxxhdpi/ic_launcher_background.png
Executable file
After Width: | Height: | Size: 1.7 KiB |
BIN
App_Resources/Android/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
Executable file
After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 48 KiB |
61
app/app.scss
|
@ -13,8 +13,7 @@ $grayL1: #e0e0e0;
|
|||
$grayL2: #eeeeee;
|
||||
$grayL3: #f5f5f5;
|
||||
$grayL4: #fafafa;
|
||||
$orange400: #ff7043;
|
||||
$orange500: #ff5722;
|
||||
$orange: #ff5722;
|
||||
|
||||
// Global SCSS styling
|
||||
// @see https://docs.nativescript.org/ui/styling
|
||||
|
@ -78,7 +77,7 @@ Page {
|
|||
}
|
||||
.fab-button {
|
||||
color: white;
|
||||
background-color: $orange500;
|
||||
background-color: $orange;
|
||||
}
|
||||
.option,
|
||||
.icon-option {
|
||||
|
@ -97,13 +96,14 @@ Page {
|
|||
}
|
||||
.instruction {
|
||||
border-color: $grayD4;
|
||||
}
|
||||
} // prettier-ignore
|
||||
.text-btn,
|
||||
.group-header,
|
||||
.category,
|
||||
ActivityIndicator,
|
||||
.selected-sd-item {
|
||||
color: $orange500;
|
||||
.selected-sd-item,
|
||||
Progress {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
.ns-dark {
|
||||
|
@ -130,8 +130,7 @@ Page {
|
|||
.recipeText,
|
||||
.overviewItem,
|
||||
.recipeItem,
|
||||
.option-highlight,
|
||||
.appIconContainer {
|
||||
.option-highlight {
|
||||
background: $grayD3;
|
||||
}
|
||||
.sd-item,
|
||||
|
@ -144,13 +143,13 @@ Page {
|
|||
}
|
||||
.fab-button {
|
||||
color: #111;
|
||||
background: $orange400;
|
||||
background: $orange;
|
||||
}
|
||||
.option,
|
||||
.icon-option {
|
||||
.bx,
|
||||
.option-info {
|
||||
color: $grayL2;
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
.imageHolder {
|
||||
|
@ -164,12 +163,14 @@ Page {
|
|||
.instruction {
|
||||
border-color: $grayL4;
|
||||
}
|
||||
// prettier-ignore
|
||||
.text-btn,
|
||||
.group-header,
|
||||
.category,
|
||||
ActivityIndicator,
|
||||
.selected-sd-item {
|
||||
color: $orange400;
|
||||
.selected-sd-item,
|
||||
Progress {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
// -----------------------------
|
||||
|
@ -200,6 +201,10 @@ TabView {
|
|||
margin-left: 8;
|
||||
padding: 0 8;
|
||||
}
|
||||
// prettier-ignore
|
||||
.progressContainer{
|
||||
width: 100%;
|
||||
}
|
||||
// -----------------------------
|
||||
// ActionBar
|
||||
ActionBar {
|
||||
|
@ -260,6 +265,10 @@ ActionBar {
|
|||
color: $gray;
|
||||
margin-bottom: 16;
|
||||
}
|
||||
.logo {
|
||||
width: 64;
|
||||
margin-bottom: 16;
|
||||
}
|
||||
.title {
|
||||
font-size: 20;
|
||||
text-align: center;
|
||||
|
@ -280,7 +289,7 @@ RadListView {
|
|||
}
|
||||
.recipeItem {
|
||||
margin: 8 16;
|
||||
border-radius: 6;
|
||||
border-radius: 4;
|
||||
.recipeInfo {
|
||||
margin: 4;
|
||||
.category,
|
||||
|
@ -304,10 +313,10 @@ RadListView {
|
|||
.imageHolder {
|
||||
vertical-alignment: center;
|
||||
&.card {
|
||||
border-radius: 6 0 0 6;
|
||||
border-radius: 4 0 0 4;
|
||||
// prettier-ignore
|
||||
Image {
|
||||
border-radius: 6 0 0 6;
|
||||
border-radius: 4 0 0 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -316,7 +325,7 @@ RadListView {
|
|||
background: #c62828;
|
||||
color: #fff;
|
||||
height: 128;
|
||||
border-radius: 6;
|
||||
border-radius: 4;
|
||||
}
|
||||
// -----------------------------
|
||||
// SETTINGS
|
||||
|
@ -333,22 +342,18 @@ RadListView {
|
|||
}
|
||||
.option-info {
|
||||
font-size: 12;
|
||||
line-height: 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
// -----------------------------
|
||||
// ABOUT
|
||||
.appIconContainer {
|
||||
padding: 32 0;
|
||||
background: $orange;
|
||||
padding: 16 0;
|
||||
width: 100%;
|
||||
.appIcon {
|
||||
width: 56;
|
||||
height: 56;
|
||||
margin: 0 6 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
.appName {
|
||||
font-size: 24;
|
||||
height: 128;
|
||||
}
|
||||
}
|
||||
// -----------------------------
|
||||
|
@ -376,7 +381,7 @@ RadListView {
|
|||
.overviewContainer {
|
||||
margin-top: 12;
|
||||
.overviewItem {
|
||||
border-radius: 6;
|
||||
border-radius: 4;
|
||||
padding: 8;
|
||||
margin: 8;
|
||||
android-elevation: 1;
|
||||
|
@ -405,7 +410,7 @@ RadListView {
|
|||
padding-top: 4%;
|
||||
margin: 0 0 0 8;
|
||||
text-align: center;
|
||||
border-radius: 100;
|
||||
border-radius: 99;
|
||||
&.square {
|
||||
clip-path: polygon(
|
||||
5% 0,
|
||||
|
@ -437,7 +442,7 @@ RadListView {
|
|||
.referenceItem {
|
||||
padding: 14 16;
|
||||
margin: 8 16 8;
|
||||
border-radius: 6;
|
||||
border-radius: 4;
|
||||
font-size: 16;
|
||||
.bx {
|
||||
font-size: 24;
|
||||
|
@ -452,7 +457,7 @@ RadListView {
|
|||
line-height: 6;
|
||||
padding: 16;
|
||||
margin: 8 16 8;
|
||||
border-radius: 6;
|
||||
border-radius: 4;
|
||||
}
|
||||
}
|
||||
// -----------------------------
|
||||
|
|
|
@ -19,12 +19,7 @@
|
|||
orientation="horizontal"
|
||||
class="appIconContainer"
|
||||
>
|
||||
<Image src="res://icon" class="appIcon" stretch="fill" />
|
||||
<Label
|
||||
text="EnRecipes"
|
||||
verticalAlignment="center"
|
||||
class="appName orkb"
|
||||
/>
|
||||
<Image src="res://logo_white" class="appIcon" stretch="aspectFit" />
|
||||
</StackLayout>
|
||||
<StackLayout orientation="horizontal" class="option">
|
||||
<Label class="bx" :text="icon.info" />
|
||||
|
@ -98,6 +93,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
viewIsScrolled: false,
|
||||
appTheme: "Light",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
:showDrawer="showDrawer"
|
||||
:hijackGlobalBackEvent="hijackGlobalBackEvent"
|
||||
:releaseGlobalBackEvent="releaseGlobalBackEvent"
|
||||
:openAppSettingsPage="openAppSettingsPage"
|
||||
/>
|
||||
</Frame>
|
||||
</GridLayout>
|
||||
|
@ -117,6 +118,7 @@ import {
|
|||
Utils,
|
||||
ApplicationSettings,
|
||||
AndroidApplication,
|
||||
Application,
|
||||
} from "@nativescript/core"
|
||||
|
||||
import Theme from "@nativescript/theme"
|
||||
|
@ -286,6 +288,7 @@ export default {
|
|||
restartApp: this.restartApp,
|
||||
hijackGlobalBackEvent: this.hijackGlobalBackEvent,
|
||||
releaseGlobalBackEvent: this.releaseGlobalBackEvent,
|
||||
openAppSettingsPage: this.openAppSettingsPage,
|
||||
},
|
||||
backstackVisible: false,
|
||||
})
|
||||
|
@ -326,6 +329,18 @@ export default {
|
|||
)
|
||||
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() {
|
||||
this.$refs.drawer.nativeView.showDrawer()
|
||||
},
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
scrollBarIndicatorVisible="false"
|
||||
>
|
||||
<StackLayout width="100%" padding="0 0 128">
|
||||
<!-- Image and camera btn -->
|
||||
<AbsoluteLayout>
|
||||
<StackLayout
|
||||
width="100%"
|
||||
|
@ -65,7 +64,6 @@
|
|||
</StackLayout>
|
||||
</AbsoluteLayout>
|
||||
|
||||
<!-- Primary information -->
|
||||
<StackLayout margin="0 16">
|
||||
<AbsoluteLayout class="inputField">
|
||||
<TextField
|
||||
|
@ -115,7 +113,6 @@
|
|||
<StackLayout class="hr" margin="24 16"></StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
<!-- Ingredients section -->
|
||||
<StackLayout margin="0 16">
|
||||
<Label text="Ingredients" class="sectionTitle" />
|
||||
<GridLayout
|
||||
|
@ -161,7 +158,6 @@
|
|||
<StackLayout class="hr" margin="24 16"></StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
<!-- Instructions section -->
|
||||
<StackLayout margin="0 16">
|
||||
<Label text="Instructions" class="sectionTitle" />
|
||||
<GridLayout
|
||||
|
@ -191,7 +187,6 @@
|
|||
<StackLayout class="hr" margin="24 16"></StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
<!-- Notes section -->
|
||||
<StackLayout margin="0 16">
|
||||
<Label text="Notes" class="sectionTitle" />
|
||||
<GridLayout
|
||||
|
@ -217,7 +212,6 @@
|
|||
<StackLayout class="hr" margin="24 16"></StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
<!-- References section -->
|
||||
<StackLayout margin="0 16">
|
||||
<Label text="References" class="sectionTitle" />
|
||||
<GridLayout
|
||||
|
@ -265,6 +259,7 @@ import {
|
|||
knownFolders,
|
||||
Utils,
|
||||
File,
|
||||
ApplicationSettings,
|
||||
} from "@nativescript/core"
|
||||
import { Mediafilepicker } from "nativescript-mediafilepicker"
|
||||
|
||||
|
@ -276,10 +271,11 @@ import ActionDialog from "./modal/ActionDialog.vue"
|
|||
import ConfirmDialog from "./modal/ConfirmDialog.vue"
|
||||
import PromptDialog from "./modal/PromptDialog.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 {
|
||||
props: ["recipeIndex", "recipeID", "selectedCategory"],
|
||||
props: ["recipeIndex", "recipeID", "selectedCategory", "openAppSettingsPage"],
|
||||
data() {
|
||||
return {
|
||||
title: "New recipe",
|
||||
|
@ -519,14 +515,64 @@ export default {
|
|||
}).then((action) => {
|
||||
this.blockModal = false
|
||||
if (action) {
|
||||
this.imagePicker()
|
||||
this.permissionCheck(
|
||||
this.imagePickerPermissionConfirmation,
|
||||
this.imagePicker
|
||||
)
|
||||
} else if (action != null) {
|
||||
this.recipeContent.imageSrc = null
|
||||
this.releaseBackEvent()
|
||||
}
|
||||
})
|
||||
} 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() {
|
||||
|
@ -541,6 +587,7 @@ export default {
|
|||
},
|
||||
})
|
||||
mediafilepicker.on("getFiles", (image) => {
|
||||
ApplicationSettings.setBoolean("storagePermissionAsked", true)
|
||||
vm.recipeContent.imageSrc = image.object.get("results")[0].file
|
||||
})
|
||||
},
|
||||
|
@ -611,7 +658,10 @@ export default {
|
|||
},
|
||||
imageSaveOperation() {
|
||||
let imgSavedToPath = path.join(
|
||||
knownFolders.documents().getFolder("EnRecipes").path,
|
||||
knownFolders
|
||||
.documents()
|
||||
.getFolder("EnRecipes")
|
||||
.getFolder("Images").path,
|
||||
`${this.getRandomID()}.jpg`
|
||||
)
|
||||
let workerService = new WorkerService()
|
||||
|
|
|
@ -76,6 +76,8 @@
|
|||
v-if="recipe.imageSrc"
|
||||
:src="recipe.imageSrc"
|
||||
stretch="aspectFill"
|
||||
decodeWidth="112"
|
||||
decodeHeight="112"
|
||||
/>
|
||||
<Label
|
||||
row="0"
|
||||
|
@ -118,7 +120,7 @@
|
|||
class="noResult"
|
||||
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
|
||||
class="title orkm"
|
||||
text="Start adding your recipes!"
|
||||
|
@ -199,10 +201,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Utils,
|
||||
AndroidApplication,
|
||||
} from "@nativescript/core"
|
||||
import { Utils, AndroidApplication } from "@nativescript/core"
|
||||
|
||||
import EditRecipe from "./EditRecipe.vue"
|
||||
import ViewRecipe from "./ViewRecipe.vue"
|
||||
|
@ -218,6 +217,7 @@ export default {
|
|||
"showDrawer",
|
||||
"hijackGlobalBackEvent",
|
||||
"releaseGlobalBackEvent",
|
||||
"openAppSettingsPage",
|
||||
],
|
||||
components: {
|
||||
EditRecipe,
|
||||
|
@ -434,6 +434,7 @@ export default {
|
|||
// },
|
||||
props: {
|
||||
selectedCategory: this.selectedCategory,
|
||||
openAppSettingsPage: this.openAppSettingsPage,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
|
|
@ -22,23 +22,46 @@
|
|||
>
|
||||
<Label verticalAlignment="center" class="bx" :text="icon.theme" />
|
||||
<StackLayout>
|
||||
<Label text="Theme" class="option-title" />
|
||||
<Label text="Theme" />
|
||||
<Label :text="appTheme" class="option-info" textWrap="true" />
|
||||
</StackLayout>
|
||||
</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">
|
||||
<Label class="bx" :text="icon.save" />
|
||||
<Label text="Backup data" />
|
||||
<Label class="bx" :text="icon.export" />
|
||||
<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
|
||||
orientation="horizontal"
|
||||
class="option"
|
||||
@tap="restoreCheck"
|
||||
>
|
||||
<Label class="bx" :text="icon.restore" />
|
||||
<Label text="Restore data" />
|
||||
<Label class="bx" :text="icon.import" />
|
||||
<StackLayout>
|
||||
<Label text="Import from backup" />
|
||||
<Label
|
||||
text="Supports full backups exported by this app."
|
||||
class="option-info"
|
||||
textWrap="true"
|
||||
/>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
@ -64,7 +87,6 @@ import ActionDialog from "./modal/ActionDialog.vue"
|
|||
import ConfirmDialog from "./modal/ConfirmDialog.vue"
|
||||
|
||||
import { Couchbase } from "nativescript-couchbase-plugin"
|
||||
const recipesDB = new Couchbase("EnRecipes")
|
||||
import { mapState, mapActions } from "vuex"
|
||||
export default {
|
||||
props: [
|
||||
|
@ -73,11 +95,14 @@ export default {
|
|||
"restartApp",
|
||||
"hijackGlobalBackEvent",
|
||||
"releaseGlobalBackEvent",
|
||||
"openAppSettingsPage",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
viewIsScrolled: false,
|
||||
appTheme: "Light",
|
||||
backupProgress: 0,
|
||||
backupInProgress: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -90,7 +115,12 @@ export default {
|
|||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["setCurrentComponentAction"]),
|
||||
...mapActions([
|
||||
"setCurrentComponentAction",
|
||||
"importCategoriesAction",
|
||||
"importYieldUnitsAction",
|
||||
"importRecipesAction",
|
||||
]),
|
||||
initializePage() {
|
||||
this.setCurrentComponentAction("Settings")
|
||||
this.releaseGlobalBackEvent()
|
||||
|
@ -112,9 +142,9 @@ export default {
|
|||
if (action && action !== "Cancel" && this.appTheme !== action) {
|
||||
this.$showModal(ConfirmDialog, {
|
||||
props: {
|
||||
title: "App Reload Required",
|
||||
title: "Reload required",
|
||||
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",
|
||||
okButtonText: "RELOAD",
|
||||
},
|
||||
|
@ -130,26 +160,22 @@ export default {
|
|||
},
|
||||
|
||||
writeFile(file, data) {
|
||||
file
|
||||
.writeText(JSON.stringify(data))
|
||||
.then((res) => {
|
||||
file.readText().then((res) => {
|
||||
// console.log("Data: ", res)
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
file.writeText(JSON.stringify(data))
|
||||
},
|
||||
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")
|
||||
)
|
||||
let userCategoriesFile, userYieldUnitsFile
|
||||
if (this.userCategories.length) {
|
||||
userCategoriesFile = File.fromPath(
|
||||
path.join(folder, "userCategories.json")
|
||||
)
|
||||
}
|
||||
if (this.userYieldUnits.length) {
|
||||
userYieldUnitsFile = File.fromPath(
|
||||
path.join(folder, "userYieldUnits.json")
|
||||
)
|
||||
}
|
||||
switch (option) {
|
||||
case "create":
|
||||
this.writeFile(EnRecipesFile, this.recipes)
|
||||
|
@ -199,28 +225,34 @@ export default {
|
|||
sdDownloadPath,
|
||||
`EnRecipes-Backup_${formattedDate}.zip`
|
||||
)
|
||||
this.backupInProgress = true
|
||||
Zip.zip({
|
||||
directory: fromPath,
|
||||
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) {
|
||||
let btn = args.object
|
||||
this.highlight(args)
|
||||
|
||||
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 {
|
||||
this.permissionCheck(this.backupPermissionConfirmation, this.backupData)
|
||||
}
|
||||
|
@ -237,7 +269,6 @@ export default {
|
|||
},
|
||||
})
|
||||
},
|
||||
restoreData() {},
|
||||
restoreCheck(args) {
|
||||
let btn = args.object
|
||||
this.highlight(args)
|
||||
|
@ -258,58 +289,77 @@ export default {
|
|||
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)
|
||||
// })
|
||||
})
|
||||
},
|
||||
importDataToDB(data, db, zipPath) {
|
||||
switch (db) {
|
||||
case "EnRecipesDB":
|
||||
this.copyImages(zipPath)
|
||||
this.importRecipesAction(data)
|
||||
break
|
||||
case "userCategoriesDB":
|
||||
this.importCategoriesAction(data)
|
||||
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) {
|
||||
Zip.unzip({
|
||||
archive: zipPath,
|
||||
overwrite: true,
|
||||
}).then((success) => {
|
||||
let cacheFolderPath = success + "/EnRecipes"
|
||||
}).then((extractedFolderPath) => {
|
||||
let cacheFolderPath = extractedFolderPath + "/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)
|
||||
})
|
||||
if (Folder.exists(cacheFolderPath)) {
|
||||
this.isImportedDataValid([
|
||||
{
|
||||
zipPath,
|
||||
path: EnRecipesFilePath,
|
||||
db: "EnRecipesDB",
|
||||
},
|
||||
{ zipPath, path: userCategoriesFilePath, db: "userCategoriesDB" },
|
||||
{ zipPath, path: userYieldUnitsFilePath, db: "userYieldUnitsDB" },
|
||||
])
|
||||
} else {
|
||||
Folder.fromPath(success).remove()
|
||||
console.log("Zip modified externally or incorrect file")
|
||||
Folder.fromPath(extractedFolderPath).remove()
|
||||
Toast.makeText(
|
||||
"Zip modified externally or incorrect file",
|
||||
"long"
|
||||
).show()
|
||||
}
|
||||
if (Folder.exists(cacheFolderPath + "/Images")) {
|
||||
this.copyImages(cacheFolderPath + "/Images")
|
||||
}
|
||||
})
|
||||
},
|
||||
restoreDataInDB() {
|
||||
// recipesDB.android
|
||||
// recipesDB.android.destroyDatabase()
|
||||
copyImages(sourcePath) {
|
||||
let dest = knownFolders.documents().path
|
||||
Zip.unzip({
|
||||
archive: sourcePath,
|
||||
directory: dest,
|
||||
overwrite: true,
|
||||
}).then((res) => {
|
||||
Toast.makeText("Import successful!", "long").show()
|
||||
})
|
||||
},
|
||||
|
||||
permissionCheck(confirmation, action) {
|
||||
if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) {
|
||||
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() {
|
||||
this.appTheme = ApplicationSettings.getString("appTheme", "Light")
|
||||
|
|
|
@ -474,7 +474,6 @@ export default {
|
|||
: 1
|
||||
},
|
||||
isLightMode() {
|
||||
console.log(Application.systemAppearance())
|
||||
return Application.systemAppearance() === "light"
|
||||
},
|
||||
},
|
||||
|
@ -555,9 +554,7 @@ export default {
|
|||
shareRecipe() {
|
||||
let overview = `${
|
||||
this.recipe.title
|
||||
}\n\nTime required: ${this.formattedTime(
|
||||
this.recipe.timeRequired
|
||||
)}\n`
|
||||
}\n\nTime required: ${this.formattedTime(this.recipe.timeRequired)}\n`
|
||||
let shareContent = overview
|
||||
if (this.recipe.ingredients.length) {
|
||||
let ingredients = `\n\nIngredients for ${
|
||||
|
|
89
app/store.js
|
@ -2,7 +2,7 @@ import Vue from "vue"
|
|||
import Vuex from "vuex"
|
||||
import { Couchbase } from "nativescript-couchbase-plugin"
|
||||
import { getFileAccess } from "@nativescript/core"
|
||||
const recipesDB = new Couchbase("EnRecipes")
|
||||
const EnRecipesDB = new Couchbase("EnRecipes")
|
||||
const userCategoriesDB = new Couchbase("userCategories")
|
||||
const userYieldUnitsDB = new Couchbase("userYieldUnits")
|
||||
|
||||
|
@ -163,7 +163,6 @@ export default new Vuex.Store({
|
|||
share: "\uedf3",
|
||||
edit: "\uedba",
|
||||
theme: "\uecaa",
|
||||
restore: "\uea72",
|
||||
link: "\ueaa0",
|
||||
file: "\ued02",
|
||||
user: "\uee33",
|
||||
|
@ -179,12 +178,14 @@ export default new Vuex.Store({
|
|||
item: "\ue99d",
|
||||
step: "\ue948",
|
||||
source: "\ueaa0",
|
||||
export: "\ued07",
|
||||
import: "\ued0c",
|
||||
},
|
||||
currentComponent: "EnRecipes",
|
||||
},
|
||||
mutations: {
|
||||
initializeRecipes(state) {
|
||||
let a = recipesDB.query({ select: [] })
|
||||
let a = EnRecipesDB.query({ select: [] })
|
||||
a.forEach((e) => {
|
||||
state.recipes.push(e)
|
||||
})
|
||||
|
@ -228,7 +229,45 @@ export default new Vuex.Store({
|
|||
},
|
||||
addRecipe(state, { id, 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) {
|
||||
let lowercase = state.categories.map((e) => e.toLowerCase())
|
||||
|
@ -241,33 +280,48 @@ export default new Vuex.Store({
|
|||
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())
|
||||
if (lowercase.indexOf(unit.toLowerCase()) == -1) {
|
||||
state.userYieldUnits.push(unit)
|
||||
if (lowercase.indexOf(yieldUnit.toLowerCase()) == -1) {
|
||||
state.userYieldUnits.push(yieldUnit)
|
||||
userYieldUnitsDB.updateDocument("userYieldUnits", {
|
||||
userYieldUnits: [...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 }) {
|
||||
Object.assign(state.recipes[index], recipe)
|
||||
recipesDB.updateDocument(id, recipe)
|
||||
EnRecipesDB.updateDocument(id, recipe)
|
||||
},
|
||||
deleteRecipe(state, { index, id }) {
|
||||
getFileAccess().deleteFile(state.recipes[index].imageSrc)
|
||||
state.recipes.splice(index, 1)
|
||||
recipesDB.deleteDocument(id)
|
||||
EnRecipesDB.deleteDocument(id)
|
||||
},
|
||||
toggleState(state, { index, id, recipe, key, setDate }) {
|
||||
state.recipes[index][key] = !state.recipes[index][key]
|
||||
if (setDate) state.recipes[index].lastTried = new Date()
|
||||
recipesDB.updateDocument(id, recipe)
|
||||
EnRecipesDB.updateDocument(id, recipe)
|
||||
},
|
||||
setLastTriedDate(state, index) {
|
||||
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) {
|
||||
state.currentComponent = comp
|
||||
|
@ -285,8 +339,8 @@ export default new Vuex.Store({
|
|||
state.recipes.forEach((e, i) => {
|
||||
if (e.category == current) {
|
||||
state.recipes[i].category = updated
|
||||
recipesDB.inBatch(() => {
|
||||
recipesDB.updateDocument(state.recipes[i].id, state.recipes[i])
|
||||
EnRecipesDB.inBatch(() => {
|
||||
EnRecipesDB.updateDocument(state.recipes[i].id, state.recipes[i])
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -305,12 +359,21 @@ export default new Vuex.Store({
|
|||
addRecipeAction({ commit }, recipe) {
|
||||
commit("addRecipe", recipe)
|
||||
},
|
||||
importRecipesAction({ commit }, recipes) {
|
||||
commit("importRecipes", recipes)
|
||||
},
|
||||
addCategoryAction({ commit }, category) {
|
||||
commit("addCategory", category)
|
||||
},
|
||||
importCategoriesAction({ commit }, categories) {
|
||||
commit("importCategories", categories)
|
||||
},
|
||||
addYieldUnitAction({ commit }, yieldUnit) {
|
||||
commit("addYieldUnit", yieldUnit)
|
||||
},
|
||||
importYieldUnitsAction({ commit }, yieldUnits) {
|
||||
commit("importYieldUnits", yieldUnits)
|
||||
},
|
||||
overwriteRecipeAction({ commit }, updatedRecipe) {
|
||||
commit("overwriteRecipe", updatedRecipe)
|
||||
},
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
require("tns-core-modules/globals")
|
||||
import { ImageSource } from "@nativescript/core"
|
||||
import { ImageSource, ImageAsset } from "@nativescript/core"
|
||||
|
||||
global.onmessage = function({ data }) {
|
||||
let imgFile = data.imgFile
|
||||
let imgSavedToPath = data.imgSavedToPath
|
||||
ImageSource.fromFile(imgFile).then((imgData) => {
|
||||
if (imgData.saveToFile(imgSavedToPath, "jpg")) {
|
||||
let imgAsset = new ImageAsset(data.imgFile)
|
||||
imgAsset.options = {
|
||||
width: 1200,
|
||||
height: 1200,
|
||||
keepAspectRatio: true,
|
||||
}
|
||||
ImageSource.fromAsset(imgAsset).then((imgData) => {
|
||||
if (imgData.saveToFile(imgSavedToPath, "jpg", 75)) {
|
||||
global.postMessage("savedToFile")
|
||||
}
|
||||
})
|
||||
|
|