Merge remote-tracking branch 'origin/main' into main

This commit is contained in:
Hosted Weblate 2021-04-14 11:28:49 +02:00
commit bb02c1779c
32 changed files with 4482 additions and 17848 deletions

View file

@ -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;
}
@ -42,7 +43,8 @@ Page {
color: $gray9;
background: $gray1;
Page,
.filters {
.filters,
.sticky {
background: $gray1;
}
TextField,
@ -54,8 +56,7 @@ Page {
border-color: $gray2;
}
.appbar,
.modal,
.topPlate {
.modal {
background: $gray0;
}
.fieldLabel,
@ -71,19 +72,16 @@ Page {
color: $gray1;
background: $gray9;
}
Progress {
progress-background-color: $gray4;
}
.fab,
.hlMsg {
color: $gray1;
.fab {
color: #fff;
}
}
.Dark {
color: $gray1;
background: $gray9;
Page,
.filters {
.filters,
.sticky {
background: $gray9;
}
TextField,
@ -95,8 +93,7 @@ Page {
border-color: $gray7;
}
.appbar,
.modal,
.topPlate {
.modal {
color: $gray0;
background: $gray8;
}
@ -113,18 +110,16 @@ Page {
color: $gray9;
background: $gray1;
}
Progress {
progress-background-color: $gray5;
}
.fab,
.hlMsg {
.fab {
color: $gray9;
}
}
.Black {
color: $gray2;
background: $gray10;
Page {
Page,
.filters,
.sticky {
background: $gray10;
}
TextField,
@ -136,8 +131,7 @@ Page {
border-color: $gray8;
}
.appbar,
.modal,
.topPlate {
.modal {
color: $gray1;
background: $gray9;
}
@ -154,11 +148,7 @@ Page {
color: $gray10;
background: $gray2;
}
Progress {
progress-background-color: $gray5;
}
.fab,
.hlMsg {
.fab {
color: $gray10;
}
}
@ -191,7 +181,8 @@ TextView {
width: 100%;
}
Progress {
progress-color: $orange;
margin: 16;
background-color: $gray5;
}
Switch {
background-color: $orange;
@ -200,7 +191,7 @@ Switch {
// -----------------------------
// Side Drawer
.segment {
border-radius: 8;
border-radius: 12;
margin: 0 4 0 0;
padding: 0 12;
.value {
@ -216,7 +207,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 +267,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 +309,7 @@ Button {
background-color: transparent;
}
.imgHolder {
border-radius: 8;
border-radius: 12;
}
// -----------------------------
// SETTINGS
@ -328,6 +319,7 @@ Button {
}
.options-list {
.option {
vertical-align: center;
padding: 14 8;
margin: 0 16;
.ico {
@ -353,7 +345,7 @@ Button {
.icon {
margin: 16;
background: $orange;
border-radius: 99;
border-radius: 16;
horizontal-alignment: center;
}
.name {
@ -367,16 +359,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 +422,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 +449,7 @@ Button {
}
}
.toolbar {
z-index: 98;
z-index: 24;
padding: 4;
margin-bottom: 0;
horizontal-alignment: left;
@ -469,7 +467,7 @@ Button {
width: 48;
height: 48;
margin: 0 4 0 0;
border-radius: 8;
border-radius: 12;
background: $orange;
}
// -----------------------------
@ -481,11 +479,12 @@ Button {
padding: 0;
margin: 32 0 16;
}
.sticky {
width: 100%;
padding: 0 16 16;
margin: 0;
}
.countdown {
background-color: transparent;
width: 48;
height: 48;
z-index: 0;
font-size: 17;
color: $orange;
}
@ -507,7 +506,7 @@ Button {
text-align: center;
}
.day {
border-radius: 8;
border-radius: 12;
}
.hasPlans {
color: $orange;
@ -536,7 +535,7 @@ Button {
.modal {
max-width: 320;
width: 100%;
border-radius: 10;
border-radius: 12;
margin: 72 0;
.title {
@extend .tb;
@ -566,7 +565,7 @@ Button {
background: transparent;
}
.shareItem {
border-radius: 8;
border-radius: 12;
margin: 0 8 8;
text-align: center;
.ico {
@ -624,6 +623,7 @@ ActivityIndicator {
}
@keyframes fade {
0% {
opacity: 1;
}
100% {
opacity: 0.5;

File diff suppressed because it is too large Load diff

View file

@ -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"
@tap="selectMode ? exportSelection() : openSearch()"
/>
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,32 +584,29 @@ 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"
? decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
)
: decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_DARK_NAVIGATION_BAR
);
if (sdkv >= 27)
this.appTheme == "Light"
? decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
)
: decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_DARK_NAVIGATION_BAR
);
},
onPageLoad(args) {
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>

View file

@ -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;

View file

@ -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>
@ -47,7 +42,6 @@ import Interface from "./Settings/Interface.vue";
import Options from "./Settings/Options.vue";
import Database from "./Settings/Database.vue";
import Reset from "./Settings/Reset.vue";
import Help from "./Settings/Help.vue";
import About from "./Settings/About.vue";
export default {
data() {
@ -55,17 +49,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 +68,6 @@ export default {
title: "rest",
view: Reset,
},
{
icon: "help",
title: "help",
view: Help,
},
{
icon: "info",
title: "About",

View file

@ -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",

View file

@ -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>

View file

@ -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,143 @@ 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();
// IMPORT HANDLERS
openZipFile() {
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();
if (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")));
}
// 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) {
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 +417,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 {
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();
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) => {
this.showImportSummary();
this.unlinkBrokenImages();
});
},
showImportSummary() {
@ -478,6 +437,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 +446,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";

View file

@ -1,78 +0,0 @@
<template>
<Page @loaded="onPageLoad" actionBarHidden="true">
<GridLayout rows="*, auto" columns="auto, *">
<ListView
colSpan="2"
rowSpan="2"
class="options-list"
for="item in items"
@loaded="listViewLoad"
>
<v-template if="$index == 0">
<Label class="pageTitle" :text="'help' | L" />
</v-template>
<v-template if="$index == 3">
<StackLayout class="listSpace"> </StackLayout>
</v-template>
<v-template>
<GridLayout
columns="auto, *"
class="option"
@touch="touch($event, item.url)"
>
<Label class="ico" :text="icon[item.icon]" />
<Label col="1" :text="item.title | L" class="info" />
</GridLayout>
</v-template>
</ListView>
<GridLayout row="1" class="appbar" rows="*" columns="auto, *">
<Button
class="ico"
:text="icon.back"
@tap="$navigateBack()"
/>
</GridLayout>
</GridLayout>
</Page>
</template>
<script>
import { Observable, Utils } from "@nativescript/core";
import { mapState } from "vuex";
export default {
computed: {
...mapState(["icon"]),
items() {
return [
{},
{
icon: "tg",
title: "joinTG",
url: "https://t.me/enrecipes",
},
{
icon: "help",
title: "guide",
url: "https://github.com/vishnuraghavb/EnRecipes/wiki/User-Guide",
},
{},
];
},
},
methods: {
onPageLoad(args) {
const page = args.object;
page.bindingContext = new Observable();
},
// HELPERS
openURL(url) {
Utils.openUrl(url);
},
touch({ object, action }, url) {
object.className = action.match(/down|move/) ? "option fade" : "option";
if (action == "up") this.openURL(url);
},
},
};
</script>

View file

@ -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) {

View file

@ -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);
},
},
};

View file

@ -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";

View file

@ -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,95 +32,81 @@
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>
<AbsoluteLayout dock="bottom">
<ScrollView
width="100%"
height="100%"
@loaded="onScrollLoad"
@scroll="!toast && onScroll($event)"
>
<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"
/>
<StackLayout @loaded="onIngsLoad">
<GridLayout
rows="auto"
columns="auto, *"
<Label
padding="0 16"
class="sectionTitle"
:hidden="!recipe.ingredients.length"
:text="getTitleCount('ings', 'ingredients')"
/>
<StackLayout
orientation="horizontal"
v-for="(item, index) in recipe.ingredients"
:key="index + 'ing'"
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"
:text="`${
roundedQuantity(item.quantity)
? roundedQuantity(item.quantity) + ' '
@ -132,33 +117,32 @@
: ''
}${item.item}`"
/>
</GridLayout>
</StackLayout>
</StackLayout>
<Label
id="steps"
padding="0 16"
v-show="recipe.instructions.length"
class="sectionTitle"
:text="'ins' | L"
/>
<StackLayout @loaded="onInsLoad">
<GridLayout
<Label
padding="0 16"
:hidden="!recipe.instructions.length"
class="sectionTitle"
:text="getTitleCount('inss', 'instructions')"
/>
<StackLayout
orientation="horizontal"
@touch="touchInstruction"
columns="auto ,*"
v-for="(instruction, index) in recipe.instructions"
:key="index + 'ins'"
class="instruction"
>
<Button class="count ico min" :text="index + 1" />
<Label col="1" class="value tw" :text="instruction" />
</GridLayout>
<Label class="value tw" :text="instruction" />
</StackLayout>
</StackLayout>
<Label
id="comb"
@loaded="onCmbLoad"
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 +152,45 @@
@tap="viewCombination(combination)"
/>
<Label
id="notes"
@loaded="onNosTLoad"
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>
</ScrollView>
<Label
class="sectionTitle sticky"
:hidden="!showTitleArr[0]"
:text="getTitleCount('ings', 'ingredients')"
/>
<Label
class="sectionTitle sticky"
:hidden="!showTitleArr[1]"
:text="getTitleCount('inss', 'instructions')"
/>
<Label
class="sectionTitle sticky"
:hidden="!showTitleArr[2]"
:text="getTitleCount('cmbs', 'combinations')"
/>
<Label
class="sectionTitle sticky"
:hidden="!showTitleArr[3]"
:text="getTitleCount('nos', 'notes')"
/>
</AbsoluteLayout>
</DockLayout>
<GridLayout
row="1"
@loaded="onAppBarLoad"
@ -187,13 +198,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 +221,7 @@
col="3"
class="ico"
:text="recipe.isFavorite ? icon.faved : icon.fav"
@tap="toggleFavourite"
@tap="toggle('isFavorite')"
/>
<Button
col="4"
@ -226,8 +241,7 @@
<GridLayout
v-show="toast"
row="1"
class="appbar"
:class="hlMsg && 'hlMsg'"
class="appbar snackBar"
columns="*"
@tap="toast = null"
>
@ -235,6 +249,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 +263,7 @@
<script>
import {
Application,
AndroidApplication,
ImageSource,
Utils,
Span,
@ -249,15 +271,15 @@ import {
Label,
Observable,
Screen,
CoreTypes,
} from "@nativescript/core";
import * as SocialShare from "@nativescript/social-share";
import { localize } from "@nativescript/localize";
const intl = require("nativescript-intl");
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() {
@ -270,43 +292,25 @@ export default {
appbar: null,
ingcon: null,
inscon: null,
cmbcon: null,
notescon: null,
notesT: null,
imgZoom: null,
checks: [],
steps: [],
checked: 0,
stepsDid: 0,
toast: null,
hlMsg: false,
selectedIndex: 0,
scrollingToId: false,
scrolled: false,
photoOpen: false,
showTitleArr: [false, false, false, 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 +325,7 @@ export default {
const page = args.object;
page.bindingContext = new Observable();
this.busy = false;
setTimeout((e) => {
this.setComponent("ViewRecipe");
}, 500);
this.setComponent("ViewRecipe");
if (this.yieldMultiplier == this.recipe.yield.quantity)
this.yieldMultiplier = this.recipe.yield.quantity;
this.keepScreenOn(true);
@ -341,31 +343,57 @@ export default {
onInsLoad({ object }) {
this.inscon = object;
},
onCmbLoad({ object }) {
this.cmbcon = object;
},
onNosTLoad({ object }) {
this.notesT = object;
},
onNosLoad({ object }) {
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;
},
// FIX: scroll not smooth
stickyTitle({ object }) {
// let vm = this;
// function isTop(label) {
// let pos = label.getLocationRelativeTo(object).y;
// return label === vm.cmbcon || label === vm.notesT
// ? pos < 0
// : pos + 32 < 0;
// }
// const isAllFalse = (e) => e == false;
// if (this.recipe.notes.length && isTop(this.notesT)) {
// this.showTitleArr = [false, false, false, true];
// } else if (this.recipe.combinations.length && isTop(this.cmbcon)) {
// this.showTitleArr = [false, false, true, false];
// } else if (this.recipe.instructions.length && isTop(this.inscon)) {
// this.showTitleArr = [false, true, false, false];
// } else if (this.recipe.ingredients.length && isTop(this.ingcon)) {
// this.showTitleArr = [true, false, false, false];
// } else {
// this.showTitleArr = [false, false, false, false];
// }
// if (
// this.recipe.ingredients.length &&
// !this.showTitleArr[0] &&
// isTop(this.ingcon)
// ) {
// this.showTitleArr = [true, false, false, false];
// } else if (!this.showTitleArr.every(isAllFalse) && !isTop(this.ingcon)) {
// this.showTitleArr = [false, false, false, false];
// }
},
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 +401,35 @@ 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,
});
// this.stickyTitle(args);
}
},
// 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 +465,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,38 +527,49 @@ 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 = 1; 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() {
for (let i = 0; i < this.inscon.getChildrenCount(); i++) {
this.stepsDid = 0;
for (let i = 1; i < this.inscon.getChildrenCount(); i++) {
this.inscon.getChildAt(i).className = "instruction";
}
},
@ -536,12 +589,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();
},
@ -556,15 +610,13 @@ export default {
}).then((result) => {
switch (result) {
case "pht":
ImageSource.fromFile(this.recipe.imageSrc).then((res) => {
SocialShare.shareImage(res, "Share recipe photo using");
});
ImageSource.fromFile(this.recipe.imageSrc).then((res) =>
utils.shareImage(res, localize("srpu"))
);
break;
case "rec":
this.shareRecipe();
break;
default:
break;
}
});
} else {
@ -572,24 +624,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 +655,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 +668,19 @@ 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");
utils.shareText(shareContent, localize("sru"));
},
// 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 +689,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 +752,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"

View file

@ -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 }) {

View file

@ -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);
},

View file

@ -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" />
<Label
@ -8,23 +12,16 @@
class="description tw"
:text="description"
/>
<GridLayout row="2" columns="auto, *, auto, auto" class="actions">
<Button
v-if="secondButtonText"
col="0"
class="text sm"
:text="secondButtonText | L"
@tap="$modal.close(-1)"
/>
<GridLayout row="2" columns="*, auto, auto" class="actions">
<Button
v-if="cancelButtonText"
col="2"
col="1"
class="text sm"
:text="cancelButtonText | L"
@tap="$modal.close(false)"
/>
<Button
col="3"
col="2"
class="text sm"
:text="okButtonText | L"
@tap="$modal.close(true)"
@ -37,27 +34,9 @@
<script>
import { mapState } from "vuex";
export default {
props: [
"title",
"description",
"secondButtonText",
"cancelButtonText",
"okButtonText",
],
props: ["title", "description", "cancelButtonText", "okButtonText"],
computed: {
...mapState(["icon", "appTheme"]),
},
methods: {
onPageLoad(args) {
args.object._dialogFragment
.getDialog()
.getWindow()
.setBackgroundDrawable(
new android.graphics.drawable.ColorDrawable(
android.graphics.Color.TRANSPARENT
)
);
},
},
};
</script>

View file

@ -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,19 +171,12 @@ 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;
if (this.localCuisine) this.filterType = "category";
if (this.localCategory || this.localTag) this.filterType = "tag";
if (this.localCategory && this.localTag) this.filterType = "tag";
this.scrollToRight();
},
onScrollLoad(args) {
@ -209,8 +192,6 @@ export default {
case "category":
this.localTag = null;
break;
default:
break;
}
},
scrollToRight() {

View file

@ -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];

View file

@ -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.

View file

@ -175,7 +175,6 @@
"nwCat": "New category",
"req": "Required",
"recPic": "Recipe photo",
"repBtn": "REPLACE",
"cPic": "Crop photo",
"breakfast": "Breakfast",
"lunch": "lunch",
@ -271,7 +270,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 +306,11 @@
"apply": "APPLY",
"fltr": "Filter",
"yld": "Yield",
"swm": "Start week on Monday"
"swm": "Start week on Monday",
"expip": "Export in progress",
"impip": "Import in progress",
"srpu": "Share recipe photo using...",
"sru": "Share recipe using...",
"aap": "Attach a photo",
"rp": "Remove photo"
}

View file

@ -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)

View file

@ -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()

View file

@ -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>

View file

@ -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
// },
},
}

View file

@ -35,8 +35,8 @@ export const openAppSettingsPage = () => {
Application.android.foregroundActivity.startActivity(intent)
}
export const hasAccelerometer = () => {
let context = Utils.ad.getApplicationContext()
let sensorManager = context.getSystemService(
let ctx = Utils.ad.getApplicationContext()
let sensorManager = ctx.getSystemService(
android.content.Context.SENSOR_SERVICE
)
return sensorManager.getDefaultSensor(
@ -62,9 +62,8 @@ export const timer = (dur, callback) => {
}, 1000)
}
// FOLDER PICKER
function callIntent(context, intent, pickerType) {
return new Promise((resolve, reject) => {
function callIntent(ctx, intent, msg, pickerType) {
return new Promise((resolve) => {
const onEvent = function(e) {
if (e.requestCode === pickerType) {
resolve(e)
@ -72,217 +71,65 @@ function callIntent(context, intent, pickerType) {
}
}
Application.android.once(AndroidApplication.activityResultEvent, onEvent)
context.startActivityForResult(intent, pickerType)
ctx.startActivityForResult(
android.content.Intent.createChooser(intent, msg),
pickerType
)
})
}
// IMAGE PICKER
export const getRecipePhoto = () => {
const ctx =
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(ctx, 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 = () => {
const context =
// BACKUP FOLDER PICKER
export const getBackupFolder = () => {
const ctx =
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(ctx, 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 =
const ctx =
Application.android.foregroundActivity || Application.android.startActivity
const DIR_CODE = Math.round(Math.random() * 10000)
const intent = new android.content.Intent(
@ -290,13 +137,137 @@ 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) => {
if (res.resultCode === android.app.Activity.RESULT_OK) {
if (res.intent != null && res.intent.getData()) {
// return getFileUri(res.intent.getData())
return res.intent.getData()
return callIntent(ctx, 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 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 ctx = Application.android.context
const shareIntent = android.content.Intent.createChooser(intent, subject)
shareIntent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
ctx.startActivity(shareIntent)
}
function getSendIntent(type) {
const intent = new android.content.Intent(android.content.Intent.ACTION_SEND)
intent.setType(type)
return intent
}
export const shareText = (text, subject) => {
const intent = getSendIntent('text/plain')
intent.putExtra(android.content.Intent.EXTRA_TEXT, text)
share(intent, subject)
}
export const shareImage = (image, subject) => {
let ctx = Application.android.context
const intent = getSendIntent('image/jpeg')
const baos = new java.io.ByteArrayOutputStream()
image.android.compress(android.graphics.Bitmap.CompressFormat.JPEG, 100, baos)
const tmpFile = new java.io.File(ctx.getExternalCacheDir(), 'EnRecipes.jpg')
const fos = new java.io.FileOutputStream(tmpFile)
fos.write(baos.toByteArray())
fos.flush()
fos.close()
intent.putExtra(
android.content.Intent.EXTRA_STREAM,
android.net.Uri.fromFile(tmpFile)
)
share(intent, subject)
}

View file

@ -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) {

View file

@ -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

View file

@ -2,7 +2,8 @@
"compilerOptions": {
"baseUrl": "./",
"paths": {
"~/*": ["app/*"]
"~/*": ["app/*"],
"@/*": ["app/*"]
}
},
"include": ["app/**/*"]

View file

@ -7,5 +7,6 @@ export default {
android: {
v8Flags: "--expose_gc",
markingMode: "none",
}
},
// profiling: 'timeline'
} as NativeScriptConfig

18800
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -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,21 @@
},
"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"
}
}