ui changes
This commit is contained in:
parent
921c16ded0
commit
1de949f4de
32 changed files with 4274 additions and 17762 deletions
88
app/app.scss
88
app/app.scss
|
@ -15,17 +15,18 @@ Page {
|
|||
font-family: "Inter-Medium";
|
||||
font-size: 14;
|
||||
}
|
||||
.tb {
|
||||
font-family: "Inter-Bold";
|
||||
}
|
||||
.ico {
|
||||
font-family: "enrecipes";
|
||||
font-size: 24;
|
||||
vertical-alignment: center;
|
||||
&.sm {
|
||||
font-size: 16;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.tb {
|
||||
font-family: "Inter-Bold";
|
||||
}
|
||||
.tac {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -54,8 +55,7 @@ Page {
|
|||
border-color: $gray2;
|
||||
}
|
||||
.appbar,
|
||||
.modal,
|
||||
.topPlate {
|
||||
.modal {
|
||||
background: $gray0;
|
||||
}
|
||||
.fieldLabel,
|
||||
|
@ -71,11 +71,7 @@ Page {
|
|||
color: $gray1;
|
||||
background: $gray9;
|
||||
}
|
||||
Progress {
|
||||
progress-background-color: $gray4;
|
||||
}
|
||||
.fab,
|
||||
.hlMsg {
|
||||
.fab {
|
||||
color: $gray1;
|
||||
}
|
||||
}
|
||||
|
@ -95,8 +91,7 @@ Page {
|
|||
border-color: $gray7;
|
||||
}
|
||||
.appbar,
|
||||
.modal,
|
||||
.topPlate {
|
||||
.modal {
|
||||
color: $gray0;
|
||||
background: $gray8;
|
||||
}
|
||||
|
@ -113,18 +108,15 @@ Page {
|
|||
color: $gray9;
|
||||
background: $gray1;
|
||||
}
|
||||
Progress {
|
||||
progress-background-color: $gray5;
|
||||
}
|
||||
.fab,
|
||||
.hlMsg {
|
||||
.fab {
|
||||
color: $gray9;
|
||||
}
|
||||
}
|
||||
.Black {
|
||||
color: $gray2;
|
||||
background: $gray10;
|
||||
Page {
|
||||
Page,
|
||||
.filters {
|
||||
background: $gray10;
|
||||
}
|
||||
TextField,
|
||||
|
@ -136,8 +128,7 @@ Page {
|
|||
border-color: $gray8;
|
||||
}
|
||||
.appbar,
|
||||
.modal,
|
||||
.topPlate {
|
||||
.modal {
|
||||
color: $gray1;
|
||||
background: $gray9;
|
||||
}
|
||||
|
@ -154,11 +145,7 @@ Page {
|
|||
color: $gray10;
|
||||
background: $gray2;
|
||||
}
|
||||
Progress {
|
||||
progress-background-color: $gray5;
|
||||
}
|
||||
.fab,
|
||||
.hlMsg {
|
||||
.fab {
|
||||
color: $gray10;
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +178,8 @@ TextView {
|
|||
width: 100%;
|
||||
}
|
||||
Progress {
|
||||
progress-color: $orange;
|
||||
margin: 16;
|
||||
background-color: $gray5;
|
||||
}
|
||||
Switch {
|
||||
background-color: $orange;
|
||||
|
@ -200,7 +188,7 @@ Switch {
|
|||
// -----------------------------
|
||||
// Side Drawer
|
||||
.segment {
|
||||
border-radius: 8;
|
||||
border-radius: 12;
|
||||
margin: 0 4 0 0;
|
||||
padding: 0 12;
|
||||
.value {
|
||||
|
@ -216,7 +204,7 @@ Button {
|
|||
background-color: transparent;
|
||||
z-index: 0;
|
||||
padding: 8;
|
||||
border-radius: 8;
|
||||
border-radius: 12;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
&:active {
|
||||
|
@ -276,7 +264,7 @@ Button {
|
|||
padding: 8 16;
|
||||
.recipeInfo {
|
||||
vertical-alignment: top;
|
||||
padding: 4 0 4 8;
|
||||
padding: 2 0 2 8;
|
||||
}
|
||||
.title {
|
||||
padding: 0 0 4;
|
||||
|
@ -318,7 +306,7 @@ Button {
|
|||
background-color: transparent;
|
||||
}
|
||||
.imgHolder {
|
||||
border-radius: 8;
|
||||
border-radius: 12;
|
||||
}
|
||||
// -----------------------------
|
||||
// SETTINGS
|
||||
|
@ -328,6 +316,7 @@ Button {
|
|||
}
|
||||
.options-list {
|
||||
.option {
|
||||
vertical-align: center;
|
||||
padding: 14 8;
|
||||
margin: 0 16;
|
||||
.ico {
|
||||
|
@ -353,7 +342,7 @@ Button {
|
|||
.icon {
|
||||
margin: 16;
|
||||
background: $orange;
|
||||
border-radius: 99;
|
||||
border-radius: 16;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
.name {
|
||||
|
@ -367,16 +356,21 @@ Button {
|
|||
// -----------------------------
|
||||
// VIEW RECIPE
|
||||
.photo {
|
||||
border-radius: 8;
|
||||
border-radius: 12;
|
||||
margin: 24 16 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.photoviewer {
|
||||
width: 96;
|
||||
height: 96;
|
||||
opacity: 0;
|
||||
background: #000;
|
||||
}
|
||||
.attribute {
|
||||
margin: 8 16;
|
||||
.title {
|
||||
margin-right: 8;
|
||||
font-size: 12;
|
||||
color: $gray6;
|
||||
}
|
||||
.value {
|
||||
@extend .tb;
|
||||
|
@ -425,18 +419,19 @@ Button {
|
|||
line-height: 4;
|
||||
padding: 16 0;
|
||||
}
|
||||
.dateInfo {
|
||||
padding: 32 16 16;
|
||||
font-size: 12;
|
||||
line-height: 4;
|
||||
}
|
||||
// -----------------------------
|
||||
// APPBAR
|
||||
.appbar {
|
||||
z-index: 99;
|
||||
z-index: 32;
|
||||
min-height: 56;
|
||||
margin: 8;
|
||||
padding: 4;
|
||||
border-radius: 10;
|
||||
elevation: 16;
|
||||
&.hlMsg {
|
||||
background: $orange;
|
||||
}
|
||||
border-radius: 16;
|
||||
.title {
|
||||
@extend .tb;
|
||||
@extend .tw;
|
||||
|
@ -451,7 +446,7 @@ Button {
|
|||
}
|
||||
}
|
||||
.toolbar {
|
||||
z-index: 98;
|
||||
z-index: 24;
|
||||
padding: 4;
|
||||
margin-bottom: 0;
|
||||
horizontal-alignment: left;
|
||||
|
@ -469,7 +464,7 @@ Button {
|
|||
width: 48;
|
||||
height: 48;
|
||||
margin: 0 4 0 0;
|
||||
border-radius: 8;
|
||||
border-radius: 12;
|
||||
background: $orange;
|
||||
}
|
||||
// -----------------------------
|
||||
|
@ -482,10 +477,6 @@ Button {
|
|||
margin: 32 0 16;
|
||||
}
|
||||
.countdown {
|
||||
background-color: transparent;
|
||||
width: 48;
|
||||
height: 48;
|
||||
z-index: 0;
|
||||
font-size: 17;
|
||||
color: $orange;
|
||||
}
|
||||
|
@ -507,7 +498,7 @@ Button {
|
|||
text-align: center;
|
||||
}
|
||||
.day {
|
||||
border-radius: 8;
|
||||
border-radius: 12;
|
||||
}
|
||||
.hasPlans {
|
||||
color: $orange;
|
||||
|
@ -536,7 +527,7 @@ Button {
|
|||
.modal {
|
||||
max-width: 320;
|
||||
width: 100%;
|
||||
border-radius: 10;
|
||||
border-radius: 12;
|
||||
margin: 72 0;
|
||||
.title {
|
||||
@extend .tb;
|
||||
|
@ -566,7 +557,7 @@ Button {
|
|||
background: transparent;
|
||||
}
|
||||
.shareItem {
|
||||
border-radius: 8;
|
||||
border-radius: 12;
|
||||
margin: 0 8 8;
|
||||
text-align: center;
|
||||
.ico {
|
||||
|
@ -624,6 +615,7 @@ ActivityIndicator {
|
|||
}
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,7 @@
|
|||
for="recipe in getList"
|
||||
@loaded="onListLoad"
|
||||
:itemTemplateSelector="getLayout"
|
||||
:colWidth="layout == 'grid' ? '50%' : '100%'"
|
||||
:colWidth="layout == 'grid' || layout == 'photogrid' ? '50%' : '100%'"
|
||||
@scroll="!selectMode && onScroll($event)"
|
||||
>
|
||||
<v-template name="header">
|
||||
|
@ -31,20 +31,20 @@
|
|||
v-for="(item, index) in topmenu"
|
||||
:key="index"
|
||||
:class="{
|
||||
select: currentComponent === item.component,
|
||||
select: currentComponent === item.title,
|
||||
}"
|
||||
@touch="touchSelector($event, item.component)"
|
||||
@touch="touchSelector($event, item.title, item.title)"
|
||||
>
|
||||
<Label class="ico" :text="icon[item.icon]" />
|
||||
<Label
|
||||
class="value"
|
||||
v-if="getRecipeCount(item.title)"
|
||||
:hidden="!getRecipeCount(item.title)"
|
||||
:text="getRecipeCount(item.title)"
|
||||
col="1"
|
||||
/>
|
||||
</GridLayout>
|
||||
<GridLayout
|
||||
v-if="currentComponent === 'Filtered recipes'"
|
||||
:hidden="currentComponent !== 'Filtered recipes'"
|
||||
rows="48"
|
||||
columns="auto, auto"
|
||||
class="segment"
|
||||
|
@ -52,7 +52,7 @@
|
|||
select: currentComponent === 'Filtered recipes',
|
||||
}"
|
||||
>
|
||||
<Label class="ico" :text="icon.sort" />
|
||||
<Label class="ico" :text="icon.filter" />
|
||||
<Label
|
||||
class="value"
|
||||
:text="getRecipeCount('filtered')"
|
||||
|
@ -65,7 +65,7 @@
|
|||
<GridLayout
|
||||
class="recipeItem"
|
||||
:class="getItemPos(recipe.id)"
|
||||
rows="96"
|
||||
rows="auto"
|
||||
columns="96, *"
|
||||
ref="recipe"
|
||||
@longPress="
|
||||
|
@ -77,9 +77,10 @@
|
|||
>
|
||||
<Image
|
||||
class="imgHolder"
|
||||
verticalAlignment="top"
|
||||
v-if="recipe.imageSrc"
|
||||
:src="recipe.imageSrc"
|
||||
stretch="aspectFit"
|
||||
stretch="none"
|
||||
decodeWidth="96"
|
||||
decodeHeight="96"
|
||||
loadMode="async"
|
||||
|
@ -87,6 +88,7 @@
|
|||
<Label
|
||||
v-else
|
||||
class="ico imgHolder"
|
||||
verticalAlignment="top"
|
||||
@loaded="centerLabel"
|
||||
width="96"
|
||||
height="96"
|
||||
|
@ -94,15 +96,15 @@
|
|||
:text="icon.img"
|
||||
/>
|
||||
<StackLayout class="recipeInfo" col="1">
|
||||
<Label :text="recipe.title" class="tb title" />
|
||||
<StackLayout class="attributes" orientation="horizontal">
|
||||
<Label class="ico sm" :text="icon.cuisine" />
|
||||
<Label :text="recipe.title" class="tb title tw" />
|
||||
<StackLayout class="attributes" orientation="horizontal"
|
||||
><Label class="ico sm" :text="icon.cuisine" />
|
||||
<Label class="attr" :text="recipe.cuisine | L" />
|
||||
<Label class="ico sm" :text="icon.category" />
|
||||
<Label class="attr" :text="recipe.category | L" />
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
v-if="recipe.tags.length"
|
||||
:hidden="!recipe.tags.length"
|
||||
class="attributes"
|
||||
orientation="horizontal"
|
||||
>
|
||||
|
@ -158,7 +160,7 @@
|
|||
:text="icon.img"
|
||||
/>
|
||||
<StackLayout class="recipeInfo" row="1">
|
||||
<Label :text="recipe.title" class="tb title" />
|
||||
<Label :text="recipe.title" class="tb title tw" />
|
||||
<StackLayout class="attributes" orientation="horizontal">
|
||||
<Label class="ico sm" :text="icon.cuisine" />
|
||||
<Label class="attr" :text="recipe.cuisine | L" />
|
||||
|
@ -168,7 +170,7 @@
|
|||
<Label class="attr" :text="recipe.category | L" />
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
v-if="recipe.tags.length"
|
||||
:hidden="!recipe.tags.length"
|
||||
class="attributes"
|
||||
orientation="horizontal"
|
||||
>
|
||||
|
@ -178,6 +180,43 @@
|
|||
</StackLayout>
|
||||
</GridLayout>
|
||||
</v-template>
|
||||
<v-template name="photogrid">
|
||||
<GridLayout
|
||||
class="recipeItem grid"
|
||||
:class="getItemPos(recipe.id)"
|
||||
rows="auto, auto"
|
||||
columns="*"
|
||||
ref="recipe"
|
||||
@longPress="
|
||||
selectMode ? viewRecipe(recipe.id) : addToSelection(recipe.id)
|
||||
"
|
||||
@tap="
|
||||
selectMode ? addToSelection(recipe.id) : viewRecipe(recipe.id)
|
||||
"
|
||||
>
|
||||
<Image
|
||||
class="imgHolder"
|
||||
v-if="recipe.imageSrc"
|
||||
:src="recipe.imageSrc"
|
||||
stretch="aspectFit"
|
||||
:decodeWidth="imgWidth"
|
||||
:decodeHeight="imgWidth"
|
||||
loadMode="async"
|
||||
/>
|
||||
<Label
|
||||
v-else
|
||||
width="100%"
|
||||
:height="imgWidth"
|
||||
@loaded="centerLabel"
|
||||
class="ico imgHolder"
|
||||
:fontSize="imgWidth / 2"
|
||||
:text="icon.img"
|
||||
/>
|
||||
<StackLayout class="recipeInfo" row="1">
|
||||
<Label :text="recipe.title" class="tb title tw" />
|
||||
</StackLayout>
|
||||
</GridLayout>
|
||||
</v-template>
|
||||
<v-template name="simple">
|
||||
<GridLayout
|
||||
class="recipeItem simple"
|
||||
|
@ -192,7 +231,7 @@
|
|||
"
|
||||
>
|
||||
<StackLayout class="recipeInfo">
|
||||
<Label :text="recipe.title" class="tb title" />
|
||||
<Label :text="recipe.title" class="tb title tw" />
|
||||
<StackLayout class="attributes" orientation="horizontal">
|
||||
<Label class="ico sm" :text="icon.cuisine" />
|
||||
<Label class="attr" :text="recipe.cuisine | L" />
|
||||
|
@ -200,7 +239,7 @@
|
|||
<Label class="attr" :text="recipe.category | L" />
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
v-if="recipe.tags.length"
|
||||
:hidden="!recipe.tags.length"
|
||||
class="attributes"
|
||||
orientation="horizontal"
|
||||
>
|
||||
|
@ -224,7 +263,7 @@
|
|||
"
|
||||
>
|
||||
<StackLayout class="recipeInfo">
|
||||
<Label :text="recipe.title" class="tb title" />
|
||||
<Label :text="recipe.title" class="tb title tw" />
|
||||
</StackLayout>
|
||||
</GridLayout>
|
||||
</v-template>
|
||||
|
@ -273,7 +312,7 @@
|
|||
rows="auto"
|
||||
columns="auto"
|
||||
class="appbar toolbar"
|
||||
v-if="showTools"
|
||||
:hidden="!showTools"
|
||||
>
|
||||
<GridLayout
|
||||
row="1"
|
||||
|
@ -319,41 +358,34 @@
|
|||
@textChange="updateList($event.value)"
|
||||
/>
|
||||
<Label
|
||||
v-if="selectMode"
|
||||
:hidden="!selectMode"
|
||||
class="title"
|
||||
:text="`${selection.length} ${$options.filters.L('sltd')}`"
|
||||
col="1"
|
||||
/>
|
||||
<Button
|
||||
v-if="recipes.length && !selectMode && !showSearch"
|
||||
class="ico"
|
||||
:text="selectMode ? icon.export : icon.sear"
|
||||
<StackLayout
|
||||
col="2"
|
||||
colSpan="3"
|
||||
orientation="horizontal"
|
||||
:hidden="!recipes.length || selectMode || showSearch"
|
||||
>
|
||||
<Button
|
||||
class="ico"
|
||||
:text="selectMode ? icon.exp : icon.sear"
|
||||
@tap="selectMode ? exportSelection() : openSearch()"
|
||||
/>
|
||||
<Button class="ico" :text="icon.sort" @tap="openSort" />
|
||||
<Button class="ico" :text="icon.filter" @tap="openFilters" />
|
||||
</StackLayout>
|
||||
<Button
|
||||
v-if="recipes.length && !showSearch && !selectMode"
|
||||
class="ico"
|
||||
:text="icon.sort"
|
||||
col="3"
|
||||
@tap="openSort"
|
||||
/>
|
||||
<Button
|
||||
v-if="recipes.length && !showSearch && !selectMode"
|
||||
class="ico"
|
||||
:text="icon.sort"
|
||||
col="4"
|
||||
@tap="openFilters"
|
||||
/>
|
||||
<Button
|
||||
v-if="!showSearch && !selectMode"
|
||||
:hidden="showSearch || selectMode"
|
||||
class="ico fab"
|
||||
:text="icon.plus"
|
||||
col="5"
|
||||
@tap="addRecipe()"
|
||||
@tap="addRecipe"
|
||||
/>
|
||||
<Button
|
||||
v-if="selectMode"
|
||||
:hidden="!selectMode"
|
||||
class="ico"
|
||||
:text="icon.del"
|
||||
col="5"
|
||||
|
@ -375,6 +407,7 @@ import {
|
|||
Device,
|
||||
Screen,
|
||||
Color,
|
||||
CoreTypes,
|
||||
} from "@nativescript/core";
|
||||
import { localize } from "@nativescript/localize";
|
||||
import {
|
||||
|
@ -390,7 +423,7 @@ import Settings from "./Settings";
|
|||
import ActionDialog from "./modal/ActionDialog.vue";
|
||||
import ConfirmDialog from "./modal/ConfirmDialog.vue";
|
||||
import Filters from "./modal/Filters.vue";
|
||||
import * as utils from "~/shared/utils.js";
|
||||
import * as utils from "~/shared/utils";
|
||||
let lastTime = 0;
|
||||
let lastShake = 0;
|
||||
let lastForce = 0;
|
||||
|
@ -416,17 +449,14 @@ export default {
|
|||
topmenu: [
|
||||
{
|
||||
title: "EnRecipes",
|
||||
component: "EnRecipes",
|
||||
icon: "home",
|
||||
},
|
||||
{
|
||||
title: "trylater",
|
||||
component: "Try Later",
|
||||
icon: "try",
|
||||
},
|
||||
{
|
||||
title: "favourites",
|
||||
component: "Favourites",
|
||||
icon: "fav",
|
||||
},
|
||||
],
|
||||
|
@ -519,13 +549,13 @@ export default {
|
|||
methods: {
|
||||
...mapActions([
|
||||
"setComponent",
|
||||
"setSortTypeAction",
|
||||
"initListItems",
|
||||
"initRecipes",
|
||||
"initMealPlans",
|
||||
"setShakeAction",
|
||||
"setShake",
|
||||
"setFirstDay",
|
||||
"setLayout",
|
||||
"setSortTypeAction",
|
||||
"setSortType",
|
||||
"deleteRecipeAction",
|
||||
"deleteRecipesAction",
|
||||
"clearFilter",
|
||||
|
@ -536,6 +566,9 @@ export default {
|
|||
const window = Application.android.startActivity.getWindow();
|
||||
const decorView = window.getDecorView();
|
||||
let sdkv = Device.sdkVersion;
|
||||
this.appTheme == "Light"
|
||||
? decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
|
||||
: decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_DARK_STATUS_BAR);
|
||||
function setColors(color) {
|
||||
window.setStatusBarColor(new Color(color).android);
|
||||
sdkv >= 27 && window.setNavigationBarColor(new Color(color).android);
|
||||
|
@ -551,11 +584,8 @@ export default {
|
|||
setColors("#000000");
|
||||
break;
|
||||
}
|
||||
sdkv >= 23 && this.appTheme == "Light"
|
||||
? decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
|
||||
: decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_DARK_STATUS_BAR);
|
||||
|
||||
sdkv >= 27 && this.appTheme == "Light"
|
||||
if (sdkv >= 27)
|
||||
this.appTheme == "Light"
|
||||
? decorView.setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
)
|
||||
|
@ -567,16 +597,16 @@ export default {
|
|||
const page = args.object;
|
||||
page.bindingContext = new Observable();
|
||||
this.filterFavourites
|
||||
? this.setComponent("Favourites")
|
||||
? this.setComponent("favourites")
|
||||
: this.filterTrylater
|
||||
? this.setComponent("Try Later")
|
||||
? this.setComponent("trylater")
|
||||
: this.selectedCuisine
|
||||
? this.setComponent("Filtered recipes")
|
||||
: this.setComponent("EnRecipes");
|
||||
if (this.shakeEnabled) {
|
||||
if (utils.hasAccelerometer())
|
||||
startAccelerometerUpdates((data) => this.onSensorData(data));
|
||||
else this.setShakeAction(false);
|
||||
else this.setShake(false);
|
||||
}
|
||||
this.hijackBackEvent();
|
||||
setTimeout(() => {
|
||||
|
@ -606,22 +636,25 @@ export default {
|
|||
this.scrollPos = Math.abs(y);
|
||||
let ab = this.appbar.translateY;
|
||||
if (!scrollUp && ab == 0) {
|
||||
this.animateInOut(
|
||||
250,
|
||||
false,
|
||||
(val) => (this.appbar.translateY = val * 64)
|
||||
);
|
||||
this.appbar.animate({
|
||||
translate: { x: 0, y: 64 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
});
|
||||
} else if (scrollUp && ab == 64) {
|
||||
this.animateInOut(
|
||||
250,
|
||||
true,
|
||||
(val) => (this.appbar.translateY = val * 64)
|
||||
);
|
||||
this.appbar.animate({
|
||||
translate: { x: 0, y: 0 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
getSpanSize(index) {
|
||||
return this.layout == "grid" && (index == 0 || index == 1) ? 2 : 1;
|
||||
return (this.layout == "grid" || this.layout == "photogrid") &&
|
||||
(index == 0 || index == 1)
|
||||
? 2
|
||||
: 1;
|
||||
},
|
||||
getLayout(args, index, items) {
|
||||
return index == 0 ? "header" : index == 1 ? "lists" : this.layout;
|
||||
|
@ -646,7 +679,7 @@ export default {
|
|||
props: {
|
||||
title: "srt",
|
||||
list: [
|
||||
"Title",
|
||||
"title",
|
||||
"Rating",
|
||||
"Quickest first",
|
||||
"Slowest first",
|
||||
|
@ -658,7 +691,7 @@ export default {
|
|||
},
|
||||
}).then((action) => {
|
||||
if (action && action !== "Cancel" && this.sortType !== action) {
|
||||
this.setSortTypeAction(action);
|
||||
this.setSortType(action);
|
||||
ApplicationSettings.setString("sortType", action);
|
||||
this.updateSort();
|
||||
}
|
||||
|
@ -668,6 +701,8 @@ export default {
|
|||
|
||||
//FILTER
|
||||
openFilters() {
|
||||
this.setComponent("EnRecipes");
|
||||
this.filterFavourites = this.filterTrylater = false;
|
||||
this.showTools = false;
|
||||
this.releaseBackEvent();
|
||||
this.$showModal(Filters).then(() => this.hijackBackEvent());
|
||||
|
@ -917,35 +952,27 @@ export default {
|
|||
let dl1 = difficultyLevel(a.difficulty);
|
||||
let dl2 = difficultyLevel(b.difficulty);
|
||||
switch (this.sortType) {
|
||||
case "Title":
|
||||
case "title":
|
||||
return titleOrder > 0 ? 1 : titleOrder < 0 ? -1 : 0;
|
||||
break;
|
||||
case "Quickest first":
|
||||
return d1 > d2 ? 1 : d1 < d2 ? -1 : 0;
|
||||
break;
|
||||
case "Slowest first":
|
||||
return d1 > d2 ? -1 : d1 < d2 ? 1 : 0;
|
||||
break;
|
||||
case "Rating":
|
||||
return r1 > r2 ? -1 : r1 < r2 ? 1 : 0;
|
||||
break;
|
||||
case "Difficulty level":
|
||||
return dl1 > dl2 ? 1 : dl1 < dl2 ? -1 : 0;
|
||||
break;
|
||||
case "Last updated":
|
||||
return ld1 < ld2 ? 1 : ld1 > ld2 ? -1 : 0;
|
||||
break;
|
||||
case "Newest first":
|
||||
return cd1 < cd2 ? 1 : cd1 > cd2 ? -1 : 0;
|
||||
break;
|
||||
case "Oldest first":
|
||||
return cd1 < cd2 ? -1 : cd1 > cd2 ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
},
|
||||
getItemPos(id) {
|
||||
let length = this.filteredRecipes.length;
|
||||
let l2 = this.layout == "grid";
|
||||
let l2 = this.layout == "grid" || this.layout == "photogrid";
|
||||
let oddOrEven = this.oddOrEven(id);
|
||||
let itemPos =
|
||||
id == this.filteredRecipes[0].id ||
|
||||
|
@ -995,7 +1022,7 @@ export default {
|
|||
args.cancel = true;
|
||||
this.clearSelection();
|
||||
} else if (
|
||||
["Favourites", "Try Later", "Filtered recipes"].includes(
|
||||
["favourites", "trylater", "Filtered recipes"].includes(
|
||||
this.currentComponent
|
||||
)
|
||||
) {
|
||||
|
@ -1019,9 +1046,9 @@ export default {
|
|||
},
|
||||
});
|
||||
} else if (title !== this.currentComponent) {
|
||||
this.setComponent(to);
|
||||
this.filterFavourites = to == "Favourites";
|
||||
this.filterTrylater = to == "Try Later";
|
||||
this.setComponent(title);
|
||||
this.filterFavourites = to == "favourites";
|
||||
this.filterTrylater = to == "trylater";
|
||||
this.clearFilter();
|
||||
}
|
||||
},
|
||||
|
@ -1051,11 +1078,9 @@ export default {
|
|||
duration: 250,
|
||||
curve: "easeOut",
|
||||
},
|
||||
// backstackVisible: false,
|
||||
});
|
||||
},
|
||||
viewRandomRecipe() {
|
||||
// this.showFAB =
|
||||
this.showTools = false;
|
||||
this.$navigateTo(ViewRecipe, {
|
||||
props: {
|
||||
|
@ -1067,15 +1092,14 @@ export default {
|
|||
duration: 250,
|
||||
curve: "easeOut",
|
||||
},
|
||||
backstackVisible: false,
|
||||
});
|
||||
},
|
||||
touchSelector({ object, action }, comp) {
|
||||
touchSelector({ object, action }, comp, title) {
|
||||
let selected = this.currentComponent == comp;
|
||||
object.className = action.match(/down|move/)
|
||||
? `segment ${selected ? "select" : "fade"}`
|
||||
: `segment ${selected && "select"}`;
|
||||
if (action == "up") this.navigateTo(comp, comp);
|
||||
if (action == "up") this.navigateTo(comp, title);
|
||||
},
|
||||
touchTool({ object, action }, comp, value) {
|
||||
object.className = action.match(/down|move/) ? `tool fade` : `tool`;
|
||||
|
@ -1088,7 +1112,8 @@ export default {
|
|||
if (!this.recipes.length) this.initRecipes();
|
||||
this.initListItems();
|
||||
if (!this.mealPlans.length) this.initMealPlans();
|
||||
this.setShakeAction(ApplicationSettings.getBoolean("shakeEnabled", true));
|
||||
this.setShake(ApplicationSettings.getBoolean("shakeEnabled", true));
|
||||
this.setFirstDay(ApplicationSettings.getBoolean("mondayFirst", false));
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
class="dayName"
|
||||
row="1"
|
||||
:col="i"
|
||||
v-for="(d, i) in dNames"
|
||||
v-for="(d, i) in getDayNames"
|
||||
:key="d"
|
||||
:text="d | L"
|
||||
/>
|
||||
|
@ -94,14 +94,14 @@
|
|||
row="1"
|
||||
@loaded="onAppBarLoad"
|
||||
class="appbar"
|
||||
v-show="!showUndo"
|
||||
:hidden="showUndo"
|
||||
columns="auto, *, auto, auto"
|
||||
>
|
||||
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
|
||||
<Button
|
||||
class="ico"
|
||||
:text="icon.tod"
|
||||
v-if="!isExactlyToday"
|
||||
:hidden="isExactlyToday"
|
||||
@tap="goToToday"
|
||||
col="2"
|
||||
/>
|
||||
|
@ -115,19 +115,19 @@
|
|||
<GridLayout
|
||||
row="1"
|
||||
class="appbar snackBar"
|
||||
v-show="showUndo"
|
||||
:hidden="!showUndo"
|
||||
columns="auto, *, auto"
|
||||
>
|
||||
<Button :text="countdown" class="countdown tb" />
|
||||
<Button :text="countdown" class="ico countdown tb" />
|
||||
<Label class="title" col="1" :text="snackMsg | L" />
|
||||
<Button class="ico fab" :text="icon.alert" @tap="undoDel" col="3" />
|
||||
<Button class="ico fab" :text="icon.undo" @tap="undoDel" col="3" />
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ApplicationSettings, Observable } from "@nativescript/core";
|
||||
import { ApplicationSettings, Observable, CoreTypes } from "@nativescript/core";
|
||||
import { mapState, mapActions } from "vuex";
|
||||
import ViewRecipe from "./ViewRecipe.vue";
|
||||
import ActionDialogWithSearch from "./modal/ActionDialogWithSearch.vue";
|
||||
|
@ -138,7 +138,7 @@ export default {
|
|||
return {
|
||||
appTheme: "Light",
|
||||
mealTimes: ["breakfast", "lunch", "dinner", "snacks"],
|
||||
dNames: ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"],
|
||||
dNames: ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"],
|
||||
year: 2021,
|
||||
mNames: [
|
||||
"January",
|
||||
|
@ -166,7 +166,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["icon", "recipes", "mealPlans"]),
|
||||
...mapState(["icon", "recipes", "mealPlans", "mondayFirst"]),
|
||||
todaysTime() {
|
||||
return new Date(this.year, this.month, this.today, 0).getTime();
|
||||
},
|
||||
|
@ -180,13 +180,16 @@ export default {
|
|||
}, {});
|
||||
} else return 0;
|
||||
},
|
||||
getDayNames() {
|
||||
if (!this.mondayFirst) this.dNames.unshift(this.dNames.pop());
|
||||
return this.dNames;
|
||||
},
|
||||
getCal() {
|
||||
let y = this.year;
|
||||
let m = this.month;
|
||||
let t = this.today;
|
||||
let d = new Date(y, m, t);
|
||||
let ds = new Date(y, m + 1, 0).getDate();
|
||||
let fd = new Date(y, m, 1).getDay();
|
||||
if (this.mondayFirst) fd -= 1;
|
||||
let days = new Array(fd).fill(0);
|
||||
for (let i = 1; i <= ds; i++) {
|
||||
days.push(i);
|
||||
|
@ -230,17 +233,17 @@ export default {
|
|||
this.scrollPos = Math.abs(y);
|
||||
let ab = this.appbar.translateY;
|
||||
if (!scrollUp && ab == 0) {
|
||||
this.animateInOut(
|
||||
250,
|
||||
false,
|
||||
(val) => (this.appbar.translateY = val * 64)
|
||||
);
|
||||
this.appbar.animate({
|
||||
translate: { x: 0, y: 64 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
});
|
||||
} else if (scrollUp && ab == 64) {
|
||||
this.animateInOut(
|
||||
250,
|
||||
true,
|
||||
(val) => (this.appbar.translateY = val * 64)
|
||||
);
|
||||
this.appbar.animate({
|
||||
translate: { x: 0, y: 0 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -275,7 +278,6 @@ export default {
|
|||
duration: 250,
|
||||
curve: "easeOut",
|
||||
},
|
||||
// backstackVisible: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -362,11 +364,11 @@ export default {
|
|||
},
|
||||
showUndoBar(message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
clearTimeout(undoTimer);
|
||||
this.showUndo = true;
|
||||
this.snackMsg = message;
|
||||
this.countdown = 5;
|
||||
let a = 5;
|
||||
clearTimeout(undoTimer);
|
||||
undoTimer = setInterval(() => {
|
||||
if (this.undo) {
|
||||
this.showUndo = this.undo = false;
|
||||
|
|
|
@ -5,13 +5,12 @@
|
|||
colSpan="2"
|
||||
rowSpan="2"
|
||||
class="options-list"
|
||||
@loaded="listViewLoad"
|
||||
for="item in items"
|
||||
>
|
||||
<v-template if="$index == 0">
|
||||
<Label class="pageTitle" :text="'Settings' | L" />
|
||||
</v-template>
|
||||
<v-template if="$index == 7">
|
||||
<v-template if="$index == 6">
|
||||
<StackLayout class="listSpace"> </StackLayout>
|
||||
</v-template>
|
||||
<v-template>
|
||||
|
@ -30,11 +29,7 @@
|
|||
</v-template>
|
||||
</ListView>
|
||||
<GridLayout row="1" class="appbar" rows="*" columns="auto, *">
|
||||
<Button
|
||||
class="ico"
|
||||
:text="icon.back"
|
||||
@tap="$navigateBack()"
|
||||
/>
|
||||
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
|
@ -55,17 +50,17 @@ export default {
|
|||
items: [
|
||||
{},
|
||||
{
|
||||
icon: "theme",
|
||||
icon: "interface",
|
||||
title: "intf",
|
||||
view: Interface,
|
||||
},
|
||||
{
|
||||
icon: "cog",
|
||||
icon: "opts",
|
||||
title: "opts",
|
||||
view: Options,
|
||||
},
|
||||
{
|
||||
icon: "exp",
|
||||
icon: "db",
|
||||
title: "db",
|
||||
view: Database,
|
||||
},
|
||||
|
@ -74,11 +69,6 @@ export default {
|
|||
title: "rest",
|
||||
view: Reset,
|
||||
},
|
||||
{
|
||||
icon: "help",
|
||||
title: "help",
|
||||
view: Help,
|
||||
},
|
||||
{
|
||||
icon: "info",
|
||||
title: "About",
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
colSpan="2"
|
||||
class="options-list"
|
||||
for="item in items"
|
||||
@loaded="listViewLoad"
|
||||
>
|
||||
<v-template if="$index == 0">
|
||||
<Label class="pageTitle" :text="'About' | L" />
|
||||
|
@ -24,7 +23,7 @@
|
|||
<Label class="info tac tw" :text="'appInfo' | L" />
|
||||
</StackLayout>
|
||||
</v-template>
|
||||
<v-template if="$index == 6">
|
||||
<v-template if="$index == 8">
|
||||
<StackLayout class="listSpace"> </StackLayout>
|
||||
</v-template>
|
||||
<v-template>
|
||||
|
@ -39,11 +38,7 @@
|
|||
</v-template>
|
||||
</ListView>
|
||||
<GridLayout row="1" class="appbar" rows="*" columns="auto, *">
|
||||
<Button
|
||||
class="ico"
|
||||
:text="icon.back"
|
||||
@tap="$navigateBack()"
|
||||
/>
|
||||
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
|
@ -65,6 +60,16 @@ export default {
|
|||
title: "gh",
|
||||
url: "https://github.com/vishnuraghavb/EnRecipes",
|
||||
},
|
||||
{
|
||||
icon: "tg",
|
||||
title: "joinTG",
|
||||
url: "https://t.me/enrecipes",
|
||||
},
|
||||
{
|
||||
icon: "help",
|
||||
title: "guide",
|
||||
url: "https://github.com/vishnuraghavb/EnRecipes/wiki/User-Guide",
|
||||
},
|
||||
{
|
||||
icon: "priv",
|
||||
title: "priv",
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<template>
|
||||
<Page>
|
||||
<GridLayout class="progressContainer" columns="*, 64">
|
||||
<Progress col="0" :value="backupProgress" maxValue="100" />
|
||||
<Label col="1" :text="` ${backupProgress}%`" />
|
||||
</GridLayout>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -28,7 +28,7 @@
|
|||
</v-template>
|
||||
</ListView>
|
||||
<GridLayout
|
||||
v-show="!toast && !backupProgress"
|
||||
v-show="!toast && !progress"
|
||||
row="1"
|
||||
class="appbar"
|
||||
rows="*"
|
||||
|
@ -49,13 +49,14 @@
|
|||
</FlexboxLayout>
|
||||
</GridLayout>
|
||||
<GridLayout
|
||||
v-show="backupProgress"
|
||||
v-show="progress"
|
||||
row="1"
|
||||
colSpan="2"
|
||||
class="appbar snackBar"
|
||||
columns="*"
|
||||
columns="auto, *"
|
||||
>
|
||||
<Progress :value="backupProgress" maxValue="100" />
|
||||
<ActivityIndicator :busy="progress ? true : false" />
|
||||
<Label col="1" class="title" :text="progress" textWrap="true" />
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
|
@ -71,9 +72,6 @@ import {
|
|||
Observable,
|
||||
Application,
|
||||
} from "@nativescript/core";
|
||||
import * as Permissions from "@nativescript-community/perms";
|
||||
import { Zip } from "@nativescript/zip";
|
||||
import { openFilePicker } from "@nativescript-community/ui-document-picker";
|
||||
import { localize } from "@nativescript/localize";
|
||||
import ConfirmDialog from "../modal/ConfirmDialog.vue";
|
||||
import { mapState, mapActions } from "vuex";
|
||||
|
@ -84,7 +82,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
backupFolder: null,
|
||||
backupProgress: 0,
|
||||
progress: null,
|
||||
toast: null,
|
||||
};
|
||||
},
|
||||
|
@ -118,7 +116,6 @@ export default {
|
|||
icon: "imp",
|
||||
title: "impBu",
|
||||
subTitle: localize("impInfo"),
|
||||
// action: this.importCheck,
|
||||
action: this.openZipFile,
|
||||
},
|
||||
{},
|
||||
|
@ -131,43 +128,63 @@ export default {
|
|||
"importRecipesAction",
|
||||
"importMealPlansAction",
|
||||
"unlinkBrokenImages",
|
||||
"clearImportSummary",
|
||||
]),
|
||||
onPageLoad(args) {
|
||||
const page = args.object;
|
||||
page.bindingContext = new Observable();
|
||||
const downloadsFolder = Folder.fromPath(
|
||||
android.os.Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
).getFolder("Download").path;
|
||||
this.backupFolder = ApplicationSettings.getString(
|
||||
"backupFolder",
|
||||
downloadsFolder
|
||||
);
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver();
|
||||
this.backupFolder = ApplicationSettings.getString("backupFolder");
|
||||
if (
|
||||
!this.backupFolder ||
|
||||
ContentResolver.getPersistedUriPermissions().isEmpty()
|
||||
) {
|
||||
this.backupFolder = null;
|
||||
}
|
||||
},
|
||||
// BACKUP FOLDER PICKER
|
||||
setBackupFolder() {
|
||||
utils.pickFolder().then((res) => {
|
||||
if (res != null) {
|
||||
this.backupFolder = res;
|
||||
setBackupFolder(startExport) {
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver();
|
||||
const FLAGS =
|
||||
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||
utils.getBackupFolder().then((uri) => {
|
||||
if (uri != null) {
|
||||
if (this.backupFolder)
|
||||
ContentResolver.releasePersistableUriPermission(
|
||||
new android.net.Uri.parse(this.backupFolder),
|
||||
FLAGS
|
||||
);
|
||||
this.backupFolder = uri.toString();
|
||||
ApplicationSettings.setString("backupFolder", this.backupFolder);
|
||||
// PERSIST PERMISSIONS
|
||||
ContentResolver.takePersistableUriPermission(uri, FLAGS);
|
||||
if (startExport && this.backupFolder) {
|
||||
this.exportBackup();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// EXPORT HANDLERS
|
||||
exportCheck() {
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver();
|
||||
if (!this.recipes.length) {
|
||||
this.toast = localize("aFBu");
|
||||
utils.timer(5, (val) => {
|
||||
if (!val) this.toast = val;
|
||||
});
|
||||
} else {
|
||||
this.permissionCheck(
|
||||
this.permissionConfirmation,
|
||||
localize("reqAcc"),
|
||||
this.exportBackup
|
||||
);
|
||||
if (
|
||||
!this.backupFolder ||
|
||||
ContentResolver.getPersistedUriPermissions().isEmpty()
|
||||
) {
|
||||
this.setBackupFolder(true);
|
||||
} else this.exportBackup();
|
||||
}
|
||||
},
|
||||
exportBackup() {
|
||||
this.progress = localize("expip");
|
||||
this.exportFiles("create");
|
||||
let date = new Date();
|
||||
let formattedDate =
|
||||
|
@ -183,17 +200,11 @@ export default {
|
|||
|
||||
let filename = `EnRecipes_${formattedDate}.zip`;
|
||||
let fromPath = path.join(knownFolders.documents().path, "EnRecipes");
|
||||
let sdcard = android.os.Environment.isExternalStorageManager();
|
||||
let destPath = path.join(this.backupFolder, filename);
|
||||
console.log(sdcard);
|
||||
Zip.zip({
|
||||
directory: fromPath,
|
||||
archive: destPath,
|
||||
onProgress: (progress) => (this.backupProgress = progress),
|
||||
}).then(() => {
|
||||
this.showExportSummary(filename);
|
||||
this.exportFiles("delete");
|
||||
});
|
||||
utils.Zip.zip(fromPath, this.backupFolder, filename)
|
||||
.then((res) => {
|
||||
if (res) this.showExportSummary(filename);
|
||||
})
|
||||
.catch((err) => console.log("Backup error: ", err));
|
||||
},
|
||||
exportFiles(option) {
|
||||
const folder = path.join(knownFolders.documents().path, "EnRecipes");
|
||||
|
@ -240,80 +251,140 @@ export default {
|
|||
this.units.length && userUnitsFile.remove();
|
||||
this.mealPlans.length && mealPlansFile.remove();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
writeDataToFile(file, data) {
|
||||
file.writeText(JSON.stringify(data));
|
||||
},
|
||||
// IMPORT HANDLERS
|
||||
importCheck() {
|
||||
this.permissionCheck(
|
||||
this.permissionConfirmation,
|
||||
localize("reqAcc"),
|
||||
this.openZipFile
|
||||
);
|
||||
showExportSummary(filename) {
|
||||
this.progress = null;
|
||||
this.$showModal(ConfirmDialog, {
|
||||
props: {
|
||||
title: "expSuc",
|
||||
description: `Backed up to ${filename}`,
|
||||
okButtonText: "OK",
|
||||
},
|
||||
openZipFile() {
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver();
|
||||
|
||||
utils.getBackupFile().then((uri) => {
|
||||
console.log(uri);
|
||||
const inputStream = ContentResolver.openInputStream(uri);
|
||||
console.log(inputStream);
|
||||
let newFile = knownFolders.temp().getFile("test.zip");
|
||||
console.log(newFile);
|
||||
try {
|
||||
const input = new java.io.BufferedInputStream(inputStream);
|
||||
console.log(input);
|
||||
const outputStream = new java.io.BufferedOutputStream(
|
||||
new java.io.FileOutputStream(newFile.path)
|
||||
);
|
||||
console.log(outputStream);
|
||||
let size = inputStream.available();
|
||||
console.log(size);
|
||||
let buffer = Array.create("byte", size);
|
||||
input.read(buffer);
|
||||
do {
|
||||
outputStream.write(buffer);
|
||||
} while (input.read(buffer) != -1);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
input.close();
|
||||
}
|
||||
|
||||
// console.log(zipInputStream);
|
||||
|
||||
// console.log(zipInputStream);
|
||||
// let extractedFile = new java.io.File(
|
||||
// knownFolders.temp().path + "/tmp.zip"
|
||||
// );
|
||||
// if (!extractedFile.exists()) {
|
||||
// extractedFile.mkdirs();
|
||||
// }
|
||||
// console.log(extractedFile);
|
||||
// let outputStream = new java.io.OutputStream(extractedFile);
|
||||
|
||||
// android.os.FileUtils.copy(inputStream, outputStream);
|
||||
// let outputStream = new java.io.FileOutputStream(extractedFile);
|
||||
// console.log(outputStream);
|
||||
// while ((readLen = zipInputStream.read(readBuffer)) != -1) {
|
||||
// outputStream.write(readBuffer, 0, readLen);
|
||||
// }
|
||||
// console.log(outputStream, 2);
|
||||
});
|
||||
// openFilePicker({
|
||||
// extensions: ["zip"],
|
||||
// }).then((res) => this.validateZipContent(res.files[0]));
|
||||
},
|
||||
importDataToDB(data, db, zipPath) {
|
||||
|
||||
// IMPORT HANDLERS
|
||||
openZipFile() {
|
||||
utils.getBackupFile().then((uri) => {
|
||||
let dest = path.join(knownFolders.temp().path, "tempUnZip");
|
||||
utils.Zip.unzip(uri, dest)
|
||||
.then((res) => {
|
||||
if (res) this.validateZipContent(res, uri);
|
||||
})
|
||||
.catch(() => this.failedImport(localize("buInc")));
|
||||
});
|
||||
},
|
||||
validateZipContent(extractedFolderPath, uri) {
|
||||
this.progress = localize("impip");
|
||||
let cacheFolderPath = extractedFolderPath + "/EnRecipes";
|
||||
const EnRecipesFilePath = cacheFolderPath + "/recipes.json";
|
||||
const ImagesFolderPath = cacheFolderPath + "/Images";
|
||||
const userCuisinesFilePath = cacheFolderPath + "/userCuisines.json";
|
||||
const userCategoriesFilePath = cacheFolderPath + "/userCategories.json";
|
||||
const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json";
|
||||
const userUnitsFilePath = cacheFolderPath + "/userUnits.json";
|
||||
const mealPlansFilePath = cacheFolderPath + "/mealPlans.json";
|
||||
if (Folder.exists(cacheFolderPath)) {
|
||||
this.isFileDataValid([
|
||||
{
|
||||
path: EnRecipesFilePath,
|
||||
db: "EnRecipesDB",
|
||||
file: "recipes.json",
|
||||
},
|
||||
{
|
||||
path: userCuisinesFilePath,
|
||||
db: "userCuisinesDB",
|
||||
file: "userCuisines.json",
|
||||
},
|
||||
{
|
||||
path: userCategoriesFilePath,
|
||||
db: "userCategoriesDB",
|
||||
file: "userCategories.json",
|
||||
},
|
||||
{
|
||||
path: userYieldUnitsFilePath,
|
||||
db: "userYieldUnitsDB",
|
||||
file: "userYieldUnits.json",
|
||||
},
|
||||
{
|
||||
path: userUnitsFilePath,
|
||||
db: "userUnitsDB",
|
||||
file: "userUnits.json",
|
||||
},
|
||||
{
|
||||
path: mealPlansFilePath,
|
||||
db: "mealPlansDB",
|
||||
file: "mealPlans.json",
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
Folder.fromPath(extractedFolderPath).remove();
|
||||
this.progress = null;
|
||||
this.failedImport(localize("buInc"));
|
||||
}
|
||||
if (Folder.exists(ImagesFolderPath)) {
|
||||
const timer = setInterval(() => {
|
||||
if (this.importSummary.found) {
|
||||
this.importImages(uri, extractedFolderPath);
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
isFileDataValid(file) {
|
||||
const files = file.filter((e) => File.exists(e.path));
|
||||
if (files.length) {
|
||||
let isValid = files.map((e) => false);
|
||||
files.forEach((file, i) => {
|
||||
File.fromPath(file.path)
|
||||
.readText()
|
||||
.then((data) => {
|
||||
isValid[i] = this.hasValidJSON(data);
|
||||
if (!isValid[i]) {
|
||||
this.failedImport(
|
||||
`${localize("buMod")}\n\n${localize("invFile")}: ${file.file}`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
if (isValid.every((e) => e === true)) {
|
||||
files.forEach((file) => {
|
||||
File.fromPath(file.path)
|
||||
.readText()
|
||||
.then((data) => {
|
||||
this.importDataToDB(JSON.parse(data), file.db);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.failedImport(localize("buEmp"));
|
||||
}
|
||||
},
|
||||
failedImport(description) {
|
||||
this.$showModal(ConfirmDialog, {
|
||||
props: {
|
||||
title: "impFail",
|
||||
description,
|
||||
okButtonText: "OK",
|
||||
},
|
||||
});
|
||||
},
|
||||
hasValidJSON(data) {
|
||||
try {
|
||||
JSON.parse(data) && Array.isArray(JSON.parse(data));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
importDataToDB(data, db) {
|
||||
switch (db) {
|
||||
case "EnRecipesDB":
|
||||
this.importImages(zipPath);
|
||||
this.importRecipesAction(data);
|
||||
break;
|
||||
case "userCuisinesDB":
|
||||
|
@ -343,133 +414,18 @@ export default {
|
|||
case "mealPlansDB":
|
||||
this.importMealPlansAction(data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
hasValidJSON(data) {
|
||||
try {
|
||||
JSON.parse(data) && Array.isArray(JSON.parse(data));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isFileDataValid(file) {
|
||||
const files = file.filter((e) => File.exists(e.path));
|
||||
if (files.length) {
|
||||
let isValid = files.map((e) => false);
|
||||
files.forEach((file, i) => {
|
||||
File.fromPath(file.path)
|
||||
.readText()
|
||||
.then((data) => {
|
||||
isValid[i] = this.hasValidJSON(data);
|
||||
if (!isValid[i]) {
|
||||
this.failedImport(
|
||||
`${localize("buMod")}\n\n${localize("invFile")}: ${file.file}`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
if (isValid.every((e) => e === true)) {
|
||||
files.forEach((file, i) => {
|
||||
File.fromPath(file.path)
|
||||
.readText()
|
||||
.then((data) => {
|
||||
this.importDataToDB(
|
||||
JSON.parse(data),
|
||||
file.db,
|
||||
file.zipPath
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.failedImport(localize("buEmp"));
|
||||
}
|
||||
},
|
||||
failedImport(description) {
|
||||
this.$showModal(ConfirmDialog, {
|
||||
props: {
|
||||
title: "impFail",
|
||||
description,
|
||||
okButtonText: "OK",
|
||||
},
|
||||
});
|
||||
},
|
||||
validateZipContent(zipPath) {
|
||||
console.log(zipPath);
|
||||
Zip.unzip({
|
||||
archive: zipPath,
|
||||
overwrite: true,
|
||||
onProgress: (progress) => (this.backupProgress = progress),
|
||||
}).then((extractedFolderPath) => {
|
||||
let cacheFolderPath = extractedFolderPath + "/EnRecipes";
|
||||
const EnRecipesFilePath = cacheFolderPath + "/recipes.json";
|
||||
const userCuisinesFilePath = cacheFolderPath + "/userCuisines.json";
|
||||
const userCategoriesFilePath = cacheFolderPath + "/userCategories.json";
|
||||
const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json";
|
||||
const userUnitsFilePath = cacheFolderPath + "/userUnits.json";
|
||||
const mealPlansFilePath = cacheFolderPath + "/mealPlans.json";
|
||||
if (Folder.exists(cacheFolderPath)) {
|
||||
this.isFileDataValid([
|
||||
{
|
||||
zipPath,
|
||||
path: EnRecipesFilePath,
|
||||
db: "EnRecipesDB",
|
||||
file: "recipes.json",
|
||||
},
|
||||
{
|
||||
zipPath,
|
||||
path: userCuisinesFilePath,
|
||||
db: "userCuisinesDB",
|
||||
file: "userCuisines.json",
|
||||
},
|
||||
{
|
||||
zipPath,
|
||||
path: userCategoriesFilePath,
|
||||
db: "userCategoriesDB",
|
||||
file: "userCategories.json",
|
||||
},
|
||||
{
|
||||
zipPath,
|
||||
path: userYieldUnitsFilePath,
|
||||
db: "userYieldUnitsDB",
|
||||
file: "userYieldUnits.json",
|
||||
},
|
||||
{
|
||||
zipPath,
|
||||
path: userUnitsFilePath,
|
||||
db: "userUnitsDB",
|
||||
file: "userUnits.json",
|
||||
},
|
||||
{
|
||||
zipPath,
|
||||
path: mealPlansFilePath,
|
||||
db: "mealPlansDB",
|
||||
file: "mealPlans.json",
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
Folder.fromPath(extractedFolderPath).remove();
|
||||
this.failedImport(localize("buInc"));
|
||||
}
|
||||
if (Folder.exists(cacheFolderPath + "/Images")) {
|
||||
this.importImages(cacheFolderPath + "/Images");
|
||||
}
|
||||
});
|
||||
},
|
||||
importImages(sourcePath) {
|
||||
let dest = knownFolders.documents().path;
|
||||
Zip.unzip({
|
||||
archive: sourcePath,
|
||||
directory: dest,
|
||||
overwrite: true,
|
||||
onProgress: (progress) => (this.backupProgress = progress),
|
||||
}).then((res) => {
|
||||
importImages(uri, extractedFolderPath) {
|
||||
let destPath = knownFolders.documents().path;
|
||||
Folder.fromPath(destPath);
|
||||
utils.Zip.unzip(uri, destPath).then((res) => {
|
||||
if (res) {
|
||||
this.showImportSummary();
|
||||
this.unlinkBrokenImages();
|
||||
this.exportFiles("delete");
|
||||
Folder.fromPath(extractedFolderPath).remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
showImportSummary() {
|
||||
|
@ -478,6 +434,7 @@ export default {
|
|||
let importedNote = `\n${imported} ${localize("recI")}`;
|
||||
let existsNote = `\n${exists} ${localize("recE")}`;
|
||||
let updatedNote = `\n${updated} ${localize("recU")}`;
|
||||
this.progress = null;
|
||||
this.$showModal(ConfirmDialog, {
|
||||
props: {
|
||||
title: "impSuc",
|
||||
|
@ -486,51 +443,9 @@ export default {
|
|||
)}${importedNote}${existsNote}${updatedNote}`,
|
||||
okButtonText: "OK",
|
||||
},
|
||||
}).then(() => (this.backupProgress = 0));
|
||||
},
|
||||
showExportSummary(filename) {
|
||||
this.$showModal(ConfirmDialog, {
|
||||
props: {
|
||||
title: "expSuc",
|
||||
description: `Backed up to ${filename}`,
|
||||
okButtonText: "OK",
|
||||
},
|
||||
}).then(() => (this.backupProgress = 0));
|
||||
},
|
||||
// PERMISSIONS HANDLER
|
||||
permissionCheck(confirmation, description, action) {
|
||||
if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) {
|
||||
confirmation(description).then((e) => {
|
||||
if (e) {
|
||||
Permissions.request("photo").then((res) => {
|
||||
let status = res[Object.keys(res)[0]];
|
||||
if (status === "authorized") action();
|
||||
if (status !== "denied")
|
||||
ApplicationSettings.setBoolean("storagePermissionAsked", true);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Permissions.check("photo").then((res) => {
|
||||
let status = res[Object.keys(res)[0]];
|
||||
if (status !== "authorized") {
|
||||
confirmation(description).then((e) => {
|
||||
e && utils.openAppSettingsPage();
|
||||
});
|
||||
} else action();
|
||||
});
|
||||
}
|
||||
},
|
||||
permissionConfirmation(description) {
|
||||
return this.$showModal(ConfirmDialog, {
|
||||
props: {
|
||||
title: "grant",
|
||||
description,
|
||||
cancelButtonText: "nNBtn",
|
||||
okButtonText: "conBtn",
|
||||
},
|
||||
});
|
||||
}).then(() => this.clearImportSummary());
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
touch({ object, action }, method) {
|
||||
object.className = action.match(/down|move/) ? "option fade" : "option";
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
rowSpan="2"
|
||||
class="options-list"
|
||||
for="item in items"
|
||||
@loaded="listViewLoad"
|
||||
>
|
||||
<v-template if="$index == 0">
|
||||
<Label class="pageTitle" :text="'help' | L" />
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
colSpan="2"
|
||||
rowSpan="2"
|
||||
class="options-list"
|
||||
@loaded="listViewLoad"
|
||||
for="item in items"
|
||||
>
|
||||
<v-template if="$index == 0">
|
||||
|
@ -29,11 +28,7 @@
|
|||
</v-template>
|
||||
</ListView>
|
||||
<GridLayout row="1" class="appbar" rows="*" columns="auto, *">
|
||||
<Button
|
||||
class="ico"
|
||||
:text="icon.back"
|
||||
@tap="$navigateBack()"
|
||||
/>
|
||||
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
|
@ -50,6 +45,7 @@ import { localize, overrideLocale } from "@nativescript/localize";
|
|||
import ActionDialog from "../modal/ActionDialog.vue";
|
||||
import ConfirmDialog from "../modal/ConfirmDialog.vue";
|
||||
import { mapState, mapActions } from "vuex";
|
||||
import * as utils from "~/shared/utils";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -75,7 +71,7 @@ export default {
|
|||
action: this.selectThemes,
|
||||
},
|
||||
{
|
||||
icon: "l1",
|
||||
icon: "layout",
|
||||
title: "listVM",
|
||||
subTitle: localize(this.layout),
|
||||
action: this.setLayoutMode,
|
||||
|
@ -142,7 +138,7 @@ export default {
|
|||
this.$showModal(ActionDialog, {
|
||||
props: {
|
||||
title: "List view mode",
|
||||
list: ["Detailed", "Grid", "Simple", "Minimal"],
|
||||
list: ["detailed", "grid", "photogrid", "simple", "minimal"],
|
||||
},
|
||||
}).then((action) => {
|
||||
if (action && action !== "Cancel" && this.layoutMode !== action) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
colSpan="2"
|
||||
rowSpan="2"
|
||||
class="options-list"
|
||||
@loaded="listViewLoad"
|
||||
for="item in items"
|
||||
>
|
||||
<v-template if="$index == 0">
|
||||
|
@ -14,15 +13,18 @@
|
|||
<v-template if="item.type == 'switch'">
|
||||
<GridLayout columns="auto, *, auto" class="option">
|
||||
<Label class="ico" :text="icon[item.icon]" />
|
||||
<StackLayout col="1">
|
||||
<StackLayout col="1" verticalAlignment="center">
|
||||
<Label :text="item.title | L" class="info" />
|
||||
<Label :text="item.subTitle | L" class="sub" />
|
||||
<Label
|
||||
v-if="item.subTitle"
|
||||
:text="item.subTitle | L"
|
||||
class="sub"
|
||||
/>
|
||||
</StackLayout>
|
||||
<Switch
|
||||
:color="item.subAction ? '#ff5200' : '#858585'"
|
||||
verticalAlignment="center"
|
||||
:color="item.checked ? '#ff5200' : '#adb5bd'"
|
||||
col="2"
|
||||
:checked="item.subAction"
|
||||
:checked="item.checked"
|
||||
@checkedChange="item.action"
|
||||
/>
|
||||
</GridLayout>
|
||||
|
@ -31,12 +33,26 @@
|
|||
<StackLayout class="listSpace"> </StackLayout>
|
||||
</v-template>
|
||||
</ListView>
|
||||
<GridLayout row="1" class="appbar" rows="*" columns="auto, *">
|
||||
<Button
|
||||
class="ico"
|
||||
:text="icon.back"
|
||||
@tap="$navigateBack()"
|
||||
/>
|
||||
<GridLayout
|
||||
v-show="!toast"
|
||||
row="1"
|
||||
class="appbar"
|
||||
rows="*"
|
||||
columns="auto, *"
|
||||
>
|
||||
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
|
||||
</GridLayout>
|
||||
<GridLayout
|
||||
v-show="toast"
|
||||
row="1"
|
||||
colSpan="2"
|
||||
class="appbar snackBar"
|
||||
columns="*"
|
||||
@tap="toast = null"
|
||||
>
|
||||
<FlexboxLayout minHeight="48" alignItems="center">
|
||||
<Label class="title msg" :text="toast" />
|
||||
</FlexboxLayout>
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
|
@ -44,13 +60,17 @@
|
|||
|
||||
<script>
|
||||
import { ApplicationSettings, Observable } from "@nativescript/core";
|
||||
import { localize } from "@nativescript/localize";
|
||||
import { mapState, mapActions } from "vuex";
|
||||
import * as utils from "~/shared/utils";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
toast: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["icon", "shakeEnabled"]),
|
||||
...mapState(["icon", "shakeEnabled", "mondayFirst"]),
|
||||
items() {
|
||||
return [
|
||||
{},
|
||||
|
@ -59,29 +79,39 @@ export default {
|
|||
icon: "shuf",
|
||||
title: "sVw",
|
||||
subTitle: "sVwInfo",
|
||||
checked: this.shakeEnabled,
|
||||
action: this.toggleShake,
|
||||
subAction: this.shakeEnabled,
|
||||
},
|
||||
{
|
||||
type: "switch",
|
||||
icon: "week",
|
||||
title: "swm",
|
||||
checked: this.mondayFirst,
|
||||
action: this.toggleFirstDay,
|
||||
},
|
||||
{},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["setShakeAction"]),
|
||||
...mapActions(["setShake", "setFirstDay"]),
|
||||
onPageLoad(args) {
|
||||
const page = args.object;
|
||||
page.bindingContext = new Observable();
|
||||
},
|
||||
toggleFirstDay({ object }) {
|
||||
this.setFirstDay(object.checked);
|
||||
},
|
||||
// SHAKE VIEW RANDOM RECIPE
|
||||
toggleShake(args) {
|
||||
let checked = args.object.checked;
|
||||
toggleShake({ object }) {
|
||||
let checked = object.checked;
|
||||
if (checked && !utils.hasAccelerometer()) {
|
||||
args.object.checked = false;
|
||||
// Toast.makeText(localize("noAccSensor"), "long").show();
|
||||
} else {
|
||||
ApplicationSettings.setBoolean("shakeEnabled", checked);
|
||||
this.setShakeAction(checked);
|
||||
}
|
||||
checked = false;
|
||||
this.toast = localize("noAccSensor");
|
||||
utils.timer(5, (val) => {
|
||||
if (!val) this.toast = val;
|
||||
});
|
||||
} else this.setShake(checked);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
colSpan="2"
|
||||
rowSpan="2"
|
||||
class="options-list"
|
||||
@loaded="listViewLoad"
|
||||
for="item in items"
|
||||
>
|
||||
<v-template if="$index == 0">
|
||||
|
@ -28,9 +27,27 @@
|
|||
</GridLayout>
|
||||
</v-template>
|
||||
</ListView>
|
||||
<GridLayout row="1" class="appbar" rows="*" columns="auto, *">
|
||||
<GridLayout
|
||||
v-show="!toast"
|
||||
row="1"
|
||||
class="appbar"
|
||||
rows="*"
|
||||
columns="auto, *"
|
||||
>
|
||||
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
|
||||
</GridLayout>
|
||||
<GridLayout
|
||||
v-show="toast"
|
||||
row="1"
|
||||
colSpan="2"
|
||||
class="appbar snackBar"
|
||||
columns="*"
|
||||
@tap="toast = null"
|
||||
>
|
||||
<FlexboxLayout minHeight="48" alignItems="center">
|
||||
<Label class="title msg" :text="toast" />
|
||||
</FlexboxLayout>
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
</template>
|
||||
|
@ -39,8 +56,13 @@
|
|||
import { Observable } from "@nativescript/core";
|
||||
import { localize } from "@nativescript/localize";
|
||||
import { mapState, mapActions } from "vuex";
|
||||
|
||||
import * as utils from "~/shared/utils";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
toast: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["icon"]),
|
||||
items() {
|
||||
|
@ -76,7 +98,10 @@ export default {
|
|||
// RESET
|
||||
resetListItems(listName) {
|
||||
this.resetListItemsAction(listName);
|
||||
// Toast.makeText(localize("restDone")).show();
|
||||
this.toast = localize("restDone");
|
||||
utils.timer(5, (val) => {
|
||||
if (!val) this.toast = val;
|
||||
});
|
||||
},
|
||||
touch({ object, action }, type) {
|
||||
object.className = action.match(/down|move/) ? "option fade" : "option";
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
dock="top"
|
||||
rows="auto,auto"
|
||||
columns="*, auto"
|
||||
paddingBottom="8"
|
||||
:class="{ topPlate: scrolled }"
|
||||
paddingBottom="24"
|
||||
>
|
||||
<StackLayout>
|
||||
<Label class="pageTitle" paddingBottom="8" :text="recipe.title" />
|
||||
|
@ -33,81 +32,67 @@
|
|||
class="photo"
|
||||
decodeWidth="96"
|
||||
decodeHeight="96"
|
||||
@tap="viewPhoto"
|
||||
/>
|
||||
<StackLayout row="1" colSpan="2" orientation="horizontal" margin="16">
|
||||
<GridLayout
|
||||
rows="48"
|
||||
columns="auto, auto"
|
||||
class="segment"
|
||||
v-for="(item, index) in topmenu"
|
||||
:key="index"
|
||||
:class="{ select: selectedIndex == index }"
|
||||
@touch="touchSelector($event, index, item.icon)"
|
||||
>
|
||||
<Label class="ico" :text="icon[item.icon]" />
|
||||
<Label class="value" :text="item.count" col="1" />
|
||||
</GridLayout>
|
||||
</StackLayout>
|
||||
</GridLayout>
|
||||
<ScrollView
|
||||
dock="bottom"
|
||||
@loaded="onScrollLoad"
|
||||
@scroll="!toast && onScroll($event)"
|
||||
>
|
||||
<StackLayout>
|
||||
<StackLayout>
|
||||
<GridLayout rows="auto" columns="*, *">
|
||||
<StackLayout class="attribute">
|
||||
<Label class="title" :text="'cui' | L" />
|
||||
<Label class="title sub" :text="'cui' | L" />
|
||||
<Label class="value" :text="recipe.cuisine | L" />
|
||||
</StackLayout>
|
||||
<StackLayout class="attribute" col="1">
|
||||
<Label class="title" :text="'cat' | L" />
|
||||
<Label class="title sub" :text="'cat' | L" />
|
||||
<Label class="value" :text="recipe.category | L" />
|
||||
</StackLayout>
|
||||
</GridLayout>
|
||||
<StackLayout v-if="recipe.tags.length" class="attribute">
|
||||
<Label class="title" :text="'ts' | L" />
|
||||
<StackLayout :hidden="!recipe.tags.length" class="attribute">
|
||||
<Label class="title sub" :text="'ts' | L" />
|
||||
<Label class="value" :text="getTags(recipe.tags)" />
|
||||
</StackLayout>
|
||||
<GridLayout rows="auto" columns="*, *">
|
||||
<StackLayout class="attribute" v-if="hasTime(recipe.prepTime)">
|
||||
<Label class="title" :text="'prepT' | L" />
|
||||
<StackLayout
|
||||
class="attribute"
|
||||
:hidden="!hasTime(recipe.prepTime)"
|
||||
>
|
||||
<Label class="title sub" :text="'prepT' | L" />
|
||||
<Label class="value" :text="formattedTime(recipe.prepTime)" />
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
:col="hasTime(recipe.prepTime) ? 1 : 0"
|
||||
class="attribute"
|
||||
v-if="hasTime(recipe.cookTime)"
|
||||
:hidden="!hasTime(recipe.cookTime)"
|
||||
>
|
||||
<Label class="title" :text="'cookT' | L" />
|
||||
<Label class="title sub" :text="'cookT' | L" />
|
||||
<Label class="value" :text="formattedTime(recipe.cookTime)" />
|
||||
</StackLayout>
|
||||
</GridLayout>
|
||||
<GridLayout rows="auto" columns="*, *">
|
||||
<StackLayout class="attribute">
|
||||
<Label class="title" :text="'yld' | L" />
|
||||
<Label class="title sub" :text="'yld' | L" />
|
||||
<Label
|
||||
@touch="touchYield"
|
||||
class="value clickable"
|
||||
:text="`${positiveYieldMultiplier} ${$options.filters.L(
|
||||
:text="`${tempYieldQuantity} ${$options.filters.L(
|
||||
recipe.yield.unit
|
||||
)}`"
|
||||
/>
|
||||
</StackLayout>
|
||||
<StackLayout class="attribute" col="1">
|
||||
<Label class="title" :text="'Difficulty level' | L" />
|
||||
<Label class="title sub" :text="'Difficulty level' | L" />
|
||||
<Label class="value" :text="recipe.difficulty | L" />
|
||||
</StackLayout>
|
||||
</GridLayout>
|
||||
</StackLayout>
|
||||
<StackLayout>
|
||||
<Label
|
||||
id="items"
|
||||
padding="0 16"
|
||||
class="sectionTitle"
|
||||
v-show="recipe.ingredients.length"
|
||||
:text="'ings' | L"
|
||||
:hidden="!recipe.ingredients.length"
|
||||
:text="getTitleCount('ings', 'ingredients')"
|
||||
/>
|
||||
<StackLayout @loaded="onIngsLoad">
|
||||
<GridLayout
|
||||
|
@ -118,7 +103,7 @@
|
|||
class="ingredient"
|
||||
@touch="touchIngredient($event, index)"
|
||||
>
|
||||
<Button class="ico min" :text="icon.done" />
|
||||
<Button class="ico min" :text="icon.uncheck" />
|
||||
<Label
|
||||
class="value tw"
|
||||
col="1"
|
||||
|
@ -135,11 +120,10 @@
|
|||
</GridLayout>
|
||||
</StackLayout>
|
||||
<Label
|
||||
id="steps"
|
||||
padding="0 16"
|
||||
v-show="recipe.instructions.length"
|
||||
:hidden="!recipe.instructions.length"
|
||||
class="sectionTitle"
|
||||
:text="'ins' | L"
|
||||
:text="getTitleCount('inss', 'instructions')"
|
||||
/>
|
||||
<StackLayout @loaded="onInsLoad">
|
||||
<GridLayout
|
||||
|
@ -154,11 +138,10 @@
|
|||
</GridLayout>
|
||||
</StackLayout>
|
||||
<Label
|
||||
id="comb"
|
||||
padding="0 16"
|
||||
v-show="recipe.combinations.length"
|
||||
:hidden="!recipe.combinations.length"
|
||||
class="sectionTitle"
|
||||
:text="'cmbs' | L"
|
||||
:text="getTitleCount('cmbs', 'combinations')"
|
||||
/>
|
||||
<Button
|
||||
v-for="(combination, index) in recipe.combinations"
|
||||
|
@ -168,18 +151,23 @@
|
|||
@tap="viewCombination(combination)"
|
||||
/>
|
||||
<Label
|
||||
id="notes"
|
||||
padding="0 16"
|
||||
v-show="recipe.notes.length"
|
||||
:hidden="!recipe.notes.length"
|
||||
class="sectionTitle"
|
||||
:text="'nos' | L"
|
||||
:text="getTitleCount('nos', 'notes')"
|
||||
/>
|
||||
<StackLayout @loaded="onNosLoad" padding="0 16"> </StackLayout>
|
||||
<Label
|
||||
class="dateInfo sub tw"
|
||||
:text="`${$options.filters.L('Last updated')}: ${formattedDate(
|
||||
recipe.lastModified
|
||||
)}\n${$options.filters.L('Created')}: ${formattedDate(
|
||||
recipe.created
|
||||
)}`"
|
||||
/>
|
||||
<StackLayout @loaded="onNosLoad" padding="0 16 72"> </StackLayout>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</DockLayout>
|
||||
|
||||
<GridLayout
|
||||
row="1"
|
||||
@loaded="onAppBarLoad"
|
||||
|
@ -187,13 +175,17 @@
|
|||
v-show="!toast"
|
||||
columns="auto, *, auto, auto, auto, auto"
|
||||
>
|
||||
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
|
||||
<Button
|
||||
class="ico"
|
||||
:text="photoOpen ? icon.x : icon.back"
|
||||
@tap="navigateBack"
|
||||
/>
|
||||
<Button
|
||||
col="2"
|
||||
v-if="!filterTrylater"
|
||||
class="ico"
|
||||
:text="recipe.tried ? icon.try : icon.tried"
|
||||
@tap="toggleTrylater"
|
||||
@tap="toggle('tried')"
|
||||
/>
|
||||
<Button
|
||||
col="2"
|
||||
|
@ -206,7 +198,7 @@
|
|||
col="3"
|
||||
class="ico"
|
||||
:text="recipe.isFavorite ? icon.faved : icon.fav"
|
||||
@tap="toggleFavourite"
|
||||
@tap="toggle('isFavorite')"
|
||||
/>
|
||||
<Button
|
||||
col="4"
|
||||
|
@ -226,8 +218,7 @@
|
|||
<GridLayout
|
||||
v-show="toast"
|
||||
row="1"
|
||||
class="appbar"
|
||||
:class="hlMsg && 'hlMsg'"
|
||||
class="appbar snackBar"
|
||||
columns="*"
|
||||
@tap="toast = null"
|
||||
>
|
||||
|
@ -235,6 +226,13 @@
|
|||
<Label class="title msg" :text="toast" />
|
||||
</FlexboxLayout>
|
||||
</GridLayout>
|
||||
<AbsoluteLayout rowSpan="2">
|
||||
<ImageZoom
|
||||
@loaded="onImgZoomLoad"
|
||||
:src="recipe.imageSrc"
|
||||
class="photoviewer"
|
||||
></ImageZoom>
|
||||
</AbsoluteLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
</template>
|
||||
|
@ -242,6 +240,7 @@
|
|||
<script>
|
||||
import {
|
||||
Application,
|
||||
AndroidApplication,
|
||||
ImageSource,
|
||||
Utils,
|
||||
Span,
|
||||
|
@ -249,6 +248,7 @@ import {
|
|||
Label,
|
||||
Observable,
|
||||
Screen,
|
||||
CoreTypes,
|
||||
} from "@nativescript/core";
|
||||
import * as SocialShare from "@nativescript/social-share";
|
||||
import { localize } from "@nativescript/localize";
|
||||
|
@ -257,7 +257,7 @@ import { mapActions, mapState } from "vuex";
|
|||
import EditRecipe from "./EditRecipe.vue";
|
||||
import ActionDialog from "./modal/ActionDialog.vue";
|
||||
import PromptDialog from "./modal/PromptDialog.vue";
|
||||
let toastTimer, scrollTimer;
|
||||
import * as utils from "~/shared/utils";
|
||||
export default {
|
||||
props: ["filterTrylater", "recipeID"],
|
||||
data() {
|
||||
|
@ -271,42 +271,21 @@ export default {
|
|||
ingcon: null,
|
||||
inscon: null,
|
||||
notescon: null,
|
||||
imgZoom: null,
|
||||
checks: [],
|
||||
steps: [],
|
||||
checked: 0,
|
||||
stepsDid: 0,
|
||||
toast: null,
|
||||
hlMsg: false,
|
||||
selectedIndex: 0,
|
||||
scrollingToId: false,
|
||||
scrolled: false,
|
||||
photoOpen: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["icon", "recipes"]),
|
||||
positiveYieldMultiplier() {
|
||||
tempYieldQuantity() {
|
||||
return Math.abs(this.yieldMultiplier) > 0
|
||||
? Math.abs(parseFloat(this.yieldMultiplier))
|
||||
: 1;
|
||||
},
|
||||
topmenu() {
|
||||
return [
|
||||
{
|
||||
icon: "items",
|
||||
count: this.recipe.ingredients.length,
|
||||
},
|
||||
{
|
||||
icon: "steps",
|
||||
count: this.recipe.instructions.length,
|
||||
},
|
||||
{
|
||||
icon: "comb",
|
||||
count: this.recipe.combinations.length,
|
||||
},
|
||||
{
|
||||
icon: "notes",
|
||||
count: this.recipe.notes.length,
|
||||
},
|
||||
].filter((e) => e.count);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
|
@ -321,9 +300,7 @@ export default {
|
|||
const page = args.object;
|
||||
page.bindingContext = new Observable();
|
||||
this.busy = false;
|
||||
setTimeout((e) => {
|
||||
this.setComponent("ViewRecipe");
|
||||
}, 500);
|
||||
if (this.yieldMultiplier == this.recipe.yield.quantity)
|
||||
this.yieldMultiplier = this.recipe.yield.quantity;
|
||||
this.keepScreenOn(true);
|
||||
|
@ -345,27 +322,16 @@ export default {
|
|||
this.notescon = object;
|
||||
this.createNotes();
|
||||
},
|
||||
|
||||
onScrollLoad(args) {
|
||||
this.scrollview = args.object;
|
||||
},
|
||||
onImgZoomLoad({ object }) {
|
||||
this.imgZoom = object;
|
||||
this.imgZoom.visibility = "collapsed";
|
||||
this.imgZoom.top = 24;
|
||||
this.imgZoom.left = Screen.mainScreen.widthDIPs - 112;
|
||||
},
|
||||
onScroll(args) {
|
||||
if (!this.scrollingToId) {
|
||||
let vm = this;
|
||||
let h = Screen.mainScreen.heightDIPs;
|
||||
function getPos(id) {
|
||||
return vm.scrollview.getViewById(id).getLocationOnScreen().y + 144;
|
||||
}
|
||||
let items = getPos("items") <= h;
|
||||
let steps = getPos("steps") <= h;
|
||||
let comb = getPos("comb") <= h;
|
||||
let notes = getPos("notes") <= h;
|
||||
if (items) this.selectedIndex = 0;
|
||||
if (steps) this.selectedIndex = 1;
|
||||
if (comb) this.selectedIndex = 2;
|
||||
if (notes) this.selectedIndex = 3;
|
||||
}
|
||||
this.scrolled = this.scrollview.verticalOffset != 0;
|
||||
let scrollUp;
|
||||
let y = args.scrollY;
|
||||
if (y) {
|
||||
|
@ -373,20 +339,34 @@ export default {
|
|||
this.scrollPos = Math.abs(y);
|
||||
let ab = this.appbar.translateY;
|
||||
if (!scrollUp && ab == 0)
|
||||
this.animateInOut(
|
||||
250,
|
||||
false,
|
||||
(val) => (this.appbar.translateY = val * 64)
|
||||
);
|
||||
this.appbar.animate({
|
||||
translate: { x: 0, y: 64 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
});
|
||||
else if (scrollUp && ab == 64)
|
||||
this.animateInOut(
|
||||
250,
|
||||
true,
|
||||
(val) => (this.appbar.translateY = val * 64)
|
||||
);
|
||||
this.appbar.animate({
|
||||
translate: { x: 0, y: 0 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
});
|
||||
}
|
||||
},
|
||||
// HELPERS
|
||||
getTitleCount(title, type) {
|
||||
let count = this.recipe[type].length;
|
||||
let selected = null;
|
||||
switch (title) {
|
||||
case "ings":
|
||||
selected = this.checked;
|
||||
break;
|
||||
case "inss":
|
||||
selected = this.stepsDid;
|
||||
break;
|
||||
}
|
||||
let text = selected ? ` (${selected}/${count})` : ` (${count})`;
|
||||
return localize(title) + text;
|
||||
},
|
||||
changeYield() {
|
||||
this.$showModal(PromptDialog, {
|
||||
props: {
|
||||
|
@ -422,18 +402,17 @@ export default {
|
|||
);
|
||||
},
|
||||
showLastTried() {
|
||||
this.showToast(
|
||||
`${localize("triedInfo")} ${this.niceDate(this.recipe.lastTried)}`,
|
||||
5,
|
||||
true
|
||||
);
|
||||
this.toast = `${localize("triedInfo")} ${this.niceDate(
|
||||
this.recipe.lastTried
|
||||
)}`;
|
||||
utils.timer(5, (val) => {
|
||||
if (!val) this.toast = val;
|
||||
});
|
||||
},
|
||||
roundedQuantity(quantity) {
|
||||
return Math.abs(
|
||||
Math.round(
|
||||
(quantity / this.recipe.yield.quantity) *
|
||||
this.positiveYieldMultiplier *
|
||||
100
|
||||
(quantity / this.recipe.yield.quantity) * this.tempYieldQuantity * 100
|
||||
) / 100
|
||||
);
|
||||
},
|
||||
|
@ -485,37 +464,48 @@ export default {
|
|||
recipe: this.recipe,
|
||||
});
|
||||
},
|
||||
checkChange(obj, index) {
|
||||
this.checks[index] = !this.checks[index];
|
||||
obj.getChildAt(0).text = this.checks[index]
|
||||
? this.icon.succ
|
||||
: this.icon.done;
|
||||
},
|
||||
touchIngredient({ object, action }, index) {
|
||||
object.className = action.match(/down|move/)
|
||||
? "ingredient fade"
|
||||
: "ingredient";
|
||||
if (action == "up") this.checkChange(object, index);
|
||||
},
|
||||
clearChecks() {
|
||||
for (let i = 0; i < this.ingcon.getChildrenCount(); i++) {
|
||||
this.ingcon.getChildAt(i).getChildAt(0).text = this.icon.done;
|
||||
checkChange(obj, index) {
|
||||
this.checks[index] = !this.checks[index];
|
||||
if (this.checks[index]) {
|
||||
this.checked++;
|
||||
obj.getChildAt(0).text = this.icon.check;
|
||||
} else {
|
||||
this.checked--;
|
||||
obj.getChildAt(0).text = this.icon.uncheck;
|
||||
}
|
||||
},
|
||||
stepDone(object) {
|
||||
let a = object;
|
||||
a.className = a.className.includes("done")
|
||||
? "instruction"
|
||||
: "instruction done";
|
||||
clearChecks() {
|
||||
this.checked = 0;
|
||||
this.checks = [];
|
||||
for (let i = 0; i < this.ingcon.getChildrenCount(); i++) {
|
||||
this.ingcon.getChildAt(i).getChildAt(0).text = this.icon.uncheck;
|
||||
}
|
||||
},
|
||||
touchInstruction({ object, action }) {
|
||||
let hasDone = object.className.includes("done");
|
||||
object.className = action.match(/down|move/)
|
||||
? `instruction fade ${hasDone ? "done" : ""}`
|
||||
? `instruction ${hasDone ? "done" : "fade"}`
|
||||
: `instruction ${hasDone ? "done" : ""}`;
|
||||
if (action == "up") this.stepDone(object);
|
||||
},
|
||||
stepDone(object) {
|
||||
let a = object;
|
||||
if (a.className.includes("done")) {
|
||||
a.className = "instruction";
|
||||
this.stepsDid--;
|
||||
} else {
|
||||
a.className = "instruction done";
|
||||
this.stepsDid++;
|
||||
}
|
||||
},
|
||||
clearSteps() {
|
||||
this.stepsDid = 0;
|
||||
for (let i = 0; i < this.inscon.getChildrenCount(); i++) {
|
||||
this.inscon.getChildAt(i).className = "instruction";
|
||||
}
|
||||
|
@ -536,12 +526,13 @@ export default {
|
|||
viewCombination(combination) {
|
||||
this.scrollview.scrollToVerticalOffset(0, true);
|
||||
this.recipe = this.recipes.filter((e) => e.id === combination)[0];
|
||||
this.recipe.ingredients.forEach((e) => this.checks.push(false));
|
||||
this.clearSteps();
|
||||
this.clearChecks();
|
||||
this.clearSteps();
|
||||
this.recipe.ingredients.forEach(() => this.checks.push(false));
|
||||
this.currentRecipeID = combination;
|
||||
this.syncCombinations();
|
||||
this.createNotes();
|
||||
this.yieldMultiplier = this.recipe.yield.quantity;
|
||||
this.recipe.tried && this.recipe.lastTried && this.showLastTried();
|
||||
},
|
||||
|
||||
|
@ -563,8 +554,6 @@ export default {
|
|||
case "rec":
|
||||
this.shareRecipe();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -572,24 +561,24 @@ export default {
|
|||
}
|
||||
},
|
||||
shareRecipe() {
|
||||
let overview = `${this.recipe.title}\n\n${localize(
|
||||
"Cuisine"
|
||||
)}: ${localize(this.recipe.cuisine)}\n${localize("Category")}: ${localize(
|
||||
this.recipe.category
|
||||
)}\n${localize("ts")}: ${this.recipe.tags.join(", ")}\n${localize(
|
||||
"stars"
|
||||
)}: ${this.recipe.rating}\n${localize("Difficulty level")}: ${localize(
|
||||
this.recipe.difficulty
|
||||
)}\n${localize("Preparation time")}: ${this.formattedTime(
|
||||
this.recipe.prepTime
|
||||
)}\n${localize("Cooking time")}: ${this.formattedTime(
|
||||
this.recipe.cookTime
|
||||
)}\n`;
|
||||
let overview = `${this.recipe.title}\n\n${localize("stars")}: ${
|
||||
this.recipe.rating
|
||||
}\n${localize("cui")}: ${localize(this.recipe.cuisine)}\n${localize(
|
||||
"cat"
|
||||
)}: ${localize(this.recipe.category)}\n${localize(
|
||||
"ts"
|
||||
)}: ${this.recipe.tags.join(", ")}\n${localize(
|
||||
"prepT"
|
||||
)}: ${this.formattedTime(this.recipe.prepTime)}\n${localize(
|
||||
"cookT"
|
||||
)}: ${this.formattedTime(this.recipe.cookTime)}\n${localize("yld")}: ${
|
||||
this.tempYieldQuantity
|
||||
} ${localize(this.recipe.yield.unit)}\n${localize(
|
||||
"Difficulty level"
|
||||
)}: ${localize(this.recipe.difficulty)}\n`;
|
||||
let shareContent = overview;
|
||||
if (this.recipe.ingredients.length) {
|
||||
let ingredients = `\n\n${localize("ings")} (${
|
||||
this.yieldMultiplier
|
||||
} ${localize(this.recipe.yield.unit)}):\n\n`;
|
||||
let ingredients = `\n\n${localize("ings")}:\n\n`;
|
||||
this.recipe.ingredients.forEach((e) => {
|
||||
ingredients += `- ${
|
||||
e.quantity
|
||||
|
@ -603,19 +592,12 @@ export default {
|
|||
shareContent += ingredients;
|
||||
}
|
||||
if (this.recipe.instructions.length) {
|
||||
let instructions = `\n\n${localize("Instructions")}:\n\n`;
|
||||
let instructions = `\n\n${localize("inss")}:\n\n`;
|
||||
this.recipe.instructions.forEach((e, i) => {
|
||||
instructions += `${i + 1}. ${e}\n\n`;
|
||||
});
|
||||
shareContent += instructions;
|
||||
}
|
||||
if (this.recipe.notes.length) {
|
||||
let notes = `\n${localize("nos")}:\n\n`;
|
||||
this.recipe.notes.forEach((e, i) => {
|
||||
notes += `${i + 1}. ${e}\n\n`;
|
||||
});
|
||||
shareContent += notes;
|
||||
}
|
||||
if (this.recipe.combinations.length) {
|
||||
let combinations = `\n${localize("cmbs")}:\n\n`;
|
||||
this.recipe.combinations.forEach((e, i) => {
|
||||
|
@ -623,24 +605,20 @@ export default {
|
|||
});
|
||||
shareContent += combinations;
|
||||
}
|
||||
if (this.recipe.notes.length) {
|
||||
let notes = `\n${localize("nos")}:\n\n`;
|
||||
this.recipe.notes.forEach((e, i) => {
|
||||
notes += `${i + 1}. ${e}\n\n`;
|
||||
});
|
||||
shareContent += notes;
|
||||
}
|
||||
let sharenote = "\n" + localize("appCrd");
|
||||
shareContent += sharenote;
|
||||
SocialShare.shareText(shareContent, "Share recipe using");
|
||||
// SocialShare.shareText(shareContent, "Share recipe using");
|
||||
utils.shareText(shareContent, "Share recipe using");
|
||||
},
|
||||
|
||||
// DATA HANDLERS
|
||||
showToast(msg, dur, hl) {
|
||||
clearInterval(toastTimer);
|
||||
this.hlMsg = hl;
|
||||
this.toast = msg;
|
||||
toastTimer = setInterval(() => {
|
||||
dur--;
|
||||
if (dur == 0) {
|
||||
this.toast = null;
|
||||
clearInterval(toastTimer);
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
toggle(key, setDate) {
|
||||
this.toggleStateAction({
|
||||
id: this.currentRecipeID,
|
||||
|
@ -649,18 +627,6 @@ export default {
|
|||
setDate,
|
||||
});
|
||||
},
|
||||
toggleFavourite() {
|
||||
this.recipe.isFavorite
|
||||
? this.showToast(localize("unfavd"), 3)
|
||||
: this.showToast(localize("favd"), 3);
|
||||
this.toggle("isFavorite");
|
||||
},
|
||||
toggleTrylater() {
|
||||
this.recipe.tried
|
||||
? this.showToast(localize("aTry"), 3)
|
||||
: this.showToast(localize("rmTry"), 3);
|
||||
this.toggle("tried");
|
||||
},
|
||||
recipeTried() {
|
||||
this.setRecipeAsTriedAction({
|
||||
id: this.currentRecipeID,
|
||||
|
@ -724,32 +690,91 @@ export default {
|
|||
getTags(tags) {
|
||||
return tags.join(" · ");
|
||||
},
|
||||
hijackBackEvent() {
|
||||
AndroidApplication.on(
|
||||
AndroidApplication.activityBackPressedEvent,
|
||||
this.backEvent
|
||||
);
|
||||
},
|
||||
releaseBackEvent() {
|
||||
AndroidApplication.off(
|
||||
AndroidApplication.activityBackPressedEvent,
|
||||
this.backEvent
|
||||
);
|
||||
},
|
||||
backEvent(args) {
|
||||
if (this.photoOpen) {
|
||||
args.cancel = true;
|
||||
this.closePhoto();
|
||||
} else this.$navigateBack();
|
||||
},
|
||||
viewPhoto() {
|
||||
this.imgZoom.initNativeView();
|
||||
this.photoOpen = true;
|
||||
this.hijackBackEvent();
|
||||
let pv = this.imgZoom;
|
||||
pv.visibility = "visible";
|
||||
let sw = Screen.mainScreen.widthDIPs;
|
||||
let sh = Screen.mainScreen.heightDIPs;
|
||||
pv.animate({
|
||||
opacity: 1,
|
||||
duration: 50,
|
||||
})
|
||||
.then(() =>
|
||||
pv.animate({
|
||||
width: sw,
|
||||
height: sw,
|
||||
translate: { x: 112 - sw, y: (sh - sw) / 3 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
})
|
||||
)
|
||||
.then(() =>
|
||||
pv.animate({
|
||||
height: sh,
|
||||
translate: { x: -sw + 112, y: -((sh - sw) / 6) },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
})
|
||||
);
|
||||
},
|
||||
closePhoto() {
|
||||
let pv = this.imgZoom;
|
||||
let sw = Screen.mainScreen.widthDIPs;
|
||||
let sh = Screen.mainScreen.heightDIPs;
|
||||
pv.animate({
|
||||
width: sw,
|
||||
height: sw,
|
||||
translate: { x: 112 - sw, y: (sh - sw) / 3 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
})
|
||||
.then(() =>
|
||||
pv.animate({
|
||||
width: 96,
|
||||
height: 96,
|
||||
translate: { x: 0, y: 0 },
|
||||
duration: 250,
|
||||
curve: CoreTypes.AnimationCurve.ease,
|
||||
})
|
||||
)
|
||||
.then(() =>
|
||||
pv.animate({
|
||||
opacity: 0,
|
||||
duration: 50,
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
pv.visibility = "collapsed";
|
||||
this.photoOpen = false;
|
||||
this.releaseBackEvent();
|
||||
});
|
||||
},
|
||||
navigateBack() {
|
||||
this.photoOpen ? this.closePhoto() : this.$navigateBack();
|
||||
},
|
||||
|
||||
//HELPERS
|
||||
touchSelector({ object, action }, index, id) {
|
||||
let selected = this.selectedIndex == index;
|
||||
object.className = action.match(/down|move/)
|
||||
? `segment ${selected ? "select" : "fade"}`
|
||||
: `segment ${selected && "select"}`;
|
||||
if (action == "up") this.scrollToId(index, id);
|
||||
},
|
||||
scrollToId(index, id) {
|
||||
this.scrollingToId = true;
|
||||
if (index != this.selectedIndex)
|
||||
this.scrollview.scrollToVerticalOffset(
|
||||
this.scrollview.getViewById(id).getLocationRelativeTo(this.scrollview)
|
||||
.y + this.scrollview.verticalOffset,
|
||||
true
|
||||
);
|
||||
clearInterval(scrollTimer);
|
||||
scrollTimer = setInterval(() => {
|
||||
if (this.selectedIndex == index) {
|
||||
this.scrollingToId = false;
|
||||
clearInterval(scrollTimer);
|
||||
}
|
||||
}, 1000);
|
||||
this.selectedIndex = index;
|
||||
},
|
||||
touchYield({ object, action }) {
|
||||
object.className = action.match(/down|move/)
|
||||
? "value clickable fade"
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<Page @loaded="onPageLoad" backgroundColor="transparent" :class="appTheme">
|
||||
<Page
|
||||
@loaded="transparentPage"
|
||||
backgroundColor="transparent"
|
||||
:class="appTheme"
|
||||
>
|
||||
<GridLayout
|
||||
columns="*"
|
||||
:rows="`auto, auto, ${stretch ? '*' : 'auto'}, auto`"
|
||||
|
@ -9,7 +13,6 @@
|
|||
<ListView
|
||||
rowHeight="48"
|
||||
row="2"
|
||||
@loaded="listViewLoad"
|
||||
for="item in newList"
|
||||
:height="stretch ? '100%' : listHeight"
|
||||
>
|
||||
|
@ -63,16 +66,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(["removeListItemAction"]),
|
||||
onPageLoad(args) {
|
||||
args.object._dialogFragment
|
||||
.getDialog()
|
||||
.getWindow()
|
||||
.setBackgroundDrawable(
|
||||
new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
);
|
||||
},
|
||||
localized(item) {
|
||||
if (this.title !== "lang") return localize(item);
|
||||
else return item;
|
||||
|
@ -118,7 +111,6 @@ export default {
|
|||
case "Unit":
|
||||
removeListItem("unit", "units", "rmUInfo");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
touch({ object, action }) {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<Page @loaded="onPageLoad" backgroundColor="transparent" :class="appTheme">
|
||||
<Page
|
||||
@loaded="transparentPage"
|
||||
backgroundColor="transparent"
|
||||
:class="appTheme"
|
||||
>
|
||||
<GridLayout columns="*" rows="auto, auto, *, auto" class="modal">
|
||||
<Label class="title" :text="title | L" />
|
||||
<StackLayout
|
||||
|
@ -9,11 +13,11 @@
|
|||
>
|
||||
<TextField
|
||||
class="modalInput"
|
||||
:hint="'Search' | L"
|
||||
:hint="'ser' | L"
|
||||
v-model="searchQuery"
|
||||
/>
|
||||
</StackLayout>
|
||||
<ListView row="2" for="recipe in filteredRecipes" @loaded="listViewLoad">
|
||||
<ListView row="2" for="recipe in filteredRecipes">
|
||||
<v-template>
|
||||
<Label
|
||||
class="listItem"
|
||||
|
@ -79,16 +83,6 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
onPageLoad(args) {
|
||||
args.object._dialogFragment
|
||||
.getDialog()
|
||||
.getWindow()
|
||||
.setBackgroundDrawable(
|
||||
new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
);
|
||||
},
|
||||
tapAction(recipe) {
|
||||
this.$modal.close(recipe.id);
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Page @loaded="onPageLoad" backgroundColor="transparent" :class="appTheme">
|
||||
<Page @loaded="transparentPage" backgroundColor="transparent" :class="appTheme">
|
||||
<GridLayout rows="auto, auto, auto" class="modal">
|
||||
<Label class="title" :text="title | L" />
|
||||
<Label
|
||||
|
@ -47,17 +47,5 @@ export default {
|
|||
computed: {
|
||||
...mapState(["icon", "appTheme"]),
|
||||
},
|
||||
methods: {
|
||||
onPageLoad(args) {
|
||||
args.object._dialogFragment
|
||||
.getDialog()
|
||||
.getWindow()
|
||||
.setBackgroundDrawable(
|
||||
new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -20,12 +20,7 @@
|
|||
</GridLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
<ListView
|
||||
row="2"
|
||||
class="options-list"
|
||||
@loaded="listViewLoad"
|
||||
for="item in filterList"
|
||||
>
|
||||
<ListView row="2" class="options-list" for="item in filterList">
|
||||
<v-template>
|
||||
<Label
|
||||
class="listItem"
|
||||
|
@ -98,10 +93,8 @@ export default {
|
|||
switch (this.filterType) {
|
||||
case "cuisine":
|
||||
return arr.slice(0, -2);
|
||||
break;
|
||||
case "category":
|
||||
return arr.slice(0, -1);
|
||||
break;
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
|
@ -109,13 +102,10 @@ export default {
|
|||
switch (this.filterType) {
|
||||
case "cuisine":
|
||||
return this.cuisineList;
|
||||
break;
|
||||
case "category":
|
||||
return this.categoryList;
|
||||
break;
|
||||
case "tag":
|
||||
return this.tagList;
|
||||
break;
|
||||
}
|
||||
},
|
||||
cuisineList() {
|
||||
|
@ -181,14 +171,7 @@ export default {
|
|||
"clearFilter",
|
||||
]),
|
||||
onPageLoad(args) {
|
||||
args.object._dialogFragment
|
||||
.getDialog()
|
||||
.getWindow()
|
||||
.setBackgroundDrawable(
|
||||
new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
);
|
||||
this.transparentPage(args);
|
||||
this.localCuisine = this.selectedCuisine;
|
||||
this.localCategory = this.selectedCategory;
|
||||
this.localTag = this.selectedTag;
|
||||
|
@ -209,8 +192,6 @@ export default {
|
|||
case "category":
|
||||
this.localTag = null;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
scrollToRight() {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<Page @loaded="onPageLoad" backgroundColor="transparent" :class="appTheme">
|
||||
<Page
|
||||
@loaded="transparentPage"
|
||||
backgroundColor="transparent"
|
||||
:class="appTheme"
|
||||
>
|
||||
<GridLayout rows="auto, auto, auto" class="modal">
|
||||
<Label class="title" :text="title | L" />
|
||||
<StackLayout
|
||||
|
@ -9,12 +13,14 @@
|
|||
horizontalAlignment="center"
|
||||
>
|
||||
<ListPicker
|
||||
@loaded="onLPLoad"
|
||||
ref="hrPicker"
|
||||
:items="hrsList"
|
||||
:selectedIndex="hrIndex"
|
||||
@selectedIndexChange="setHrs"
|
||||
></ListPicker>
|
||||
<ListPicker
|
||||
@loaded="onLPLoad"
|
||||
ref="minPicker"
|
||||
:items="minsList"
|
||||
:selectedIndex="minIndex"
|
||||
|
@ -40,7 +46,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { Application } from "@nativescript/core";
|
||||
import { mapState } from "vuex";
|
||||
import { localize } from "@nativescript/localize";
|
||||
export default {
|
||||
|
@ -81,15 +86,8 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
onPageLoad(args) {
|
||||
args.object._dialogFragment
|
||||
.getDialog()
|
||||
.getWindow()
|
||||
.setBackgroundDrawable(
|
||||
new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
);
|
||||
onLPLoad({ object }) {
|
||||
object.android.setWrapSelectorWheel(true);
|
||||
},
|
||||
setHrs(args) {
|
||||
let hr = "0" + this.hrs[args.object.selectedIndex];
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<Page @loaded="onPageLoad" backgroundColor="transparent" :class="appTheme">
|
||||
<Page
|
||||
@loaded="transparentPage"
|
||||
backgroundColor="transparent"
|
||||
:class="appTheme"
|
||||
>
|
||||
<GridLayout rows="auto, auto, auto" class="modal">
|
||||
<Label class="title" :text="title | L" />
|
||||
<StackLayout row="1" class="input">
|
||||
|
@ -43,16 +47,6 @@ export default {
|
|||
...mapState(["icon", "appTheme"]),
|
||||
},
|
||||
methods: {
|
||||
onPageLoad(args) {
|
||||
args.object._dialogFragment
|
||||
.getDialog()
|
||||
.getWindow()
|
||||
.setBackgroundDrawable(
|
||||
new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
);
|
||||
},
|
||||
focusField({ object }) {
|
||||
let a = this.placeholder;
|
||||
typeof a == "number"
|
||||
|
|
Binary file not shown.
|
@ -271,7 +271,6 @@
|
|||
"buInc": "Malformed or corrupt backup file",
|
||||
"buEmp": "The backup file is empty",
|
||||
"buMod": "The backup file was modified elsewhere",
|
||||
"buSuc": "Backup saved successfully to the Download folder",
|
||||
"invFile": "Invalid file",
|
||||
"donate": "Donate",
|
||||
"appInfo": "EnRecipes is an open source, privacy-friendly digital cookbook that lets you create, manage and share your recipes",
|
||||
|
@ -308,5 +307,7 @@
|
|||
"apply": "APPLY",
|
||||
"fltr": "Filter",
|
||||
"yld": "Yield",
|
||||
"swm": "Start week on Monday"
|
||||
"swm": "Start week on Monday",
|
||||
"expip": "Export in progress",
|
||||
"impip": "Import in progress"
|
||||
}
|
||||
|
|
|
@ -15,10 +15,15 @@ import store from './store'
|
|||
import CollectionView from '@nativescript-community/ui-collectionview/vue'
|
||||
Vue.use(CollectionView)
|
||||
|
||||
Vue.registerElement(
|
||||
'ImageZoom',
|
||||
() => require('@triniwiz/nativescript-image-zoom').ImageZoom
|
||||
)
|
||||
|
||||
import { lvMixin } from './shared/mixins.js'
|
||||
Vue.mixin(lvMixin)
|
||||
|
||||
Vue.config.silent = TNS_ENV === 'production'
|
||||
Vue.config.silent = false
|
||||
|
||||
Vue.filter('L', localize)
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@ android {
|
|||
versionCode 7
|
||||
versionName '1.3.2'
|
||||
applicationId 'com.vishnuraghav.enrecipes'
|
||||
minSdkVersion 22
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
compileSdkVersion 30
|
||||
generatedDensities = []
|
||||
ndk {
|
||||
abiFilters.clear()
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="__PACKAGE__" android:versionCode="10000" android:versionName="1.0">
|
||||
<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" />
|
||||
<uses-permission android:name="android.permission.CAMERA" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.INTERNET" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
|
||||
<application android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:hardwareAccelerated="true" android:largeHeap="true" android:theme="@style/AppTheme" android:requestLegacyExternalStorage="true">
|
||||
<application android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:hardwareAccelerated="true" android:largeHeap="true" android:theme="@style/AppTheme" android:preserveLegacyExternalStorage="true">
|
||||
<activity android:name="com.tns.NativeScriptActivity" android:label="@string/title_activity_kimera" android:screenOrientation="userPortrait" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|uiMode" android:theme="@style/LaunchScreenTheme" android:windowSoftInputMode="adjustResize">
|
||||
<meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" />
|
||||
<intent-filter>
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
export const lvMixin = {
|
||||
methods: {
|
||||
listViewLoad(args) {
|
||||
let e = args.object.android
|
||||
e.setSelector(new android.graphics.drawable.StateListDrawable())
|
||||
e.setDivider(null)
|
||||
e.setDividerHeight(0)
|
||||
},
|
||||
animateInOut(dur, rev, draw) {
|
||||
const start = Date.now()
|
||||
let timer = setInterval(() => {
|
||||
const passed = Date.now() - start
|
||||
let val = passed / dur
|
||||
if (val > 1) val = 1
|
||||
draw(rev ? 1 - val : val)
|
||||
if (val === 1) clearInterval(timer)
|
||||
}, 17) // 1000ms/60fps=16.66ms => 17ms
|
||||
transparentPage({ object }) {
|
||||
object._dialogFragment
|
||||
.getDialog()
|
||||
.getWindow()
|
||||
.setBackgroundDrawable(
|
||||
new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
)
|
||||
},
|
||||
// animateInOut(dur, rev, draw) {
|
||||
// const start = Date.now()
|
||||
// let timer = setInterval(() => {
|
||||
// const passed = Date.now() - start
|
||||
// let val = passed / dur
|
||||
// if (val > 1) val = 1
|
||||
// draw(rev ? 1 - val : val)
|
||||
// if (val === 1) clearInterval(timer)
|
||||
// }, 17) // 1000ms/60fps=16.66ms => 17ms
|
||||
// },
|
||||
},
|
||||
}
|
||||
|
|
|
@ -62,8 +62,7 @@ export const timer = (dur, callback) => {
|
|||
}, 1000)
|
||||
}
|
||||
|
||||
// FOLDER PICKER
|
||||
function callIntent(context, intent, pickerType) {
|
||||
function callIntent(context, intent, msg, pickerType) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onEvent = function(e) {
|
||||
if (e.requestCode === pickerType) {
|
||||
|
@ -72,215 +71,63 @@ function callIntent(context, intent, pickerType) {
|
|||
}
|
||||
}
|
||||
Application.android.once(AndroidApplication.activityResultEvent, onEvent)
|
||||
context.startActivityForResult(intent, pickerType)
|
||||
context.startActivityForResult(
|
||||
android.content.Intent.createChooser(intent, msg),
|
||||
pickerType
|
||||
)
|
||||
})
|
||||
}
|
||||
// IMAGE PICKER
|
||||
export const getRecipePhoto = () => {
|
||||
const context =
|
||||
Application.android.foregroundActivity || Application.android.startActivity
|
||||
const DIR_CODE = Math.round(Math.random() * 10000)
|
||||
const intent = new android.content.Intent(
|
||||
android.content.Intent.ACTION_GET_CONTENT
|
||||
)
|
||||
intent.setType('image/*')
|
||||
return callIntent(context, intent, 'Select photo', DIR_CODE).then((res) => {
|
||||
if (res.resultCode === android.app.Activity.RESULT_OK)
|
||||
if (res.intent != null && res.intent.getData())
|
||||
return res.intent.getData()
|
||||
})
|
||||
}
|
||||
export const copyPhotoToCache = (uri, filepath) => {
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver()
|
||||
return new Promise((resolve) => {
|
||||
const inputStream = ContentResolver.openInputStream(uri)
|
||||
const input = new java.io.BufferedInputStream(inputStream)
|
||||
let size = input.available()
|
||||
let buffer = Array.create('byte', size)
|
||||
const output = new java.io.BufferedOutputStream(
|
||||
new java.io.FileOutputStream(filepath)
|
||||
)
|
||||
input.read(buffer)
|
||||
do {
|
||||
output.write(buffer)
|
||||
} while (input.read(buffer) != -1)
|
||||
input.close()
|
||||
output.close()
|
||||
resolve(filepath)
|
||||
})
|
||||
}
|
||||
|
||||
//SOURCE: https://github.com/NativeScript/nativescript-imagepicker/blob/bc9209dd7d773eb437ff812f07873c72c789d572/src/imagepicker.android.ts
|
||||
|
||||
function getDataColumn(uri, selection, selectionArgs, isDownload) {
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver()
|
||||
let cursor = null
|
||||
let filePath
|
||||
if (isDownload) {
|
||||
let columns = ['_display_name']
|
||||
try {
|
||||
cursor = ContentResolver.query(
|
||||
uri,
|
||||
columns,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null
|
||||
)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
let column_index = cursor.getColumnIndexOrThrow(columns[0])
|
||||
filePath = cursor.getString(column_index)
|
||||
if (filePath) {
|
||||
const dl = android.os.Environment.getExternalStoragePublicDirectory(
|
||||
android.os.Environment.DIRECTORY_DOWNLOADS
|
||||
)
|
||||
filePath = `${dl}/${filePath}`
|
||||
return filePath
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
if (cursor) {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let columns = [android.provider.MediaStore.MediaColumns.DATA]
|
||||
let filePath
|
||||
|
||||
try {
|
||||
cursor = ContentResolver.query(
|
||||
uri,
|
||||
columns,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null
|
||||
)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
let column_index = cursor.getColumnIndexOrThrow(columns[0])
|
||||
filePath = cursor.getString(column_index)
|
||||
if (filePath) {
|
||||
return filePath
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
if (cursor) {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
function getTreeUri(uri) {
|
||||
const DocumentsContract = android.provider.DocumentsContract
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver()
|
||||
if (DocumentsContract.isTreeUri(uri)) {
|
||||
let docId, id, type
|
||||
let contentUri = null
|
||||
if ('com.android.externalstorage.documents' === uri.getAuthority()) {
|
||||
docId = DocumentsContract.getTreeDocumentId(uri).split(':')
|
||||
type = docId[0]
|
||||
id = docId[1]
|
||||
|
||||
if ('primary' === type.toLowerCase())
|
||||
return android.os.Environment.getExternalStorageDirectory() + '/' + id
|
||||
else {
|
||||
ContentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
const externalMediaDirs = Application.android.context.getExternalMediaDirs()
|
||||
if (externalMediaDirs.length > 1) {
|
||||
let filePath = externalMediaDirs[1].getAbsolutePath()
|
||||
filePath = filePath.substring(0, filePath.indexOf('Android')) + id
|
||||
return filePath
|
||||
}
|
||||
}
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (
|
||||
'com.android.providers.downloads.documents' === uri.getAuthority()
|
||||
) {
|
||||
return getDataColumn(uri, null, null, true)
|
||||
}
|
||||
// MediaProvider
|
||||
else if ('com.android.providers.media.documents' === uri.getAuthority()) {
|
||||
docId = DocumentsContract.getTreeDocumentId(uri).split(':')
|
||||
type = docId[0]
|
||||
id = docId[1]
|
||||
|
||||
if ('image' === type)
|
||||
contentUri =
|
||||
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||
else if ('video' === type)
|
||||
contentUri =
|
||||
android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
else if ('audio' === type)
|
||||
contentUri =
|
||||
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
|
||||
let selection = '_id=?'
|
||||
let selectionArgs = [id]
|
||||
|
||||
return getDataColumn(contentUri, selection, selectionArgs, false)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
function getFileUri(uri) {
|
||||
const DocumentsContract = android.provider.DocumentsContract
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver()
|
||||
if (DocumentsContract.isDocumentUri(Application.android.context, uri)) {
|
||||
let docId, id, type
|
||||
let contentUri = null
|
||||
if ('com.android.externalstorage.documents' === uri.getAuthority()) {
|
||||
docId = DocumentsContract.getDocumentId(uri).split(':')
|
||||
type = docId[0]
|
||||
id = docId[1]
|
||||
|
||||
if ('primary' === type.toLowerCase())
|
||||
return android.os.Environment.getExternalStorageDirectory() + '/' + id
|
||||
else {
|
||||
ContentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
const externalMediaDirs = Application.android.context.getExternalMediaDirs()
|
||||
if (externalMediaDirs.length > 1) {
|
||||
let filePath = externalMediaDirs[1].getAbsolutePath()
|
||||
filePath = filePath.substring(0, filePath.indexOf('Android')) + id
|
||||
return filePath
|
||||
}
|
||||
}
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (
|
||||
'com.android.providers.downloads.documents' === uri.getAuthority()
|
||||
) {
|
||||
return getDataColumn(uri, null, null, true)
|
||||
}
|
||||
// MediaProvider
|
||||
else if ('com.android.providers.media.documents' === uri.getAuthority()) {
|
||||
docId = DocumentsContract.getDocumentId(uri).split(':')
|
||||
type = docId[0]
|
||||
id = docId[1]
|
||||
|
||||
if ('image' === type)
|
||||
contentUri =
|
||||
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||
else if ('video' === type)
|
||||
contentUri =
|
||||
android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
else if ('audio' === type)
|
||||
contentUri =
|
||||
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
|
||||
let selection = '_id=?'
|
||||
let selectionArgs = [id]
|
||||
|
||||
return getDataColumn(contentUri, selection, selectionArgs, false)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const pickFolder = () => {
|
||||
// BACKUP FOLDER PICKER
|
||||
export const getBackupFolder = () => {
|
||||
const context =
|
||||
Application.android.foregroundActivity || Application.android.startActivity
|
||||
const DIR_CODE = Math.round(Math.random() * 10000)
|
||||
const intent = new android.content.Intent(
|
||||
android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
||||
)
|
||||
intent.addCategory(android.content.Intent.CATEGORY_DEFAULT)
|
||||
return callIntent(context, intent, DIR_CODE).then((res) => {
|
||||
return callIntent(context, intent, 'Select folder', DIR_CODE).then((res) => {
|
||||
if (res.resultCode === android.app.Activity.RESULT_OK)
|
||||
if (res.intent != null && res.intent.getData())
|
||||
return getTreeUri(res.intent.getData())
|
||||
return res.intent.getData()
|
||||
})
|
||||
}
|
||||
|
||||
function callBackupIntent(context, intent, pickerType) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onEvent = function(e) {
|
||||
if (e.requestCode === pickerType) {
|
||||
resolve(e)
|
||||
Application.android.off(AndroidApplication.activityResultEvent, onEvent)
|
||||
}
|
||||
}
|
||||
Application.android.once(AndroidApplication.activityResultEvent, onEvent)
|
||||
context.startActivityForResult(intent, pickerType)
|
||||
})
|
||||
}
|
||||
// BACKUP FILE PICKER
|
||||
export const getBackupFile = () => {
|
||||
const context =
|
||||
Application.android.foregroundActivity || Application.android.startActivity
|
||||
|
@ -290,13 +137,118 @@ export const getBackupFile = () => {
|
|||
)
|
||||
intent.addCategory(android.content.Intent.CATEGORY_OPENABLE)
|
||||
intent.setType('application/zip')
|
||||
// intent.setFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
return callBackupIntent(context, intent, DIR_CODE).then((res) => {
|
||||
return callIntent(context, intent, 'Select file to import', DIR_CODE).then(
|
||||
(res) => {
|
||||
if (res.resultCode === android.app.Activity.RESULT_OK) {
|
||||
if (res.intent != null && res.intent.getData()) {
|
||||
// return getFileUri(res.intent.getData())
|
||||
if (res.intent != null && res.intent.getData())
|
||||
return res.intent.getData()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// ZIP OPERATIONS
|
||||
export class Zip {
|
||||
static getSubFiles(src, isRootFolder) {
|
||||
const fileList = new java.util.ArrayList()
|
||||
const sourceFile = new java.io.File(src)
|
||||
let tempList = sourceFile.listFiles()
|
||||
for (let i = 0; i < tempList.length; i++) {
|
||||
if (tempList[i].isFile()) {
|
||||
fileList.add(tempList[i])
|
||||
}
|
||||
if (tempList[i].isDirectory()) {
|
||||
if (isRootFolder) {
|
||||
fileList.add(tempList[i])
|
||||
}
|
||||
fileList.addAll(Zip.getSubFiles(tempList[i].getAbsolutePath()))
|
||||
}
|
||||
}
|
||||
return fileList
|
||||
}
|
||||
static zip(src, destUri, filename) {
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver()
|
||||
const parsedUri = new android.net.Uri.parse(destUri)
|
||||
const uri = new androidx.documentfile.provider.DocumentFile.fromTreeUri(
|
||||
Application.android.context,
|
||||
parsedUri
|
||||
)
|
||||
.createFile('application/zip', filename)
|
||||
.getUri()
|
||||
const outputStream = ContentResolver.openOutputStream(uri)
|
||||
const zipOutputStream = new java.util.zip.ZipOutputStream(outputStream)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const sourceFiles = Zip.getSubFiles(src, true)
|
||||
for (let i = 0; i < sourceFiles.size(); i++) {
|
||||
let len
|
||||
let buffer = Array.create('byte', 4096)
|
||||
let file = sourceFiles.get(i)
|
||||
let entry = new java.util.zip.ZipEntry(
|
||||
'EnRecipes/' +
|
||||
new java.io.File(src)
|
||||
.toURI()
|
||||
.relativize(file.toURI())
|
||||
.getPath()
|
||||
)
|
||||
zipOutputStream.putNextEntry(entry)
|
||||
if (!file.isDirectory()) {
|
||||
let inputStream = new java.io.FileInputStream(file)
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
zipOutputStream.write(buffer, 0, len)
|
||||
}
|
||||
inputStream.close()
|
||||
}
|
||||
zipOutputStream.closeEntry()
|
||||
}
|
||||
zipOutputStream.close()
|
||||
resolve(uri)
|
||||
})
|
||||
}
|
||||
static unzip(uri, dest) {
|
||||
const ContentResolver = Application.android.nativeApp.getContentResolver()
|
||||
return new Promise((resolve) => {
|
||||
const inputStream = ContentResolver.openInputStream(uri)
|
||||
const zipInputStream = new java.util.zip.ZipInputStream(inputStream)
|
||||
let len, filepath
|
||||
let buffer = Array.create('byte', 4096)
|
||||
let entry = zipInputStream.getNextEntry()
|
||||
filepath = dest + '/' + entry.getName()
|
||||
while (entry != null) {
|
||||
filepath = dest + '/' + entry.getName()
|
||||
if (!entry.isDirectory()) {
|
||||
const output = new java.io.BufferedOutputStream(
|
||||
new java.io.FileOutputStream(filepath)
|
||||
)
|
||||
while ((len = zipInputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, len)
|
||||
}
|
||||
output.close()
|
||||
} else {
|
||||
let dir = new java.io.File(filepath)
|
||||
dir.mkdirs()
|
||||
}
|
||||
zipInputStream.closeEntry()
|
||||
entry = zipInputStream.getNextEntry()
|
||||
}
|
||||
zipInputStream.close()
|
||||
resolve(dest)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SHARE OPERATIONS
|
||||
|
||||
function share(intent, subject) {
|
||||
const context = Application.android.context
|
||||
const shareIntent = android.content.Intent.createChooser(intent, subject)
|
||||
shareIntent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(shareIntent)
|
||||
}
|
||||
|
||||
export const shareText = (text, subject) => {
|
||||
const intent = new android.context.Intent(android.content.Intent.ACTION_SEND)
|
||||
intent.setType('text/plain')
|
||||
intent.putExtra(android.context.Intent.EXTRA_TEXT, text)
|
||||
share(intent, subject)
|
||||
}
|
||||
|
|
142
app/store.js
142
app/store.js
|
@ -160,72 +160,61 @@ export default new Vuex.Store({
|
|||
units: [],
|
||||
mealPlans: [],
|
||||
icon: {
|
||||
alert: '\ue900',
|
||||
back: '\ue901',
|
||||
bag: '\ue902',
|
||||
bagged: '\ue903',
|
||||
cal: '\ue904',
|
||||
cam: '\ue905',
|
||||
category: '\ue906',
|
||||
cog: '\ue907',
|
||||
comb: '\ue908',
|
||||
cuisine: '\ue909',
|
||||
del: '\ue90a',
|
||||
diff: '\ue90b',
|
||||
don: '\ue90c',
|
||||
done: '\ue90d',
|
||||
edit: '\ue90e',
|
||||
exp: '\ue910',
|
||||
fav: '\ue911',
|
||||
faved: '\ue912',
|
||||
folder: '\ue913',
|
||||
gh: '\ue914',
|
||||
help: '\ue915',
|
||||
home: '\ue916',
|
||||
img: '\ue917',
|
||||
imp: '\ue918',
|
||||
info: '\ue919',
|
||||
items: '\ue91a',
|
||||
l1: '\ue91b',
|
||||
l2: '\ue91c',
|
||||
l3: '\ue91d',
|
||||
lang: '\ue91e',
|
||||
left: '\ue91f',
|
||||
menu: '\ue920',
|
||||
noresult: '\ue921',
|
||||
notes: '\ue922',
|
||||
plus: '\ue923',
|
||||
plusc: '\ue924',
|
||||
price: '\ue925',
|
||||
priv: '\ue926',
|
||||
err: '\ue90f',
|
||||
res: '\ue927',
|
||||
reset: '\ue928',
|
||||
right: '\ue929',
|
||||
save: '\ue92a',
|
||||
sear: '\ue92b',
|
||||
selall: '\ue92c',
|
||||
share: '\ue92d',
|
||||
shuf: '\ue92e',
|
||||
sort: '\ue92f',
|
||||
star: '\ue930',
|
||||
starred: '\ue931',
|
||||
steps: '\ue932',
|
||||
succ: '\ue933',
|
||||
tag: '\ue934',
|
||||
text: '\ue935',
|
||||
tg: '\ue936',
|
||||
theme: '\ue937',
|
||||
time: '\ue938',
|
||||
timer: '\ue939',
|
||||
tod: '\ue93a',
|
||||
trans: '\ue93b',
|
||||
tried: '\ue93c',
|
||||
try: '\ue93d',
|
||||
unit: '\ue93e',
|
||||
x: '\ue93f',
|
||||
yield: '\ue940',
|
||||
zip: '\ue941',
|
||||
back: '\ue900',
|
||||
bag: '\ue901',
|
||||
bagged: '\ue902',
|
||||
cal: '\ue903',
|
||||
category: '\ue904',
|
||||
check: '\ue905',
|
||||
cog: '\ue906',
|
||||
cuisine: '\ue907',
|
||||
db: '\ue908',
|
||||
del: '\ue909',
|
||||
diff: '\ue90a',
|
||||
don: '\ue90b',
|
||||
done: '\ue90c',
|
||||
edit: '\ue90d',
|
||||
exp: '\ue90e',
|
||||
fav: '\ue90f',
|
||||
faved: '\ue910',
|
||||
filter: '\ue911',
|
||||
folder: '\ue912',
|
||||
gh: '\ue913',
|
||||
help: '\ue914',
|
||||
home: '\ue915',
|
||||
img: '\ue916',
|
||||
imp: '\ue917',
|
||||
info: '\ue918',
|
||||
interface: '\ue919',
|
||||
lang: '\ue91a',
|
||||
layout: '\ue91b',
|
||||
left: '\ue91c',
|
||||
menu: '\ue91d',
|
||||
opts: '\ue91e',
|
||||
plus: '\ue91f',
|
||||
price: '\ue920',
|
||||
priv: '\ue921',
|
||||
reset: '\ue922',
|
||||
right: '\ue923',
|
||||
save: '\ue924',
|
||||
sear: '\ue925',
|
||||
share: '\ue926',
|
||||
shuf: '\ue927',
|
||||
sort: '\ue928',
|
||||
star: '\ue929',
|
||||
starred: '\ue92a',
|
||||
tag: '\ue92b',
|
||||
tg: '\ue92c',
|
||||
theme: '\ue92d',
|
||||
time: '\ue92e',
|
||||
tod: '\ue92f',
|
||||
trans: '\ue930',
|
||||
tried: '\ue931',
|
||||
try: '\ue932',
|
||||
uncheck: '\ue933',
|
||||
undo: '\ue934',
|
||||
week: '\ue935',
|
||||
x: '\ue936',
|
||||
},
|
||||
currentComponent: 'EnRecipes',
|
||||
sortType: 'Oldest first',
|
||||
|
@ -298,11 +287,19 @@ export default new Vuex.Store({
|
|||
selectedCategory: null,
|
||||
selectedTag: null,
|
||||
appTheme: 'Light',
|
||||
mondayFirst: false,
|
||||
},
|
||||
mutations: {
|
||||
clearImportSummary(state) {
|
||||
for (const key in state.importSummary) state.importSummary[key] = 0
|
||||
},
|
||||
setFirstDay(state, bool) {
|
||||
state.mondayFirst = bool
|
||||
ApplicationSettings.setBoolean('mondayFirst', bool)
|
||||
},
|
||||
setTheme(state, theme) {
|
||||
ApplicationSettings.setString('appTheme', theme)
|
||||
state.appTheme = theme
|
||||
ApplicationSettings.setString('appTheme', theme)
|
||||
},
|
||||
clearFilter(state) {
|
||||
state.selectedCuisine = state.selectedCategory = state.selectedTag = null
|
||||
|
@ -671,6 +668,7 @@ export default new Vuex.Store({
|
|||
},
|
||||
setShake(state, shake) {
|
||||
state.shakeEnabled = shake
|
||||
ApplicationSettings.setBoolean('shakeEnabled', shake)
|
||||
},
|
||||
setRating(state, { id, recipe, rating }) {
|
||||
let index = state.recipes.indexOf(
|
||||
|
@ -706,6 +704,12 @@ export default new Vuex.Store({
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
clearImportSummary({ commit }) {
|
||||
commit('clearImportSummary')
|
||||
},
|
||||
setFirstDay({ commit }, bool) {
|
||||
commit('setFirstDay', bool)
|
||||
},
|
||||
setTheme({ commit }, theme) {
|
||||
commit('setTheme', theme)
|
||||
},
|
||||
|
@ -715,7 +719,7 @@ export default new Vuex.Store({
|
|||
setLayout({ commit }, type) {
|
||||
commit('setLayout', type)
|
||||
},
|
||||
setSortTypeAction({ commit }, sortType) {
|
||||
setSortType({ commit }, sortType) {
|
||||
commit('setSortType', sortType)
|
||||
},
|
||||
initRecipes({ commit }) {
|
||||
|
@ -778,7 +782,7 @@ export default new Vuex.Store({
|
|||
unSyncCombinationsAction({ commit }, combinations) {
|
||||
commit('unSyncCombinations', combinations)
|
||||
},
|
||||
setShakeAction({ commit }, shake) {
|
||||
setShake({ commit }, shake) {
|
||||
commit('setShake', shake)
|
||||
},
|
||||
setRatingAction({ commit }, rating) {
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
- Improvement: Faster app start and navigation
|
||||
- Added app languages: French, Italian, Norwegian Bokmål
|
||||
- Updated translations
|
||||
- Dropped support for Android Lollipop
|
||||
- Several bug fixes
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"~/*": ["app/*"]
|
||||
"~/*": ["app/*"],
|
||||
"@/*": ["app/*"]
|
||||
}
|
||||
},
|
||||
"include": ["app/**/*"]
|
||||
|
|
|
@ -7,5 +7,6 @@ export default {
|
|||
android: {
|
||||
v8Flags: "--expose_gc",
|
||||
markingMode: "none",
|
||||
}
|
||||
},
|
||||
// profiling: 'timeline'
|
||||
} as NativeScriptConfig
|
||||
|
|
18541
package-lock.json
generated
18541
package-lock.json
generated
File diff suppressed because it is too large
Load diff
21
package.json
21
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "enrecipes",
|
||||
"main": "main.js",
|
||||
"main": "./app/main.js",
|
||||
"version": "1.0.0",
|
||||
"description": "A native application built with NativeScript-Vue",
|
||||
"homepage": "https://enrecipes.vercel.app/",
|
||||
|
@ -18,27 +18,22 @@
|
|||
},
|
||||
"private": "true",
|
||||
"dependencies": {
|
||||
"@nativescript-community/perms": "^2.1.5",
|
||||
"@nativescript-community/ui-collectionview": "^4.0.29",
|
||||
"@nativescript/core": "7.3.0",
|
||||
"@nativescript/core": "~8.0.1",
|
||||
"@nativescript/localize": "^5.0.4",
|
||||
"@nativescript/social-share": "^2.0.4",
|
||||
"@nativescript/zip": "^5.0.0",
|
||||
"@triniwiz/nativescript-accelerometer": "^4.0.3",
|
||||
"@triniwiz/nativescript-couchbase": "^1.2.2",
|
||||
"@triniwiz/nativescript-image-zoom": "^4.0.0",
|
||||
"nativescript-imagecropper": "^4.0.1",
|
||||
"nativescript-intl": "^4.0.2",
|
||||
"nativescript-vue": "^2.8.4",
|
||||
"nativescript-vue": "~2.9.0",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@nativescript/android": "7.0.1",
|
||||
"@nativescript/webpack": "4.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"nativescript-vue-template-compiler": "^2.8.3",
|
||||
"node-sass": "^4.14.1",
|
||||
"vue-loader": "^15.9.6"
|
||||
"@nativescript/android": "8.0.0",
|
||||
"@nativescript/webpack": "beta",
|
||||
"nativescript-vue-template-compiler": "~2.9.0",
|
||||
"sass": "^1.32.8"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue