empty states started, minor fixes, try later added

This commit is contained in:
Vishnu Raghav B 2020-10-30 01:42:53 +05:30
parent 52446b3cd7
commit 399fc6bc20
15 changed files with 915 additions and 545 deletions

View file

@ -1,29 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Application theme -->
<style name="AppTheme" parent="AppThemeBase"> <!-- Application theme -->
<item name="android:datePickerStyle"> <style name="AppThemeBase21" parent="AppThemeBase">
@style/SpinnerDatePicker <item name="android:windowTranslucentStatus">true</item>
</item> <item name="android:datePickerStyle">@style/SpinnerDatePicker</item>
<item name="android:timePickerStyle"> <item name="android:timePickerStyle">@style/SpinnerTimePicker</item>
@style/SpinnerTimePicker </style>
</item>
</style> <style name="AppTheme" parent="AppThemeBase21">
<!-- Default style for DatePicker - in spinner mode --> </style>
<style name="SpinnerDatePicker" parent="android:Widget.Material.Light.DatePicker">
<item name="android:datePickerMode"> <!-- Default style for DatePicker - in spinner mode -->
spinner <style name="SpinnerDatePicker" parent="android:Widget.Material.Light.DatePicker">
</item> <item name="android:datePickerMode">spinner</item>
</style> </style>
<!-- Default style for TimePicker - in spinner mode -->
<style name="SpinnerTimePicker" parent="android:Widget.Material.Light.TimePicker"> <!-- Default style for TimePicker - in spinner mode -->
<item name="android:timePickerMode"> <style name="SpinnerTimePicker" parent="android:Widget.Material.Light.TimePicker">
spinner <item name="android:timePickerMode">spinner</item>
</item> </style>
</style>
<style name="NativeScriptToolbarStyle" parent="NativeScriptToolbarStyleBase"> <style name="NativeScriptToolbarStyle" parent="NativeScriptToolbarStyleBase">
<item name="android:elevation"> <item name="android:elevation">4dp</item>
0dp <item name="android:paddingTop">24dp</item>
</item> </style>
</style> </resources>
</resources>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Application theme -->
<style name="AppThemeBase29" parent="AppThemeBase21">
<item name="android:forceDarkAllowed">true</item>
</style>
<style name="AppTheme" parent="AppThemeBase29">
</style>
</resources>

View file

@ -20,9 +20,7 @@
<!-- theme to use AFTER launch screen is loaded--> <!-- theme to use AFTER launch screen is loaded-->
<style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:forceDarkAllowed">false</item>
<item name="toolbarStyle">@style/NativeScriptToolbarStyle</item> <item name="toolbarStyle">@style/NativeScriptToolbarStyle</item>
<item name="colorPrimary">@color/ns_primary</item> <item name="colorPrimary">@color/ns_primary</item>
<item name="colorPrimaryDark">@color/ns_primaryDark</item> <item name="colorPrimaryDark">@color/ns_primaryDark</item>
<item name="colorAccent">@color/ns_accent</item> <item name="colorAccent">@color/ns_accent</item>

View file

