bug fixes, added localize, minor ui improvements

This commit is contained in:
vishnuraghavb 2020-12-07 20:15:00 +05:30
parent 77e022eb58
commit 62c25fcf22
3366 changed files with 18283 additions and 16865 deletions

View file

@ -25,7 +25,7 @@ EnRecipes is an easy to use, privacy-friendly digital cookbook that lets you cre
- Add photo, notes and combinations to your recipes
- Organise your recipes by category
- Quickly search for your recipes
- Mark recipes as favorites and add them to your Try Later list
- Mark recipes as favourites and add them to your Try Later list
- Scale your recipe ingredients to serve more or less people
- Get notified of the last time you tried a recipe
- Share your recipe to anyone by any means as a nicely formatted message. You can share the recipe photo too.

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">EnRecipes</string>
<string name="title_activity_kimera">EnRecipes</string>
</resources>

View file

@ -1,7 +1,6 @@
// NativeScript core theme
// @see https://docs.nativescript.org/ui/theme
@import "~@nativescript/theme/core";
// Override variables here
@import "~@nativescript/theme/core"; // Override variables here
$gray1: #f1f3f5;
$gray2: #e9ecef;
$gray3: #dee2e6;
@ -17,20 +16,17 @@ $red: #c92a2a;
$breakfast: #ffb180;
$lunch: #ceff80;
$dinner: #80ceff;
$snacks: #b180ff;
// Global SCSS styling
$snacks: #b180ff; // Global SCSS styling
// @see https://docs.nativescript.org/ui/styling
Page,
.ns-modal {
font-family: "Orkney-Regular";
.ns-modal,
Page {
font-family: 'Orkney-Regular';
}
.orkm {
font-family: "Orkney-Medium";
font-family: 'Orkney-Medium';
}
.bx {
font-family: "boxicons";
font-family: 'boxicons';
font-size: 24;
vertical-alignment: center;
&.small {
@ -39,38 +35,37 @@ Page,
}
}
.ns-light {
Page,
ActionBar,
ListPicker,
Page,
SearchBar,
Tabs,
TabStripItem,
ListPicker {
Tabs {
color: $gray9;
background: $gray1;
}
MDRipple,
MDButton {
MDButton,
MDRipple {
ripple-color: rgba($gray6, 0.2);
}
.hr {
border-color: $gray3;
}
.sd,
.fieldLabel {
.fieldLabel,
.sd {
background: $gray1;
}
.urlCard,
.textCard,
.overviewItem,
.recipeItem {
.recipeItem,
.textCard,
.urlCard {
background: white;
}
TextField.combinationToken {
background: $gray3;
}
.sd-item,
.sd-group-header,
.sd-item,
.time .bx {
color: $gray8;
}
@ -87,8 +82,8 @@ Page,
color: $gray1;
background: $gray9;
}
.instruction,
.dayContainer {
.dayContainer,
.instruction {
border-color: $gray9;
}
MDProgress {
@ -101,14 +96,13 @@ Page,
background: $orange;
}
}
.ns-dark {
Page,
ActionBar,
ListPicker,
Page,
SearchBar,
Tabs,
TabStripItem,
ListPicker {
Tabs {
color: $gray1;
background: $gray9;
}
@ -116,27 +110,26 @@ Page,
// tab-background-color: $gray9;
// selected-tab-text-color: $gray1;
// }
MDRipple,
MDButton {
MDButton,
MDRipple {
ripple-color: rgba($gray4, 0.1);
}
.hr {
border-color: #111;
}
.sd,
.fieldLabel {
.fieldLabel,
.sd {
background: $gray9;
}
.urlCard,
.textCard,
.overviewItem,
.recipeItem,
.textCard,
.urlCard,
TextField.combinationToken {
background: $gray8;
}
.sd-item,
.sd-group-header,
.sd-item,
.time .bx {
color: $gray3;
}
@ -153,8 +146,8 @@ Page,
color: $gray9;
background: $gray1;
}
.instruction,
.dayContainer {
.dayContainer,
.instruction {
border-color: $gray1;
}
MDProgress {
@ -176,7 +169,7 @@ TimePickerField {
border-width: 1;
font-size: 14;
padding: 14 14 13;
margin: 8 0 0 0;
margin: 8 0 0;
border-radius: 4;
border-color: $gray6;
placeholder-color: $gray6;
@ -184,8 +177,11 @@ TimePickerField {
TextView {
line-height: 12;
}
ListPicker {
width: 25%;
}
SearchBar {
font-family: "Orkney-Regular";
font-family: 'Orkney-Regular';
font-size: 16;
text-field-hint-color: $gray6;
}
@ -203,16 +199,15 @@ TabView {
.progressContainer {
width: 100%;
}
.text-btn,
.group-header,
.category,
.group-header,
.text-btn,
MDActivityIndicator {
color: $orange;
}
MDProgress {
progress-color: $orange;
}
// -----------------------------
// ActionBar
ActionBar {
@ -260,17 +255,20 @@ ActionBar {
padding: 2 16 0 0;
font-size: 16;
vertical-alignment: center;
&.bx{
&.bx {
padding: 0 0 0 16;
}
}
MDRipple {
padding: 0 16;
}
MDButton.bx {
margin: 0;
}
}
.sd-group-header {
width: 100%;
padding: 8;
padding: 0 0 8 8;
}
MDRipple {
border-radius: 4;
@ -313,7 +311,7 @@ MDButton {
font-size: 20;
text-align: center;
padding: 0;
margin: 0;
margin: 0 0 8;
horizontal-alignment: center;
.bx {
font-size: 24;
@ -356,8 +354,8 @@ RadListView {
border-radius: 4 0 0 4;
// prettier-ignore
Image {
border-radius: 4 0 0 4;
}
border-radius: 4 0 0 4;
}
}
}
.swipe-item {
@ -404,8 +402,8 @@ RadListView {
// VIEW RECIPE
.viewRecipe {
.category,
.time,
.ingredient {
.ingredient,
.time {
font-size: 16;
}
.category {
@ -413,14 +411,15 @@ RadListView {
}
.time {
margin: 0 8;
.bx {
vertical-align: top;
}
}
.title {
font-size: 22;
line-height: 6;
}
.subTitle {
font-size: 18;
line-height: 4;
}
.overviewContainer {
margin-top: 12;
.overviewItem {
@ -507,7 +506,7 @@ MDFloatingActionButton {
font-size: 14;
horizontal-alignment: left;
padding: 12;
margin: 8 0 0 0;
margin: 8 0 0;
min-width: 0;
}
MDButton.closeBtn {
@ -544,7 +543,7 @@ MDButton.closeBtn {
padding: 0 0 0 16;
}
.recipes {
margin: 4 8 4;
margin: 4 8;
.recipeTitle {
font-size: 14;
padding: 6 8;
@ -555,8 +554,8 @@ MDButton.closeBtn {
vertical-alignment: top;
}
}
MDRipple,
MDButton {
MDButton,
MDRipple {
ripple-color: rgba($gray6, 0.2);
}
}
@ -612,7 +611,6 @@ MDButton.closeBtn {
margin: 0 16 16;
}
}
// -----------------------------
MDActivityIndicator {
width: 24;
@ -647,7 +645,6 @@ MDActivityIndicator {
75% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
@ -656,7 +653,6 @@ MDActivityIndicator {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
opacity: 0;

View file

@ -1,95 +1,71 @@
<template>
<Page @loaded="onPageLoad">
<ActionBar :androidElevation="viewIsScrolled ? 4 : 0">
<GridLayout rows="*" columns="auto, *">
<MDButton
variant="text"
class="bx"
:text="icon.menu"
automationText="Back"
@tap="showDrawer"
col="0"
/>
<Label class="title orkm" text="About" col="1" />
</GridLayout>
</ActionBar>
<ScrollView @scroll="onScroll">
<StackLayout class="main-container">
<StackLayout
horizontalAlignment="center"
orientation="horizontal"
class="appIconContainer"
>
<Image src="res://logo" class="appIcon" stretch="aspectFit" />
</StackLayout>
<StackLayout class="m-8"></StackLayout>
<GridLayout columns="auto, *" class="option">
<Label col="0" class="bx" :text="icon.info" />
<StackLayout col="1">
<Label text="Version" />
<Label :text="getVersion" class="info" textWrap="true" />
</StackLayout>
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple
colSpan="2"
@tap="openURL('https://github.com/vishnuraghavb/enrecipes')"
/>
<Label col="0" class="bx" :text="icon.link" />
<Label
verticalAlignment="center"
col="1"
text="View project on GitHub"
/>
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="openURL('https://t.me/enrecipes')" />
<Label col="0" class="bx" :text="icon.telegram" />
<StackLayout col="1">
<Label text="Join the Telegram group" />
<Label
text="for reporting issues, suggestions and feedback"
class="info"
textWrap="true"
/>
</StackLayout>
</GridLayout>
<StackLayout class="hr m-10"></StackLayout>
<Label text="Author" class="group-header orkm" />
<GridLayout columns="auto, *" class="option">
<MDRipple
colSpan="2"
@tap="openURL('https://www.vishnuraghav.com')"
/>
<Label col="0" class="bx" :text="icon.user" />
<Label verticalAlignment="center" col="1" text="Vishnu Raghav B" />
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple
colSpan="2"
@tap="openURL('https://github.com/vishnuraghavb')"
/>
<Label col="0" class="bx" :text="icon.link" />
<Label verticalAlignment="center" col="1" text="Follow on GitHub" />
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple
colSpan="2"
@tap="openURL('https://mastodon.social/@vishnuraghavb')"
/>
<Label col="0" class="bx" :text="icon.link" />
<Label verticalAlignment="center" col="1" text="Follow on Mastodon" />
</GridLayout>
<Page @loaded="onPageLoad">
<ActionBar :androidElevation="viewIsScrolled ? 4 : 0">
<GridLayout rows="*" columns="auto, *">
<MDButton variant="text" class="bx" :text="icon.menu" automationText="Back" @tap="showDrawer" col="0" />
<Label class="title orkm" :text="'About' | L" col="1" />
</GridLayout>
</ActionBar>
<ScrollView @scroll="onScroll">
<StackLayout class="main-container">
<StackLayout horizontalAlignment="center" orientation="horizontal" class="appIconContainer">
<Image src="res://logo" class="appIcon" stretch="aspectFit" />
</StackLayout>
</ScrollView>
</Page>
<StackLayout class="m-8"></StackLayout>
<GridLayout columns="auto, *" class="option">
<Label col="0" class="bx" :text="icon.info" />
<StackLayout col="1">
<Label :text="'Version' | L" />
<Label :text="getVersion" class="info" textWrap="true" />
</StackLayout>
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="openURL('https://github.com/vishnuraghavb/enrecipes')" />
<Label col="0" class="bx" :text="icon.link" />
<Label verticalAlignment="center" col="1" :text="'View project on GitHub' | L" textWrap="true" />
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="openURL('https://t.me/enrecipes')" />
<Label col="0" class="bx" :text="icon.telegram" />
<StackLayout col="1">
<Label :text="'Join the Telegram group' | L" textWrap="true" />
<Label :text="'for reporting issues, suggestions and feedback' | L" class="info" textWrap="true" />
</StackLayout>
</GridLayout>
<StackLayout class="hr m-10"></StackLayout>
<Label :text="'Author' | L" class="group-header orkm" />
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="openURL('https://www.vishnuraghav.com')" />
<Label col="0" class="bx" :text="icon.user" />
<Label verticalAlignment="center" col="1" :text="'Vishnu Raghav B' | L" textWrap="true" />
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="openURL('https://github.com/vishnuraghavb')" />
<Label col="0" class="bx" :text="icon.link" />
<Label verticalAlignment="center" col="1" :text="'Follow on GitHub' | L" textWrap="true" />
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="openURL('https://mastodon.social/@vishnuraghavb')" />
<Label col="0" class="bx" :text="icon.link" />
<Label verticalAlignment="center" col="1" :text="'Follow on Mastodon' | L" textWrap="true" />
</GridLayout>
</StackLayout>
</ScrollView>
</Page>
</template>
<script>
import { Application, Utils } from "@nativescript/core"
import { mapActions, mapState } from "vuex"
import {
Application,
Utils,
Observable
} from "@nativescript/core"
import {
mapActions,
mapState
} from "vuex"
import * as utils from "~/shared/utils"
@ -110,7 +86,9 @@ export default {
},
methods: {
...mapActions(["setCurrentComponentAction"]),
onPageLoad() {
onPageLoad(args) {
const page = args.object;
page.bindingContext = new Observable();
this.setCurrentComponentAction("About")
},
// HELPERS

View file

@ -1,141 +1,60 @@
<template>
<Page
@loaded="onPageLoad"
actionBarHidden="true"
:androidStatusBarBackground="appTheme == 'Light' ? '#f1f3f5' : '#212529'"
>
<RadSideDrawer
allowEdgeSwipe="true"
showOverNavigation="true"
ref="drawer"
id="sideDrawer"
drawerContentSize="270"
gesturesEnabled="true"
drawerTransition="SlideInOnTopTransition"
>
<GridLayout rows="*, auto" columns="*" ~drawerContent class="sd">
<StackLayout row="0">
<GridLayout
rows="48"
columns="auto, 24, *"
v-for="(item, index) in topmenu"
:key="index"
class="sd-item orkm"
:class="{
'selected-sd-item': currentComponent === item.component,
}"
>
<MDRipple
row="0"
colSpan="3"
@tap="navigateTo(item.component, false, false)"
/>
<Label col="0" row="0" class="bx" :text="icon[item.icon]" />
<Label col="2" row="0" :text="item.title" />
</GridLayout>
<StackLayout class="hr m-8"></StackLayout>
<GridLayout
rows="48"
columns="auto, 24, *"
class="sd-item orkm"
:class="{
'selected-sd-item': currentComponent === 'MealPlanner',
}"
>
<MDRipple
row="0"
colSpan="3"
@tap="navigateTo(MealPlanner, true, false)"
/>
<Page @loaded="onPageLoad" actionBarHidden="true" :androidStatusBarBackground="appTheme == 'Light' ? '#f1f3f5' : '#212529'">
<Label col="0" row="0" class="bx" :text="icon.calendar" />
<Label col="2" row="0" text="Meal Planner" />
</GridLayout>
<StackLayout class="hr m-8"></StackLayout>
<GridLayout
class="sd-group-header orkm"
rows="auto"
columns="*, auto"
v-if="categoriesWithRecipes.length"
>
<Label verticalAlignment="center" col="0" text="Categories" />
<MDButton
variant="text"
@tap="toggleCatEdit"
col="2"
:text="editCategory ? 'DONE' : 'RENAME'"
/>
</GridLayout>
<ScrollView height="100%">
<StackLayout>
<GridLayout
v-for="(item, index) in categoriesWithRecipes"
:key="index"
class="sd-item orkm"
:class="{
<RadSideDrawer allowEdgeSwipe="true" showOverNavigation="true" ref="drawer" id="sideDrawer" drawerContentSize="270" gesturesEnabled="true" drawerTransition="SlideInOnTopTransition">
<GridLayout rows="*, auto" columns="*" ~drawerContent class="sd">
<StackLayout row="0">
<GridLayout rows="48" columns="auto, 24, *" v-for="(item, index) in topmenu" :key="index" class="sd-item orkm" :class="{
'selected-sd-item': currentComponent === item.component,
}">
<MDRipple row="0" colSpan="3" @tap="navigateTo(item.component, item.component, false, false)" />
<Label col="0" row="0" class="bx" :text="icon[item.icon]" />
<Label col="2" row="0" :text="`${item.title}` | L" />
</GridLayout>
<StackLayout class="hr" margin="8"></StackLayout>
<GridLayout rows="48" columns="auto, 24, *" class="sd-item orkm" :class="{
'selected-sd-item': currentComponent === 'MealPlanner',
}">
<MDRipple row="0" colSpan="3" @tap="navigateTo(MealPlanner, 'MealPlanner', true, false)" />
<Label col="0" row="0" class="bx" :text="icon.calendar" />
<Label col="2" row="0" :text="'Meal Planner' | L" />
</GridLayout>
<StackLayout class="hr" margin="8"></StackLayout>
<GridLayout class="sd-group-header orkm" rows="auto" columns="*, auto" v-if="categoriesWithRecipes.length">
<Label verticalAlignment="center" col="0" :text="'Categories' | L" />
<MDButton variant="text" @tap="toggleCatEdit" col="2" :text="`${editCategory ? 'DONE' : 'RENAME'}` | L" />
</GridLayout>
<ScrollView height="100%">
<StackLayout>
<GridLayout v-for="(item, index) in categoriesWithRecipes" :key="index" class="sd-item orkm" :class="{
'selected-sd-item': currentComponent == item,
}"
columns="auto, *, auto"
>
<MDRipple
row="0"
colSpan="3"
@tap="navigateTo(item, false, true)"
/>
<Label
col="0"
class="bx"
:text="icon.label"
margin="0 24 0 0"
/>
<Label col="1" :text="item" />
<MDButton
variant="text"
v-if="editCategory"
@tap="renameCategory(item)"
col="2"
class="bx"
:text="icon.edit"
/>
</GridLayout>
</StackLayout>
</ScrollView>
</StackLayout>
<StackLayout row="1">
<StackLayout class="hr m-10"></StackLayout>
<GridLayout
class="sd-item orkm"
:class="{
}" columns="auto, *, auto">
<MDRipple row="0" colSpan="3" @tap="navigateTo(item, item, false, true)" />
<Label col="0" class="bx" :text="icon.label" margin="0 24 0 0" />
<Label col="1" :text="`${item}` | L" />
<MDButton variant="text" v-if="editCategory" @tap="renameCategory(item)" col="2" class="bx" :text="icon.edit" />
</GridLayout>
</StackLayout>
</ScrollView>
</StackLayout>
<StackLayout row="1">
<StackLayout class="hr" margin="0 8 8"></StackLayout>
<GridLayout class="sd-item orkm" :class="{
'selected-sd-item': currentComponent == item.title,
}"
v-for="(item, index) in bottommenu"
:key="index"
rows="48"
columns="auto, 24, *"
>
<MDRipple
colSpan="3"
@tap="navigateTo(item.component, true, false)"
/>
<Label class="bx" col="0" :text="icon[item.icon]" />
<Label col="2" :text="item.title" />
</GridLayout>
</StackLayout>
</GridLayout>
<Frame ~mainContent id="main-frame">
<!-- Home -->
<EnRecipes
ref="enrecipes"
:filterFavorites="filterFavorites"
:filterTrylater="filterTrylater"
:selectedCategory="selectedCategory"
:closeDrawer="closeDrawer"
:hijackGlobalBackEvent="hijackGlobalBackEvent"
:releaseGlobalBackEvent="releaseGlobalBackEvent"
/>
</Frame>
</RadSideDrawer>
</Page>
}" v-for="(item, index) in bottommenu" :key="index" rows="48" columns="auto, 24, *">
<MDRipple colSpan="3" @tap="navigateTo(item.component, 'item.title', true, false)" />
<Label class="bx" col="0" :text="icon[item.icon]" />
<Label col="2" :text="`${item.title}` | L" />
</GridLayout>
</StackLayout>
</GridLayout>
<Frame ~mainContent id="main-frame">
<EnRecipes ref="enrecipes" :filterFavourites="filterFavourites" :filterTrylater="filterTrylater" :selectedCategory="selectedCategory" :closeDrawer="closeDrawer" :hijackGlobalBackEvent="hijackGlobalBackEvent"
:releaseGlobalBackEvent="releaseGlobalBackEvent" />
</Frame>
</RadSideDrawer>
</Page>
</template>
<script>
@ -143,11 +62,17 @@ import {
ApplicationSettings,
AndroidApplication,
Application,
} from "@nativescript/core"
Device
}
from "@nativescript/core"
import Theme from "@nativescript/theme"
import * as Toast from "nativescript-toast"
import * as application from "tns-core-modules/application"
import { mapActions, mapState } from "vuex"
import {
mapActions,
mapState
}
from "vuex"
import EnRecipes from "./EnRecipes"
import MealPlanner from "./MealPlanner"
@ -160,11 +85,10 @@ export default {
data() {
return {
selectedCategory: null,
filterFavorites: false,
filterFavourites: false,
filterTrylater: false,
MealPlanner: MealPlanner,
topmenu: [
{
topmenu: [ {
title: "EnRecipes",
component: "EnRecipes",
icon: "home",
@ -175,13 +99,12 @@ export default {
icon: "trylater",
},
{
title: "Favorites",
component: "Favorites",
title: "Favourites",
component: "Favourites",
icon: "heart",
},
],
bottommenu: [
{
bottommenu: [ {
title: "Settings",
component: Settings,
icon: "cog",
@ -201,68 +124,68 @@ export default {
MealPlanner,
},
computed: {
...mapState([
...mapState( [
"icon",
"recipes",
"categories",
"yieldUnits",
"mealPlans",
"currentComponent",
]),
] ),
categoriesWithRecipes() {
let arr = this.recipes.map((e) => {
let arr = this.recipes.map( ( e ) => {
return e.category
})
return [...new Set(arr)]
} )
return [ ...new Set( arr ) ]
},
},
methods: {
...mapActions([
...mapActions( [
"setCurrentComponentAction",
"initializeRecipes",
"initializeCategories",
"initializeYieldUnits",
"initializeMealPlans",
"renameCategoryAction",
]),
] ),
onPageLoad() {
if (this.appTheme === "Light") {
if ( this.appTheme === "Light" ) {
const View = android.view.View
const window = Application.android.startActivity.getWindow()
const decorView = window.getDecorView()
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR )
}
},
// HELPERS
toggleCatEdit() {
this.editCategory = !this.editCategory
if (this.selectedCategory) this.setComponent("EnRecipes")
this.filterFavorites = this.filterTrylater = false
if ( this.selectedCategory ) this.setCurrentComponentAction( "EnRecipes" )
this.filterFavourites = this.filterTrylater = false
this.selectedCategory = null
this.$refs.enrecipes.updateFilter()
},
setComponent(comp) {
this.setCurrentComponentAction(comp)
},
renameCategory(category) {
renameCategory( category ) {
this.releaseGlobalBackEvent()
this.$showModal(PromptDialog, {
this.$showModal( PromptDialog, {
props: {
title: `Rename category`,
text: category,
action: "RENAME",
},
}).then((newCategory) => {
} ).then( ( newCategory ) => {
this.hijackGlobalBackEvent()
if (newCategory.length) {
this.renameCategoryAction({ current: category, updated: newCategory })
if ( newCategory.length ) {
this.renameCategoryAction( {
current: category,
updated: newCategory
} )
this.editCategory = false
this.navigateTo(newCategory, false, true)
this.navigateTo( newCategory, newCategory, false, true )
}
})
} )
},
setSelectedCategory(e) {
setSelectedCategory( e ) {
this.selectedCategory = e.item
this.closeDrawer()
},
@ -284,67 +207,68 @@ export default {
this.globalBackEvent
)
},
globalBackEvent(args) {
globalBackEvent( args ) {
function preventDefault() {
args.cancel = true
}
let vm = this
function isFiltered() {
vm.filterFavorites
? vm.setComponent("Favorites")
: vm.filterTrylater
? vm.setComponent("Try Later")
: vm.selectedCategory
? vm.setComponent(vm.selectedCategory)
: vm.setComponent("EnRecipes")
}
if (this.$refs.drawer && this.$refs.drawer.nativeView.getIsOpen()) {
if ( this.$refs.drawer && this.$refs.drawer.nativeView.getIsOpen() ) {
preventDefault()
this.closeDrawer()
this.editCategory = false
} else if (
["Favorites", "Try Later", this.selectedCategory].includes(
}
else if (
[ "Favourites", "Try Later", this.selectedCategory ].includes(
this.currentComponent
)
) {
preventDefault()
this.setComponent("EnRecipes")
this.filterFavorites = this.filterTrylater = false
this.setCurrentComponentAction( "EnRecipes" )
this.filterFavourites = this.filterTrylater = false
this.selectedCategory = null
this.$refs.enrecipes.updateFilter()
this.releaseGlobalBackEvent()
}
},
navigateTo(to, isTrueComponent, isCategory) {
if (isTrueComponent) {
this.$navigateTo(to, {
frame: "main-frame",
backstackVisible: false,
})
this.closeDrawer()
} else if (!this.editCategory || !isCategory) {
this.releaseGlobalBackEvent()
this.hijackGlobalBackEvent()
this.setComponent(to)
this.$navigateBack({ frame: "main-frame", backstackVisible: false })
this.filterFavorites = to === "Favorites" ? true : false
this.filterTrylater = to === "Try Later" ? true : false
this.selectedCategory = isCategory ? to : null
this.$refs.enrecipes.updateFilter()
navigateTo( to, title, isTrueComponent, isCategory ) {
if ( title !== this.currentComponent ) {
if ( isTrueComponent ) {
this.$navigateTo( to, {
frame: "main-frame",
backstackVisible: false
} )
this.closeDrawer()
}
else if ( !this.editCategory || !isCategory ) {
this.releaseGlobalBackEvent()
this.hijackGlobalBackEvent()
this.setCurrentComponentAction( to )
this.$navigateBack( {
frame: "main-frame",
backstackVisible: false
} )
this.filterFavourites = to === "Favourites" ? true : false
this.filterTrylater = to === "Try Later" ? true : false
this.selectedCategory = isCategory ? to : null
this.$refs.enrecipes.updateFilter()
this.closeDrawer()
}
this.editCategory = false
}
else {
this.closeDrawer()
}
this.editCategory = false
},
},
created() {
this.appTheme = ApplicationSettings.getString("appTheme", "Light")
setTimeout((e) => {
Theme.setMode(Theme[this.appTheme])
}, 10)
if (!this.recipes.length) this.initializeRecipes()
if (!this.categories.length) this.initializeCategories()
if (!this.yieldUnits.length) this.initializeYieldUnits()
if (!this.mealPlans.length) this.initializeMealPlans()
this.appTheme = ApplicationSettings.getString( "appTheme", "Light" )
setTimeout( ( e ) => {
Theme.setMode( Theme[ this.appTheme ] )
}, 10 )
if ( !this.recipes.length ) this.initializeRecipes()
if ( !this.categories.length ) this.initializeCategories()
if ( !this.yieldUnits.length ) this.initializeYieldUnits()
if ( !this.mealPlans.length ) this.initializeMealPlans()
console.log( Device.language );
},
}
</script>

File diff suppressed because it is too large Load diff

View file

@ -1,246 +1,123 @@
<template>
<Page @loaded="onPageLoad">
<ActionBar :androidElevation="viewIsScrolled ? 4 : 0">
<!-- Search Actionbar -->
<GridLayout
v-if="showSearch"
columns="auto, *"
verticalAlignment="center"
>
<MDButton
class="bx"
:text="icon.back"
variant="text"
automationText="Back"
col="0"
@tap="closeSearch"
/>
<SearchBar
col="1"
hint="Search"
v-model="searchQuery"
@textChange="updateFilter"
@clear="clearSearch"
/>
</GridLayout>
<!-- Home Actionbar -->
<GridLayout v-else columns="auto, *, auto, auto">
<MDButton
class="bx"
col="0"
variant="text"
@tap="showDrawer"
:text="icon.menu"
automationText="Back"
/>
<Label class="title orkm" :text="currentComponent" col="1" />
<MDButton
v-if="recipes.length"
class="bx"
:text="icon.search"
variant="text"
col="2"
@tap="openSearch"
/>
<MDButton
v-if="recipes.length"
class="bx"
:text="icon.sort"
variant="text"
col="3"
@tap="sortDialog"
/>
</GridLayout>
</ActionBar>
<AbsoluteLayout>
<RadListView
ref="listView"
itemHeight="112"
for="recipe in recipes"
swipeActions="true"
@itemSwipeProgressChanged="onSwiping"
@itemSwipeProgressEnded="onSwipeEnded"
@scrolled="onScroll"
:filteringFunction="filterFunction"
:sortingFunction="sortFunction"
>
<v-template>
<GridLayout
class="recipeItem"
rows="112"
columns="112, *"
androidElevation="1"
>
<MDRipple colSpan="2" @tap="viewRecipe(recipe.id)" />
<GridLayout class="imageHolder card" rows="112" columns="112">
<Image
row="0"
col="0"
v-if="recipe.imageSrc"
:src="recipe.imageSrc"
stretch="aspectFill"
decodeWidth="112"
decodeHeight="112"
loadMode="async"
/>
<Label
v-else
row="0"
col="0"
horizontalAlignment="center"
class="bx"
fontSize="56"
:text="icon.image"
/>
</GridLayout>
<StackLayout class="recipeInfo" col="1">
<Label :text="recipe.category" class="orkm category" />
<Label :text="recipe.title" class="orkm title" />
<StackLayout class="timeContainer" orientation="horizontal">
<Label class="bx small" :text="icon.time" />
<Label
class="time"
:text="
<Page @loaded="onPageLoad" @unloaded="onPageUnload">
<ActionBar :androidElevation="viewIsScrolled ? 4 : 0">
<GridLayout v-if="showSearch" columns="auto, *" verticalAlignment="center">
<MDButton class="bx" :text="icon.back" variant="text" automationText="Back" col="0" @tap="closeSearch" />
<SearchBar col="1" :hint="'Search' | L" v-model="searchQuery" @textChange="updateFilter" @clear="clearSearch" />
</GridLayout>
<GridLayout v-else columns="auto, *, auto, auto">
<MDButton class="bx" col="0" variant="text" @tap="showDrawer" :text="icon.menu" automationText="Back" />
<Label class="title orkm" :text="`${currentComponent}` | L" col="1" />
<MDButton v-if="recipes.length" class="bx" :text="icon.search" variant="text" col="2" @tap="openSearch" />
<MDButton v-if="recipes.length" class="bx" :text="icon.sort" variant="text" col="3" @tap="sortDialog" />
</GridLayout>
</ActionBar>
<AbsoluteLayout>
<RadListView ref="listView" itemHeight="112" for="recipe in recipes" swipeActions="true" @itemSwipeProgressChanged="onSwiping" @itemSwipeProgressEnded="onSwipeEnded" @scrolled="onScroll" :filteringFunction="filterFunction"
:sortingFunction="sortFunction">
<v-template>
<GridLayout class="recipeItem" rows="112" columns="112, *" androidElevation="1">
<MDRipple colSpan="2" @tap="viewRecipe(recipe.id)" />
<GridLayout class="imageHolder card" rows="112" columns="112">
<Image row="0" col="0" v-if="recipe.imageSrc" :src="recipe.imageSrc" stretch="aspectFill" decodeWidth="112" decodeHeight="112" loadMode="async" />
<Label v-else row="0" col="0" horizontalAlignment="center" class="bx" fontSize="56" :text="icon.image" />
</GridLayout>
<StackLayout class="recipeInfo" col="1">
<Label :text="`${recipe.category}` | L" class="orkm category" />
<Label :text="recipe.title" class="orkm title" />
<StackLayout class="timeContainer" orientation="horizontal">
<Label class="bx small" :text="icon.time" />
<Label class="time" :text="
`${
formattedTotalTime(recipe.prepTime, recipe.cookTime).time
}`
"
/>
</StackLayout>
" />
</StackLayout>
</GridLayout>
</v-template>
<v-template name="itemswipe">
<GridLayout columns="*, auto">
<StackLayout id="delete-action" col="1" class="swipe-item right">
<Label class="bx" padding="8 0 0 6" :text="icon.trash" />
</StackLayout>
</GridLayout>
</v-template>
<v-template name="footer">
<StackLayout height="80"></StackLayout>
</v-template>
</RadListView>
<GridLayout rows="*, auto, *, 88" columns="*" class="emptyStateContainer">
<StackLayout
row="1"
class="emptyState"
v-if="
</StackLayout>
</GridLayout>
</v-template>
<v-template name="itemswipe">
<GridLayout columns="*, auto">
<StackLayout id="delete-action" col="1" class="swipe-item right">
<Label class="bx" padding="8 0 0 6" :text="icon.trash" />
</StackLayout>
</GridLayout>
</v-template>
<v-template name="footer">
<StackLayout height="80"></StackLayout>
</v-template>
</RadListView>
<GridLayout rows="*, auto, *, 88" columns="*" class="emptyStateContainer">
<StackLayout row="1" class="emptyState" v-if="
!recipes.length &&
!filterFavorites &&
!filterFavourites &&
!filterTrylater &&
!selectedCategory
"
@tap="addRecipe"
>
<Label class="bx icon" :text="icon.plusCircle" />
<Label
class="title orkm"
text="Start adding your recipes!"
textWrap="true"
/>
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Label text="Use the " />
<Label class="bx" :text="icon.plus" />
<Label text=" button to add one" />
</StackLayout>
" @tap="addRecipe">
<Label class="bx icon" :text="icon.plusCircle" />
<Label class="title orkm" :text="'Start adding your recipes!' | L" textWrap="true" />
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Label :text="'Use the plus button to add one' | L" textWrap="true" />
</StackLayout>
<StackLayout
row="1"
class="emptyState"
v-if="!filteredRecipes.length && filterTrylater && !searchQuery"
>
<Label class="bx icon" :text="icon.trylaterOutline" textWrap="true" />
<Label class="title orkm" text="All done!" textWrap="true" />
<Label
text="Recipes you mark as try later will be listed here"
textWrap="true"
/>
</StackLayout>
<StackLayout row="1" class="emptyState" v-if="!filteredRecipes.length && filterTrylater && !searchQuery">
<Label class="bx icon" :text="icon.trylaterOutline" textWrap="true" />
<Label class="title orkm" :text="'All done!' | L" textWrap="true" />
<Label :text="'Recipes you mark as try later will be listed here' | L" textWrap="true" />
</StackLayout>
<StackLayout row="1" class="emptyState" v-if="!filteredRecipes.length && filterFavourites && !searchQuery">
<Label class="bx icon" :text="icon.heartOutline" textWrap="true" />
<Label class="title orkm" :text="'No favourites yet' | L" textWrap="true" />
<Label :text="'Recipes you mark as favourite will be listed here' | L" textWrap="true" />
</StackLayout>
<StackLayout row="1" class="emptyState" v-if="!filteredRecipes.length && !searchQuery && selectedCategory">
<Label class="bx icon" :text="icon.labelOutline" textWrap="true" />
<Label class="title orkm" :text="'Category looks empty' | L" textWrap="true" />
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Label :text="'Use the plus button to add one' | L" textWrap="true" />
</StackLayout>
<StackLayout
row="1"
class="emptyState"
v-if="!filteredRecipes.length && filterFavorites && !searchQuery"
>
<Label class="bx icon" :text="icon.heartOutline" textWrap="true" />
<Label class="title orkm" text="No favorites yet" textWrap="true" />
<Label
text="Recipes you mark as favorite will be listed here"
textWrap="true"
/>
</StackLayout>
<StackLayout
row="1"
class="emptyState"
v-if="!filteredRecipes.length && !searchQuery && selectedCategory"
>
<Label class="bx icon" :text="icon.labelOutline" textWrap="true" />
<Label
class="title orkm"
text="Category looks empty"
textWrap="true"
/>
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Label text="Use the " textWrap="true" />
<Label class="bx" :text="icon.plus" />
<Label text=" button to add a recipe" textWrap="true" />
</StackLayout>
</StackLayout>
<StackLayout
row="1"
class="emptyState"
v-if="!filteredRecipes.length && searchQuery"
>
<Label class="bx icon" :text="icon.search" textWrap="true" />
<Label class="title orkm" text="No recipes found" textWrap="true" />
<Label
:text="
`Your search &quot;${searchQuery}&quot; did not match any recipes${noResultFor}`
"
textWrap="true"
/>
</StackLayout>
</GridLayout>
<GridLayout id="btnFabContainer" rows="*, auto" columns="*, auto">
<transition name="bounce">
<MDFloatingActionButton
v-if="showFAB"
row="1"
col="1"
class="bx fab-button"
src="res://plus"
@tap="addRecipe"
/>
</transition>
</GridLayout>
</AbsoluteLayout>
</Page>
</StackLayout>
<StackLayout row="1" class="emptyState" v-if="!filteredRecipes.length && searchQuery">
<Label class="bx icon" :text="icon.search" textWrap="true" />
<Label class="title orkm" :text="'No recipes found' | L" textWrap="true" />
<Label :text="`${noResultFor}` | L" textWrap="true" />
</StackLayout>
</GridLayout>
<GridLayout id="btnFabContainer" rows="*, auto" columns="*, auto">
<transition name="bounce">
<MDFloatingActionButton v-if="showFAB" row="1" col="1" class="bx fab-button" src="res://plus" @tap="addRecipe" />
</transition>
</GridLayout>
</AbsoluteLayout>
</Page>
</template>
<script>
import { AndroidApplication, Utils } from "@nativescript/core"
import { mapActions, mapState } from "vuex"
import EditRecipe from "./EditRecipe.vue"
import ViewRecipe from "./ViewRecipe.vue"
import ActionDialog from "./modal/ActionDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue"
import * as utils from "~/shared/utils"
import {
AndroidApplication,
Utils,
Observable
}
from "@nativescript/core";
import {
localize
}
from "@nativescript/localize"
import {
mapActions,
mapState
}
from "vuex";
import EditRecipe from "./EditRecipe.vue";
import ViewRecipe from "./ViewRecipe.vue";
import ActionDialog from "./modal/ActionDialog.vue";
import ConfirmDialog from "./modal/ConfirmDialog.vue";
import * as utils from "~/shared/utils";
export default {
props: [
"filterFavorites",
"filterTrylater",
"closeDrawer",
"selectedCategory",
"hijackGlobalBackEvent",
"releaseGlobalBackEvent",
],
props: [ "filterFavourites", "filterTrylater", "closeDrawer", "selectedCategory", "hijackGlobalBackEvent", "releaseGlobalBackEvent" ],
components: {
EditRecipe,
ViewRecipe,
ViewRecipe
},
data() {
return {
@ -250,245 +127,228 @@ export default {
rightAction: false,
sortType: "Natural order",
deletionDialogActive: false,
showFAB: false,
}
showFAB: false
};
},
computed: {
...mapState(["icon", "recipes", "currentComponent"]),
...mapState( [ "icon", "recipes", "currentComponent" ] ),
filteredRecipes() {
if (this.filterFavorites) {
return this.recipes.filter(
(e) =>
e.isFavorite && e.title.toLowerCase().includes(this.searchQuery)
)
} else if (this.filterTrylater) {
return this.recipes.filter(
(e) => !e.tried && e.title.toLowerCase().includes(this.searchQuery)
)
} else if (this.selectedCategory) {
return this.recipes.filter(
(e) =>
e.category === this.selectedCategory &&
e.title.toLowerCase().includes(this.searchQuery)
)
} else {
return this.recipes.filter((e) =>
e.title.toLowerCase().includes(this.searchQuery)
)
if ( this.filterFavourites ) {
return this.recipes.filter( e => e.isFavorite && e.title.toLowerCase().includes( this.searchQuery ) );
}
else if ( this.filterTrylater ) {
return this.recipes.filter( e => !e.tried && e.title.toLowerCase().includes( this.searchQuery ) );
}
else if ( this.selectedCategory ) {
return this.recipes.filter( e => e.category === this.selectedCategory && e.title.toLowerCase().includes( this.searchQuery ) );
}
else {
return this.recipes.filter( e => e.title.toLowerCase().includes( this.searchQuery ) );
}
},
noResultFor() {
if (this.selectedCategory) return " in this category"
if (this.filterFavorites) return " in your favorites"
if (this.filterTrylater) return " in your try later list"
return ""
},
if ( this.selectedCategory ) return "Your search did not match any recipes in this category";
if ( this.filterFavourites ) return "Your search did not match any recipes in your favourites";
if ( this.filterTrylater ) return "Your search did not match any recipes in your try later list";
return "Your search did not match any recipes";
}
},
methods: {
...mapActions(["setCurrentComponentAction", "deleteRecipeAction"]),
onPageLoad() {
this.filterFavorites
? this.setComponent("Favorites")
: this.filterTrylater
? this.setComponent("Try Later")
: this.selectedCategory
? this.setComponent(this.selectedCategory)
: this.setComponent("EnRecipes")
this.showFAB = true
...mapActions( [ "setCurrentComponentAction", "deleteRecipeAction" ] ),
onPageLoad( args ) {
const page = args.object;
page.bindingContext = new Observable();
this.filterFavourites ? this.setComponent( "Favourites" ) : this.filterTrylater ? this.setComponent( "Try Later" ) : this.selectedCategory ? this.setComponent( this.selectedCategory ) : this.setComponent( "EnRecipes" );
this.showFAB = true;
},
onPageUnload() {
this.releaseGlobalBackEvent();
},
// HELPERS
showDrawer() {
utils.showDrawer()
utils.showDrawer();
},
openSearch() {
this.showSearch = true
this.showFAB = false
this.hijackLocalBackEvent()
this.showSearch = true;
this.showFAB = false;
this.hijackLocalBackEvent();
},
closeSearch() {
if (this.searchQuery) this.updateFilter()
this.searchQuery = ""
Utils.ad.dismissSoftInput()
this.showSearch = false
this.showFAB = true
this.releaseLocalBackEvent()
if ( this.searchQuery ) this.updateFilter();
this.searchQuery = "";
Utils.ad.dismissSoftInput();
this.showSearch = false;
this.showFAB = true;
this.releaseLocalBackEvent();
},
setComponent(comp) {
this.setCurrentComponentAction(comp)
this.hijackGlobalBackEvent()
setComponent( comp ) {
this.setCurrentComponentAction( comp );
this.hijackGlobalBackEvent();
},
clearSearch() {
if (this.searchQuery !== "") {
this.updateFilter()
if ( this.searchQuery !== "" ) {
this.updateFilter();
}
},
formattedTotalTime(prepTime, cookTime) {
let t1 = prepTime.split(":")
let t2 = cookTime.split(":")
let h = parseInt(t1[0]) + parseInt(t2[0])
let m = parseInt(t1[1]) + parseInt(t2[1])
formattedTotalTime( prepTime, cookTime ) {
let t1 = prepTime.split( ":" );
let t2 = cookTime.split( ":" );
let h = parseInt( t1[ 0 ] ) + parseInt( t2[ 0 ] );
let m = parseInt( t1[ 1 ] ) + parseInt( t2[ 1 ] );
let hr = localize( 'hr' )
let min = localize( 'min' )
return {
time: h ? (m ? `${h} hr ${m} min` : `${h} hr`) : `${m} min`,
duration: `${h}${m}`,
}
time: h ? ( m ? `${h} ${hr} ${m} ${min}` : `${h} ${hr}` ) : `${m} ${min}`,
duration: `${h}${m}`
};
},
onScroll(args) {
this.viewIsScrolled = args.scrollOffset ? true : false
onScroll( args ) {
this.viewIsScrolled = args.scrollOffset ? true : false;
},
// NAVIGATION HANDLERS
hijackLocalBackEvent() {
this.releaseGlobalBackEvent()
AndroidApplication.on(
AndroidApplication.activityBackPressedEvent,
this.searchBackEvent
)
this.releaseGlobalBackEvent();
AndroidApplication.on( AndroidApplication.activityBackPressedEvent, this.searchBackEvent );
},
releaseLocalBackEvent() {
AndroidApplication.off(
AndroidApplication.activityBackPressedEvent,
this.searchBackEvent
)
this.hijackGlobalBackEvent()
AndroidApplication.off( AndroidApplication.activityBackPressedEvent, this.searchBackEvent );
this.hijackGlobalBackEvent();
},
searchBackEvent(args) {
args.cancel = true
this.closeDrawer()
this.closeSearch()
searchBackEvent( args ) {
args.cancel = true;
this.closeDrawer();
this.closeSearch();
},
addRecipe() {
this.showFAB = false
this.releaseGlobalBackEvent()
this.$navigateTo(EditRecipe, {
this.showFAB = false;
this.releaseGlobalBackEvent();
this.$navigateTo( EditRecipe, {
props: {
selectedCategory: this.selectedCategory,
filterFavorites: this.filterFavorites,
},
})
filterFavourites: this.filterFavourites
}
} );
},
viewRecipe(recipeID) {
this.showFAB = false
this.$navigateTo(ViewRecipe, {
viewRecipe( recipeID ) {
this.showFAB = false;
this.$navigateTo( ViewRecipe, {
props: {
filterTrylater: this.filterTrylater,
recipeID,
recipeID
},
backstackVisible: false,
})
backstackVisible: false
} );
},
// LIST HANDLERS
sortDialog() {
this.releaseGlobalBackEvent()
this.$showModal(ActionDialog, {
this.releaseGlobalBackEvent();
this.$showModal( ActionDialog, {
props: {
title: "Sort by",
list: ["Natural order", "Title", "Duration", "Last modified"],
height: "192",
},
}).then((action) => {
if (action && action !== "Cancel" && this.sortType !== action) {
this.sortType = action
this.updateSort()
list: [ "Natural order", "Title", "Duration", "Last modified" ],
height: "192"
}
this.hijackGlobalBackEvent()
})
} ).then( action => {
if ( action && action !== "Cancel" && this.sortType !== action ) {
this.sortType = action;
this.updateSort();
}
this.hijackGlobalBackEvent();
} );
},
updateSort() {
let listView = this.$refs.listView.nativeView
listView.sortingFunction = undefined
listView.sortingFunction = this.sortFunction
let listView = this.$refs.listView.nativeView;
listView.sortingFunction = undefined;
listView.sortingFunction = this.sortFunction;
},
sortFunction(item, otherItem) {
const titleOrder = item.title
.toLowerCase()
.localeCompare(otherItem.title.toLowerCase(), "en", {
ignorePunctuation: true,
})
let d1 = this.formattedTotalTime(item.prepTime, item.cookTime).duration
let d2 = this.formattedTotalTime(otherItem.prepTime, otherItem.cookTime)
.duration
let ld1 = new Date(item.lastModified)
let ld2 = new Date(otherItem.lastModified)
switch (this.sortType) {
sortFunction( item, otherItem ) {
const titleOrder = item.title.toLowerCase().localeCompare( otherItem.title.toLowerCase(), "en", {
ignorePunctuation: true
} );
let d1 = this.formattedTotalTime( item.prepTime, item.cookTime ).duration;
let d2 = this.formattedTotalTime( otherItem.prepTime, otherItem.cookTime ).duration;
let ld1 = new Date( item.lastModified );
let ld2 = new Date( otherItem.lastModified );
switch ( this.sortType ) {
case "Title":
return titleOrder > 0 ? -1 : titleOrder < 0 ? 1 : 0
break
return titleOrder > 0 ? -1 : titleOrder < 0 ? 1 : 0;
break;
case "Duration":
return d1 > d2 ? -1 : d1 < d2 ? 1 : 0
break
return d1 > d2 ? -1 : d1 < d2 ? 1 : 0;
break;
case "Last modified":
return ld1 < ld2 ? -1 : ld1 > ld2 ? 1 : 0
break
return ld1 < ld2 ? -1 : ld1 > ld2 ? 1 : 0;
break;
default:
return 0
break
return 0;
break;
}
},
updateFilter() {
let listView = this.$refs.listView.nativeView
setTimeout((e) => {
listView.filteringFunction = undefined
listView.filteringFunction = this.filterFunction
}, 1)
let listView = this.$refs.listView.nativeView;
setTimeout( e => {
listView.filteringFunction = undefined;
listView.filteringFunction = this.filterFunction;
}, 1 );
},
filterFunction(item) {
if (this.filterFavorites) {
return item.isFavorite
? item.title.toLowerCase().includes(this.searchQuery)
: false
} else if (this.filterTrylater) {
return item.tried
? false
: item.title.toLowerCase().includes(this.searchQuery)
} else if (this.selectedCategory) {
return item.category === this.selectedCategory
? item.title.toLowerCase().includes(this.searchQuery)
: false
} else {
return item.title.toLowerCase().includes(this.searchQuery)
filterFunction( item ) {
if ( this.filterFavourites ) {
return item.isFavorite ? item.title.toLowerCase().includes( this.searchQuery ) : false;
}
else if ( this.filterTrylater ) {
return item.tried ? false : item.title.toLowerCase().includes( this.searchQuery );
}
else if ( this.selectedCategory ) {
return item.category === this.selectedCategory ? item.title.toLowerCase().includes( this.searchQuery ) : false;
}
else {
return item.title.toLowerCase().includes( this.searchQuery );
}
},
onSwiping({ data, object }) {
const swipeLimits = data.swipeLimits
const swipeView = object
const rightItem = swipeView.getViewById("delete-action")
swipeLimits.right = rightItem.getMeasuredWidth() - 12
swipeLimits.threshold = swipeLimits.right - 6
if (data.x < -swipeLimits.threshold) {
this.rightAction = true
swipeView.notifySwipeToExecuteFinished()
onSwiping( {
data,
object
} ) {
const swipeLimits = data.swipeLimits;
const swipeView = object;
const rightItem = swipeView.getViewById( "delete-action" );
swipeLimits.right = rightItem.getMeasuredWidth() - 12;
swipeLimits.threshold = swipeLimits.right - 6;
if ( data.x < -swipeLimits.threshold ) {
this.rightAction = true;
swipeView.notifySwipeToExecuteFinished();
}
},
onSwipeEnded({ index }) {
let recipeID = this.recipes[index].id
if (this.rightAction && !this.deletionDialogActive)
this.deleteRecipe(index, recipeID)
this.rightAction = false
onSwipeEnded( {
index
} ) {
let recipeID = this.recipes[ index ].id;
if ( this.rightAction && !this.deletionDialogActive ) this.deleteRecipe( index, recipeID );
this.rightAction = false;
},
// DATA HANDLERS
deleteRecipe(index, recipeID) {
this.deletionDialogActive = true
this.$showModal(ConfirmDialog, {
deleteRecipe( index, recipeID ) {
this.deletionDialogActive = true;
this.$showModal( ConfirmDialog, {
props: {
title: "Delete recipe",
description: `Are you sure you want to delete the recipe "${this.recipes[index].title}"?`,
title: localize( "Delete recipe?" ),
description: `${localize('Are you sure you want to delete the recipe')} "${this.recipes[index].title}"?`,
cancelButtonText: "CANCEL",
okButtonText: "DELETE",
},
}).then((action) => {
if (action) {
this.deleteRecipeAction({ index, id: recipeID })
okButtonText: "DELETE"
}
this.deletionDialogActive = false
})
},
} ).then( action => {
if ( action ) {
this.deleteRecipeAction( {
index,
id: recipeID
} );
}
this.deletionDialogActive = false;
} );
}
},
mounted() {
this.showFAB = true
},
}
this.showFAB = true;
}
};
</script>

View file

@ -1,90 +1,43 @@
<template>
<Page @loaded="onPageLoad">
<ActionBar flat="true">
<GridLayout rows="*" columns="auto, *, auto">
<MDButton
class="bx left"
variant="text"
:text="icon.menu"
automationText="Back"
@tap="showDrawer"
col="0"
/>
<Label class="title orkm" text="Meal Planner" col="1" />
<MDButton
class="bx left"
variant="text"
:text="icon.today"
automationText="today"
@tap="goToToday"
col="2"
/>
</GridLayout>
</ActionBar>
<GridLayout rows="280, *">
<RadCalendar
:androidElevation="viewIsScrolled ? 4 : 0"
class="orkm"
row="0"
ref="calendar"
locale="en-US"
@loaded="onCalendarLoad"
@dateSelected="onDateSelected"
:viewMode="viewMode"
:transitionMode="transitionMode"
:selectionMode="selectionMode"
:eventsViewMode="eventsViewMode"
:eventSource="getMealPlans"
></RadCalendar>
<ScrollView row="1" width="100%" height="100%" @scroll="onScroll">
<StackLayout class="dayPlan">
<StackLayout
v-for="(mealType, index) in mealTimes"
:key="'mealType' + index"
class="plansContainer"
:class="mealType"
>
<GridLayout columns="*, auto" class="header">
<Label col="0" class="periodLabel orkm" :text="mealType" />
<MDButton
col="1"
variant="text"
class="bx addMeal"
:text="icon.plus"
@tap="addRecipe(mealType)"
/>
</GridLayout>
<GridLayout
class="recipes"
columns="*, auto"
v-for="(recipeID, index) in getRecipes[mealType]"
:key="mealType + index"
>
<MDRipple @tap="viewRecipe(recipeID)" />
<Label
verticalAlignment="center"
class="recipeTitle"
col="0"
:text="getRecipeTitle(recipeID)"
textWrap="true"
/>
<MDButton
variant="text"
col="1"
class="bx closeBtn"
:text="icon.close"
@tap="removeRecipe(mealType, recipeID)"
/>
</GridLayout>
</StackLayout>
</StackLayout>
</ScrollView>
<Page @loaded="onPageLoad">
<ActionBar flat="true">
<GridLayout rows="*" columns="auto, *, auto">
<MDButton class="bx left" variant="text" :text="icon.menu" automationText="Back" @tap="showDrawer" col="0" />
<Label class="title orkm" :text="'Meal Planner' | L" col="1" />
<MDButton class="bx left" variant="text" :text="icon.today" automationText="today" @tap="goToToday" col="2" />
</GridLayout>
</Page>
</ActionBar>
<GridLayout rows="280, *">
<RadCalendar :androidElevation="viewIsScrolled ? 4 : 0" class="orkm" row="0" ref="calendar" @loaded="onCalendarLoad" @dateSelected="onDateSelected" :viewMode="viewMode" :transitionMode="transitionMode" :selectionMode="selectionMode"
:eventsViewMode="eventsViewMode" :eventSource="getMealPlans"></RadCalendar>
<ScrollView row="1" width="100%" height="100%" @scroll="onScroll">
<StackLayout class="dayPlan">
<StackLayout v-for="(mealType, index) in mealTimes" :key="'mealType' + index" class="plansContainer" :class="mealType">
<GridLayout columns="*, auto" class="header">
<Label col="0" class="periodLabel orkm" :text="mealType | L" />
<MDButton col="1" variant="text" class="bx addMeal" :text="icon.plus" @tap="addRecipe(mealType)" />
</GridLayout>
<GridLayout class="recipes" columns="*, auto" v-for="(recipeID, index) in getRecipes[mealType]" :key="mealType + index">
<MDRipple @tap="viewRecipe(recipeID)" />
<Label verticalAlignment="center" class="recipeTitle" col="0" :text="getRecipeTitle(recipeID)" textWrap="true" />
<MDButton variant="text" col="1" class="bx closeBtn" :text="icon.close" @tap="removeRecipe(mealType, recipeID)" />
</GridLayout>
</StackLayout>
</StackLayout>
</ScrollView>
</GridLayout>
</Page>
</template>
<script>
import { ApplicationSettings, Color, Page } from "@nativescript/core"
import {
ApplicationSettings,
Color,
Page,
Observable,
Device
}
from "@nativescript/core"
import {
CalendarViewMode,
CalendarTransitionMode,
@ -96,9 +49,14 @@ import {
CalendarCellAlignment,
CellStyle,
CalendarEventsViewMode,
CalendarEvent,
} from "nativescript-ui-calendar"
import { mapState, mapActions } from "vuex"
CalendarEvent
}
from "nativescript-ui-calendar"
import {
mapState,
mapActions
}
from "vuex"
import ViewRecipe from "./ViewRecipe.vue"
@ -112,7 +70,7 @@ export default {
return {
viewIsScrolled: false,
appTheme: "Light",
mealTimes: ["breakfast", "lunch", "dinner", "snacks"],
mealTimes: [ "breakfast", "lunch", "dinner", "snacks" ],
eventList: [],
selectedDayMealPlans: [],
viewMode: CalendarViewMode.Month,
@ -120,18 +78,18 @@ export default {
selectionMode: CalendarSelectionMode.Single,
eventsViewMode: CalendarEventsViewMode.None,
color: {
white: new Color("#ffffff"),
gray1: new Color("#f1f3f5"),
gray2: new Color("#e9ecef"),
gray3: new Color("#dee2e6"),
gray4: new Color("#ced4da"),
gray5: new Color("#adb5bd"),
gray6: new Color("#868e96"),
gray7: new Color("#495057"),
gray8: new Color("#343a40"),
gray9: new Color("#212529"),
black: new Color("#111111"),
orange: new Color("#ff5200"),
white: new Color( "#ffffff" ),
gray1: new Color( "#f1f3f5" ),
gray2: new Color( "#e9ecef" ),
gray3: new Color( "#dee2e6" ),
gray4: new Color( "#ced4da" ),
gray5: new Color( "#adb5bd" ),
gray6: new Color( "#868e96" ),
gray7: new Color( "#495057" ),
gray8: new Color( "#343a40" ),
gray9: new Color( "#212529" ),
black: new Color( "#111111" ),
orange: new Color( "#ff5200" ),
breakfast: "#ffb180",
lunch: "#ceff80",
dinner: "#80ceff",
@ -143,45 +101,45 @@ export default {
}
},
computed: {
...mapState(["icon", "recipes", "mealPlans"]),
...mapState( [ "icon", "recipes", "mealPlans" ] ),
isLightMode() {
return this.appTheme === "Light"
},
monthViewStyle() {
const monthViewStyle = new CalendarMonthViewStyle()
monthViewStyle.backgroundColor = this.isLightMode
? this.color.gray1
: this.color.gray9
monthViewStyle.backgroundColor = this.isLightMode ?
this.color.gray1 :
this.color.gray9
monthViewStyle.showTitle = true
monthViewStyle.showWeekNumbers = false
monthViewStyle.showDayNames = true
const titleCellStyle = new DayCellStyle()
titleCellStyle.cellBackgroundColor = this.isLightMode
? this.color.gray2
: this.color.black
titleCellStyle.cellBackgroundColor = this.isLightMode ?
this.color.gray2 :
this.color.black
titleCellStyle.cellBorderWidth = 1
titleCellStyle.cellBorderColor = this.isLightMode
? this.color.gray2
: this.color.black
titleCellStyle.cellBorderColor = this.isLightMode ?
this.color.gray2 :
this.color.black
titleCellStyle.cellTextSize = 16
titleCellStyle.cellTextColor = this.isLightMode
? this.color.gray9
: this.color.gray1
titleCellStyle.cellTextColor = this.isLightMode ?
this.color.gray9 :
this.color.gray1
titleCellStyle.cellTextFontName = this.appFontMedium
monthViewStyle.titleCellStyle = titleCellStyle
const dayNameCellStyle = new CellStyle()
dayNameCellStyle.cellBackgroundColor = this.isLightMode
? this.color.gray2
: this.color.black
dayNameCellStyle.cellTextColor = this.isLightMode
? this.color.gray9
: this.color.gray1
dayNameCellStyle.cellBackgroundColor = this.isLightMode ?
this.color.gray2 :
this.color.black
dayNameCellStyle.cellTextColor = this.isLightMode ?
this.color.gray9 :
this.color.gray1
dayNameCellStyle.cellBorderWidth = 1
dayNameCellStyle.cellBorderColor = this.isLightMode
? this.color.gray2
: this.color.black
dayNameCellStyle.cellBorderColor = this.isLightMode ?
this.color.gray2 :
this.color.black
dayNameCellStyle.cellTextSize = 10
dayNameCellStyle.cellAlignment = CalendarCellAlignment.Center
dayNameCellStyle.cellTextFontName = this.appFontMedium
@ -194,46 +152,46 @@ export default {
dayCellStyle.eventFontStyle = CalendarFontStyle.Bold
dayCellStyle.eventTextSize = 8
dayCellStyle.cellTextSize = 16
dayCellStyle.cellTextColor = this.isLightMode
? this.color.gray9
: this.color.gray1
dayCellStyle.cellTextColor = this.isLightMode ?
this.color.gray9 :
this.color.gray1
dayCellStyle.cellAlignment = CalendarCellAlignment.Bottom
dayCellStyle.cellBackgroundColor = this.isLightMode
? this.color.gray1
: this.color.gray9
dayCellStyle.cellBackgroundColor = this.isLightMode ?
this.color.gray1 :
this.color.gray9
dayCellStyle.cellTextFontName = this.appFontRegular
dayCellStyle.cellBorderWidth = 1
dayCellStyle.cellBorderColor = this.isLightMode
? this.color.gray2
: this.color.black
dayCellStyle.cellBorderColor = this.isLightMode ?
this.color.gray2 :
this.color.black
monthViewStyle.dayCellStyle = dayCellStyle
const todayCellStyle = new DayCellStyle()
todayCellStyle.cellBackgroundColor = this.isLightMode
? this.color.gray1
: this.color.gray9
todayCellStyle.cellBackgroundColor = this.isLightMode ?
this.color.gray1 :
this.color.gray9
todayCellStyle.cellTextColor = this.color.orange
todayCellStyle.cellBorderWidth = 1
todayCellStyle.cellTextFontName = this.appFontMedium
todayCellStyle.cellTextFontStyle = CalendarFontStyle.Bold
todayCellStyle.cellTextSize = 16
todayCellStyle.cellAlignment = CalendarCellAlignment.Bottom
todayCellStyle.cellBorderColor = this.isLightMode
? this.color.gray2
: this.color.black
todayCellStyle.cellBorderColor = this.isLightMode ?
this.color.gray2 :
this.color.black
monthViewStyle.todayCellStyle = todayCellStyle
const selectedCellStyle = new DayCellStyle()
selectedCellStyle.eventTextSize = 1
selectedCellStyle.cellAlignment = CalendarCellAlignment.Bottom
selectedCellStyle.cellBackgroundColor = this.isLightMode
? this.color.white
: this.color.gray8
selectedCellStyle.cellBackgroundColor = this.isLightMode ?
this.color.white :
this.color.gray8
selectedCellStyle.cellBorderWidth = 1
selectedCellStyle.cellBorderColor = this.color.orange
selectedCellStyle.cellTextColor = this.isLightMode
? this.color.gray9
: this.color.gray1
selectedCellStyle.cellTextColor = this.isLightMode ?
this.color.gray9 :
this.color.gray1
selectedCellStyle.cellTextFontName = this.appFontMedium
selectedCellStyle.cellTextFontStyle = CalendarFontStyle.Bold
selectedCellStyle.cellTextSize = 16
@ -242,31 +200,32 @@ export default {
return monthViewStyle
},
getRecipes() {
if (this.selectedDayMealPlans.length) {
return this.selectedDayMealPlans.reduce((acc, e) => {
switch (e.startDate.getHours()) {
if ( this.selectedDayMealPlans.length ) {
return this.selectedDayMealPlans.reduce( ( acc, e ) => {
switch ( e.startDate.getHours() ) {
case 0: //breakfast
acc["breakfast"] = [...(acc["breakfast"] || []), e.title]
acc[ "breakfast" ] = [ ...( acc[ "breakfast" ] || [] ), e.title ]
break
case 5: //lunch
acc["lunch"] = [...(acc["lunch"] || []), e.title]
acc[ "lunch" ] = [ ...( acc[ "lunch" ] || [] ), e.title ]
break
case 10: //dinner
acc["dinner"] = [...(acc["dinner"] || []), e.title]
acc[ "dinner" ] = [ ...( acc[ "dinner" ] || [] ), e.title ]
break
case 15: //snacks
acc["snacks"] = [...(acc["snacks"] || []), e.title]
acc[ "snacks" ] = [ ...( acc[ "snacks" ] || [] ), e.title ]
break
default:
break
}
return acc
}, {})
} else return 0
}, {} )
}
else return 0
},
getMealPlans() {
const getDate = (date) => {
let d = new Date(date)
const getDate = ( date ) => {
let d = new Date( date )
let result = new Date(
d.getFullYear(),
d.getMonth(),
@ -276,40 +235,43 @@ export default {
return result
}
let events = []
this.mealPlans.forEach((plan) => {
this.mealPlans.forEach( ( plan ) => {
let e = new CalendarEvent(
plan.title,
getDate(plan.startDate),
getDate(plan.endDate),
getDate( plan.startDate ),
getDate( plan.endDate ),
false,
new Color(plan.eventColor)
new Color( plan.eventColor )
)
events = [...events, e]
})
events = [ ...events, e ]
} )
return events
},
},
methods: {
...mapActions([
...mapActions( [
"setCurrentComponentAction",
"initializeMealPlans",
"addMealPlanAction",
"deleteMealPlanAction",
]),
onPageLoad(args) {
this.setCurrentComponentAction("MealPlanner")
] ),
onPageLoad( args ) {
const page = args.object;
page.bindingContext = new Observable();
this.setCurrentComponentAction( "MealPlanner" )
},
onCalendarLoad(args) {
onCalendarLoad( args ) {
args.object.locale = `${Device.language}-${Device.language.toUpperCase()}`
args.object.monthViewStyle = this.monthViewStyle
args.object.android
.getGestureManager()
.setDoubleTapToChangeDisplayMode(false)
.setDoubleTapToChangeDisplayMode( false )
args.object.android
.getGestureManager()
.setPinchCloseToChangeDisplayMode(false)
if (args.object.selectedDate == null)
.setPinchCloseToChangeDisplayMode( false )
if ( args.object.selectedDate == null )
args.object.selectedDate = new Date()
if (args.object.nativeView.getEventAdapter()) {
if ( args.object.nativeView.getEventAdapter() ) {
args.object.nativeView
.getEventAdapter()
.getRenderer()
@ -323,102 +285,102 @@ export default {
showDrawer() {
utils.showDrawer()
},
onScroll(args) {
onScroll( args ) {
this.viewIsScrolled = args.scrollY ? true : false
},
getDate(index) {
getDate( index ) {
let date = new Date()
date.setDate(date.getDate() + index)
date.setDate( date.getDate() + index )
return date.getTime()
},
getDateString(days) {
getDateString( days ) {
let date = new Date()
date.setDate(date.getDate() + days)
return date.toDateString().slice(0, -5)
date.setDate( date.getDate() + days )
return date.toDateString().slice( 0, -5 )
},
getRecipeTitle(id) {
let recipe = this.recipes.filter((e) => e.id === id)[0]
getRecipeTitle( id ) {
let recipe = this.recipes.filter( ( e ) => e.id === id )[ 0 ]
return recipe ? recipe.title : "[Deleted Recipe]"
},
// NAVIGATION HANDLERS
viewRecipe(recipeID) {
let recipe = this.recipes.filter((e) => e.id === recipeID)[0]
if (recipe) {
this.$navigateTo(ViewRecipe, {
viewRecipe( recipeID ) {
let recipe = this.recipes.filter( ( e ) => e.id === recipeID )[ 0 ]
if ( recipe ) {
this.$navigateTo( ViewRecipe, {
props: {
filterTrylater: true,
recipeID,
},
backstackVisible: false,
})
} )
}
},
// DATA HANDLERS
addRecipe(mealType) {
let filteredRecipes = this.recipes.filter((e) =>
this.getRecipes[mealType]
? !this.getRecipes[mealType].includes(e.id)
: true
addRecipe( mealType ) {
let filteredRecipes = this.recipes.filter( ( e ) =>
this.getRecipes[ mealType ] ?
!this.getRecipes[ mealType ].includes( e.id ) :
true
)
this.$showModal(ActionDialogWithSearch, {
this.$showModal( ActionDialogWithSearch, {
props: {
title: "Select a recipe",
recipes: filteredRecipes,
},
}).then((recipeID) => {
recipeID && this.newEvent(recipeID, mealType)
})
} ).then( ( recipeID ) => {
recipeID && this.newEvent( recipeID, mealType )
} )
},
removeRecipeConfirm(mealType) {
return this.$showModal(ConfirmDialog, {
removeRecipeConfirm( mealType ) {
return this.$showModal( ConfirmDialog, {
props: {
title: `Remove recipe from ${mealType}`,
cancelButtonText: "CANCEL",
okButtonText: "REMOVE",
},
})
} )
},
removeRecipe(mealType, recipeID) {
removeRecipe( mealType, recipeID ) {
let startHour = {
breakfast: 0,
lunch: 5,
dinner: 10,
snacks: 15,
}
this.removeRecipeConfirm(mealType).then((res) => {
if (res) {
this.removeRecipeConfirm( mealType ).then( ( res ) => {
if ( res ) {
let actualMealPlan = this.selectedDayMealPlans.filter(
(e) =>
e.startDate.getHours() === startHour[mealType] &&
e.title === recipeID
)[0]
( e ) =>
e.startDate.getHours() === startHour[ mealType ] &&
e.title === recipeID
)[ 0 ]
let mealPlan = {
title: actualMealPlan.title,
startDate: actualMealPlan.startDate,
}
this.deleteMealPlanAction(mealPlan)
this.deleteMealPlanAction( mealPlan )
this.updateSelectedDatePlans()
}
})
} )
},
// CALENDAR
updateSelectedDatePlans() {
let date = new Date(this.selectedDate)
setTimeout(() => {
let date = new Date( this.selectedDate )
setTimeout( () => {
this.selectedDayMealPlans = this.$refs.calendar.nativeView.getEventsForDate(
date
)
}, 100)
}, 100 )
},
onDateSelected(args) {
onDateSelected( args ) {
this.selectedDate = args.date
this.selectedDayMealPlans = args.object.getEventsForDate(args.date)
this.selectedDayMealPlans = args.object.getEventsForDate( args.date )
},
newEvent(recipeID, mealType) {
let date = new Date(this.selectedDate)
newEvent( recipeID, mealType ) {
let date = new Date( this.selectedDate )
const selectedDate = () => {
return {
y: date.getFullYear(),
@ -426,45 +388,52 @@ export default {
d: date.getDate(),
}
}
let { y, m, d } = selectedDate()
let {
y,
m,
d
} = selectedDate()
let mealTime = {
breakfast: {
start: new Date(y, m, d, 0),
end: new Date(y, m, d, 4),
start: new Date( y, m, d, 0 ),
end: new Date( y, m, d, 4 ),
},
lunch: {
start: new Date(y, m, d, 5),
end: new Date(y, m, d, 9),
start: new Date( y, m, d, 5 ),
end: new Date( y, m, d, 9 ),
},
dinner: {
start: new Date(y, m, d, 10),
end: new Date(y, m, d, 14),
start: new Date( y, m, d, 10 ),
end: new Date( y, m, d, 14 ),
},
snacks: {
start: new Date(y, m, d, 15),
end: new Date(y, m, d, 19),
start: new Date( y, m, d, 15 ),
end: new Date( y, m, d, 19 ),
},
}
let event = new CalendarEvent(
recipeID,
mealTime[mealType].start,
mealTime[mealType].end,
mealTime[ mealType ].start,
mealTime[ mealType ].end,
false,
new Color(this.color[mealType])
new Color( this.color[ mealType ] )
)
this.addMealPlanAction({ event, eventColor: this.color[mealType] })
this.addMealPlanAction( {
event,
eventColor: this.color[ mealType ]
} )
this.updateSelectedDatePlans()
},
goToToday() {
const date = new Date()
this.$refs.calendar.goToDate(date)
this.$refs.calendar.goToDate( date )
this.$refs.calendar.nativeView.selectedDate = date
},
},
created() {
this.appTheme = ApplicationSettings.getString("appTheme", "Light")
this.appTheme = ApplicationSettings.getString( "appTheme", "Light" )
let d = new Date()
d.setHours(0, 0, 0)
d.setHours( 0, 0, 0 )
this.selectedDate = d
},
}

View file

@ -1,73 +1,55 @@
<template>
<Page @loaded="onPageLoad">
<ActionBar :androidElevation="viewIsScrolled ? 4 : 0">
<GridLayout rows="*" columns="auto, *">
<MDButton
class="bx left"
variant="text"
:text="icon.menu"
automationText="Back"
@tap="showDrawer"
col="0"
/>
<Label class="title orkm" text="Settings" col="1" />
<Page @loaded="onPageLoad">
<ActionBar :androidElevation="viewIsScrolled ? 4 : 0">
<GridLayout rows="*" columns="auto, *">
<MDButton class="bx left" variant="text" :text="icon.menu" automationText="Back" @tap="showDrawer" col="0" />
<Label class="title orkm" :text="'Settings' | L" col="1" />
</GridLayout>
</ActionBar>
<ScrollView @scroll="onScroll">
<StackLayout class="main-container">
<Label :text="'Interface' | L" class="group-header orkm" />
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="selectAppLanguage" />
<Label col="0" verticalAlignment="center" class="bx" :text="icon.globe" />
<StackLayout col="1">
<Label :text="'App language' | L" />
<Label :text="appLanguage" class="info" />
</StackLayout>
</GridLayout>
</ActionBar>
<ScrollView @scroll="onScroll">
<StackLayout class="main-container">
<Label text="Interface" class="group-header orkm" />
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="selectThemes" />
<Label
col="0"
verticalAlignment="center"
class="bx"
:text="icon.theme"
/>
<StackLayout col="1">
<Label text="Theme" />
<Label :text="appTheme" class="info" />
</StackLayout>
</GridLayout>
<StackLayout class="hr m-10"></StackLayout>
<Label text="Database" class="group-header orkm" />
<GridLayout columns="auto, *" class="option">
<!-- <MDRipple colSpan="2" @tap="folderPicker" /> -->
<MDRipple colSpan="2" @tap="exportCheck" />
<Label col="0" class="bx" :text="icon.export" />
<StackLayout col="1">
<Label text="Export a full backup" />
<Label
v-if="!backupInProgress"
text="Generates a zip file that contains all your data. This file can be imported back."
class="info"
textWrap="true"
/>
<GridLayout class="progressContainer" v-else columns="*, 64">
<MDProgress
col="0"
:value="backupProgress"
maxValue="100"
></MDProgress>
<Label col="1" :text="` ${backupProgress}%`" />
</GridLayout>
</StackLayout>
</GridLayout>
<GridLayout columns="auto, *" class="option"
><MDRipple colSpan="2" @tap="importCheck" />
<Label col="0" class="bx" :text="icon.import" />
<StackLayout col="1">
<Label text="Import from backup" />
<Label
text="Supports full backups exported by this app"
class="info"
textWrap="true"
/>
</StackLayout>
</GridLayout>
</StackLayout>
</ScrollView>
</Page>
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="selectThemes" />
<Label col="0" verticalAlignment="center" class="bx" :text="icon.theme" />
<StackLayout col="1">
<Label :text="'Theme' | L" />
<Label :text="`${appTheme}` | L" class="info" />
</StackLayout>
</GridLayout>
<StackLayout class="hr m-10"></StackLayout>
<Label :text="'Database' | L" class="group-header orkm" />
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="exportCheck" />
<Label col="0" class="bx" :text="icon.export" />
<StackLayout col="1">
<Label :text="'Export a full backup' | L" textWrap="true" />
<Label v-if="!backupInProgress" :text="'Generates a zip file that contains all your data. This file can be imported back.' | L" class="info" textWrap="true" />
<GridLayout class="progressContainer" v-else columns="*, 64">
<MDProgress col="0" :value="backupProgress" maxValue="100"></MDProgress>
<Label col="1" :text="` ${backupProgress}%`" />
</GridLayout>
</StackLayout>
</GridLayout>
<GridLayout columns="auto, *" class="option">
<MDRipple colSpan="2" @tap="importCheck" />
<Label col="0" class="bx" :text="icon.import" />
<StackLayout col="1">
<Label :text="'Import from backup' | L" textWrap="true" />
<Label :text="'Supports full backups exported by this app' | L" class="info" textWrap="true" />
</StackLayout>
</GridLayout>
</StackLayout>
</ScrollView>
</Page>
</template>
<script>
@ -77,13 +59,28 @@ import {
knownFolders,
File,
Folder,
} from "@nativescript/core"
Observable,
Device
}
from "@nativescript/core"
import * as Permissions from "@nativescript-community/perms"
import { Zip } from "@nativescript/zip"
import {
Zip
}
from "@nativescript/zip"
import * as Toast from "nativescript-toast"
import * as Filepicker from "nativescript-plugin-filepicker"
import Theme from "@nativescript/theme"
import { mapState, mapActions } from "vuex"
import {
localize,
overrideLocale
}
from "@nativescript/localize"
import {
mapState,
mapActions
}
from "vuex"
import ActionDialog from "./modal/ActionDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue"
@ -95,145 +92,182 @@ export default {
return {
viewIsScrolled: false,
appTheme: "Light",
appLanguage: "English",
backupProgress: 0,
backupInProgress: false,
}
},
computed: {
...mapState([
...mapState( [
"icon",
"recipes",
"userCategories",
"userYieldUnits",
"mealPlans",
"currentComponent",
]),
"language",
] ),
},
methods: {
...mapActions([
...mapActions( [
"setCurrentComponentAction",
"importCategoriesAction",
"importYieldUnitsAction",
"importRecipesAction",
"importMealPlansAction",
]),
onPageLoad() {
this.setCurrentComponentAction("Settings")
] ),
onPageLoad( args ) {
const page = args.object;
page.bindingContext = new Observable();
this.setCurrentComponentAction( "Settings" )
},
// HELPERS
showDrawer() {
utils.showDrawer()
},
onScroll(args) {
onScroll( args ) {
this.viewIsScrolled = args.scrollY ? true : false
},
// LANGUAGE SELECTION
selectAppLanguage() {
let languages = this.language.map( e => e.title )
this.$showModal( ActionDialog, {
props: {
title: "App language",
list: [ ...languages ],
},
} ).then( ( action ) => {
if ( action && action !== "Cancel" && this.appLanguage !== action ) {
let currentLocale = Device.language.split( '-' )[ 0 ]
let locale = this.language.filter( e => e.title === action )[ 0 ].locale
if ( currentLocale !== locale ) {
this.$showModal( ConfirmDialog, {
props: {
title: "Restart required",
description: localize( "EnRecipes needs to be restarted for the app language to take effect." ),
cancelButtonText: "CANCEL",
okButtonText: "RESTART",
},
} ).then( ( result ) => {
if ( result ) {
this.appLanguage = action
ApplicationSettings.setString( "appLanguage", action )
overrideLocale( locale )
setTimeout( ( e ) => utils.restartApp(), 250 )
}
} )
}
}
} )
},
// THEME SELECTION
selectThemes() {
this.$showModal(ActionDialog, {
this.$showModal( ActionDialog, {
props: {
title: "Theme",
list: ["Light", "Dark"],
list: [ "Light", "Dark" ],
height: "96",
},
}).then((action) => {
if (action && action !== "Cancel" && this.appTheme !== action) {
this.$showModal(ConfirmDialog, {
} ).then( ( action ) => {
if ( action && action !== "Cancel" && this.appTheme !== action ) {
this.$showModal( ConfirmDialog, {
props: {
title: "Reload required",
description:
"EnRecipes needs to be reloaded for the theme change to take effect.",
title: "Restart required",
description: localize( "EnRecipes needs to be restarted for the theme change to take effect." ),
cancelButtonText: "CANCEL",
okButtonText: "RELOAD",
okButtonText: "RESTART",
},
}).then((result) => {
if (result) {
} ).then( ( result ) => {
if ( result ) {
this.appTheme = action
ApplicationSettings.setString("appTheme", action)
setTimeout((e) => utils.restartApp(), 250)
ApplicationSettings.setString( "appTheme", action )
setTimeout( ( e ) => utils.restartApp(), 250 )
}
})
} )
}
})
} )
},
// EXPORT HANDLERS
exportCheck() {
if (!this.recipes.length) {
if ( !this.recipes.length ) {
Toast.makeText(
"Add at least one recipe to perform a backup",
localize( "Add at least one recipe to perform a backup" ),
"long"
).show()
} else {
}
else {
this.permissionCheck(
this.permissionConfirmation,
"EnRecipes requires storage permission in order to backup your data to this device.",
localize( "EnRecipes requires storage permission in order to backup your data to this device." ),
this.exportBackup
)
}
},
exportBackup() {
this.exportFiles("create")
this.exportFiles( "create" )
let date = new Date()
let formattedDate =
date.getFullYear() +
"-" +
("0" + (date.getMonth() + 1)).slice(-2) +
( "0" + ( date.getMonth() + 1 ) ).slice( -2 ) +
"-" +
("0" + date.getDate()).slice(-2) +
( "0" + date.getDate() ).slice( -2 ) +
"_" +
("0" + date.getHours()).slice(-2) +
("0" + date.getMinutes()).slice(-2) +
("0" + date.getSeconds()).slice(-2)
( "0" + date.getHours() ).slice( -2 ) +
( "0" + date.getMinutes() ).slice( -2 ) +
( "0" + date.getSeconds() ).slice( -2 )
const sdDownloadPath = Folder.fromPath(
android.os.Environment.getExternalStorageDirectory().getAbsolutePath()
).getFolder("Download").path
let fromPath = path.join(knownFolders.documents().path, "EnRecipes")
).getFolder( "Download" ).path
let fromPath = path.join( knownFolders.documents().path, "EnRecipes" )
let destPath = path.join(
sdDownloadPath,
`EnRecipes-Backup_${formattedDate}.zip`
)
this.backupInProgress = true
Zip.zip({
Zip.zip( {
directory: fromPath,
archive: destPath,
onProgress: (progress) => {
onProgress: ( progress ) => {
this.backupProgress = progress
},
}).then((success) => {
} ).then( ( success ) => {
Toast.makeText(
"Backup file successfully saved to Download folder",
"long"
).show()
this.exportFiles("delete")
})
this.exportFiles( "delete" )
} )
},
exportFiles(option) {
const folder = path.join(knownFolders.documents().path, "EnRecipes")
const EnRecipesFile = File.fromPath(path.join(folder, "recipes.json"))
exportFiles( option ) {
const folder = path.join( knownFolders.documents().path, "EnRecipes" )
const EnRecipesFile = File.fromPath( path.join( folder, "recipes.json" ) )
let userCategoriesFile, userYieldUnitsFile, mealPlansFile
if (this.userCategories.length) {
if ( this.userCategories.length ) {
userCategoriesFile = File.fromPath(
path.join(folder, "userCategories.json")
path.join( folder, "userCategories.json" )
)
}
if (this.userYieldUnits.length) {
if ( this.userYieldUnits.length ) {
userYieldUnitsFile = File.fromPath(
path.join(folder, "userYieldUnits.json")
path.join( folder, "userYieldUnits.json" )
)
}
if (this.mealPlans.length) {
mealPlansFile = File.fromPath(path.join(folder, "mealPlans.json"))
if ( this.mealPlans.length ) {
mealPlansFile = File.fromPath( path.join( folder, "mealPlans.json" ) )
}
switch (option) {
switch ( option ) {
case "create":
this.writeDataToFile(EnRecipesFile, this.recipes)
this.writeDataToFile( EnRecipesFile, this.recipes )
this.userCategories.length &&
this.writeDataToFile(userCategoriesFile, this.userCategories)
this.writeDataToFile( userCategoriesFile, this.userCategories )
this.userYieldUnits.length &&
this.writeDataToFile(userYieldUnitsFile, this.userYieldUnits)
this.writeDataToFile( userYieldUnitsFile, this.userYieldUnits )
this.mealPlans.length &&
this.writeDataToFile(mealPlansFile, this.mealPlans)
this.writeDataToFile( mealPlansFile, this.mealPlans )
break
case "delete":
EnRecipesFile.remove()
@ -245,144 +279,159 @@ export default {
break
}
},
writeDataToFile(file, data) {
file.writeText(JSON.stringify(data))
writeDataToFile( file, data ) {
file.writeText( JSON.stringify( data ) )
},
// IMPORT HANDLERS
importCheck() {
this.permissionCheck(
this.permissionConfirmation,
"EnRecipes requires storage permission in order to import your data from a previous backup.",
localize( "EnRecipes requires storage permission in order to import your data from a previous backup." ),
this.openFilePicker
)
},
openFilePicker() {
Filepicker.create({
mode: "single",
extensions: ["zip"],
})
Filepicker.create( {
mode: "single",
extensions: [ "zip" ],
} )
.present()
.then((selection) => {
Toast.makeText("Processing...").show()
let zipPath = selection[0]
this.validateZipContent(zipPath)
})
.then( ( selection ) => {
Toast.makeText( localize( "Verifying..." ) ).show()
let zipPath = selection[ 0 ]
this.validateZipContent( zipPath )
} )
},
importDataToDB(data, db, zipPath) {
switch (db) {
importDataToDB( data, db, zipPath ) {
switch ( db ) {
case "EnRecipesDB":
this.importImages(zipPath)
this.importRecipesAction(data)
this.importImages( zipPath )
this.importRecipesAction( data )
break
case "userCategoriesDB":
this.importCategoriesAction(data)
this.importCategoriesAction( data )
break
case "userYieldUnitsDB":
this.importYieldUnitsAction(data)
this.importYieldUnitsAction( data )
break
case "mealPlansDB":
this.importMealPlansAction(data)
this.importMealPlansAction( data )
break
default:
break
}
},
isFileDataValid(file) {
file.forEach((file, i) => {
if (File.exists(file.path)) {
File.fromPath(file.path)
isFileDataValid( file ) {
file.forEach( ( file, i ) => {
if ( File.exists( file.path ) ) {
File.fromPath( file.path )
.readText()
.then((data) => {
Array.isArray(JSON.parse(data)) &&
this.importDataToDB(JSON.parse(data), file.db, file.zipPath)
})
.then( ( data ) => {
Array.isArray( JSON.parse( data ) ) &&
this.importDataToDB( JSON.parse( data ), file.db, file.zipPath )
} )
}
})
} )
},
validateZipContent(zipPath) {
Zip.unzip({
validateZipContent( zipPath ) {
Zip.unzip( {
archive: zipPath,
overwrite: true,
}).then((extractedFolderPath) => {
} ).then( ( extractedFolderPath ) => {
let cacheFolderPath = extractedFolderPath + "/EnRecipes"
const EnRecipesFilePath = cacheFolderPath + "/recipes.json"
const userCategoriesFilePath = cacheFolderPath + "/userCategories.json"
const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json"
const mealPlansFilePath = cacheFolderPath + "/mealPlans.json"
if (Folder.exists(cacheFolderPath)) {
this.isFileDataValid([
{
if ( Folder.exists( cacheFolderPath ) ) {
this.isFileDataValid( [ {
zipPath,
path: EnRecipesFilePath,
db: "EnRecipesDB",
},
{ zipPath, path: userCategoriesFilePath, db: "userCategoriesDB" },
{ zipPath, path: userYieldUnitsFilePath, db: "userYieldUnitsDB" },
{ zipPath, path: mealPlansFilePath, db: "mealPlansDB" },
])
} else {
Folder.fromPath(extractedFolderPath).remove()
{
zipPath,
path: userCategoriesFilePath,
db: "userCategoriesDB"
},
{
zipPath,
path: userYieldUnitsFilePath,
db: "userYieldUnitsDB"
},
{
zipPath,
path: mealPlansFilePath,
db: "mealPlansDB"
},
] )
}
else {
Folder.fromPath( extractedFolderPath ).remove()
Toast.makeText(
"Import failed. Backup file is incorrect or corrupt",
"long"
).show()
}
if (Folder.exists(cacheFolderPath + "/Images")) {
this.importImages(cacheFolderPath + "/Images")
if ( Folder.exists( cacheFolderPath + "/Images" ) ) {
this.importImages( cacheFolderPath + "/Images" )
}
})
} )
},
importImages(sourcePath) {
importImages( sourcePath ) {
let dest = knownFolders.documents().path
Zip.unzip({
Zip.unzip( {
archive: sourcePath,
directory: dest,
overwrite: true,
}).then((res) => {
Toast.makeText("Import successful", "long").show()
} ).then( ( res ) => {
Toast.makeText( localize( "Import successful" ), "long" ).show()
this.$navigateBack()
})
} )
},
// 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 Toast.makeText("Permission denied").show()
})
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 Toast.makeText( localize( "Permission denied" ) ).show()
} )
}
})
} else {
Permissions.check("photo").then((res) => {
let status = res[Object.keys(res)[0]]
if (status !== "authorized") {
confirmation(description).then((e) => {
} )
}
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()
})
} )
}
else action()
} )
}
},
permissionConfirmation(description) {
return this.$showModal(ConfirmDialog, {
permissionConfirmation( description ) {
return this.$showModal( ConfirmDialog, {
props: {
title: "Grant permission",
description,
cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE",
},
})
} )
},
},
mounted() {
this.appTheme = ApplicationSettings.getString("appTheme", "Light")
this.appTheme = ApplicationSettings.getString( "appTheme", "Light" )
this.appLanguage = ApplicationSettings.getString( "appLanguage", localize( "System default" ) )
},
}
</script>

File diff suppressed because it is too large Load diff

View file

@ -1,67 +1,44 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" />
<ScrollView width="100%" :height="height ? height : screenHeight - 256">
<StackLayout>
<MDButton
v-for="(item, index) in list"
:key="index"
class="actionItem"
variant="text"
:rippleColor="rippleColor"
:text="item"
@loaded="onLabelLoaded"
@tap="tapAction(item)"
/>
</StackLayout>
</ScrollView>
<GridLayout rows="auto" columns="auto, *, auto" class="actionsContainer">
<MDButton
:rippleColor="rippleColor"
variant="text"
v-if="action"
col="0"
class="action orkm pull-left"
:text="action"
@tap="$modal.close(action)"
/>
<MDButton
:rippleColor="rippleColor"
variant="text"
col="2"
class="action orkm pull-right"
text="CANCEL"
@tap="$modal.close(false)"
/>
</GridLayout>
</StackLayout>
</Page>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="`${title}` | L" />
<ScrollView width="100%" :height="height ? height : screenHeight - 256">
<StackLayout>
<MDButton v-for="(item, index) in list" :key="index" class="actionItem" variant="text" :rippleColor="rippleColor" :text="`${item}` | L" @loaded="onLabelLoaded" @tap="tapAction(item)" />
</StackLayout>
</ScrollView>
<GridLayout rows="auto" columns="auto, *, auto" class="actionsContainer">
<MDButton :rippleColor="rippleColor" variant="text" v-if="action" col="0" class="action orkm pull-left" :text="`${action}` | L" @tap="$modal.close(action)" />
<MDButton :rippleColor="rippleColor" variant="text" col="2" class="action orkm pull-right" :text="'CANCEL' | L" @tap="$modal.close(false)" />
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application, Screen } from "@nativescript/core"
import {
Application,
Screen
} from "@nativescript/core"
export default {
props: ["title", "list", "height", "action"],
props: [ "title", "list", "height", "action" ],
computed: {
appTheme() {
return Application.systemAppearance()
},
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
return this.appTheme == "light" ? "rgba(134,142,150,0.2)" : "rgba(206,212,218,0.1)"
},
screenHeight() {
return Math.round(Screen.mainScreen.heightDIPs)
return Math.round( Screen.mainScreen.heightDIPs )
},
},
methods: {
tapAction(item) {
this.$modal.close(item)
tapAction( item ) {
this.$modal.close( item )
},
onLabelLoaded(args) {
args.object.android.setGravity(16)
onLabelLoaded( args ) {
args.object.android.setGravity( 16 )
},
},
}

View file

@ -1,62 +1,32 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" />
<StackLayout
v-if="filteredRecipes.length || searchTerm"
padding="0 24 24"
>
<TextField hint="Search" v-model="searchTerm" />
</StackLayout>
<ScrollView width="100%" :height="height ? height : screenHeight - 320">
<StackLayout>
<MDButton
v-for="(recipe, index) in filteredRecipes"
:key="index"
class="actionItem"
variant="text"
:rippleColor="rippleColor"
:text="recipe.title"
@loaded="onLabelLoaded"
@tap="tapAction(recipe)"
/>
<Label
padding="24"
lineHeight="6"
v-if="!filteredRecipes.length"
text="Nothing here! Add some recipes and try again."
textAlignment="center"
textWrap="true"
/>
</StackLayout>
</ScrollView>
<GridLayout rows="auto" columns="auto, *, auto" class="actionsContainer">
<MDButton
:rippleColor="rippleColor"
variant="text"
v-if="action"
col="0"
class="action orkm pull-left"
:text="action"
@tap="$modal.close(action)"
/>
<MDButton
:rippleColor="rippleColor"
variant="text"
col="2"
class="action orkm pull-right"
text="CANCEL"
@tap="$modal.close(false)"
/>
</GridLayout>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="`${title}` | L" textWrap='true' />
<StackLayout v-if="filteredRecipes.length || searchTerm" padding="0 24 24">
<TextField :hint="'Search' | L" v-model="searchTerm" />
</StackLayout>
</Page>
<ScrollView width="100%" :height="height ? height : screenHeight - 320">
<StackLayout>
<MDButton v-for="(recipe, index) in filteredRecipes" :key="index" class="actionItem" variant="text" :rippleColor="rippleColor" :text="recipe.title" @loaded="onLabelLoaded" @tap="tapAction(recipe)" />
<Label padding="24" lineHeight="6" v-if="!filteredRecipes.length" :text="'Nothing here! Add some recipes and try again.' | L" textAlignment="center" textWrap="true" />
</StackLayout>
</ScrollView>
<GridLayout rows="auto" columns="auto, *, auto" class="actionsContainer">
<MDButton :rippleColor="rippleColor" variant="text" v-if="action" col="0" class="action orkm pull-left" :text="`${action}` | L" @tap="$modal.close(action)" />
<MDButton :rippleColor="rippleColor" variant="text" col="2" class="action orkm pull-right" :text="'CANCEL' | L" @tap="$modal.close(false)" />
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application, Screen } from "@nativescript/core"
import {
Application,
Screen
}
from "@nativescript/core"
export default {
props: ["title", "recipes", "height", "action"],
props: [ "title", "recipes", "height", "action" ],
data() {
return {
searchTerm: "",
@ -67,30 +37,26 @@ export default {
return Application.systemAppearance()
},
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
return this.appTheme == "light" ? "rgba(134,142,150,0.2)" : "rgba(206,212,218,0.1)"
},
screenHeight() {
return Math.round(Screen.mainScreen.heightDIPs)
return Math.round( Screen.mainScreen.heightDIPs )
},
filteredRecipes() {
return this.recipes
.map((e, i) => {
return {
id: e.id,
title: e.title,
}
})
.filter((e) => e.title.includes(this.searchTerm))
return this.recipes.map( ( e, i ) => {
return {
id: e.id,
title: e.title,
}
} ).filter( ( e ) => e.title.includes( this.searchTerm ) )
},
},
methods: {
tapAction(recipe) {
this.$modal.close(recipe.id)
tapAction( recipe ) {
this.$modal.close( recipe.id )
},
onLabelLoaded(args) {
args.object.android.setGravity(16)
onLabelLoaded( args ) {
args.object.android.setGravity( 16 )
},
},
}

View file

@ -1,47 +1,31 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" textWrap="true"/>
<Label
v-if="description"
class="dialogDescription"
:text="description"
textWrap="true"
/>
<GridLayout rows="auto" columns="*, auto, auto" class="actionsContainer">
<MDButton
:rippleColor="rippleColor"
variant="text"
col="1"
class="action orkm"
:text="cancelButtonText"
@tap="$modal.close(false)"
/>
<MDButton
:rippleColor="rippleColor"
variant="text"
col="2"
class="action orkm"
:text="okButtonText"
@tap="$modal.close(true)"
/>
</GridLayout>
</StackLayout>
</Page>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="`${title}` | L" textWrap="true" />
<Label v-if="description" class="dialogDescription" :text="description" textWrap="true" />
<GridLayout rows="auto" columns="*, auto, auto" class="actionsContainer">
<MDButton :rippleColor="rippleColor" variant="text" col="1" class="action orkm" :text="`${cancelButtonText}` | L" @tap="$modal.close(false)" />
<MDButton :rippleColor="rippleColor" variant="text" col="2" class="action orkm" :text="`${okButtonText}` | L" @tap="$modal.close(true)" />
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application } from "@nativescript/core"
import {
Application
}
from "@nativescript/core"
export default {
props: ["title", "description", "cancelButtonText", "okButtonText"],
props: [ "title", "description", "cancelButtonText", "okButtonText" ],
computed: {
appTheme() {
return Application.systemAppearance()
},
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
return this.appTheme == "light" ?
"rgba(134,142,150,0.2)" :
"rgba(206,212,218,0.1)"
},
},
}

View file

@ -1,138 +1,73 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" />
<StackLayout
class="dialogListPicker"
orientation="horizontal"
horizontalAlignment="center"
>
<ListPicker
ref="hrPicker"
:items="hrs"
:selectedIndex="hrIndex"
@selectedIndexChange="setHrs"
></ListPicker>
<ListPicker
ref="minPicker"
:items="mins"
:selectedIndex="minIndex"
@selectedIndexChange="setMins"
></ListPicker>
</StackLayout>
<GridLayout rows="auto" columns="*, auto, auto" class="actionsContainer">
<MDButton
:rippleColor="rippleColor"
variant="text"
col="1"
class="action orkm"
text="CANCEL"
@tap="$modal.close(false)"
/>
<MDButton
:rippleColor="rippleColor"
variant="text"
col="2"
class="action orkm"
:text="action"
@tap="$modal.close(selectedTime)"
/>
</GridLayout>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="`${title}` | L" />
<StackLayout class="dialogListPicker" orientation="horizontal" horizontalAlignment="center">
<ListPicker ref="hrPicker" :items="hrsList" :selectedIndex="hrIndex" @selectedIndexChange="setHrs"></ListPicker>
<ListPicker ref="minPicker" :items="minsList" :selectedIndex="minIndex" @selectedIndexChange="setMins"></ListPicker>
</StackLayout>
</Page>
<GridLayout rows="auto" columns="*, auto, auto" class="actionsContainer">
<MDButton :rippleColor="rippleColor" variant="text" col="1" class="action orkm" :text="'CANCEL' | L" @tap="$modal.close(false)" />
<MDButton :rippleColor="rippleColor" variant="text" col="2" class="action orkm" :text="`${action}` | L" @tap="$modal.close(selectedTime)" />
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application } from "@nativescript/core"
import {
Application
}
from "@nativescript/core"
import {
localize
}
from "@nativescript/localize"
export default {
props: ["title", "selectedHr", "selectedMin", "action"],
props: [ "title", "selectedHr", "selectedMin", "action" ],
data() {
return {
hrs: [
"0 hr",
"1 hr",
"2 hr",
"3 hr",
"4 hr",
"5 hr",
"6 hr",
"7 hr",
"8 hr",
"9 hr",
"10 hr",
"11 hr",
"12 hr",
"13 hr",
"14 hr",
"15 hr",
"16 hr",
"17 hr",
"18 hr",
"19 hr",
"20 hr",
"21 hr",
"22 hr",
"23 hr",
],
mins: [
"0 min",
"1 min",
"2 min",
"3 min",
"4 min",
"5 min",
"6 min",
"7 min",
"8 min",
"9 min",
"10 min",
"15 min",
"20 min",
"25 min",
"30 min",
"35 min",
"40 min",
"45 min",
"50 min",
"55 min",
],
hrs: [],
mins: [],
selectedHrs: "00",
selectedMins: "00",
}
},
computed: {
hrsList() {
let h = [ ...Array( 24 ).keys() ]
this.hrs = h
return h.map( e => `${e} ${localize( 'hr' )}` )
},
minsList() {
let m = [ ...new Set( [ ...Array( 11 ).keys(), ...Array.from( Array( 12 ), ( _, x ) => x * 5 ) ] ) ]
this.mins = m
return m.map( e => `${e} ${localize( 'min' )}` )
},
hrIndex() {
let hr = this.selectedHr
if (hr.charAt(0) == "0") hr = hr.slice(-1) + " hr"
else hr = hr + " hr"
return this.hrs.indexOf(hr)
return this.hrs.indexOf( parseInt( this.selectedHr ) )
},
minIndex() {
let min = this.selectedMin
if (min.charAt(0) == "0") min = min.slice(-1) + " min"
else min = min + " min"
return this.mins.indexOf(min)
return this.mins.indexOf( parseInt( this.selectedMin ) )
},
appTheme() {
return Application.systemAppearance()
},
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
return this.appTheme == "light" ? "rgba(134,142,150,0.2)" : "rgba(206,212,218,0.1)"
},
selectedTime() {
return this.selectedHrs + ":" + this.selectedMins
},
},
methods: {
setHrs(args) {
let hr = "0" + this.hrs[args.object.selectedIndex]
this.selectedHrs = hr.slice(-5).slice(0, -3)
setHrs( args ) {
let hr = "0" + this.hrs[ args.object.selectedIndex ]
this.selectedHrs = hr.slice( -2 )
},
setMins(args) {
let min = "0" + this.mins[args.object.selectedIndex]
this.selectedMins = min.slice(-6).slice(0, -4)
setMins( args ) {
let min = "0" + this.mins[ args.object.selectedIndex ]
this.selectedMins = min.slice( -2 )
},
},
}

View file

@ -1,41 +1,30 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" />
<StackLayout class="dialogInput">
<TextField
@loaded="focusField"
:hint="hint ? hint : ''"
v-model="category"
autocapitalizationType="words"
/>
</StackLayout>
<GridLayout rows="auto" columns="*, auto, auto" class="actionsContainer">
<MDButton
:rippleColor="rippleColor"
variant="text"
col="1"
class="action orkm"
text="CANCEL"
@tap="$modal.close(false)"
/>
<MDButton
:rippleColor="rippleColor"
variant="text"
col="2"
class="action orkm"
:text="action"
@tap="$modal.close(category)"
/>
</GridLayout>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="`${title}` | L" textWrap='true' />
<StackLayout class="dialogInput">
<TextField @loaded="focusField" :hint="hint ? hint : ''" v-model="category" autocapitalizationType="words" />
</StackLayout>
</Page>
<GridLayout rows="auto" columns="*, auto, auto" class="actionsContainer">
<MDButton :rippleColor="rippleColor" variant="text" col="1" class="action orkm" :text="'CANCEL' | L" @tap="$modal.close(false)" />
<MDButton :rippleColor="rippleColor" variant="text" col="2" class="action orkm" :text="`${action}` | L" @tap="$modal.close(category)" />
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application, Utils } from "@nativescript/core"
import {
Application,
Utils
}
from "@nativescript/core"
import {
localize
}
from '@nativescript/localize'
export default {
props: ["title", "hint", "text", "action"],
props: [ "title", "hint", "text", "action" ],
data() {
return {
category: null,
@ -46,20 +35,18 @@ export default {
return Application.systemAppearance()
},
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
return this.appTheme == "light" ? "rgba(134,142,150,0.2)" : "rgba(206,212,218,0.1)"
},
},
methods: {
focusField(args) {
focusField( args ) {
args.object.focus()
setTimeout((e) => Utils.ad.showSoftInput(args.object.android), 1)
setTimeout( ( e ) => Utils.ad.showSoftInput( args.object.android ), 1 )
},
},
mounted() {
if (this.text) {
this.category = this.text
if ( this.text ) {
this.category = localize( this.text )
}
},
}

View file

@ -1,45 +1,31 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" />
<GridLayout rows="auto, auto" columns="*" class="actionsContainer">
<MDButton
:rippleColor="rippleColor"
:backgroundColor="backgroundColor"
row="0"
class="actionIcon"
src="res://photo"
text="Photo"
@tap="$modal.close('photo')"
/>
<MDButton
:rippleColor="rippleColor"
:backgroundColor="backgroundColor"
row="1"
class="actionIcon"
src="res://detail"
text="Recipe"
@tap="$modal.close('recipe')"
/>
</GridLayout>
</StackLayout>
</Page>
<Page>
<StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="`${title}` | L" />
<GridLayout rows="auto, auto" columns="*" class="actionsContainer">
<MDButton :rippleColor="rippleColor" :backgroundColor="backgroundColor" row="0" class="actionIcon" src="res://photo" :text="'Photo' | L" @tap="$modal.close('photo')" />
<MDButton :rippleColor="rippleColor" :backgroundColor="backgroundColor" row="1" class="actionIcon" src="res://detail" :text="'Recipe' | L" @tap="$modal.close('recipe')" />
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application } from "@nativescript/core"
import { mapState } from "vuex"
import {
Application
} from "@nativescript/core"
import {
mapState
} from "vuex"
export default {
props: ["title"],
props: [ "title" ],
computed: {
...mapState(["icon"]),
...mapState( [ "icon" ] ),
appTheme() {
return Application.systemAppearance()
},
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
return this.appTheme == "light" ? "rgba(134,142,150,0.2)" : "rgba(206,212,218,0.1)"
},
backgroundColor() {
return this.appTheme == "light" ? "#fff" : "#343a40"

206
app/i18n/en.default.json Normal file
View file

@ -0,0 +1,206 @@
{
"app.name": "EnRecipes",
"EnRecipes": "EnRecipes",
"Try Later": "Try Later",
"Favourites": "Favourites",
"Meal Planner": "Meal Planner",
"Categories": "Categories",
"DONE": "DONE",
"RENAME": "RENAME",
"Settings": "Settings",
"About": "About",
"Appetizers": "Appetizers",
"Barbecue": "Barbecue",
"Beverages": "Beverages",
"Breads": "Breads",
"Breakfast": "Breakfast",
"Desserts": "Desserts",
"Dinner": "Dinner",
"Drinks": "Drinks",
"Healthy": "Healthy",
"Lunch": "Lunch",
"Main dishes": "Main dishes",
"Meat": "Meat",
"Noodles": "Noodles",
"Pasta": "Pasta",
"Poultry": "Poultry",
"Rice": "Rice",
"Salads": "Salads",
"Sauces": "Sauces",
"Seafood": "Seafood",
"Side dishes": "Side dishes",
"Snacks": "Snacks",
"Soups": "Soups",
"Undefined": "Undefined",
"Vegan": "Vegan",
"Vegetarian": "Vegetarian",
"Serving": "Serving",
"Piece": "Piece",
"Teaspoon": "Teaspoon",
"Tablespoon": "Tablespoon",
"Fluid Ounce": "Fluid Ounce",
"Ounce": "Ounce",
"Pound": "Pound",
"Gram": "Gram",
"Kilogram": "Kilogram",
"Cup": "Cup",
"Gallon": "Gallon",
"Millilitre": "Millilitre",
"Litre": "Litre",
"Roll": "Roll",
"Patty": "Patty",
"Loaf": "Loaf",
"unit": "unit",
"Unit": "Unit",
"tsp": "tsp",
"dsp": "dsp",
"tbsp": "tbsp",
"fl oz": "fl oz",
"cup": "cup",
"pt": "pt",
"qt": "qt",
"gal": "gal",
"ml": "ml",
"l": "l",
"oz": "oz",
"lb": "lb",
"mg": "mg",
"g": "g",
"kg": "kg",
"cm": "cm",
"in": "in",
"leaf": "leaf",
"clove": "clove",
"piece": "piece",
"pinch": "pinch",
"drop": "drop",
"dozen": "dozen",
"stick": "stick",
"small": "small",
"medium": "medium",
"large": "large",
"Start adding your recipes!": "Start adding your recipes!",
"Use the plus button to add one": "Use the plus button to add one",
"Use the pencil button to add some ingredients": "Use the pencil button to add some ingredients",
"Use the pencil button to add some instructions": "Use the pencil button to add some instructions",
"Use the pencil button to add some combinations": "Use the pencil button to add some combinations",
"Use the pencil button to add some notes": "Use the pencil button to add some notes",
"All done!": "All done!",
"Recipes you mark as try later will be listed here": "Recipes you mark as try later will be listed here",
"No favourites yet": "No favourites yet",
"Recipes you mark as favourite will be listed here": "Recipes you mark as favourite will be listed here",
"Category looks empty": "Category looks empty",
"No recipes found": "No recipes found",
"Your search did not match any recipes": "Your search did not match any recipes",
"Your search did not match any recipes in this category": "Your search did not match any recipes in this category",
"Your search did not match any recipes in your favourites": "Your search did not match any recipes in your favourites",
"Your search did not match any recipes in your try later list": "Your search did not match any recipes in your try later list",
"Interface": "Interface",
"App language": "App language",
"System default": "System default",
"Theme": "Theme",
"Light": "Light",
"Dark": "Dark",
"Database": "Database",
"Export a full backup": "Export a full backup",
"Generates a zip file that contains all your data. This file can be imported back.": "Generates a zip file that contains all your data. This file can be imported back.",
"Import from backup": "Import from backup",
"Supports full backups exported by this app": "Supports full backups exported by this app",
"Version": "Version",
"View project on GitHub": "View project on GitHub",
"Join the Telegram group": "Join the Telegram group",
"for reporting issues, suggestions and feedback": "for reporting issues, suggestions and feedback",
"Author": "Author",
"Vishnu Raghav B": "Vishnu Raghav B",
"Follow on GitHub": "Follow on GitHub",
"Follow on Mastodon": "Follow on Mastodon",
"New recipe": "New recipe",
"Edit recipe": "Edit recipe",
"Title": "Title",
"Category": "Category",
"Preparation time": "Preparation time",
"Cooking time": "Cooking time",
"Yield quantity": "Yield quantity",
"Yield measured in": "Yield measured in",
"Ingredient": "Ingredient",
"Ingredients": "Ingredients",
"ADD INGREDIENT": "ADD INGREDIENT",
"Instruction": "Instruction",
"Instructions": "Instructions",
"ADD STEP": "ADD STEP",
"Combinations": "Combinations",
"ADD COMBINATION": "ADD COMBINATION",
"Note": "Note",
"Notes": "Notes",
"ADD NOTE": "ADD NOTE",
"Item": "Item",
"Step": "Step",
"Note": "Note",
"Sort by": "Sort by",
"Natural order": "Natural order",
"Duration": "Duration",
"Last modified": "Last modified",
"CANCEL": "CANCEL",
"REMOVE": "REMOVE",
"ADD": "ADD",
"Select a recipe": "Select a recipe",
"Nothing here! Add some recipes and try again.": "Nothing here! Add some recipes and try again.",
"My Healthy Recipe": "My Healthy Recipe",
"hr": "hr",
"min": "min",
"Photo": "Photo",
"Recipe": "Recipe",
"Overview": "Overview",
"Share": "Share",
"ADD NEW": "ADD NEW",
"SET": "SET",
"Restart required": "Restart required",
"RESTART": "RESTART",
"EnRecipes needs to be restarted for the theme change to take effect.": "EnRecipes needs to be restarted for the theme change to take effect.",
"EnRecipes needs to be restarted for the app language to take effect.": "EnRecipes needs to be restarted for the app language to take effect.",
"Grant permission": "Grant permission",
"EnRecipes requires storage permission in order to import your data from a previous backup.": "EnRecipes requires storage permission in order to import your data from a previous backup.",
"NOT NOW": "NOT NOW",
"CONTINUE": "CONTINUE",
"EnRecipes requires storage permission in order to backup your data to this device.": "EnRecipes requires storage permission in order to backup your data to this device.",
"Add at least one recipe to perform a backup": "Add at least one recipe to perform a backup",
"Added to Favourites": "Added to Favourites",
"Removed from Favourites": "Removed from Favourites",
"Added to Try Later": "Added to Try Later",
"Removed from Try Later": "Removed from Try Later",
"Delete recipe?": "Delete recipe?",
"DELETE": "DELETE",
"Are you sure you want to delete the recipe": "Are you sure you want to delete the recipe",
"Search": "Search",
"Rename category": "Rename category",
"Unsaved changes": "Unsaved changes",
"Are you sure you want to discard unsaved changes to this recipe?": "Are you sure you want to discard unsaved changes to this recipe?",
"DISCARD": "DISCARD",
"KEEP EDITING": "KEEP EDITING",
"Remove ingredient?": "Remove ingredient?",
"Remove instruction?": "Remove instruction?",
"Remove combination?": "Remove combination?",
"Remove note?": "Remove note?",
"New yield unit": "New yield unit",
"New category": "New category",
"Required": "Required",
"EnRecipes requires storage permission in order to set recipe photo.": "EnRecipes requires storage permission in order to set recipe photo.",
"Recipe photo": "Recipe photo",
"REPLACE PHOTO": "REPLACE PHOTO",
"Crop photo": "Crop photo",
"breakfast": "breakfast",
"lunch": "lunch",
"dinner": "dinner",
"snacks": "snacks",
"You tried this recipe:": "You tried this recipe:",
"today": "today",
"yesterday": "yesterday",
"days ago": "days ago",
"weeks ago": "weeks ago",
"months ago": "months ago",
"long time ago": "long time ago",
"Verifying...": "Verifying...",
"Import successful": "Import successful",
"Permission denied": "Permission denied",
"Shared via EnRecipes. Get it on Play Store or F-Droid.": "Shared via EnRecipes. Get it on Play Store or F-Droid."
}

206
app/i18n/ta.json Normal file
View file

@ -0,0 +1,206 @@
{
"app.name": "என்ரெசிபீஸ்",
"EnRecipes": "என்ரெசிபீஸ்",
"Try Later": "பின்னர் முயற்சிக்க",
"Favourites": "பிடித்தவை",
"Meal Planner": "உணவுத் திட்டம்",
"Categories": "வகைகள்",
"DONE": "முடிந்தது",
"RENAME": "மறுபெயரிடு",
"Settings": "அமைப்புகள்",
"About": "பற்றி",
"Appetizers": "பசித்தூண்டி",
"Barbecue": "பார்பிக்யூ",
"Beverages": "பானங்கள்",
"Breads": "ரொட்டிகள்",
"Breakfast": "காலை உணவு",
"Desserts": "இனிப்புகள்",
"Dinner": "இரவு உணவு",
"Drinks": "மதுபானம்",
"Healthy": "ஆரோக்கியமானவை",
"Lunch": "மதிய உணவு",
"Main dishes": "முக்கிய உணவுகள்",
"Meat": "இறைச்சி",
"Noodles": "நூடுல்ஸ்",
"Pasta": "பாஸ்தா",
"Poultry": "கோழி",
"Rice": "சோறு",
"Salads": "சாலடுகள்",
"Sauces": "சாஸ்கள்",
"Seafood": "கடல் உணவு",
"Side dishes": "தொடு கறிகள்",
"Snacks": "தின்பண்டங்கள்",
"Soups": "சூப்கள்",
"Undefined": "வரையறுக்கப்படாதவை",
"Vegan": "வேகன்",
"Vegetarian": "சைவம்",
"Serving": "கூறு",
"Piece": "துண்டு",
"Teaspoon": "டீஸ்பூன்",
"Tablespoon": "தேக்கரண்டி",
"Fluid Ounce": "திரவ அவுன்ஸ்",
"Ounce": "அவுன்ஸ்",
"Pound": "பவுண்டு",
"Gram": "கிராம்",
"Kilogram": "கிலோகிராம்",
"Cup": "கோப்பை",
"Gallon": "கேலன்",
"Millilitre": "மில்லிலிட்டர்",
"Litre": "லிட்டர்",
"Roll": "ரோல்",
"Patty": "வடை",
"Loaf": "ரொட்டித் துண்டு",
"unit": "அலகு",
"Unit": "அலகு",
"tsp": "டீஸ்பூன்",
"dsp": "இனிப்பு ஸ்பூன்",
"tbsp": "தேக்கரண்டி",
"fl oz": "திரவ அவுன்ஸ",
"cup": "கோப்பை",
"pt": "பைண்ட்",
"qt": "குவிண்ட்",
"gal": "கேலன்",
"ml": "மில்லிலிட்டர்",
"l": "லிட்டர்",
"oz": "அவுன்ஸ்",
"lb": "பவுண்டு",
"mg": "மில்லிகிராம்",
"g": "கிராம்",
"kg": "கிலோகிராம்",
"cm": "சென்டிமீட்டர்",
"in": "அங்குலம்",
"leaf": "இலை",
"clove": "பல்",
"piece": "துண்டு",
"pinch": "கிள்ளு",
"drop": "துளி",
"dozen": "டஜன்",
"stick": "குச்சி",
"small": "சிறிய",
"medium": "நடுத்தர",
"large": "பெரிய",
"Start adding your recipes!": "உங்கள் சமையல் குறிப்புகளைச் சேர்க்கத் தொடங்குங்கள்!",
"Use the plus button to add one": "ஒரு செய்முறையைச் சேர்க்க பிளஸ் பொத்தானைப் பயன்படுத்தவும்",
"Use the pencil button to add some ingredients": "தேவையான பொருட்களைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்",
"Use the pencil button to add some instructions": "செய்முறைகளைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்",
"Use the pencil button to add some combinations": "சில சேர்க்கைகளைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்",
"Use the pencil button to add some notes": "சில குறிப்புகளைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்",
"All done!": "அனைத்தும் செய்யப்பட்டுள்ளன!",
"Recipes you mark as try later will be listed here": "முயற்சிக்க வேண்டும் என நீங்கள் குறிக்கும் சமையல் குறிப்புகள் இங்கே பட்டியலிடப்படும்",
"No favourites yet": "இன்னும் பிடித்தவை எதுவும் இல்லை",
"Recipes you mark as favourite will be listed here": "நீங்கள் பிடித்ததாகக் குறிக்கும் சமையல் குறிப்புகள் இங்கே பட்டியலிடப்படும்",
"Category looks empty": "இந்த பிரிவில் எந்த சமையல் குறிப்புகளும் இல்லை",
"No recipes found": "எந்த சமையல் குறிப்புகளும் இல்லை",
"Your search did not match any recipes": "உங்கள் தேடல் எந்த சமையல் குறிப்புகளுக்கும் பொருந்தவில்லை",
"Your search did not match any recipes in this category": "உங்கள் தேடல் இந்த வகையில் எந்த சமையல் குறிப்புகளுடன் பொருந்தவில்லை",
"Your search did not match any recipes in your favourites": "உங்கள் தேடல் உங்களுக்கு பிடித்தவற்றில் எந்த சமையல் குறிப்புகளுடன் பொருந்தவில்லைை",
"Your search did not match any recipes in your try later list": "உங்கள் தேடல் நீங்கள் முயற்சிக்க வேண்டிய பட்டியலில் உள்ள எந்த சமையல் குறிப்புகளுடன் பொருந்தவில்லைை",
"Interface": "இடைமுகம்",
"App language": "பயன்பாட்டு மொழி",
"System default": "கணினி இயல்புநிலை",
"Theme": "தீம்",
"Light": "வெளிச்சம்",
"Dark": "இருள்",
"Database": "தரவுத்தளம்",
"Export a full backup": "முழு காப்புப்பிரதியை ஏற்றுமதி செய்க",
"Generates a zip file that contains all your data. This file can be imported back.": "இது உங்கள் எல்லா தரவையும் கொண்ட ஒரு ஜிப் கோப்பை உருவாக்குகிறது. இந்த கோப்பை மீண்டும் இறக்குமதி செய்யலாம்.",
"Import from backup": "காப்புப்பிரதியிலிருந்து இறக்குமதி செய்க",
"Supports full backups exported by this app": "இந்த பயன்பாட்டின் மூலம் ஏற்றுமதி செய்யப்பட்ட முழு காப்புப்பிரதிகளை ஆதரிக்கும்",
"Version": "பதிப்பு",
"View project on GitHub": "கிட்ஹப்பில் திட்டத்தைக் காண்க",
"Join the Telegram group": "டெலிகிராம் குழுவில் சேரவும்",
"for reporting issues, suggestions and feedback": "சிக்கல்கள், பரிந்துரைகள் மற்றும் கருத்துக்களைப் புகாரளிக்க",
"Author": "பயன்பாட்டு ஆசிரியர்",
"Vishnu Raghav B": "விஷ்ணு ராகவ் .பா",
"Follow on GitHub": "கிட்ஹப்பில் அவரைப் பின்தொடரவும்",
"Follow on Mastodon": "மஸ்டோடனில் அவரைப் பின்தொடரவும்",
"New recipe": "புதிய செய்முறை",
"Edit recipe": "செய்முறையைத் திருத்து",
"Title": "தலைப்பு",
"Category": "வகை",
"Preparation time": "தயாரிப்பு நேரம்",
"Cooking time": "சமைக்கும் நேரம்",
"Yield quantity": "மகசூல் அளவு",
"Yield measured in": "மகசூல் அலகு",
"Ingredient": "தேவையான பொருள்",
"Ingredients": "தேவையான பொருட்கள்",
"ADD INGREDIENT": "பொருளைச் சேர்",
"Instruction": "செய்முறை",
"Instructions": "செய்முறைகள்",
"ADD STEP": "செய்முறையைச் சேர்",
"Combinations": "சேர்க்கைகள்",
"ADD COMBINATION": "சேர்க்கையைச் சேர்",
"Note": "குறிப்பு",
"Notes": "குறிப்புகள்",
"ADD NOTE": "குறிப்பைச் சேர்",
"Item": "பொருள்",
"Step": "செயஂமுறை",
"Note": "குறிப்பு",
"Sort by": "வரிசை விதி",
"Natural order": "இயற்கை வரிசை",
"Duration": "நேரம்",
"Last modified": "கடைசியாக மாற்றப்பட்டது",
"CANCEL": "ரத்துசெய்",
"REMOVE": "அகற்று",
"ADD": "சேர்",
"Select a recipe": "ஒரு செய்முறையைத் தேர்ந்தெடுக்கவும்",
"Nothing here! Add some recipes and try again.": "இங்கு எதுவுமில்லை! சில சமையல் வகைகளைச் சேர்த்து மீண்டும் முயற்சிக்கவும்.",
"My Healthy Recipe": "எனது ஆரோக்கியமான செய்முறை",
"hr": "மணி",
"min": "நிமிடம்",
"Photo": "புகைப்படம்",
"Recipe": "செய்முறை",
"Overview": "கண்ணோட்டம்",
"Share": "பகிர்",
"ADD NEW": "புதியனவற்றை சேர்",
"SET": "அமை",
"Restart required": "மறுதொடக்கம் தேவை",
"RESTART": "மறுதொடக்கம்",
"EnRecipes needs to be restarted for the theme change to take effect.": "தீம் மாற்றம் நடைமுறைக்கு வருவதற்கு என்ரெசிபீஸ் ஐ மீண்டும் தொடங்க வேண்டும்.",
"EnRecipes needs to be restarted for the app language to take effect.": "பயன்பாட்டு மொழி நடைமுறைக்கு வருவதற்கு என்ரெசிபீஸ் ஐ மீண்டும் தொடங்க வேண்டும்.",
"Grant permission": "அனுமதி வழங்கவும்",
"EnRecipes requires storage permission in order to import your data from a previous backup.": "முந்தைய காப்புப்பிரதியிலிருந்து உங்கள் தரவை இறக்குமதி செய்ய என்ரெசிபீஸ்க்கு சேமிப்பு அனுமதி தேவைப்படுகிறது.",
"NOT NOW": "இப்போது இல்லை",
"CONTINUE": "தொடர்",
"EnRecipes requires storage permission in order to backup your data to this device.": "இந்தச் சாதனத்தில் உங்கள் தரவை காப்புப் பிரதி எடுக்க என்ரெசிபீஸ்க்கு சேமிப்பக அனுமதி தேவைப்படுகிறது.",
"Add at least one recipe to perform a backup": "காப்புப்பிரதி செய்ய குறைந்தபட்சம் ஒரு செய்முறையாவது சேர்க்கவும்",
"Added to Favourites": "பிடித்தவையில் சேர்க்கப்பட்டது",
"Removed from Favourites": "பிடித்தவற்றிலிருந்து அகற்றப்பட்டது",
"Added to Try Later": "பின்னர் முயற்சிக்க சேர்க்கப்பட்டது",
"Removed from Try Later": "பின்னர் முயற்சிப்பதில் இருந்து நீக்கப்பட்டது",
"Delete recipe?": "செய்முறையை நீக்கவா?",
"DELETE": "நீக்கு",
"Are you sure you want to delete the recipe": "இந்த செய்முறையை நீக்க விரும்புகிறீர்களா",
"Search": "தேடு",
"Rename category": "வகையை மறுபெயரிடுங்கள்",
"Unsaved changes": "சேமிக்கப்படாத மாற்றங்கள்",
"Are you sure you want to discard unsaved changes to this recipe?": "இந்த செய்முறையில் சேமிக்கப்படாத மாற்றங்களை நிராகரிக்க விரும்புகிறீர்களா?",
"DISCARD": "நிராகரி",
"KEEP EDITING": "தொடர்ந்து திருத்து",
"Remove ingredient?": "பொருளை அகற்றவா?",
"Remove instruction?": "செய்முறையை அகற்றவா?",
"Remove combination?": "சேர்க்கையை அகற்றவா?",
"Remove note?": "குறிப்பை அகற்றவா?",
"New yield unit": "புதிய மகசூல் அலகு",
"New category": "புதிய வகை",
"Required": "தேவையான",
"EnRecipes requires storage permission in order to set recipe photo.": "செய்முறை புகைப்படத்தை அமைக்க என்ரெசிபீஸ்க்கு சேமிப்பு அனுமதி தேவை.",
"Recipe photo": "செய்முறை புகைப்படம்",
"REPLACE PHOTO": "புகைப்படத்தை மாற்றவும்",
"Crop photo": "படத்தை வெட்டு",
"breakfast": "காலை உணவு",
"lunch": "மதிய உணவு",
"dinner": "இரவு உணவு",
"snacks": "சிற்றிடை உணவு",
"You tried this recipe:": "இந்த செய்முறையை முயற்சித்தீர்கள்:",
"today": "இன்று",
"yesterday": "நேற்று",
"days ago": "நாட்களுக்கு முன்பு",
"weeks ago": "வாரங்களுக்கு முன்பு",
"months ago": "மாதங்களுக்கு முன்பு",
"long time ago": "வெகுகாலத்திற்கு முன்பு",
"Verifying...": "சரிபார்க்கிறது...",
"Import successful": "வெற்றிகரமாக இறக்குமதி செய்யப்பட்டது",
"Permission denied": "அனுமதி மறுக்கப்பட்டது",
"Shared via EnRecipes. Get it on Play Store or F-Droid.": "என்ரெசிபீஸ் வழியாக பகிரப்பட்டது. அதை ப்ளே ஸ்டோர் அல்லது எஃப்-டிரயோடு இருந்து பெறுங்கள்."
}

View file

@ -1,3 +1,16 @@
import {
localize,
androidLaunchEventLocalizationHandler
} from '@nativescript/localize'
import {
on,
launchEvent
} from '@nativescript/core/application';
on(launchEvent, (args) => {
if (args.android) {
androidLaunchEventLocalizationHandler();
}
})
import Vue from "nativescript-vue"
import App from "./components/App"
import store from "./store"
@ -26,7 +39,9 @@ Vue.use(CalendarView)
import RadSideDrawer from "nativescript-ui-sidedrawer/vue"
Vue.use(RadSideDrawer)
import { CheckBox } from "@nstudio/nativescript-checkbox"
import {
CheckBox
} from "@nstudio/nativescript-checkbox"
Vue.registerElement("CheckBox", () => CheckBox, {
model: {
prop: "checked",
@ -35,6 +50,7 @@ Vue.registerElement("CheckBox", () => CheckBox, {
})
Vue.config.silent = TNS_ENV === "production"
Vue.filter('L', localize)
new Vue({
store,

View file

@ -15,17 +15,22 @@
android {
defaultConfig {
versionCode 2
versionName '1.0.1'
versionCode 3
versionName '1.1.2'
applicationId 'com.vishnuraghav.enrecipes'
minSdkVersion 21
generatedDensities = []
// ndk {
// abiFilters.clear()
// abiFilters.addAll(['x86','armeabi-v7a','arm64-v8a'])
// }
ndk {
abiFilters.clear()
abiFilters.addAll(['x86','armeabi-v7a','arm64-v8a'])
}
}
aaptOptions {
additionalParameters "--no-version-vectors"
}
// bundle {
// language {
// enableSplit = false
// }
// }
}

View file

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 687 B

After

Width:  |  Height:  |  Size: 687 B

View file

Before

Width:  |  Height:  |  Size: 567 B

After

Width:  |  Height:  |  Size: 567 B

View file

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 339 B

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 517 B

View file

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 197 B

View file

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

View file

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

View file

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 336 B

View file

Before

Width:  |  Height:  |  Size: 229 B

After

Width:  |  Height:  |  Size: 229 B

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

Before

Width:  |  Height:  |  Size: 302 B

After

Width:  |  Height:  |  Size: 302 B

View file

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 173 B

View file

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 303 B

View file

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 345 B

View file

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 324 B

View file

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

Before

Width:  |  Height:  |  Size: 300 B

After

Width:  |  Height:  |  Size: 300 B

View file

Before

Width:  |  Height:  |  Size: 161 B

After

Width:  |  Height:  |  Size: 161 B

View file

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

View file

Before

Width:  |  Height:  |  Size: 626 B

After

Width:  |  Height:  |  Size: 626 B

View file

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 547 B

View file

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 266 B

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View file

Before

Width:  |  Height:  |  Size: 165 B

After

Width:  |  Height:  |  Size: 165 B

View file

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 969 B

After

Width:  |  Height:  |  Size: 969 B

View file

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 434 B

View file

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View file

Before

Width:  |  Height:  |  Size: 165 B

After

Width:  |  Height:  |  Size: 165 B

View file

Before

Width:  |  Height:  |  Size: 979 B

After

Width:  |  Height:  |  Size: 979 B

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 818 B

After

Width:  |  Height:  |  Size: 818 B

View file

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 449 B

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 697 B

View file

Before

Width:  |  Height:  |  Size: 171 B

After

Width:  |  Height:  |  Size: 171 B

View file

Before

Width:  |  Height:  |  Size: 1,020 B

After

Width:  |  Height:  |  Size: 1,020 B

View file

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="_Shared_via_EnRecipes__Get_it_on_Play_Store_or_F_Droid__Z2oTOK1">"என்ரெசிபீஸ் வழியாக பகிரப்பட்டது. அதை ப்ளே ஸ்டோர் அல்லது எஃப்-டிரயோடு இருந்து பெறுங்கள்."</string>
<string name="_Permission_denied_Z7wlWK">"அனுமதி மறுக்கப்பட்டது"</string>
<string name="_Import_successful_2bLM3e">"வெற்றிகரமாக இறக்குமதி செய்யப்பட்டது"</string>
<string name="_Verifying____cS102">"சரிபார்க்கிறது..."</string>
<string name="_long_time_ago_1nwlFA">"வெகுகாலத்திற்கு முன்பு"</string>
<string name="_months_ago_Z1TsskA">"மாதங்களுக்கு முன்பு"</string>
<string name="_weeks_ago_Z2sT1GA">"வாரங்களுக்கு முன்பு"</string>
<string name="_days_ago_2iCYkm">"நாட்களுக்கு முன்பு"</string>
<string name="yesterday">"நேற்று"</string>
<string name="today">"இன்று"</string>
<string name="_You_tried_this_recipe__ZUJOl3">"இந்த செய்முறையை முயற்சித்தீர்கள்:"</string>
<string name="snacks">"சிற்றிடை உணவு"</string>
<string name="dinner">"இரவு உணவு"</string>
<string name="lunch">"மதிய உணவு"</string>
<string name="breakfast">"காலை உணவு"</string>
<string name="_Crop_photo_1VL04J">"படத்தை வெட்டு"</string>
<string name="_REPLACE_PHOTO_ZLHhwH">"புகைப்படத்தை மாற்றவும்"</string>
<string name="_Recipe_photo_eqm9B">"செய்முறை புகைப்படம்"</string>
<string name="_EnRecipes_requires_storage_permission_in_order_to_set_recipe_photo__23OnNT">"செய்முறை புகைப்படத்தை அமைக்க என்ரெசிபீஸ்க்கு சேமிப்பு அனுமதி தேவை."</string>
<string name="Required">"தேவையான"</string>
<string name="_New_category_Weg73">"புதிய வகை"</string>
<string name="_New_yield_unit_1Oevd2">"புதிய மகசூல் அலகு"</string>
<string name="_Remove_note__2wyMVE">"குறிப்பை அகற்றவா?"</string>
<string name="_Remove_combination__1dr2LS">"சேர்க்கையை அகற்றவா?"</string>
<string name="_Remove_instruction__vkJfi">"செய்முறையை அகற்றவா?"</string>
<string name="_Remove_ingredient__2puvMO">"பொருளை அகற்றவா?"</string>
<string name="_KEEP_EDITING_1o9uy3">"தொடர்ந்து திருத்து"</string>
<string name="DISCARD">"நிராகரி"</string>
<string name="_Are_you_sure_you_want_to_discard_unsaved_changes_to_this_recipe__Z1G7sVC">"இந்த செய்முறையில் சேமிக்கப்படாத மாற்றங்களை நிராகரிக்க விரும்புகிறீர்களா?"</string>
<string name="_Unsaved_changes_9fqb4">"சேமிக்கப்படாத மாற்றங்கள்"</string>
<string name="_Rename_category_Z1WzCco">"வகையை மறுபெயரிடுங்கள்"</string>
<string name="Search">"தேடு"</string>
<string name="_Are_you_sure_you_want_to_delete_the_recipe_29DH9w">"இந்த செய்முறையை நீக்க விரும்புகிறீர்களா"</string>
<string name="DELETE">"நீக்கு"</string>
<string name="_Delete_recipe__Z2tMYtE">"செய்முறையை நீக்கவா?"</string>
<string name="_Removed_from_Try_Later_1h5Bkp">"பின்னர் முயற்சிப்பதில் இருந்து நீக்கப்பட்டது"</string>
<string name="_Added_to_Try_Later_Z1gaF37">"பின்னர் முயற்சிக்க சேர்க்கப்பட்டது"</string>
<string name="_Removed_from_Favourites_Z5k7LR">"பிடித்தவற்றிலிருந்து அகற்றப்பட்டது"</string>
<string name="_Added_to_Favourites_2mzllO">"பிடித்தவையில் சேர்க்கப்பட்டது"</string>
<string name="_Add_at_least_one_recipe_to_perform_a_backup_17xYaH">"காப்புப்பிரதி செய்ய குறைந்தபட்சம் ஒரு செய்முறையாவது சேர்க்கவும்"</string>
<string name="_EnRecipes_requires_storage_permission_in_order_to_backup_your_data_to_this_device__2cbvIq">"இந்தச் சாதனத்தில் உங்கள் தரவை காப்புப் பிரதி எடுக்க என்ரெசிபீஸ்க்கு சேமிப்பக அனுமதி தேவைப்படுகிறது."</string>
<string name="CONTINUE">"தொடர்"</string>
<string name="_NOT_NOW_Z1HD7qX">"இப்போது இல்லை"</string>
<string name="_EnRecipes_requires_storage_permission_in_order_to_import_your_data_from_a_previous_backup__1CKjb7">"முந்தைய காப்புப்பிரதியிலிருந்து உங்கள் தரவை இறக்குமதி செய்ய என்ரெசிபீஸ்க்கு சேமிப்பு அனுமதி தேவைப்படுகிறது."</string>
<string name="_Grant_permission_Z1UQBuh">"அனுமதி வழங்கவும்"</string>
<string name="_EnRecipes_needs_to_be_restarted_for_the_app_language_to_take_effect__ZWGUtY">"பயன்பாட்டு மொழி நடைமுறைக்கு வருவதற்கு என்ரெசிபீஸ் ஐ மீண்டும் தொடங்க வேண்டும்."</string>
<string name="_EnRecipes_needs_to_be_restarted_for_the_theme_change_to_take_effect__2kU319">"தீம் மாற்றம் நடைமுறைக்கு வருவதற்கு என்ரெசிபீஸ் ஐ மீண்டும் தொடங்க வேண்டும்."</string>
<string name="RESTART">"மறுதொடக்கம்"</string>
<string name="_Restart_required_Z22YINL">"மறுதொடக்கம் தேவை"</string>
<string name="SET">"அமை"</string>
<string name="_ADD_NEW_Zv8FjM">"புதியனவற்றை சேர்"</string>
<string name="Share">"பகிர்"</string>
<string name="Overview">"கண்ணோட்டம்"</string>
<string name="Recipe">"செய்முறை"</string>
<string name="Photo">"புகைப்படம்"</string>
<string name="min">"நிமிடம்"</string>
<string name="hr">"மணி"</string>
<string name="_My_Healthy_Recipe_Z1EqB8f">"எனது ஆரோக்கியமான செய்முறை"</string>
<string name="_Nothing_here__Add_some_recipes_and_try_again__Z25A7Q">"இங்கு எதுவுமில்லை! சில சமையல் வகைகளைச் சேர்த்து மீண்டும் முயற்சிக்கவும்."</string>
<string name="_Select_a_recipe_2lL8x2">"ஒரு செய்முறையைத் தேர்ந்தெடுக்கவும்"</string>
<string name="ADD">"சேர்"</string>
<string name="REMOVE">"அகற்று"</string>
<string name="CANCEL">"ரத்துசெய்"</string>
<string name="_Last_modified_Z1yBAS3">"கடைசியாக மாற்றப்பட்டது"</string>
<string name="Duration">"நேரம்"</string>
<string name="_Natural_order_Z1O8On5">"இயற்கை வரிசை"</string>
<string name="_Sort_by_Zq01TF">"வரிசை விதி"</string>
<string name="Step">"செயஂமுறை"</string>
<string name="Item">"பொருள்"</string>
<string name="_ADD_NOTE_ZyNw1r">"குறிப்பைச் சேர்"</string>
<string name="Notes">"குறிப்புகள்"</string>
<string name="Note">"குறிப்பு"</string>
<string name="_ADD_COMBINATION_2q9HWq">"சேர்க்கையைச் சேர்"</string>
<string name="Combinations">"சேர்க்கைகள்"</string>
<string name="_ADD_STEP_ZyMPOe">"செய்முறையைச் சேர்"</string>
<string name="Instructions">"செய்முறைகள்"</string>
<string name="Instruction">"செய்முறை"</string>
<string name="_ADD_INGREDIENT_29sSLU">"பொருளைச் சேர்"</string>
<string name="Ingredients">"தேவையான பொருட்கள்"</string>
<string name="Ingredient">"தேவையான பொருள்"</string>
<string name="_Yield_measured_in_2OoRT">"மகசூல் அலகு"</string>
<string name="_Yield_quantity_ZHx3jw">"மகசூல் அளவு"</string>
<string name="_Cooking_time_2w2fEw">"சமைக்கும் நேரம்"</string>
<string name="_Preparation_time_15BfxT">"தயாரிப்பு நேரம்"</string>
<string name="Category">"வகை"</string>
<string name="Title">"தலைப்பு"</string>
<string name="_Edit_recipe_ZPvwDP">"செய்முறையைத் திருத்து"</string>
<string name="_New_recipe_Z1Y2YKJ">"புதிய செய்முறை"</string>
<string name="_Follow_on_Mastodon_Z1v6e4V">"மஸ்டோடனில் அவரைப் பின்தொடரவும்"</string>
<string name="_Follow_on_GitHub_Z1GjptH">"கிட்ஹப்பில் அவரைப் பின்தொடரவும்"</string>
<string name="_Vishnu_Raghav_B_1qnRnC">"விஷ்ணு ராகவ் .பா"</string>
<string name="Author">"பயன்பாட்டு ஆசிரியர்"</string>
<string name="_for_reporting_issues__suggestions_and_feedback_Z1JUmyE">"சிக்கல்கள், பரிந்துரைகள் மற்றும் கருத்துக்களைப் புகாரளிக்க"</string>
<string name="_Join_the_Telegram_group_ZAXQgc">"டெலிகிராம் குழுவில் சேரவும்"</string>
<string name="_View_project_on_GitHub_1hrJ0U">"கிட்ஹப்பில் திட்டத்தைக் காண்க"</string>
<string name="Version">"பதிப்பு"</string>
<string name="_Supports_full_backups_exported_by_this_app_ZYs4f2">"இந்த பயன்பாட்டின் மூலம் ஏற்றுமதி செய்யப்பட்ட முழு காப்புப்பிரதிகளை ஆதரிக்கும்"</string>
<string name="_Import_from_backup_Z28HsC0">"காப்புப்பிரதியிலிருந்து இறக்குமதி செய்க"</string>
<string name="_Generates_a_zip_file_that_contains_all_your_data__This_file_can_be_imported_back__Z19m3xY">"இது உங்கள் எல்லா தரவையும் கொண்ட ஒரு ஜிப் கோப்பை உருவாக்குகிறது. இந்த கோப்பை மீண்டும் இறக்குமதி செய்யலாம்."</string>
<string name="_Export_a_full_backup_TJfra">"முழு காப்புப்பிரதியை ஏற்றுமதி செய்க"</string>
<string name="Database">"தரவுத்தளம்"</string>
<string name="Dark">"இருள்"</string>
<string name="Light">"வெளிச்சம்"</string>
<string name="Theme">"தீம்"</string>
<string name="_System_default_Z1jruVS">"கணினி இயல்புநிலை"</string>
<string name="_App_language_DzFsk">"பயன்பாட்டு மொழி"</string>
<string name="Interface">"இடைமுகம்"</string>
<string name="_Your_search_did_not_match_any_recipes_in_your_try_later_list_Z2wWbA1">"உங்கள் தேடல் நீங்கள் முயற்சிக்க வேண்டிய பட்டியலில் உள்ள எந்த சமையல் குறிப்புகளுடன் பொருந்தவில்லைை"</string>
<string name="_Your_search_did_not_match_any_recipes_in_your_favourites_1ylvHN">"உங்கள் தேடல் உங்களுக்கு பிடித்தவற்றில் எந்த சமையல் குறிப்புகளுடன் பொருந்தவில்லைை"</string>
<string name="_Your_search_did_not_match_any_recipes_in_this_category_P7J4V">"உங்கள் தேடல் இந்த வகையில் எந்த சமையல் குறிப்புகளுடன் பொருந்தவில்லை"</string>
<string name="_Your_search_did_not_match_any_recipes_Z1eppHH">"உங்கள் தேடல் எந்த சமையல் குறிப்புகளுக்கும் பொருந்தவில்லை"</string>
<string name="_No_recipes_found_Z125IxD">"எந்த சமையல் குறிப்புகளும் இல்லை"</string>
<string name="_Category_looks_empty_ZAK5qU">"இந்த பிரிவில் எந்த சமையல் குறிப்புகளும் இல்லை"</string>
<string name="_Recipes_you_mark_as_favourite_will_be_listed_here_Z1iIHgY">"நீங்கள் பிடித்ததாகக் குறிக்கும் சமையல் குறிப்புகள் இங்கே பட்டியலிடப்படும்"</string>
<string name="_No_favourites_yet_aPSoG">"இன்னும் பிடித்தவை எதுவும் இல்லை"</string>
<string name="_Recipes_you_mark_as_try_later_will_be_listed_here_Z1ITwPV">"முயற்சிக்க வேண்டும் என நீங்கள் குறிக்கும் சமையல் குறிப்புகள் இங்கே பட்டியலிடப்படும்"</string>
<string name="_All_done__72KYl">"அனைத்தும் செய்யப்பட்டுள்ளன!"</string>
<string name="_Use_the_pencil_button_to_add_some_notes_Z1wyyeb">"சில குறிப்புகளைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்"</string>
<string name="_Use_the_pencil_button_to_add_some_combinations_6NhFS">"சில சேர்க்கைகளைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்"</string>
<string name="_Use_the_pencil_button_to_add_some_instructions_ZAi0PH">"செய்முறைகளைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்"</string>
<string name="_Use_the_pencil_button_to_add_some_ingredients_Z1xOX8S">"தேவையான பொருட்களைச் சேர்க்க பென்சில் பொத்தானைப் பயன்படுத்தவும்"</string>
<string name="_Use_the_plus_button_to_add_one_1h20ms">"ஒரு செய்முறையைச் சேர்க்க பிளஸ் பொத்தானைப் பயன்படுத்தவும்"</string>
<string name="_Start_adding_your_recipes__rwnVV">"உங்கள் சமையல் குறிப்புகளைச் சேர்க்கத் தொடங்குங்கள்!"</string>
<string name="large">"பெரிய"</string>
<string name="medium">"நடுத்தர"</string>
<string name="small">"சிறிய"</string>
<string name="stick">"குச்சி"</string>
<string name="dozen">"டஜன்"</string>
<string name="drop">"துளி"</string>
<string name="pinch">"கிள்ளு"</string>
<string name="piece">"துண்டு"</string>
<string name="clove">"பல்"</string>
<string name="leaf">"இலை"</string>
<string name="in">"அங்குலம்"</string>
<string name="cm">"சென்டிமீட்டர்"</string>
<string name="kg">"கிலோகிராம்"</string>
<string name="g">"கிராம்"</string>
<string name="mg">"மில்லிகிராம்"</string>
<string name="lb">"பவுண்டு"</string>
<string name="oz">"அவுன்ஸ்"</string>
<string name="l">"லிட்டர்"</string>
<string name="ml">"மில்லிலிட்டர்"</string>
<string name="gal">"கேலன்"</string>
<string name="qt">"குவிண்ட்"</string>
<string name="pt">"பைண்ட்"</string>
<string name="cup">"கோப்பை"</string>
<string name="_fl_oz_72kqu">"திரவ அவுன்ஸ"</string>
<string name="tbsp">"தேக்கரண்டி"</string>
<string name="dsp">"இனிப்பு ஸ்பூன்"</string>
<string name="tsp">"டீஸ்பூன்"</string>
<string name="Unit">"அலகு"</string>
<string name="unit">"அலகு"</string>
<string name="Loaf">"ரொட்டித் துண்டு"</string>
<string name="Patty">"வடை"</string>
<string name="Roll">"ரோல்"</string>
<string name="Litre">"லிட்டர்"</string>
<string name="Millilitre">"மில்லிலிட்டர்"</string>
<string name="Gallon">"கேலன்"</string>
<string name="Cup">"கோப்பை"</string>
<string name="Kilogram">"கிலோகிராம்"</string>
<string name="Gram">"கிராம்"</string>
<string name="Pound">"பவுண்டு"</string>
<string name="Ounce">"அவுன்ஸ்"</string>
<string name="_Fluid_Ounce_bd10L">"திரவ அவுன்ஸ்"</string>
<string name="Tablespoon">"தேக்கரண்டி"</string>
<string name="Teaspoon">"டீஸ்பூன்"</string>
<string name="Piece">"துண்டு"</string>
<string name="Serving">"கூறு"</string>
<string name="Vegetarian">"சைவம்"</string>
<string name="Vegan">"வேகன்"</string>
<string name="Undefined">"வரையறுக்கப்படாதவை"</string>
<string name="Soups">"சூப்கள்"</string>
<string name="Snacks">"தின்பண்டங்கள்"</string>
<string name="_Side_dishes_Z1Et4Vg">"தொடு கறிகள்"</string>
<string name="Seafood">"கடல் உணவு"</string>
<string name="Sauces">"சாஸ்கள்"</string>
<string name="Salads">"சாலடுகள்"</string>
<string name="Rice">"சோறு"</string>
<string name="Poultry">"கோழி"</string>
<string name="Pasta">"பாஸ்தா"</string>
<string name="Noodles">"நூடுல்ஸ்"</string>
<string name="Meat">"இறைச்சி"</string>
<string name="_Main_dishes_u6RGQ">"முக்கிய உணவுகள்"</string>
<string name="Lunch">"மதிய உணவு"</string>
<string name="Healthy">"ஆரோக்கியமானவை"</string>
<string name="Drinks">"மதுபானம்"</string>
<string name="Dinner">"இரவு உணவு"</string>
<string name="Desserts">"இனிப்புகள்"</string>
<string name="Breakfast">"காலை உணவு"</string>
<string name="Breads">"ரொட்டிகள்"</string>
<string name="Beverages">"பானங்கள்"</string>
<string name="Barbecue">"பார்பிக்யூ"</string>
<string name="Appetizers">"பசித்தூண்டி"</string>
<string name="About">"பற்றி"</string>
<string name="Settings">"அமைப்புகள்"</string>
<string name="RENAME">"மறுபெயரிடு"</string>
<string name="DONE">"முடிந்தது"</string>
<string name="Categories">"வகைகள்"</string>
<string name="_Meal_Planner_2aq4oj">"உணவுத் திட்டம்"</string>
<string name="Favourites">"பிடித்தவை"</string>
<string name="_Try_Later_If7PG">"பின்னர் முயற்சிக்க"</string>
<string name="EnRecipes">"என்ரெசிபீஸ்"</string>
<string name="_app_name_1k3Sbz">"என்ரெசிபீஸ்"</string>
<string name="app_name">"என்ரெசிபீஸ்"</string>
<string name="title_activity_kimera">"என்ரெசிபீஸ்"</string>
</resources>

View file

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="_Shared_via_EnRecipes__Get_it_on_Play_Store_or_F_Droid__Z2oTOK1">"Shared via EnRecipes. Get it on Play Store or F-Droid."</string>
<string name="_Permission_denied_Z7wlWK">"Permission denied"</string>
<string name="_Import_successful_2bLM3e">"Import successful"</string>
<string name="_Verifying____cS102">"Verifying..."</string>
<string name="_long_time_ago_1nwlFA">"long time ago"</string>
<string name="_months_ago_Z1TsskA">"months ago"</string>
<string name="_weeks_ago_Z2sT1GA">"weeks ago"</string>
<string name="_days_ago_2iCYkm">"days ago"</string>
<string name="yesterday">"yesterday"</string>
<string name="today">"today"</string>
<string name="_You_tried_this_recipe__ZUJOl3">"You tried this recipe:"</string>
<string name="snacks">"snacks"</string>
<string name="dinner">"dinner"</string>
<string name="lunch">"lunch"</string>
<string name="breakfast">"breakfast"</string>
<string name="_Crop_photo_1VL04J">"Crop photo"</string>
<string name="_REPLACE_PHOTO_ZLHhwH">"REPLACE PHOTO"</string>
<string name="_Recipe_photo_eqm9B">"Recipe photo"</string>
<string name="_EnRecipes_requires_storage_permission_in_order_to_set_recipe_photo__23OnNT">"EnRecipes requires storage permission in order to set recipe photo."</string>
<string name="Required">"Required"</string>
<string name="_New_category_Weg73">"New category"</string>
<string name="_New_yield_unit_1Oevd2">"New yield unit"</string>
<string name="_Remove_note__2wyMVE">"Remove note?"</string>
<string name="_Remove_combination__1dr2LS">"Remove combination?"</string>
<string name="_Remove_instruction__vkJfi">"Remove instruction?"</string>
<string name="_Remove_ingredient__2puvMO">"Remove ingredient?"</string>
<string name="_KEEP_EDITING_1o9uy3">"KEEP EDITING"</string>
<string name="DISCARD">"DISCARD"</string>
<string name="_Are_you_sure_you_want_to_discard_unsaved_changes_to_this_recipe__Z1G7sVC">"Are you sure you want to discard unsaved changes to this recipe?"</string>
<string name="_Unsaved_changes_9fqb4">"Unsaved changes"</string>
<string name="_Rename_category_Z1WzCco">"Rename category"</string>
<string name="Search">"Search"</string>
<string name="_Are_you_sure_you_want_to_delete_the_recipe_29DH9w">"Are you sure you want to delete the recipe"</string>
<string name="DELETE">"DELETE"</string>
<string name="_Delete_recipe__Z2tMYtE">"Delete recipe?"</string>
<string name="_Removed_from_Try_Later_1h5Bkp">"Removed from Try Later"</string>
<string name="_Added_to_Try_Later_Z1gaF37">"Added to Try Later"</string>
<string name="_Removed_from_Favourites_Z5k7LR">"Removed from Favourites"</string>
<string name="_Added_to_Favourites_2mzllO">"Added to Favourites"</string>
<string name="_Add_at_least_one_recipe_to_perform_a_backup_17xYaH">"Add at least one recipe to perform a backup"</string>
<string name="_EnRecipes_requires_storage_permission_in_order_to_backup_your_data_to_this_device__2cbvIq">"EnRecipes requires storage permission in order to backup your data to this device."</string>
<string name="CONTINUE">"CONTINUE"</string>
<string name="_NOT_NOW_Z1HD7qX">"NOT NOW"</string>
<string name="_EnRecipes_requires_storage_permission_in_order_to_import_your_data_from_a_previous_backup__1CKjb7">"EnRecipes requires storage permission in order to import your data from a previous backup."</string>
<string name="_Grant_permission_Z1UQBuh">"Grant permission"</string>
<string name="_EnRecipes_needs_to_be_restarted_for_the_app_language_to_take_effect__ZWGUtY">"EnRecipes needs to be restarted for the app language to take effect."</string>
<string name="_EnRecipes_needs_to_be_restarted_for_the_theme_change_to_take_effect__2kU319">"EnRecipes needs to be restarted for the theme change to take effect."</string>
<string name="RESTART">"RESTART"</string>
<string name="_Restart_required_Z22YINL">"Restart required"</string>
<string name="SET">"SET"</string>
<string name="_ADD_NEW_Zv8FjM">"ADD NEW"</string>
<string name="Share">"Share"</string>
<string name="Overview">"Overview"</string>
<string name="Recipe">"Recipe"</string>
<string name="Photo">"Photo"</string>
<string name="min">"min"</string>
<string name="hr">"hr"</string>
<string name="_My_Healthy_Recipe_Z1EqB8f">"My Healthy Recipe"</string>
<string name="_Nothing_here__Add_some_recipes_and_try_again__Z25A7Q">"Nothing here! Add some recipes and try again."</string>
<string name="_Select_a_recipe_2lL8x2">"Select a recipe"</string>
<string name="ADD">"ADD"</string>
<string name="REMOVE">"REMOVE"</string>
<string name="CANCEL">"CANCEL"</string>
<string name="_Last_modified_Z1yBAS3">"Last modified"</string>
<string name="Duration">"Duration"</string>
<string name="_Natural_order_Z1O8On5">"Natural order"</string>
<string name="_Sort_by_Zq01TF">"Sort by"</string>
<string name="Step">"Step"</string>
<string name="Item">"Item"</string>
<string name="_ADD_NOTE_ZyNw1r">"ADD NOTE"</string>
<string name="Notes">"Notes"</string>
<string name="Note">"Note"</string>
<string name="_ADD_COMBINATION_2q9HWq">"ADD COMBINATION"</string>
<string name="Combinations">"Combinations"</string>
<string name="_ADD_STEP_ZyMPOe">"ADD STEP"</string>
<string name="Instructions">"Instructions"</string>
<string name="Instruction">"Instruction"</string>
<string name="_ADD_INGREDIENT_29sSLU">"ADD INGREDIENT"</string>
<string name="Ingredients">"Ingredients"</string>
<string name="Ingredient">"Ingredient"</string>
<string name="_Yield_measured_in_2OoRT">"Yield measured in"</string>
<string name="_Yield_quantity_ZHx3jw">"Yield quantity"</string>
<string name="_Cooking_time_2w2fEw">"Cooking time"</string>
<string name="_Preparation_time_15BfxT">"Preparation time"</string>
<string name="Category">"Category"</string>
<string name="Title">"Title"</string>
<string name="_Edit_recipe_ZPvwDP">"Edit recipe"</string>
<string name="_New_recipe_Z1Y2YKJ">"New recipe"</string>
<string name="_Follow_on_Mastodon_Z1v6e4V">"Follow on Mastodon"</string>
<string name="_Follow_on_GitHub_Z1GjptH">"Follow on GitHub"</string>
<string name="_Vishnu_Raghav_B_1qnRnC">"Vishnu Raghav B"</string>
<string name="Author">"Author"</string>
<string name="_for_reporting_issues__suggestions_and_feedback_Z1JUmyE">"for reporting issues, suggestions and feedback"</string>
<string name="_Join_the_Telegram_group_ZAXQgc">"Join the Telegram group"</string>
<string name="_View_project_on_GitHub_1hrJ0U">"View project on GitHub"</string>
<string name="Version">"Version"</string>
<string name="_Supports_full_backups_exported_by_this_app_ZYs4f2">"Supports full backups exported by this app"</string>
<string name="_Import_from_backup_Z28HsC0">"Import from backup"</string>
<string name="_Generates_a_zip_file_that_contains_all_your_data__This_file_can_be_imported_back__Z19m3xY">"Generates a zip file that contains all your data. This file can be imported back."</string>
<string name="_Export_a_full_backup_TJfra">"Export a full backup"</string>
<string name="Database">"Database"</string>
<string name="Dark">"Dark"</string>
<string name="Light">"Light"</string>
<string name="Theme">"Theme"</string>
<string name="_System_default_Z1jruVS">"System default"</string>
<string name="_App_language_DzFsk">"App language"</string>
<string name="Interface">"Interface"</string>
<string name="_Your_search_did_not_match_any_recipes_in_your_try_later_list_Z2wWbA1">"Your search did not match any recipes in your try later list"</string>
<string name="_Your_search_did_not_match_any_recipes_in_your_favourites_1ylvHN">"Your search did not match any recipes in your favourites"</string>
<string name="_Your_search_did_not_match_any_recipes_in_this_category_P7J4V">"Your search did not match any recipes in this category"</string>
<string name="_Your_search_did_not_match_any_recipes_Z1eppHH">"Your search did not match any recipes"</string>
<string name="_No_recipes_found_Z125IxD">"No recipes found"</string>
<string name="_Category_looks_empty_ZAK5qU">"Category looks empty"</string>
<string name="_Recipes_you_mark_as_favourite_will_be_listed_here_Z1iIHgY">"Recipes you mark as favourite will be listed here"</string>
<string name="_No_favourites_yet_aPSoG">"No favourites yet"</string>
<string name="_Recipes_you_mark_as_try_later_will_be_listed_here_Z1ITwPV">"Recipes you mark as try later will be listed here"</string>
<string name="_All_done__72KYl">"All done!"</string>
<string name="_Use_the_pencil_button_to_add_some_notes_Z1wyyeb">"Use the pencil button to add some notes"</string>
<string name="_Use_the_pencil_button_to_add_some_combinations_6NhFS">"Use the pencil button to add some combinations"</string>
<string name="_Use_the_pencil_button_to_add_some_instructions_ZAi0PH">"Use the pencil button to add some instructions"</string>
<string name="_Use_the_pencil_button_to_add_some_ingredients_Z1xOX8S">"Use the pencil button to add some ingredients"</string>
<string name="_Use_the_plus_button_to_add_one_1h20ms">"Use the plus button to add one"</string>
<string name="_Start_adding_your_recipes__rwnVV">"Start adding your recipes!"</string>
<string name="large">"large"</string>
<string name="medium">"medium"</string>
<string name="small">"small"</string>
<string name="stick">"stick"</string>
<string name="dozen">"dozen"</string>
<string name="drop">"drop"</string>
<string name="pinch">"pinch"</string>
<string name="piece">"piece"</string>
<string name="clove">"clove"</string>
<string name="leaf">"leaf"</string>
<string name="in">"in"</string>
<string name="cm">"cm"</string>
<string name="kg">"kg"</string>
<string name="g">"g"</string>
<string name="mg">"mg"</string>
<string name="lb">"lb"</string>
<string name="oz">"oz"</string>
<string name="l">"l"</string>
<string name="ml">"ml"</string>
<string name="gal">"gal"</string>
<string name="qt">"qt"</string>
<string name="pt">"pt"</string>
<string name="cup">"cup"</string>
<string name="_fl_oz_72kqu">"fl oz"</string>
<string name="tbsp">"tbsp"</string>
<string name="dsp">"dsp"</string>
<string name="tsp">"tsp"</string>
<string name="Unit">"Unit"</string>
<string name="unit">"unit"</string>
<string name="Loaf">"Loaf"</string>
<string name="Patty">"Patty"</string>
<string name="Roll">"Roll"</string>
<string name="Litre">"Litre"</string>
<string name="Millilitre">"Millilitre"</string>
<string name="Gallon">"Gallon"</string>
<string name="Cup">"Cup"</string>
<string name="Kilogram">"Kilogram"</string>
<string name="Gram">"Gram"</string>
<string name="Pound">"Pound"</string>
<string name="Ounce">"Ounce"</string>
<string name="_Fluid_Ounce_bd10L">"Fluid Ounce"</string>
<string name="Tablespoon">"Tablespoon"</string>
<string name="Teaspoon">"Teaspoon"</string>
<string name="Piece">"Piece"</string>
<string name="Serving">"Serving"</string>
<string name="Vegetarian">"Vegetarian"</string>
<string name="Vegan">"Vegan"</string>
<string name="Undefined">"Undefined"</string>
<string name="Soups">"Soups"</string>
<string name="Snacks">"Snacks"</string>
<string name="_Side_dishes_Z1Et4Vg">"Side dishes"</string>
<string name="Seafood">"Seafood"</string>
<string name="Sauces">"Sauces"</string>
<string name="Salads">"Salads"</string>
<string name="Rice">"Rice"</string>
<string name="Poultry">"Poultry"</string>
<string name="Pasta">"Pasta"</string>
<string name="Noodles">"Noodles"</string>
<string name="Meat">"Meat"</string>
<string name="_Main_dishes_u6RGQ">"Main dishes"</string>
<string name="Lunch">"Lunch"</string>
<string name="Healthy">"Healthy"</string>
<string name="Drinks">"Drinks"</string>
<string name="Dinner">"Dinner"</string>
<string name="Desserts">"Desserts"</string>
<string name="Breakfast">"Breakfast"</string>
<string name="Breads">"Breads"</string>
<string name="Beverages">"Beverages"</string>
<string name="Barbecue">"Barbecue"</string>
<string name="Appetizers">"Appetizers"</string>
<string name="About">"About"</string>
<string name="Settings">"Settings"</string>
<string name="RENAME">"RENAME"</string>
<string name="DONE">"DONE"</string>
<string name="Categories">"Categories"</string>
<string name="_Meal_Planner_2aq4oj">"Meal Planner"</string>
<string name="Favourites">"Favourites"</string>
<string name="_Try_Later_If7PG">"Try Later"</string>
<string name="EnRecipes">"EnRecipes"</string>
<string name="_app_name_1k3Sbz">"EnRecipes"</string>
<string name="app_name">"EnRecipes"</string>
<string name="title_activity_kimera">"EnRecipes"</string>
</resources>

View file

@ -1,7 +1,11 @@
import Vue from "vue"
import Vuex from "vuex"
import { Couchbase } from "nativescript-couchbase-plugin"
import { getFileAccess } from "@nativescript/core"
import {
Couchbase
} from "nativescript-couchbase-plugin"
import {
getFileAccess
} from "@nativescript/core"
const EnRecipesDB = new Couchbase("EnRecipes")
const userCategoriesDB = new Couchbase("userCategories")
const userYieldUnitsDB = new Couchbase("userYieldUnits")
@ -11,7 +15,7 @@ Vue.use(Vuex)
const defaultCategories = [
"Appetizers",
"BBQ",
"Barbecue",
"Beverages",
"Breads",
"Breakfast",
@ -81,6 +85,7 @@ export default new Vuex.Store({
"in",
"leaf",
"clove",
"piece",
"pinch",
"drop",
"dozen",
@ -135,17 +140,29 @@ export default new Vuex.Store({
outline: "\ueb07",
calendar: "\uec55",
today: "\ue97c",
globe: "\uea5a",
},
currentComponent: "EnRecipes",
language: [{
locale: 'en',
title: 'English'
}, {
locale: 'ta',
title: 'தமிழ்'
}, ]
},
mutations: {
initializeRecipes(state) {
EnRecipesDB.query({ select: [] }).forEach((recipe) => {
EnRecipesDB.query({
select: []
}).forEach((recipe) => {
state.recipes.push(recipe)
})
},
initializeCategories(state) {
let isCategoriesStored = userCategoriesDB.query({ select: [] }).length
let isCategoriesStored = userCategoriesDB.query({
select: []
}).length
if (isCategoriesStored) {
state.userCategories = userCategoriesDB.getDocument(
"userCategories"
@ -155,8 +172,9 @@ export default new Vuex.Store({
categoriesWithRecipes.includes(e)
)
} else {
userCategoriesDB.createDocument(
{ userCategories: [] },
userCategoriesDB.createDocument({
userCategories: []
},
"userCategories"
)
}
@ -164,7 +182,9 @@ export default new Vuex.Store({
state.categories.sort()
},
initializeYieldUnits(state) {
let isYieldUnitsStored = userYieldUnitsDB.query({ select: [] }).length
let isYieldUnitsStored = userYieldUnitsDB.query({
select: []
}).length
if (isYieldUnitsStored) {
state.userYieldUnits = userYieldUnitsDB.getDocument(
"userYieldUnits"
@ -174,19 +194,24 @@ export default new Vuex.Store({
yieldUnitsWithRecipes.includes(e)
)
} else {
userYieldUnitsDB.createDocument(
{ userYieldUnits: [] },
userYieldUnitsDB.createDocument({
userYieldUnits: []
},
"userYieldUnits"
)
}
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
},
initializeMealPlans(state) {
let isMealPlansDBStored = mealPlansDB.query({ select: [] }).length
let isMealPlansDBStored = mealPlansDB.query({
select: []
}).length
if (isMealPlansDBStored) {
state.mealPlans = mealPlansDB.getDocument("mealPlans").mealPlans
} else {
mealPlansDB.createDocument({ mealPlans: [] }, "mealPlans")
mealPlansDB.createDocument({
mealPlans: []
}, "mealPlans")
}
},
@ -196,18 +221,23 @@ export default new Vuex.Store({
localRecipesIDs = state.recipes.map((e) => e.id)
partition = recipes.reduce(
(result, recipe, i) => {
localRecipesIDs.indexOf(recipe.id) < 0
? result[0].push(recipe) // create candidates
: result[1].push(recipe) // update candidates
localRecipesIDs.indexOf(recipe.id) < 0 ?
result[0].push(recipe) // create candidates
:
result[1].push(recipe) // update candidates
return result
},
[[], []]
[
[],
[]
]
)
if (partition[0].length) createDocuments(partition[0])
if (partition[1].length) updateDocuments(partition[1])
} else {
createDocuments(recipes)
}
function getUpdatedData(data) {
return data.map((recipe) => {
let r = Object.assign({}, recipe)
@ -219,6 +249,7 @@ export default new Vuex.Store({
return r
})
}
function createDocuments(data) {
data = getUpdatedData(data)
state.recipes = [...state.recipes, ...data]
@ -226,6 +257,7 @@ export default new Vuex.Store({
EnRecipesDB.createDocument(recipe, recipe.id)
})
}
function updateDocuments(data) {
data = getUpdatedData(data)
data.forEach((recipe) => {
@ -261,9 +293,9 @@ export default new Vuex.Store({
importMealPlans(state, mealPlans) {
let newMealPlans = mealPlans.filter(
(e) =>
!state.mealPlans.some(
(f) => f.title === e.title && f.startDate === e.startDate
)
!state.mealPlans.some(
(f) => f.title === e.title && f.startDate === e.startDate
)
)
state.mealPlans = [...state.mealPlans, ...newMealPlans]
mealPlansDB.updateDocument("mealPlans", {
@ -271,7 +303,10 @@ export default new Vuex.Store({
})
},
addRecipe(state, { id, recipe }) {
addRecipe(state, {
id,
recipe
}) {
state.recipes.push(recipe)
EnRecipesDB.createDocument(recipe, id)
},
@ -296,7 +331,10 @@ export default new Vuex.Store({
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
}
},
addMealPlan(state, { event, eventColor }) {
addMealPlan(state, {
event,
eventColor
}) {
state.mealPlans.push({
title: event.title,
startDate: event.startDate,
@ -308,7 +346,10 @@ export default new Vuex.Store({
})
},
deleteRecipe(state, { index, id }) {
deleteRecipe(state, {
index,
id
}) {
getFileAccess().deleteFile(state.recipes[index].imageSrc)
state.recipes.splice(index, 1)
EnRecipesDB.deleteDocument(id)
@ -319,7 +360,10 @@ export default new Vuex.Store({
}
})
},
deleteMealPlan(state, { title, startDate }) {
deleteMealPlan(state, {
title,
startDate
}) {
let mealPlan = state.mealPlans.filter((e) => {
console.log(e.startDate, startDate)
let sd = new Date(e.startDate).getTime()
@ -335,14 +379,22 @@ export default new Vuex.Store({
})
},
overwriteRecipe(state, { id, recipe }) {
overwriteRecipe(state, {
id,
recipe
}) {
let index = state.recipes.indexOf(
state.recipes.filter((e) => e.id === id)[0]
)
Object.assign(state.recipes[index], recipe)
EnRecipesDB.updateDocument(id, recipe)
},
toggleState(state, { id, recipe, key, setDate }) {
toggleState(state, {
id,
recipe,
key,
setDate
}) {
let index = state.recipes.indexOf(
state.recipes.filter((e) => e.id === id)[0]
)
@ -350,7 +402,10 @@ export default new Vuex.Store({
if (setDate) state.recipes[index].lastTried = new Date()
EnRecipesDB.updateDocument(id, recipe)
},
setRecipeAsTried(state, { id, recipe }) {
setRecipeAsTried(state, {
id,
recipe
}) {
let index = state.recipes.indexOf(
state.recipes.filter((e) => e.id === id)[0]
)
@ -362,7 +417,10 @@ export default new Vuex.Store({
state.recipes[index].lastTried = new Date()
EnRecipesDB.updateDocument(state.recipes[index].id, state.recipes[index])
},
renameCategory(state, { current, updated }) {
renameCategory(state, {
current,
updated
}) {
let lowercase = state.categories.map((e) => e.toLowerCase())
if (lowercase.indexOf(updated.toLowerCase()) == -1) {
state.userCategories.push(updated)
@ -384,7 +442,10 @@ export default new Vuex.Store({
setCurrentComponent(state, comp) {
state.currentComponent = comp
},
unSyncCombinations(state, { id, combinations }) {
unSyncCombinations(state, {
id,
combinations
}) {
state.recipes.forEach((e, i) => {
if (combinations.includes(e.id)) {
state.recipes[i].combinations.splice(e.combinations.indexOf(id), 1)
@ -394,71 +455,113 @@ export default new Vuex.Store({
},
},
actions: {
initializeRecipes({ commit }) {
initializeRecipes({
commit
}) {
commit("initializeRecipes")
},
initializeCategories({ commit }) {
initializeCategories({
commit
}) {
commit("initializeCategories")
},
initializeYieldUnits({ commit }) {
initializeYieldUnits({
commit
}) {
commit("initializeYieldUnits")
},
initializeMealPlans({ commit }) {
initializeMealPlans({
commit
}) {
commit("initializeMealPlans")
},
importRecipesAction({ commit }, recipes) {
importRecipesAction({
commit
}, recipes) {
commit("importRecipes", recipes)
},
importCategoriesAction({ commit }, categories) {
importCategoriesAction({
commit
}, categories) {
commit("importCategories", categories)
},
importYieldUnitsAction({ commit }, yieldUnits) {
importYieldUnitsAction({
commit
}, yieldUnits) {
commit("importYieldUnits", yieldUnits)
},
importMealPlansAction({ commit }, mealPlans) {
importMealPlansAction({
commit
}, mealPlans) {
commit("importMealPlans", mealPlans)
},
addRecipeAction({ commit }, recipe) {
addRecipeAction({
commit
}, recipe) {
commit("addRecipe", recipe)
},
addYieldUnitAction({ commit }, yieldUnit) {
addYieldUnitAction({
commit
}, yieldUnit) {
commit("addYieldUnit", yieldUnit)
},
addCategoryAction({ commit }, category) {
addCategoryAction({
commit
}, category) {
commit("addCategory", category)
},
addMealPlanAction({ commit }, mealPlan) {
addMealPlanAction({
commit
}, mealPlan) {
commit("addMealPlan", mealPlan)
},
deleteMealPlanAction({ commit }, mealPlan) {
deleteMealPlanAction({
commit
}, mealPlan) {
commit("deleteMealPlan", mealPlan)
},
deleteRecipeAction({ commit }, recipe) {
deleteRecipeAction({
commit
}, recipe) {
commit("deleteRecipe", recipe)
},
overwriteRecipeAction({ commit }, updatedRecipe) {
overwriteRecipeAction({
commit
}, updatedRecipe) {
commit("overwriteRecipe", updatedRecipe)
},
toggleStateAction({ commit }, toggledRecipe) {
toggleStateAction({
commit
}, toggledRecipe) {
commit("toggleState", toggledRecipe)
},
setRecipeAsTriedAction({ commit }, recipe) {
setRecipeAsTriedAction({
commit
}, recipe) {
commit("setRecipeAsTried", recipe)
},
setLastTriedDateAction({ commit }, index) {
setLastTriedDateAction({
commit
}, index) {
commit("setLastTriedDate", index)
},
renameCategoryAction({ commit }, category) {
renameCategoryAction({
commit
}, category) {
commit("renameCategory", category)
},
setCurrentComponentAction({ commit }, comp) {
setCurrentComponentAction({
commit
}, comp) {
commit("setCurrentComponent", comp)
},
unSyncCombinationsAction({ commit }, combinations) {
unSyncCombinationsAction({
commit
}, combinations) {
commit("unSyncCombinations", combinations)
},
},

View file

@ -0,0 +1,5 @@
- Fixed several bugs causing app crashes
- Minor UI improvements
- Application language support
- Added Tamil
- More languages coming soon!

View file

@ -5,9 +5,9 @@ EnRecipes is an easy to use, privacy-friendly digital cookbook that lets you cre
▸ Add photo, notes and combinations to your recipes
▸ Organise your recipes by category
▸ Quickly search for your recipes
▸ Mark recipes as favorites and add them to your Try Later list
▸ Mark recipes as favourites and add them to your Try Later list
▸ Scale your recipe ingredients to serve more or less people
▸ Get notified of the last time you tried a recipe
▸ Get notified of when you last tried a recipe
▸ Share your recipe to anyone by any means as a nicely formatted message. You can share the recipe photo too.
▸ Create meal plans
▸ Import/Export recipes
@ -16,7 +16,5 @@ EnRecipes is an easy to use, privacy-friendly digital cookbook that lets you cre
▸ No internet access is required and never asks for any unwanted permissions
▸ 100% free and open-source
Lots of interesting features on the way...
<b>Credits</b>
This app was written in my free time using NativeScript. I would like to thank all those people who helped me understand the concepts during the process and my special thanks to the NativeScript team and the community.

View file

@ -0,0 +1 @@
module.exports = require("@nativescript/localize/hooks/before-checkForChanges.js");

View file

@ -0,0 +1 @@
module.exports = require("@nativescript/localize/hooks/before-watchPatterns.js");

View file

@ -2,7 +2,7 @@ import { NativeScriptConfig } from "@nativescript/core"
export default {
id: "com.vishnuraghav.enrecipes",
appResourcesPath: "app/App_Resources",
appResourcesPath: "app/resources",
android: {
v8Flags: "--expose_gc",
markingMode: "none",

80
package-lock.json generated
View file

@ -1131,6 +1131,17 @@
"mkdirp": "^1.0.4"
}
},
"@nativescript/localize": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@nativescript/localize/-/localize-5.0.2.tgz",
"integrity": "sha512-TokwtXhb3oROr3ST0vCsuDHkTl+pi2caNEkTZNTlXXt5lBd4qyLpooo6ua0O7rVZ6VDcFD9rmJU0sPyNJCMvcw==",
"requires": {
"mkdirp": "^1.0.4",
"shorthash": "0.0.2",
"simple-plist": "^1.1.0",
"sprintf-js": "^1.1.1"
}
},
"@nativescript/social-share": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@nativescript/social-share/-/social-share-2.0.1.tgz",
@ -2032,8 +2043,7 @@
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
@ -2056,6 +2066,11 @@
"tryer": "^1.0.1"
}
},
"big-integer": {
"version": "1.6.48",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@ -2135,6 +2150,22 @@
}
}
},
"bplist-creator": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.8.tgz",
"integrity": "sha512-Za9JKzD6fjLC16oX2wsXfc+qBEhJBJB1YPInoAQpMLhDuj5aVOv1baGeIQSq1Fr3OCqzvsoQcSBSwGId/Ja2PA==",
"requires": {
"stream-buffers": "~2.2.0"
}
},
"bplist-parser": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
"integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
"requires": {
"big-integer": "^1.6.44"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -6220,6 +6251,16 @@
"find-up": "^4.0.0"
}
},
"plist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz",
"integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==",
"requires": {
"base64-js": "^1.2.3",
"xmlbuilder": "^9.0.7",
"xmldom": "0.1.x"
}
},
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -7333,12 +7374,27 @@
"rechoir": "^0.6.2"
}
},
"shorthash": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/shorthash/-/shorthash-0.0.2.tgz",
"integrity": "sha1-WbJo7sveWQOLMNogK8+93rLEpOs="
},
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
"simple-plist": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.1.tgz",
"integrity": "sha512-pKMCVKvZbZTsqYR6RKgLfBHkh2cV89GXcA/0CVPje3sOiNOnXA8+rp/ciAMZ7JRaUdLzlEM6JFfUn+fS6Nt3hg==",
"requires": {
"bplist-creator": "0.0.8",
"bplist-parser": "0.2.0",
"plist": "^3.0.1"
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -7569,6 +7625,11 @@
"extend-shallow": "^3.0.0"
}
},
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
},
"sshpk": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
@ -7641,6 +7702,11 @@
"readable-stream": "^2.0.2"
}
},
"stream-buffers": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
"integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ="
},
"stream-each": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
@ -9082,6 +9148,16 @@
"async-limiter": "~1.0.0"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
},
"xmldom": {
"version": "0.1.31",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
"integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ=="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View file

@ -12,6 +12,7 @@
"@nativescript-community/ui-material-progress": "^5.0.30",
"@nativescript-community/ui-material-ripple": "^5.0.30",
"@nativescript/core": "~7.0.0",
"@nativescript/localize": "^5.0.2",
"@nativescript/social-share": "^2.0.1",
"@nativescript/theme": "^3.0.0",
"@nativescript/zip": "^5.0.0",

Some files were not shown because too many files have changed in this diff Show more