@ -53,6 +53,10 @@ Page {
.fieldLabel { .fieldLabel {
background: $grayL4; background: $grayL4;
} }
.view-reference-text,
.view-reference-container {
background: white;
}
.option-highlight { .option-highlight {
background: $grayL2; background: $grayL2;
} }
@ -108,7 +112,9 @@ Page {
background: $grayD4; background: $grayD4;
} }
.recipe-li, .recipe-li,
.option-highlight { .option-highlight,
.view-reference-text,
.view-reference-container {
background: $grayD3; background: $grayD3;
} }
.sd-item, .sd-item,
@ -151,7 +157,7 @@ TimePickerField {
width: 100%; width: 100%;
border-width: 1; border-width: 1;
font-size: 14; font-size: 14;
padding: 14; padding: 14 14 13;
margin: 8 0 0 0; margin: 8 0 0 0;
border-radius: 4; border-radius: 4;
border-color: $gray; border-color: $gray;
@ -188,7 +194,7 @@ TabView {
// ActionBar // ActionBar
ActionBar { ActionBar {
width: 100%; width: 100%;
margin: 0; margin: 24 0 0 0;
padding: 0; padding: 0;
height: 64; height: 64;
.bx { .bx {
@ -215,6 +221,10 @@ SearchBar {
} }
// Side Drawer // Side Drawer
.sd {
margin-top: 24;
}
.sd-item { .sd-item {
border-radius: 4; border-radius: 4;
padding: 0 16; padding: 0 16;
@ -238,7 +248,7 @@ SearchBar {
// Home // Home
RadListView { RadListView {
margin-bottom: 128; margin: 8 0 128;
} }
.recipe-li { .recipe-li {
background: white; background: white;
@ -265,12 +275,29 @@ RadListView {
color: $orange; color: $orange;
} }
} }
.emptyState {
width: 100%;
height: 100%;
}
.noResults { .noResults {
width: 100%; width: 100%;
height: 100%;
font-size: 16; font-size: 16;
line-height: 8; line-height: 8;
padding: 32 16; padding: 64 16;
text-align: center; text-align: center;
horizontal-alignment: center;
// vertical-alignment: center;
.title {
font-size: 20;
text-align: center;
}
.bx {
font-size: 64;
text-align: center;
color: $gray;
margin-bottom: 16;
}
} }
.swipe-item { .swipe-item {
margin: 0 8; margin: 0 8;
@ -336,11 +363,11 @@ RadListView {
// View Recipe // View Recipe
.view-cat { .view-cat {
font-size: 14; font-size: 16;
color: #ff7043; color: #ff7043;
} }
.view-other { .view-other {
font-size: 14; font-size: 16;
} }
.view-title { .view-title {
font-size: 22; font-size: 22;
@ -348,46 +375,71 @@ RadListView {
margin-bottom: 16; margin-bottom: 16;
} }
.view-ingredient { .view-ingredient {
font-size: 14; font-size: 16;
line-height: 6; line-height: 6;
padding-bottom: 16; padding-bottom: 12;
}
.view-favorited {
color: #ff7043;
} }
.activity-indicator { .activity-indicator {
background: #ff7043; background: #ff7043;
} }
.view-count { .view-count {
font-size: 10; font-size: 12;
width: 20; width: 24;
height: 20; height: 24;
padding-top: 3%; padding-top: 4%;
margin: 0 0 0 6; margin: 0 0 0 8;
text-align: center; text-align: center;
border-radius: 100; border-radius: 100;
&.note {
clip-path: polygon(
5% 0,
95% 0,
100% 5%,
100% 65%,
65% 100%,
5% 100%,
0 95%,
0 5%
);
}
} }
.view-instruction, .view-instruction,
.view-note, .view-note,
.view-reference { .view-reference {
font-size: 14; font-size: 16;
line-height: 6; line-height: 6;
padding: 0 0 16 24; padding: 2 0 14 36;
margin: 0 0 0 15; margin: 0 0 0 19;
border-width: 0 0 0 2; border-width: 0 0 0 2;
} }
.view-instruction.instructionWOBorder { .view-instruction.instructionWOBorder {
border-color: transparent; border-color: transparent;
} }
.view-note { .view-note {
border-width: 0; border-width: 0;
} }
.view-reference-container {
padding: 14 16;
margin: 8 16 8;
border-radius: 6;
}
.view-reference { .view-reference {
padding: 0 16 0 0;
margin: 0;
border-width: 0; border-width: 0;
} }
.view-reference-text {
font-size: 16;
line-height: 6;
padding: 16;
margin: 8 16 8;
border-radius: 6;
}
// .view-copyReference {
// color: #ff7043;
// }
#btnFabContainer { #btnFabContainer {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -398,11 +450,15 @@ RadListView {
height: 56; height: 56;
width: 56; width: 56;
background-color: #ff7043; background-color: #ff7043;
horizontal-align: center; vertical-alignment: center;
vertical-align: center; // horizontal-alignment: center;
border-radius: 28; border-radius: 28;
padding: 16; padding: 16;
margin: 16; margin: 16;
android-elevation: 6;
&.negative {
background-color: #e53935;
}
} }
.sectionTitle { .sectionTitle {
font-size: 20; font-size: 20;
@ -421,6 +477,7 @@ RadListView {
// Dialogs // Dialogs
.dialogContainer { .dialogContainer {
width: 100%;
&.light { &.light {
color: $grayD4; color: $grayD4;
background: $grayL4; background: $grayL4;
@ -438,6 +495,7 @@ RadListView {
} }
.dialogDescription { .dialogDescription {
font-size: 16; font-size: 16;
line-height: 4;
padding: 0 24 16; padding: 0 24 16;
} }
.action { .action {
@ -456,3 +514,65 @@ RadListView {
color: #ff7043; color: #ff7043;
} }
} }
ActivityIndicator {
color: #ff7043;
padding: 16 12;
margin: 16 10 16 8;
}
// Vue Transitions
.bounce-enter-active {
animation-name: bounce-in;
animation-duration: 0.5s;
animation-fill-mode: forwards;
animation-timing-function: ease-out;
}
.bounce-leave-active {
animation-name: bounce-in;
animation-duration: 0.1s;
animation-fill-mode: forwards;
animation-direction: reverse;
animation-timing-function: ease-in;
}
@keyframes bounce-in {
0% {
transform: scale(0);
opacity: 0;
}
50% {
transform: scale(1.2);
opacity: 1;
}
100% {
transform: scale(1);
}
}
.dolly-enter-active {
animation-name: dolly;
animation-duration: 1s;
animation-delay: 0.25s;
animation-fill-mode: forwards;
animation-timing-function: ease;
}
.dolly-leave-active {
opacity: 0;
}
@keyframes dolly {
0% {
transform: rotate(20deg);
}
25% {
transform: rotate(-20deg);
}
50% {
transform: rotate(10deg);
}
75% {
transform: rotate(-10deg);
}
100% {
transform: rotate(0deg);
}
}

View file

@ -42,10 +42,18 @@
<Label class="bx" :text="icon.link" /> <Label class="bx" :text="icon.link" />
<Label text="View project on GitHub" class="option-title" /> <Label text="View project on GitHub" class="option-title" />
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" class="icon-option"> <StackLayout
orientation="horizontal"
class="icon-option"
@tap="openURL($event, 'https://t.me/enrecipes')"
>
<Label class="bx" :text="icon.telegram" />
<Label text="Join the telegram group" class="option-title" />
</StackLayout>
<!-- <StackLayout orientation="horizontal" class="icon-option">
<Label class="bx" :text="icon.file" /> <Label class="bx" :text="icon.file" />
<Label text="Licenses" class="option-title" /> <Label text="Licenses" class="option-title" />
</StackLayout> </StackLayout> -->
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>

View file

@ -34,7 +34,7 @@
class="sd-group-header orkm" class="sd-group-header orkm"
rows="auto" rows="auto"
columns="*, auto" columns="*, auto"
v-if="categories.length" v-if="categoriesWithRecipes.length"
> >
<Label col="0" text="Categories" /> <Label col="0" text="Categories" />
<Label <Label
@ -47,7 +47,7 @@
<StackLayout> <StackLayout>
<GridLayout <GridLayout
@tap="navigateTo(item, false, true)" @tap="navigateTo(item, false, true)"
v-for="(item, index) in categories" v-for="(item, index) in categoriesWithRecipes"
:key="index" :key="index"
class="sd-item orkm" class="sd-item orkm"
:class="{ :class="{
@ -75,14 +75,14 @@
</StackLayout> </StackLayout>
<StackLayout row="1"> <StackLayout row="1">
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<StackLayout <!-- <StackLayout
orientation="horizontal" orientation="horizontal"
class="sd-item orkm" class="sd-item orkm"
@tap="donate" @tap="donate"
> >
<Label class="bx" :text="icon.donate" margin="0 24 0 0" /> <Label class="bx" :text="icon.donate" margin="0 24 0 0" />
<Label text="Donate" /> <Label text="Donate" />
</StackLayout> </StackLayout> -->
<StackLayout <StackLayout
@tap="navigateTo(item.component, true, false)" @tap="navigateTo(item.component, true, false)"
v-for="(item, index) in bottommenu" v-for="(item, index) in bottommenu"
@ -105,7 +105,7 @@
<EnRecipes <EnRecipes
ref="enrecipes" ref="enrecipes"
:filterFavorites="filterFavorites" :filterFavorites="filterFavorites"
:filterMustTry="filterMustTry" :filterTrylater="filterTrylater"
:selectedCategory="selectedCategory" :selectedCategory="selectedCategory"
:showDrawer="showDrawer" :showDrawer="showDrawer"
:hijackGlobalBackEvent="hijackGlobalBackEvent" :hijackGlobalBackEvent="hijackGlobalBackEvent"
@ -145,7 +145,7 @@ export default {
return { return {
selectedCategory: null, selectedCategory: null,
filterFavorites: false, filterFavorites: false,
filterMustTry: false, filterTrylater: false,
topmenu: [ topmenu: [
{ {
title: "Home", title: "Home",
@ -158,9 +158,9 @@ export default {
icon: "heart", icon: "heart",
}, },
{ {
title: "Must-Try", title: "Try later",
component: "Must-Try", component: "Try later",
icon: "musttry", icon: "trylater",
}, },
], ],
bottommenu: [ bottommenu: [
@ -179,8 +179,8 @@ export default {
} }
}, },
computed: { computed: {
...mapState(["icon", "recipes", "currentComponent"]), ...mapState(["icon", "recipes", "categories", "currentComponent"]),
categories() { categoriesWithRecipes() {
let arr = this.recipes.map((e) => { let arr = this.recipes.map((e) => {
return e.category return e.category
}) })
@ -197,7 +197,7 @@ export default {
toggleCatEdit() { toggleCatEdit() {
this.catEditMode = !this.catEditMode this.catEditMode = !this.catEditMode
this.setComponent("EnRecipes") this.setComponent("EnRecipes")
this.filterFavorites = this.filterMustTry = false this.filterFavorites = this.filterTrylater = false
this.selectedCategory = null this.selectedCategory = null
this.$refs.enrecipes.updateFilter() this.$refs.enrecipes.updateFilter()
}, },
@ -223,9 +223,7 @@ export default {
highlight(args) { highlight(args) {
let temp = args.object.className let temp = args.object.className
args.object.className = `${temp} option-highlight` args.object.className = `${temp} option-highlight`
setTimeout(() => { setTimeout(() => (args.object.className = temp), 100)
args.object.className = temp
}, 100)
}, },
// Navigation // Navigation
setSelectedCategory(e) { setSelectedCategory(e) {
@ -252,8 +250,8 @@ export default {
function isFiltered() { function isFiltered() {
vm.filterFavorites vm.filterFavorites
? vm.setComponent("Favorites") ? vm.setComponent("Favorites")
: vm.filterMustTry : vm.filterTrylater
? vm.setComponent("Must-Try") ? vm.setComponent("Try later")
: vm.selectedCategory : vm.selectedCategory
? vm.setComponent(vm.selectedCategory) ? vm.setComponent(vm.selectedCategory)
: vm.setComponent("EnRecipes") : vm.setComponent("EnRecipes")
@ -263,13 +261,13 @@ export default {
this.closeDrawer() this.closeDrawer()
this.catEditMode = false this.catEditMode = false
} else if ( } else if (
["Favorites", "Must-Try", this.selectedCategory].includes( ["Favorites", "Try later", this.selectedCategory].includes(
this.currentComponent this.currentComponent
) )
) { ) {
preventDefault() preventDefault()
this.setComponent("EnRecipes") this.setComponent("EnRecipes")
this.filterFavorites = this.filterMustTry = false this.filterFavorites = this.filterTrylater = false
this.selectedCategory = null this.selectedCategory = null
this.$refs.enrecipes.updateFilter() this.$refs.enrecipes.updateFilter()
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
@ -296,7 +294,7 @@ export default {
this.setComponent(to) this.setComponent(to)
this.$navigateBack({ frame: "main-frame", backstackVisible: false }) this.$navigateBack({ frame: "main-frame", backstackVisible: false })
this.filterFavorites = to === "Favorites" ? true : false this.filterFavorites = to === "Favorites" ? true : false
this.filterMustTry = to === "Must-Try" ? true : false this.filterTrylater = to === "Try later" ? true : false
this.selectedCategory = isCategory ? to : null this.selectedCategory = isCategory ? to : null
this.$refs.enrecipes.updateFilter() this.$refs.enrecipes.updateFilter()
this.closeDrawer() this.closeDrawer()

View file

@ -1,7 +1,7 @@
<template> <template>
<Page @unloaded="releaseBackEvent"> <Page @loaded="initialize" @unloaded="releaseBackEvent">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<GridLayout rows="*" columns="auto, *, auto," class="actionBarContainer"> <GridLayout rows="*" columns="auto, *, auto" class="actionBarContainer">
<Label <Label
class="bx leftAction" class="bx leftAction"
:text="icon.back" :text="icon.back"
@ -10,7 +10,6 @@
@tap="navigateBack" @tap="navigateBack"
/> />
<Label class="title orkm" :text="title" col="1" /> <Label class="title orkm" :text="title" col="1" />
<Label <Label
v-if="hasEnoughDetails" v-if="hasEnoughDetails"
class="bx" class="bx"
@ -20,251 +19,237 @@
/> />
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<ScrollView <AbsoluteLayout>
width="100%" <ScrollView
height="100%" width="100%"
@scroll="onScroll($event)" height="100%"
scrollBarIndicatorVisible="false" @scroll="onScroll($event)"
> scrollBarIndicatorVisible="false"
<StackLayout width="100%"> >
<!-- Image and camera btn --> <StackLayout width="100%" padding="0 0 128">
<AbsoluteLayout> <!-- Image and camera btn -->
<StackLayout <AbsoluteLayout>
width="100%" <StackLayout
:height="screenWidth"
class="view-imageHolder"
verticalAlignment="center"
>
<Image
v-if="recipeContent.imageSrc"
:src="recipeContent.imageSrc"
stretch="aspectFill"
width="100%" width="100%"
:height="screenWidth" :height="screenWidth"
/> class="view-imageHolder"
<Label verticalAlignment="center"
v-else >
horizontalAlignment="center" <Image
class="bx" v-if="recipeContent.imageSrc"
fontSize="160" :src="recipeContent.imageSrc"
:text="icon.image" stretch="aspectFill"
/> width="100%"
</StackLayout> :height="screenWidth"
<StackLayout />
width="100%"
horizontalAlignment="center"
:top="screenWidth - 42"
>
<Label
v-if="recipeContent.imageSrc"
@tap="removePicture"
class="bx fab-button"
:text="icon.close"
androidElevation="8"
/>
<GridLayout v-else rows="auto" columns="*, auto, auto, *">
<Label <Label
col="1" v-else
@tap="takePicture" horizontalAlignment="center"
class="bx fab-button" class="bx"
:text="icon.camera" fontSize="160"
androidElevation="8" :text="icon.image"
/>
</StackLayout>
<StackLayout width="100%" :top="screenWidth - 42">
<transition :name="recipeContent.imageSrc ? 'null' : 'bounce'">
<Label
v-if="showFab"
horizontalAlignment="right"
@tap="photoHandler"
class="bx fab-button"
:text="icon.camera"
androidElevation="6"
/>
</transition>
</StackLayout>
</AbsoluteLayout>
<!-- Primary information -->
<StackLayout margin="0 16">
<AbsoluteLayout class="inputField">
<TextField
hint="My Healthy Recipe"
v-model="recipeContent.title"
autocapitalizationType="words"
/>
<Label top="0" class="fieldLabel" text="Title" />
</AbsoluteLayout>
<AbsoluteLayout class="inputField">
<TextField
v-model="recipeContent.category"
editable="false"
@tap="showCategories()"
/>
<Label top="0" class="fieldLabel" text="Category" />
</AbsoluteLayout>
<GridLayout columns="*, 8, *">
<AbsoluteLayout class="inputField" col="0">
<TimePickerField
titleTextColor="red"
timeFormat="HH:mm"
pickerTitle="Approx. preparation time"
@timeChange="onPrepTimeChange"
:time="recipeContent.prepTime"
></TimePickerField>
<Label top="0" class="fieldLabel" text="Preparation time" />
</AbsoluteLayout>
<AbsoluteLayout class="inputField" col="2">
<TimePickerField
timeFormat="HH:mm"
pickerTitle="Approx. cooking time"
@timeChange="onCookTimeChange"
:time="recipeContent.cookTime"
></TimePickerField>
<Label top="0" class="fieldLabel" text="Cooking time" />
</AbsoluteLayout>
</GridLayout>
<GridLayout columns="*, 16, *">
<AbsoluteLayout class="inputField" col="0">
<TextField
width="100%"
keyboardType="number"
v-model="recipeContent.portionSize"
/>
<Label top="0" class="fieldLabel" text="Portion size" />
</AbsoluteLayout>
</GridLayout>
<StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout>
<!-- Ingredients section -->
<StackLayout margin="0 16">
<Label text="Ingredients" class="sectionTitle" />
<GridLayout
columns="*,8,auto,8,auto,8,auto"
v-for="(ingredient, index) in recipeContent.ingredients"
:key="index"
>
<TextField
col="0"
v-model="recipeContent.ingredients[index].item"
:hint="`Item ${index + 1}`"
autocapitalizationType="words"
/>
<TextField
width="72"
col="2"
v-model="recipeContent.ingredients[index].quantity"
hint="1.000"
keyboardType="number"
/>
<TextField
width="64"
col="4"
v-model="recipeContent.ingredients[index].unit"
hint="Unit"
editable="false"
@tap="showUnits($event)"
/>
<Label
col="6"
class="bx closeBtn"
:text="icon.close"
@tap="removeIngredient(index)"
/>
</GridLayout>
<Label
class="sec-btn pull-left orkm"
text="+ ADD INGREDIENT"
@tap="addIngredient()"
/>
<StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout>
<!-- Instructions section -->
<StackLayout margin="0 16">
<Label text="Instructions" class="sectionTitle" />
<GridLayout
columns="*,8,auto"
v-for="(instruction, index) in recipeContent.instructions"
:key="index"
>
<TextView
col="0"
:hint="`Step ${index + 1}`"
v-model="recipeContent.instructions[index]"
editable="true"
/> />
<Label <Label
col="2" col="2"
@tap="selectPicture" class="bx closeBtn"
class="bx fab-button" :text="icon.close"
:text="icon.image" @tap="removeInstruction(index)"
androidElevation="8"
/> />
</GridLayout> </GridLayout>
<Label
class="sec-btn pull-left orkm"
text="+ ADD STEP"
@tap="addInstruction()"
/>
<StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
</AbsoluteLayout>
<!-- Primary information --> <!-- Notes section -->
<StackLayout margin="0 16"> <StackLayout margin="0 16">
<AbsoluteLayout class="inputField"> <Label text="Notes" class="sectionTitle" />
<TextField <GridLayout
hint="My Healthy Recipe" columns="*,8,auto"
v-model="recipeContent.title" v-for="(note, index) in recipeContent.notes"
autocapitalizationType="words" :key="index"
/> >
<Label top="0" class="fieldLabel" text="Title" /> <TextView
</AbsoluteLayout> col="0"
<AbsoluteLayout class="inputField"> v-model="recipeContent.notes[index]"
<TextField :hint="`Note ${index + 1}`"
v-model="recipeContent.category" editable="true"
editable="false"
@tap="showCategories()"
/>
<Label top="0" class="fieldLabel" text="Category" />
</AbsoluteLayout>
<GridLayout columns="*, 8, *">
<AbsoluteLayout class="inputField" col="0">
<TimePickerField
titleTextColor="red"
timeFormat="HH:mm"
pickerTitle="Approx. preparation time"
@timeChange="onPrepTimeChange"
:time="recipeContent.prepTime"
></TimePickerField>
<Label top="0" class="fieldLabel" text="Preparation time" />
</AbsoluteLayout>
<AbsoluteLayout class="inputField" col="2">
<TimePickerField
timeFormat="HH:mm"
pickerTitle="Approx. cooking time"
@timeChange="onCookTimeChange"
:time="recipeContent.cookTime"
></TimePickerField>
<Label top="0" class="fieldLabel" text="Cooking time" />
</AbsoluteLayout>
</GridLayout>
<GridLayout columns="*, 16, *">
<AbsoluteLayout class="inputField" col="0">
<TextField
width="100%"
keyboardType="number"
v-model="recipeContent.portionSize"
/> />
<Label top="0" class="fieldLabel" text="Portion size" /> <Label
</AbsoluteLayout> col="2"
</GridLayout> class="bx closeBtn"
<StackLayout class="hr" margin="24 16"></StackLayout> :text="icon.close"
</StackLayout> @tap="removeNote(index)"
/>
<!-- Ingredients section --> </GridLayout>
<StackLayout margin="0 16">
<Label text="Ingredients" class="sectionTitle" />
<GridLayout
columns="*,8,auto,8,auto,8,auto"
v-for="(ingredient, index) in recipeContent.ingredients"
:key="index"
>
<TextField
col="0"
v-model="recipeContent.ingredients[index].item"
:hint="`Item ${index + 1}`"
autocapitalizationType="words"
/>
<TextField
width="72"
col="2"
v-model="recipeContent.ingredients[index].quantity"
hint="1.000"
keyboardType="number"
/>
<TextField
width="64"
col="4"
v-model="recipeContent.ingredients[index].unit"
hint="Unit"
editable="false"
@tap="showUnits($event)"
/>
<Label <Label
col="6" class="sec-btn pull-left orkm"
class="bx closeBtn" text="+ ADD NOTE"
:text="icon.close" @tap="addNote()"
@tap="removeIngredient(index)"
/> />
</GridLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
<Label </StackLayout>
class="sec-btn pull-left orkm"
text="+ ADD INGREDIENT"
@tap="addIngredient()"
/>
<StackLayout class="hr" margin="24 16"></StackLayout> <!-- References section -->
</StackLayout> <StackLayout margin="0 16">
<Label text="References" class="sectionTitle" />
<!-- Instructions section --> <GridLayout
<StackLayout margin="0 16"> columns="*,8,auto"
<Label text="Instructions" class="sectionTitle" /> v-for="(reference, index) in recipeContent.references"
<GridLayout :key="index"
columns="*,8,auto" >
v-for="(instruction, index) in recipeContent.instructions" <TextView
:key="index" col="0"
> v-model="recipeContent.references[index]"
<TextView hint="Text or Website/Video URL"
col="0" editable="true"
:hint="`Step ${index + 1}`" />
v-model="recipeContent.instructions[index]" <Label
editable="true" col="2"
/> class="bx closeBtn"
:text="icon.close"
@tap="removeReference(index)"
/>
</GridLayout>
<Label <Label
col="2" class="sec-btn pull-left orkm"
class="bx closeBtn" text="+ ADD REFERENCE"
:text="icon.close" @tap="addReference()"
@tap="removeInstruction(index)"
/> />
</GridLayout> <StackLayout margin="32"></StackLayout>
<Label </StackLayout>
class="sec-btn pull-left orkm"
text="+ ADD STEP"
@tap="addInstruction()"
/>
<StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
</ScrollView>
<!-- Notes section --> </AbsoluteLayout>
<StackLayout margin="0 16">
<Label text="Notes" class="sectionTitle" />
<GridLayout
columns="*,8,auto"
v-for="(note, index) in recipeContent.notes"
:key="index"
>
<TextView
col="0"
v-model="recipeContent.notes[index]"
:hint="`Note ${index + 1}`"
editable="true"
/>
<Label
col="2"
class="bx closeBtn"
:text="icon.close"
@tap="removeNote(index)"
/>
</GridLayout>
<Label
class="sec-btn pull-left orkm"
text="+ ADD NOTE"
@tap="addNote()"
/>
<StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout>
<!-- References section -->
<StackLayout margin="0 16">
<Label text="References" class="sectionTitle" />
<GridLayout
columns="*,8,auto"
v-for="(reference, index) in recipeContent.references"
:key="index"
>
<TextField
col="0"
v-model="recipeContent.references[index]"
hint="Website or Video URL"
/>
<Label
col="2"
class="bx closeBtn"
:text="icon.close"
@tap="removeReference(index)"
/>
</GridLayout>
<Label
class="sec-btn pull-left orkm"
text="+ ADD REFERENCE"
@tap="addReference()"
/>
<StackLayout margin="32"></StackLayout>
</StackLayout>
</StackLayout>
</ScrollView>
</Page> </Page>
</template> </template>
@ -277,14 +262,13 @@ import {
getFileAccess, getFileAccess,
knownFolders, knownFolders,
} from "@nativescript/core" } from "@nativescript/core"
import * as imagepicker from "nativescript-imagepicker" import { Mediafilepicker } from "nativescript-mediafilepicker"
import * as camera from "@nativescript/camera"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
import ActionDialog from "./modal/ActionDialog.vue" import ActionDialog from "./modal/ActionDialog.vue"
import PromptDialog from "./modal/PromptDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import PromptDialog from "./modal/PromptDialog.vue"
export default { export default {
props: ["recipeIndex", "recipeID", "selectedCategory"], props: ["recipeIndex", "recipeID", "selectedCategory"],
@ -294,36 +278,15 @@ export default {
viewIsScrolled: false, viewIsScrolled: false,
recipeContent: { recipeContent: {
imageSrc: null, imageSrc: null,
title: null, title: undefined,
category: null, category: "Undefined",
prepTime: "00:00", prepTime: "00:00",
cookTime: "00:00", cookTime: "00:00",
portionSize: 1, portionSize: 1,
ingredients: [ ingredients: [
{ {
item: "", item: "",
quantity: null, quantity: undefined,
unit: "unit",
},
],
instructions: [""],
notes: [""],
references: [""],
isFavorite: false,
tried: false,
lastModified: null,
},
tempRecipeContent: {
imageSrc: null,
title: null,
category: null,
prepTime: "00:00",
cookTime: "00:00",
portionSize: 1,
ingredients: [
{
item: "",
quantity: null,
unit: "unit", unit: "unit",
}, },
], ],
@ -334,8 +297,10 @@ export default {
tried: false, tried: false,
lastModified: null, lastModified: null,
}, },
tempRecipeContent: {},
blockModal: false, blockModal: false,
newRecipeID: null, newRecipeID: null,
showFab: false,
} }
}, },
computed: { computed: {
@ -344,14 +309,10 @@ export default {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
hasEnoughDetails() { hasEnoughDetails() {
if (this.recipeID) { return (
return ( JSON.stringify(this.recipeContent) !==
JSON.stringify(this.recipeContent) !== JSON.stringify(this.tempRecipeContent)
JSON.stringify(this.tempRecipeContent) )
)
} else {
return this.recipeContent.title
}
}, },
}, },
methods: { methods: {
@ -361,6 +322,9 @@ export default {
"overwriteRecipeAction", "overwriteRecipeAction",
"addCategoryAction", "addCategoryAction",
]), ]),
initialize() {
this.showFab = true
},
getRandomID() { getRandomID() {
let res = "" let res = ""
let chars = "abcdefghijklmnopqrstuvwxyz0123456789" let chars = "abcdefghijklmnopqrstuvwxyz0123456789"
@ -380,12 +344,12 @@ export default {
} }
}, },
clearEmptyFields() { clearEmptyFields() {
if (!this.recipeContent.title) {
this.recipeContent.title = "Untitled Recipe"
}
if (!this.recipeContent.portionSize) { if (!this.recipeContent.portionSize) {
this.recipeContent.portionSize = 1 this.recipeContent.portionSize = 1
} }
if (!this.recipeContent.category) {
this.recipeContent.category = "Undefined"
}
this.recipeContent.ingredients.forEach((e, i) => { this.recipeContent.ingredients.forEach((e, i) => {
if (!e.item.length) { if (!e.item.length) {
this.recipeContent.ingredients.splice(i, 1) this.recipeContent.ingredients.splice(i, 1)
@ -404,6 +368,10 @@ export default {
removeEmpty("references") removeEmpty("references")
}, },
saveRecipe() { saveRecipe() {
console.log(
JSON.stringify(this.recipeContent),
JSON.stringify(this.tempRecipeContent)
)
this.clearEmptyFields() this.clearEmptyFields()
this.recipeContent.lastModified = new Date() this.recipeContent.lastModified = new Date()
if (this.recipeID) { if (this.recipeID) {
@ -471,15 +439,17 @@ export default {
this.blockModal = true this.blockModal = true
this.$showModal(ConfirmDialog, { this.$showModal(ConfirmDialog, {
props: { props: {
title: "Discard changes", title: "Unsaved changes",
description: description:
"Are you sure you want discard unsaved changes to this recipe?", "Do you want to save the changes you made in this recipe?",
cancelButtonText: "KEEP EDITING", cancelButtonText: "DISCARD",
okButtonText: "DISCARD", okButtonText: "SAVE",
}, },
}).then((action) => { }).then((action) => {
this.blockModal = false this.blockModal = false
if (action) { if (action) {
this.saveRecipe()
} else if (action != null) {
this.$navigateBack() this.$navigateBack()
this.releaseBackEvent() this.releaseBackEvent()
} }
@ -507,59 +477,59 @@ export default {
this.navigateBack() this.navigateBack()
} }
}, },
photoHandler() {
if (this.recipeContent.imageSrc) {
this.blockModal = true
this.$showModal(ConfirmDialog, {
props: {
title: "Recipe photo",
cancelButtonText: "REMOVE",
okButtonText: "REPLACE PHOTO",
},
}).then((action) => {
this.blockModal = false
if (action) {
this.takePicture()
} else if (action != null) {
this.removePicture()
this.releaseBackEvent()
}
})
} else {
this.takePicture()
}
},
takePicture() { takePicture() {
const vm = this const vm = this
camera.requestPermissions().then( const mediafilepicker = new Mediafilepicker()
() => { mediafilepicker.openImagePicker({
camera android: {
.takePicture({ isCaptureMood: false, // if true then camera will open directly.
width: vm.screenWidth, isNeedCamera: true,
height: vm.screenWidth, maxNumberFiles: 1,
keepAspectRatio: false, isNeedFolderList: true,
saveToGallery: false,
})
.then((imageAsset) => {
let result = imageAsset._android
ImageSource.fromFile(result).then((savedImg) => {
let savedImgPath = path.join(
knownFolders.documents().getFolder("enrecipes").path,
`${vm.getRandomID()}.jpg`
)
savedImg.saveToFile(savedImgPath, "jpg")
vm.recipeContent.imageSrc = savedImgPath
})
})
.catch((err) => {
console.log("Error -> " + err.message)
})
}, },
() => {
console.log("permission request rejected")
}
)
},
selectPicture() {
let context = imagepicker.create({
mode: "single",
mediaType: "Image",
}) })
context mediafilepicker.on("getFiles", (image) => {
.authorize() let result = image.object.get("results")[0].file
.then(() => context.present()) ImageSource.fromFile(result).then((savedImg) => {
.then((selection) => { let savedImgPath = path.join(
let result = selection[0]._android knownFolders.documents().getFolder("enrecipes").path,
ImageSource.fromFile(result).then((savedImg) => { `${vm.getRandomID()}.jpg`
let savedImgPath = path.join( )
knownFolders.documents().getFolder("enrecipes").path, savedImg.saveToFile(savedImgPath, "jpg")
`${this.getRandomID()}.jpg` vm.recipeContent.imageSrc = savedImgPath
)
savedImg.saveToFile(savedImgPath, "jpg")
this.recipeContent.imageSrc = savedImgPath
})
})
.catch(function(e) {
console.log(e)
}) })
})
mediafilepicker.on("error", function(res) {
let msg = res.object.get("msg")
console.log(msg)
})
mediafilepicker.on("cancel", function(res) {
let msg = res.object.get("msg")
console.log(msg)
})
}, },
removePicture() { removePicture() {
confirm({ confirm({
@ -627,9 +597,13 @@ export default {
this.title = this.recipeID ? "Edit recipe" : "New recipe" this.title = this.recipeID ? "Edit recipe" : "New recipe"
if (this.recipeID) { if (this.recipeID) {
let recipe = this.recipes.filter((e) => e.id === this.recipeID)[0] let recipe = this.recipes.filter((e) => e.id === this.recipeID)[0]
Object.assign(this.recipeContent, recipe) Object.assign(this.recipeContent, JSON.parse(JSON.stringify(recipe)))
Object.assign(this.tempRecipeContent, recipe) Object.assign(this.tempRecipeContent, JSON.parse(JSON.stringify(recipe)))
} else { } else {
Object.assign(
this.tempRecipeContent,
JSON.parse(JSON.stringify(this.recipeContent))
)
if (this.selectedCategory) if (this.selectedCategory)
this.recipeContent.category = this.selectedCategory this.recipeContent.category = this.selectedCategory
this.newRecipeID = this.getRandomID() this.newRecipeID = this.getRandomID()

View file

@ -15,7 +15,6 @@
col="0" col="0"
@tap="closeSearch" @tap="closeSearch"
/> />
<!-- @loaded="searchBarLoaded" -->
<SearchBar <SearchBar
col="1" col="1"
hint="Search" hint="Search"
@ -114,41 +113,92 @@
<StackLayout height="128"></StackLayout> <StackLayout height="128"></StackLayout>
</v-template> </v-template>
</RadListView> </RadListView>
<Label <GridLayout rows="*" columns="*" class="emptyState">
v-if="!recipes.length && !filterFavorites && !filterMustTry" <StackLayout
class="noResults" col="0"
text='Click the "+" icon to add a new recipe.' row="0"
textWrap="true" class="noResults"
/> v-if="!recipes.length && !filterFavorites && !filterTrylater"
<Label verticalAlignment="center"
v-if="!filteredRecipes.length && searchQuery" >
class="noResults" <Label
:text=" @tap="addRecipe"
`Your search &quot;${searchQuery}&quot; did not match any recipes in this category.` class="bx"
" :text="icon.plusCircle"
textWrap="true" textWrap="true"
/> />
<Label <Label
v-if="!filteredRecipes.length && filterFavorites && !searchQuery" class="title orkb"
class="noResults" text="Start adding your recipes!"
text="Your favorite recipes will be listed here." textWrap="true"
textWrap="true" />
/> <Label text='Tap the "+" icon to add a new recipe' textWrap="true" />
<Label </StackLayout>
v-if="!filteredRecipes.length && filterMustTry && !searchQuery" <StackLayout
class="noResults" col="0"
text="Your must-try recipes will be listed here." row="0"
textWrap="true" class="noResults"
/> v-if="!filteredRecipes.length && searchQuery"
<GridLayout id="btnFabContainer" rows="*,88" columns="*,88"> verticalAlignment="top"
<Label >
row="1" <Label class="bx" :text="icon.search" textWrap="true" />
col="1" <Label class="title orkb" text="No recipes found" textWrap="true" />
class="bx fab-button" <Label
:text="icon.plus" :text="
androidElevation="8" `Your search &quot;${searchQuery}&quot; did not match any recipes${
@tap="addRecipe" filterFavorites || filterTrylater || selectedCategory
/> ? ' in this category'
: ''
}`
"
textWrap="true"
/>
</StackLayout>
<StackLayout
col="0"
row="0"
class="noResults"
verticalAlignment="center"
v-if="!filteredRecipes.length && filterFavorites && !searchQuery"
>
<Label class="bx" :text="icon.heartOutline" textWrap="true" />
<Label class="title orkb" text="No favorites yet!" textWrap="true" />
<Label
text="Your favorited recipes will be listed here"
textWrap="true"
/>
</StackLayout>
<StackLayout
col="0"
row="0"
class="noResults"
verticalAlignment="center"
v-if="!filteredRecipes.length && filterTrylater && !searchQuery"
>
<Label class="bx" :text="icon.trylaterOutline" textWrap="true" />
<Label
class="title orkb"
text="No recipes here to try!"
textWrap="true"
/>
<!-- text="Your Try later recipes will be listed here" -->
<Label
text="Your recipes to try later will be listed here"
textWrap="true"
/>
</StackLayout>
</GridLayout>
<GridLayout id="btnFabContainer" rows="*,auto" columns="*,auto">
<transition name="bounce">
<Label
v-if="showFAB"
row="1"
col="1"
class="bx fab-button"
:text="icon.plus"
@tap="addRecipe"
/>
</transition>
</GridLayout> </GridLayout>
</AbsoluteLayout> </AbsoluteLayout>
</Page> </Page>
@ -166,7 +216,7 @@ import { mapState, mapActions } from "vuex"
export default { export default {
props: [ props: [
"filterFavorites", "filterFavorites",
"filterMustTry", "filterTrylater",
"selectedCategory", "selectedCategory",
"showDrawer", "showDrawer",
"hijackGlobalBackEvent", "hijackGlobalBackEvent",
@ -184,6 +234,7 @@ export default {
rightAction: false, rightAction: false,
sortType: "Natural order", sortType: "Natural order",
deletionDialogActive: false, deletionDialogActive: false,
showFAB: false,
} }
}, },
computed: { computed: {
@ -194,7 +245,7 @@ export default {
(e) => (e) =>
e.isFavorite && e.title.toLowerCase().includes(this.searchQuery) e.isFavorite && e.title.toLowerCase().includes(this.searchQuery)
) )
} else if (this.filterMustTry) { } else if (this.filterTrylater) {
return this.recipes.filter( return this.recipes.filter(
(e) => !e.tried && e.title.toLowerCase().includes(this.searchQuery) (e) => !e.tried && e.title.toLowerCase().includes(this.searchQuery)
) )
@ -216,11 +267,12 @@ export default {
initializePage() { initializePage() {
this.filterFavorites this.filterFavorites
? this.setComponent("Favorites") ? this.setComponent("Favorites")
: this.filterMustTry : this.filterTrylater
? this.setComponent("Must-Try") ? this.setComponent("Try later")
: this.selectedCategory : this.selectedCategory
? this.setComponent(this.selectedCategory) ? this.setComponent(this.selectedCategory)
: this.setComponent("EnRecipes") : this.setComponent("EnRecipes")
this.showFAB = true
}, },
openSearch() { openSearch() {
this.showSearch = true this.showSearch = true
@ -313,7 +365,7 @@ export default {
return item.isFavorite return item.isFavorite
? item.title.toLowerCase().includes(this.searchQuery) ? item.title.toLowerCase().includes(this.searchQuery)
: false : false
} else if (this.filterMustTry) { } else if (this.filterTrylater) {
return item.tried return item.tried
? false ? false
: item.title.toLowerCase().includes(this.searchQuery) : item.title.toLowerCase().includes(this.searchQuery)
@ -387,13 +439,14 @@ export default {
: (this.viewIsScrolled = false) : (this.viewIsScrolled = false)
}, },
addRecipe() { addRecipe() {
this.showFAB = false
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.$navigateTo(EditRecipe, { this.$navigateTo(EditRecipe, {
transition: { // transition: {
name: "slide", // name: "slide",
duration: 250, // duration: 250,
curve: "easeIn", // curve: "easeIn",
}, // },
props: { props: {
viewIsScrolled: this.viewIsScrolled, viewIsScrolled: this.viewIsScrolled,
selectedCategory: this.selectedCategory, selectedCategory: this.selectedCategory,
@ -401,13 +454,15 @@ export default {
}) })
}, },
viewRecipe({ item, index }) { viewRecipe({ item, index }) {
this.showFAB = false
this.$navigateTo(ViewRecipe, { this.$navigateTo(ViewRecipe, {
transition: { // transition: {
name: "fade", // name: "fade",
duration: 250, // duration: 250,
curve: "easeIn", // curve: "easeIn",
}, // },
props: { props: {
filterTrylater: this.filterTrylater,
recipeIndex: index, recipeIndex: index,
recipeID: item.id, recipeID: item.id,
hijackGlobalBackEvent: this.hijackGlobalBackEvent, hijackGlobalBackEvent: this.hijackGlobalBackEvent,
@ -416,5 +471,8 @@ export default {
}) })
}, },
}, },
mounted() {
this.showFAB = true
},
} }
</script> </script>

View file

@ -33,7 +33,7 @@
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<Label text="Backup/Restore" class="group-header" /> <Label text="Backup/Restore" class="group-header" />
<StackLayout <!-- <StackLayout
orientation="horizontal" orientation="horizontal"
class="option" class="option"
@tap="selectBackupDir" @tap="selectBackupDir"
@ -43,7 +43,7 @@
<Label text="EnRecipes Backup Directory" class="option-title" /> <Label text="EnRecipes Backup Directory" class="option-title" />
<Label text="/storage/emulated/0/EnRecipes" class="option-info" /> <Label text="/storage/emulated/0/EnRecipes" class="option-info" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout> -->
<StackLayout orientation="horizontal" class="option" @tap="backupData"> <StackLayout orientation="horizontal" class="option" @tap="backupData">
<Label class="bx" :text="icon.backup" /> <Label class="bx" :text="icon.backup" />
<Label text="Backup Data" class="option-title" /> <Label text="Backup Data" class="option-title" />
@ -63,7 +63,6 @@ import {
path, path,
getFileAccess, getFileAccess,
knownFolders, knownFolders,
Application,
} from "@nativescript/core" } from "@nativescript/core"
import * as permissions from "nativescript-permissions" import * as permissions from "nativescript-permissions"
import { Zip } from "nativescript-zip" import { Zip } from "nativescript-zip"

View file

@ -1,13 +1,13 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="initializePage">
<ActionBar height="128" margin="0" flat="true"> <ActionBar height="152" margin="0" flat="true">
<GridLayout <GridLayout
rows="64, 64" rows="24, 64, 64"
columns="auto, *, auto,auto, auto" columns="auto, *, auto,auto, auto"
class="actionBarContainer" class="actionBarContainer"
> >
<Label <Label
row="0" row="1"
col="0" col="0"
class="bx leftAction" class="bx leftAction"
:text="icon.back" :text="icon.back"
@ -15,7 +15,7 @@
@tap="$navigateBack()" @tap="$navigateBack()"
/> />
<ScrollView <ScrollView
row="1" row="2"
col="1" col="1"
colSpan="3" colSpan="3"
orientation="horizontal" orientation="horizontal"
@ -28,28 +28,29 @@
/> />
</ScrollView> </ScrollView>
<Label <Label
row="0" row="1"
col="2"
class="bx"
:text="icon.share"
@tap="shareRecipe"
/>
<Label
row="0"
col="3" col="3"
class="bx" class="bx"
:class="{ 'view-favorited': recipe.isFavorite }"
:text="recipe.isFavorite ? icon.heart : icon.heartOutline" :text="recipe.isFavorite ? icon.heart : icon.heartOutline"
@tap="toggleFavorite" @tap="toggleFavorite"
/> />
<Label <Label
row="0" v-if="!filterTrylater"
row="1"
col="4" col="4"
class="bx" class="bx"
:class="{ 'view-favorited': !recipe.tried }" :text="recipe.tried ? icon.trylaterOutline : icon.trylater"
:text="recipe.tried ? icon.musttryOutline : icon.musttry" @tap="toggleTrylater"
@tap="toggleMustTry"
/> />
<Label
v-if="!busy"
row="1"
col="2"
class="bx"
:text="icon.edit"
@tap="editRecipe"
/>
<ActivityIndicator v-else row="1" col="4" :busy="busy" />
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<AbsoluteLayout> <AbsoluteLayout>
@ -93,10 +94,6 @@
class="view-other" class="view-other"
:text="`Cooking time: ${getTime(recipe.cookTime)}`" :text="`Cooking time: ${getTime(recipe.cookTime)}`"
/> />
<Label
class="view-other"
:text="`Portion size: ${recipe.portionSize}`"
/>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
@ -112,7 +109,7 @@
<StackLayout v-else padding="16 16 124"> <StackLayout v-else padding="16 16 124">
<AbsoluteLayout class="inputField"> <AbsoluteLayout class="inputField">
<TextField <TextField
width="50%" width="165"
v-model="portionScale" v-model="portionScale"
keyboardType="number" keyboardType="number"
/> />
@ -207,7 +204,7 @@
<Label <Label
verticalAlignment="top" verticalAlignment="top"
horizontalAlignment="center" horizontalAlignment="center"
class="view-count orkb" class="view-count note orkb"
col="0" col="0"
:text="index + 1" :text="index + 1"
/> />
@ -223,52 +220,74 @@
text="Click the edit button to add references to this recipe" text="Click the edit button to add references to this recipe"
textWrap="true" textWrap="true"
/> />
<StackLayout v-else padding="32 16 132"> <StackLayout v-else padding="10 0 132">
<GridLayout <StackLayout
columns="auto ,*"
v-for="(reference, index) in recipe.references" v-for="(reference, index) in recipe.references"
:key="index" :key="index"
@tap="openURL($event, reference)"
> >
<GridLayout
v-if="isValidURL(reference)"
columns="*, auto"
class="view-reference-container"
androidElevation="1"
>
<Label
col="0"
verticalAlignment="center"
class="view-reference"
:text="reference"
textWrap="false"
@tap="openURL($event, reference)"
/>
<Label
col="1"
class="view-copyReference bx"
:text="icon.copy"
@tap="copyURL($event, reference)"
/>
</GridLayout>
<Label <Label
col="0" v-else
colSpan="2" class="view-reference-text"
class="view-reference"
:text="reference" :text="reference"
textWrap="true" textWrap="true"
/> />
<Label </StackLayout>
verticalAlignment="top"
horizontalAlignment="center"
class="orkb view-count"
col="0"
:text="index + 1"
/>
</GridLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
</TabView> </TabView>
<GridLayout id="btnFabContainer" rows="*,88" columns="*,88"> <GridLayout id="btnFabContainer" rows="*,auto" columns="*,auto">
<Label <Label
v-if="!busy"
@tap="editRecipe"
row="1" row="1"
col="1" col="1"
class="bx fab-button" class="bx fab-button"
:text="icon.edit" :text="icon.unchecked"
androidElevation="8" @tap="recipeTried"
v-if="filterTrylater"
/> />
<ActivityIndicator v-else row="1" col="1" :busy="busy" /> <transition name="dolly" appear>
<Label
row="1"
col="1"
class="bx fab-button"
:text="icon.share"
@tap="shareRecipe"
v-if="!filterTrylater && showFab"
/>
</transition>
</GridLayout> </GridLayout>
</AbsoluteLayout> </AbsoluteLayout>
</Page> </Page>
</template> </template>
<script> <script>
import { Screen, Utils, ImageSource, Device } from "@nativescript/core" import { Screen, Utils, ImageSource, Device, File } from "@nativescript/core"
import { Feedback, FeedbackType, FeedbackPosition } from "nativescript-feedback"
import * as application from "tns-core-modules/application"
import * as Toast from "nativescript-toast" import * as Toast from "nativescript-toast"
import * as SocialShare from "nativescript-social-share" import * as SocialShare from "nativescript-social-share-ns-7"
import { setText } from "nativescript-clipboard"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
@ -276,6 +295,7 @@ import EditRecipe from "./EditRecipe.vue"
export default { export default {
props: [ props: [
"filterTrylater",
"recipeIndex", "recipeIndex",
"recipeID", "recipeID",
"hijackGlobalBackEvent", "hijackGlobalBackEvent",
@ -286,6 +306,7 @@ export default {
busy: false, busy: false,
portionScale: 1, portionScale: 1,
recipe: null, recipe: null,
showFab: false,
} }
}, },
computed: { computed: {
@ -308,6 +329,47 @@ export default {
this.setCurrentComponentAction("ViewRecipe") this.setCurrentComponentAction("ViewRecipe")
}, 500) }, 500)
this.portionScale = this.recipe.portionSize this.portionScale = this.recipe.portionSize
this.showFab = true
this.showInfo()
},
niceDates(time) {
let date = new Date(time)
let diff = (new Date().getTime() - date.getTime()) / 1000
console.log(diff)
let dayDiff = Math.ceil(diff / 86400)
console.log(dayDiff)
if (isNaN(dayDiff) || dayDiff < 0) return ""
return (
(dayDiff == 0 &&
((diff < 60 && "just now") ||
(diff < 120 && "1 minute ago") ||
(diff < 3600 && Math.floor(diff / 60) + " minutes ago") ||
(diff < 7200 && "1 hour ago") ||
(diff < 86400 && Math.floor(diff / 3600) + " hours ago"))) ||
(dayDiff == 1 && "yesterday") ||
(dayDiff < 7 && dayDiff + " days ago") ||
(dayDiff < 31 && Math.ceil(dayDiff / 7) + " week(s) ago") ||
(dayDiff > 30 &&
dayDiff < 365 &&
Math.round(dayDiff / 30) + " month(s) ago") ||
(dayDiff > 364 && Math.round(dayDiff / 365) + " year(s) ago")
)
},
showInfo() {
let feedback = new Feedback()
feedback.show({
type: FeedbackType.Info,
message: `You tried this recipe ${this.niceDates(
this.recipe.triedOn
)}!`,
})
},
highlight(args) {
let temp = args.object.className
args.object.className = `${temp} option-highlight`
setTimeout(() => (args.object.className = temp), 100)
}, },
roundedQuantity(quantity) { roundedQuantity(quantity) {
return ( return (
@ -319,13 +381,14 @@ export default {
) )
}, },
editRecipe() { editRecipe() {
this.showFab = false
this.busy = true this.busy = true
this.$navigateTo(EditRecipe, { this.$navigateTo(EditRecipe, {
transition: { // transition: {
name: "slide", // name: "slide",
duration: 250, // duration: 250,
curve: "easeIn", // curve: "easeOut",
}, // },
props: { props: {
recipeIndex: this.recipeIndex, recipeIndex: this.recipeIndex,
recipeID: this.recipeID, recipeID: this.recipeID,
@ -333,6 +396,55 @@ export default {
// backstackVisible: false, // backstackVisible: false,
}) })
}, },
shareRecipe() {
let overview = `${
this.recipe.title
} Recipe\n\nPreparation time: ${this.getTime(
this.recipe.prepTime
)}\nCooking time: ${this.getTime(this.recipe.cookTime)}\n`
let shareContent = overview
if (this.recipe.ingredients.length) {
let ingredients = `\n\nIngredients for ${this.recipe.portionSize} ${
this.recipe.portionSize === 1 ? "postion:" : "portions:"
}\n\n`
this.recipe.ingredients.forEach((e) => {
ingredients += `- ${this.roundedQuantity(e.quantity)} ${e.unit} ${
e.item
}\n`
})
shareContent += ingredients
}
if (this.recipe.instructions.length) {
let instructions = `\n\nInstructions:\n\n`
this.recipe.instructions.forEach((e, i) => {
instructions += `${i + 1}. ${e}\n\n`
})
shareContent += instructions
}
if (this.recipe.notes.length) {
let notes = `\nNotes:\n\n`
this.recipe.notes.forEach((e, i) => {
notes += `${i + 1}. ${e}\n\n`
})
shareContent += notes
}
if (this.recipe.references.length) {
let references = `\nReferences:\n\n`
this.recipe.references.forEach((e, i) => {
references += `${e}\n\n`
})
shareContent += references
}
let sharenote =
"\nCreated and shared via EnRecipes.\n\nDownload the app on f-droid:\nhttps://www.vishnuraghav.com/"
shareContent += sharenote
SocialShare.shareText(
shareContent,
"How would you like to share this recipe?"
)
},
toggle(key) { toggle(key) {
this.toggleStateAction({ this.toggleStateAction({
index: this.recipeIndex, index: this.recipeIndex,
@ -341,39 +453,52 @@ export default {
key, key,
}) })
}, },
shareRecipe() {
// if (this.recipe.imageSrc) {
// let image = ImageSource.fromFile(this.recipe.imageSrc)
// SocialShare.shareImage(image)
// } else {
// SocialShare.shareText("Text only")
// }
alert(Device.sdkVersion)
},
toggleFavorite() { toggleFavorite() {
this.recipe.isFavorite this.recipe.isFavorite
? Toast.makeText("Removed from Favorites").show() ? Toast.makeText("Removed from Favorites").show()
: Toast.makeText("Added to Favorites").show() : Toast.makeText("Added to Favorites").show()
this.toggle("isFavorite") this.toggle("isFavorite")
}, },
toggleMustTry() { toggleTrylater() {
this.recipe.tried this.recipe.tried
? Toast.makeText("Added to Must-Try").show() ? this.filterTrylater
: Toast.makeText("Removed from Must-Try").show() ? Toast.makeText("Added back to Try later").show()
: Toast.makeText("Added to Try later").show()
: this.filterTrylater
? Toast.makeText("You tried this recipe").show()
: Toast.makeText("Removed from Try later").show()
// : Toast.makeText("You tried this recipe").show()
this.toggle("tried") this.toggle("tried")
}, },
recipeTried() {
this.toggle("tried")
this.$navigateBack()
},
getTime(time) { getTime(time) {
let t = time.split(":") let t = time.split(":")
let h = t[0] let h = t[0]
let m = t[1] let m = t[1]
return h !== "00" ? `${h}h ${m}m` : `${m}m` return h !== "00" ? `${h}h ${m}m` : `${m}m`
}, },
isValidURL(string) {
let pattern = new RegExp("^https?|www", "ig")
return pattern.test(string)
},
openURL(args, url) { openURL(args, url) {
// this.highlight(args)
Utils.openUrl(url) Utils.openUrl(url)
}, },
copyURL(args, url) {
setText(url).then((e) => {
Toast.makeText("URL Copied").show()
})
},
}, },
created() { created() {
this.recipe = this.recipes.filter((e) => e.id === this.recipeID)[0] this.recipe = this.recipes.filter((e) => e.id === this.recipeID)[0]
}, },
mounted() {
this.showFab = true
},
} }
</script> </script>

View file

@ -2,7 +2,7 @@
<Page> <Page>
<StackLayout class="dialogContainer" :class="isLightMode"> <StackLayout class="dialogContainer" :class="isLightMode">
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<Label class="dialogDescription" :text="description" textWrap="true" /> <Label v-if="description" class="dialogDescription" :text="description" textWrap="true" />
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="actionsContainer" class="actionsContainer"

View file

@ -14,9 +14,9 @@ Vue.registerElement(
) )
// Vue.registerElement( // Vue.registerElement(
// "Fab", // 'Fab',
// () => require("@nstudio/nativescript-floatingactionbutton").Fab // () => require('@nstudio/nativescript-floatingactionbutton').Fab
// ) // );
if (TNS_ENV !== "production") { if (TNS_ENV !== "production") {
// Vue.use(VueDevtools) // Vue.use(VueDevtools)

View file

@ -8,7 +8,59 @@ Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
recipes: [], recipes: [
{
imageSrc: null,
title: "Mushroom & Spinach Risotto",
category: "Lunch",
prepTime: "00:25",
cookTime: "00:45",
portionSize: "8",
ingredients: [
{
item: "reduced-sodium chicken broth",
unit: "cup",
quantity: "5.25",
},
{ item: "sliced fresh mushrooms", unit: "cup", quantity: "2.5" },
{ item: "medium onion, finely chopped", unit: "unit", quantity: "1" },
{ item: "butter", unit: "Tbsp", quantity: "3" },
{ item: "Garlic", unit: "unit", quantity: "3" },
{
item: "white wine or reduced-sodium chicken broth",
unit: "cup",
quantity: ".75",
},
{ item: "heavy whipping cream", unit: "cup", quantity: "1" },
{ item: "uncooked arborio rice", unit: "cup", quantity: "1.75" },
{ item: "olive oil", unit: "Tbsp", quantity: "2" },
{
item: "frozen chopped spinach, thawed and squeezed dry",
unit: "cup",
quantity: "1.5",
},
{ item: "pepper", unit: "tsp", quantity: ".5" },
{ item: "Salt", unit: "tsp", quantity: ".25" },
{ item: "grated Parmesan cheese", unit: "cup", quantity: "1" },
],
instructions: [
"In a large saucepan, heat broth and keep warm. In a large skillet, saute mushrooms and onion in butter until tender. Add garlic; cook 1 minute longer. Stir in wine. Bring to a boil; cook until liquid is reduced by half. Add cream; cook and stir over medium heat until slightly thickened",
"In a large saucepan, saute rice in oil for 2-3 minutes or until rice is lightly browned. Stir in 1/2 cup hot broth. Reduce heat; cook and stir for 20 minutes or until broth is absorbed.",
"Continue adding hot broth, 1/2 cup at a time, and stirring until all the broth has been absorbed and rice is tender but firm. Add the mushroom mixture, spinach, pepper, salt and grated Parmesan cheese; cook and stir until heated through. If desired, sprinkle with parsley and shaved Parmesan cheese. Serve immediately.",
],
notes: [
"Nutrition Facts\n3/4 cup: 409 calories, 22g fat (12g saturated fat), 61mg cholesterol, 667mg sodium, 41g carbohydrate (3g sugars, 2g fiber), 11g protein.",
],
references: [
"https://www.tasteofhome.com/recipes/mushroom-spinach-risotto/",
],
isFavorite: false,
tried: true,
triedOn: "2020-10-28T18:19:06.528Z",
lastModified: "2020-10-28T06:19:06.528Z",
id: "57qm8oqxdr",
},
],
categories: [], categories: [],
units: [ units: [
"unit", "unit",
@ -45,10 +97,11 @@ export default new Vuex.Store({
plus: "\ueb89", plus: "\ueb89",
close: "\uec4e", close: "\uec4e",
image: "\ueae9", image: "\ueae9",
addImage: "\ueae8",
back: "\ue988", back: "\ue988",
save: "\uee48", save: "\uee48",
camera: "\uecc2", camera: "\uecc2",
share: "\uee51", share: "\uee50",
edit: "\uee17", edit: "\uee17",
theme: "\ued09", theme: "\ued09",
folder: "\ued7c", folder: "\ued7c",
@ -59,8 +112,14 @@ export default new Vuex.Store({
user: "\uee8e", user: "\uee8e",
trash: "\uee83", trash: "\uee83",
donate: "\ued41", donate: "\ued41",
musttry: "\uec96", trylater: "\uec96",
musttryOutline: "\ue9bb", trylaterOutline: "\ue9bb",
note: "\uee0a",
copy: "\uea51",
plusCircle: "\ueb8a",
unchecked: "\uea16",
checked: "\uece6",
telegram: "\ue95e",
}, },
currentComponent: "EnRecipes", currentComponent: "EnRecipes",
}, },

54
package-lock.json generated
View file

@ -1095,20 +1095,17 @@
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
"@nativescript-community/perms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@nativescript-community/perms/-/perms-2.1.1.tgz",
"integrity": "sha512-Ay4v1lEGTQ5rYYlYA8CKcCXuxOuyU4633r/JXi9aRG8MgxfOT+rDuQLgSz+LLCYmBK1ndfHHfyUTilkaUj1H8Q=="
},
"@nativescript/android": { "@nativescript/android": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@nativescript/android/-/android-7.0.1.tgz", "resolved": "https://registry.npmjs.org/@nativescript/android/-/android-7.0.1.tgz",
"integrity": "sha512-VsZCJ5zfZo0+/lFwKz+S7iFb7MA2jgACB7y8dNje3/cnZl+moKPNjFqitoEP0DY4gLz9LJNbFIIaUt84tMdUSQ==", "integrity": "sha512-VsZCJ5zfZo0+/lFwKz+S7iFb7MA2jgACB7y8dNje3/cnZl+moKPNjFqitoEP0DY4gLz9LJNbFIIaUt84tMdUSQ==",
"dev": true "dev": true
}, },
"@nativescript/camera": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@nativescript/camera/-/camera-5.0.2.tgz",
"integrity": "sha512-frNeCLhdQ+W6oXIv05pALdmZDcwilw/NopLtQILtUwuLS7xhE+UMx6CqQxxCxMYzWKsvET2k9VLAo3mJGAoSeg==",
"requires": {
"nativescript-permissions": "~1.3.0"
}
},
"@nativescript/core": { "@nativescript/core": {
"version": "7.0.12", "version": "7.0.12",
"resolved": "https://registry.npmjs.org/@nativescript/core/-/core-7.0.12.tgz", "resolved": "https://registry.npmjs.org/@nativescript/core/-/core-7.0.12.tgz",
@ -5096,17 +5093,28 @@
"to-regex": "^3.0.1" "to-regex": "^3.0.1"
} }
}, },
"nativescript-clipboard": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/nativescript-clipboard/-/nativescript-clipboard-2.0.0.tgz",
"integrity": "sha512-w7qRJiWiBeq55f3IW+pAkbrl+v+yqZf3bhcl1wH6Qm1JqZLWDv7HemzHTM5CvaqQ4gfz5dnGhJW1q1fsr5KSOw=="
},
"nativescript-couchbase-plugin": { "nativescript-couchbase-plugin": {
"version": "0.9.6", "version": "0.9.6",
"resolved": "https://registry.npmjs.org/nativescript-couchbase-plugin/-/nativescript-couchbase-plugin-0.9.6.tgz", "resolved": "https://registry.npmjs.org/nativescript-couchbase-plugin/-/nativescript-couchbase-plugin-0.9.6.tgz",
"integrity": "sha512-kMA9KHQX82TFaGnGUhY94KLOLss4pb5QmghgoEdu1sLwd94I/f1MQ+kHWbuBOdFmdQJw5oCK+Sey+A22Nd5jgA==" "integrity": "sha512-kMA9KHQX82TFaGnGUhY94KLOLss4pb5QmghgoEdu1sLwd94I/f1MQ+kHWbuBOdFmdQJw5oCK+Sey+A22Nd5jgA=="
}, },
"nativescript-imagepicker": { "nativescript-feedback": {
"version": "7.1.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/nativescript-imagepicker/-/nativescript-imagepicker-7.1.0.tgz", "resolved": "https://registry.npmjs.org/nativescript-feedback/-/nativescript-feedback-2.0.0.tgz",
"integrity": "sha512-YFVwmPz7mv7mNXA7vmnIXmqPZiWxH4RoJPDL3m34egV8Ae9mKJCXZxl2LyPraOP+T4v6iXsxV9NSbjg0kMDuNQ==", "integrity": "sha512-6ZE/g+s1xxez3pMRJa/r0f144VuyapgDjbo8D37nMC4F0WDQLKk8dC0405czhQxD2djVq3GEMfM/n0cuMbY53A=="
},
"nativescript-mediafilepicker": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/nativescript-mediafilepicker/-/nativescript-mediafilepicker-4.0.1.tgz",
"integrity": "sha512-rBrZQR+46dCypIyLrzIlzmHgpmTSMGFR5a6snq8uUhtIqLlc674/nwWlNM1kFOxMh1kKxA+qyk74Of+NCKYoqQ==",
"requires": { "requires": {
"nativescript-permissions": "~1.3.0" "@nativescript-community/perms": "^2.1.1",
"ts-node": "^9.0.0"
} }
}, },
"nativescript-permissions": { "nativescript-permissions": {
@ -5122,10 +5130,10 @@
"nativescript-permissions": "~1.3.0" "nativescript-permissions": "~1.3.0"
} }
}, },
"nativescript-social-share": { "nativescript-social-share-ns-7": {
"version": "1.6.0", "version": "11.6.0",
"resolved": "https://registry.npmjs.org/nativescript-social-share/-/nativescript-social-share-1.6.0.tgz", "resolved": "https://registry.npmjs.org/nativescript-social-share-ns-7/-/nativescript-social-share-ns-7-11.6.0.tgz",
"integrity": "sha512-PjSMseCWPGJbW0KPMgQBiTQke6I8cYxf0CGXtuJ0BnRhXrEjF3d+3kAnI8E3O8PeW/BFwNIqLYG4fkoQF4obyA==" "integrity": "sha512-NI6U8iOz3CKKV6nuZYSYUwA5JYFt1uPZAaMAWxySbMVQaKdP7H3lt+Sa/4ENZTvOkaAparD3ZZUxf0PITsrstA=="
}, },
"nativescript-toast": { "nativescript-toast": {
"version": "2.0.0", "version": "2.0.0",
@ -7356,6 +7364,18 @@
} }
} }
}, },
"ts-node": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz",
"integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==",
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
}
},
"tslib": { "tslib": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",

View file

@ -8,16 +8,16 @@
"run": "ns run android" "run": "ns run android"
}, },
"dependencies": { "dependencies": {
"@nativescript/camera": "^5.0.2",
"@nativescript/core": "~7.0.0", "@nativescript/core": "~7.0.0",
"@nativescript/datetimepicker": "^2.0.4", "@nativescript/datetimepicker": "^2.0.4",
"@nativescript/theme": "^3.0.0", "@nativescript/theme": "^3.0.0",
"@nativescript/webpack": "3.0.0", "@nativescript/webpack": "3.0.0",
"nativescript-clipboard": "^2.0.0",
"nativescript-couchbase-plugin": "^0.9.6", "nativescript-couchbase-plugin": "^0.9.6",
"nativescript-imagepicker": "^7.1.0", "nativescript-feedback": "^2.0.0",
"nativescript-permissions": "^1.3.9", "nativescript-mediafilepicker": "^4.0.1",
"nativescript-plugin-filepicker": "^1.0.0", "nativescript-plugin-filepicker": "^1.0.0",
"nativescript-social-share": "^1.6.0", "nativescript-social-share-ns-7": "^11.6.0",
"nativescript-toast": "^2.0.0", "nativescript-toast": "^2.0.0",
"nativescript-ui-listview": "^9.0.4", "nativescript-ui-listview": "^9.0.4",
"nativescript-ui-sidedrawer": "^9.0.3", "nativescript-ui-sidedrawer": "^9.0.3",