optimized recipe editing

This commit is contained in:
Vishnu Raghav B 2020-11-02 17:06:53 +05:30
parent 399fc6bc20
commit efc254425d
50 changed files with 3220 additions and 2607 deletions

View file

@ -15,6 +15,8 @@
android { android {
defaultConfig { defaultConfig {
versionCode 1
versionName '1.0.0'
minSdkVersion 25 minSdkVersion 25
generatedDensities = [] generatedDensities = []
ndk { ndk {

View file

@ -7,8 +7,8 @@
<!-- <uses-permission android:name="android.permission.READ_USER_DICTIONARY" /> --> <!-- <uses-permission android:name="android.permission.READ_USER_DICTIONARY" /> -->
<!-- <uses-permission android:name="android.permission.INTERNET"/> --> <!-- <uses-permission android:name="android.permission.INTERNET"/> -->
<!-- <uses-feature android:name="android.hardware.camera" android:required="true" /> --> <!-- <uses-feature android:name="android.hardware.camera" android:required="true" /> -->
<application android:usesCleartextTraffic="true" android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@style/AppTheme" android:requestLegacyExternalStorage="true"> <application android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@style/AppTheme" android:requestLegacyExternalStorage="true">
<activity android:name="com.tns.NativeScriptActivity" android:label="@string/title_activity_kimera" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|uiMode" android:theme="@style/LaunchScreenTheme"> <activity android:name="com.tns.NativeScriptActivity" android:label="@string/title_activity_kimera" android:screenOrientation="portrait" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|uiMode" android:theme="@style/LaunchScreenTheme" android:windowSoftInputMode="adjustPan">
<meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" /> <meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View file

Before

Width:  |  Height:  |  Size: 550 B

After

Width:  |  Height:  |  Size: 550 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View file

Before

Width:  |  Height:  |  Size: 461 B

After

Width:  |  Height:  |  Size: 461 B

View file

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 664 B

View file

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

Before

Width:  |  Height:  |  Size: 472 B

After

Width:  |  Height:  |  Size: 472 B

View file

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 780 B

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 680 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 861 B

After

Width:  |  Height:  |  Size: 861 B

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -3,7 +3,7 @@
<!-- Application theme --> <!-- Application theme -->
<style name="AppThemeBase29" parent="AppThemeBase21"> <style name="AppThemeBase29" parent="AppThemeBase21">
<item name="android:forceDarkAllowed">true</item> <item name="android:forceDarkAllowed">false</item>
</style> </style>
<style name="AppTheme" parent="AppThemeBase29"> <style name="AppTheme" parent="AppThemeBase29">

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- theme to use FOR launch screen -->
<style name="LaunchScreenThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
<item name="toolbarStyle">
@style/NativeScriptToolbarStyle
</item>
<item name="colorPrimary">
@color/ns_primary
</item>
<item name="colorPrimaryDark">
@color/ns_primaryDark
</item>
<item name="colorAccent">
@color/ns_accent
</item>
<item name="android:windowBackground">
@drawable/splash_screen
</item>
<item name="android:windowActionBarOverlay">
true
</item>
<item name="android:windowTranslucentStatus">
true
</item>
</style>
<style name="LaunchScreenTheme" parent="LaunchScreenThemeBase">
</style>
<!-- theme to use AFTER launch screen is loaded -->
<style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
<item name="toolbarStyle">
@style/NativeScriptToolbarStyle
</item>
<item name="colorPrimary">
@color/ns_primary
</item>
<item name="colorPrimaryDark">
@color/ns_primaryDark
</item>
<item name="colorAccent">
@color/ns_accent
</item>
</style>
<style name="AppTheme" parent="AppThemeBase">
</style>
<!-- theme for action-bar -->
<style name="NativeScriptToolbarStyleBase" parent="Widget.AppCompat.Toolbar">
<item name="android:background">
@color/ns_primary
</item>
<item name="theme">
@style/ThemeOverlay.AppCompat.ActionBar
</item>
<item name="popupTheme">
@style/ThemeOverlay.AppCompat
</item>
</style>
<style name="NativeScriptToolbarStyle" parent="NativeScriptToolbarStyleBase">
</style>
</resources>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- theme to use FOR launch screen-->
<style name="LaunchScreenThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
<item name="toolbarStyle">@style/NativeScriptToolbarStyle</item>
<item name="colorPrimary">@color/ns_primary</item>
<item name="colorPrimaryDark">@color/ns_primaryDark</item>
<item name="colorAccent">@color/ns_accent</item>
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowActionBarOverlay">true</item>
<item name="android:windowTranslucentStatus">false</item>
</style>
<style name="LaunchScreenTheme" parent="LaunchScreenThemeBase">
</style>
<!-- theme to use AFTER launch screen is loaded-->
<style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
<item name="toolbarStyle">@style/NativeScriptToolbarStyle</item>
<item name="colorPrimary">@color/ns_primary</item>
<item name="colorPrimaryDark">@color/ns_primaryDark</item>
<item name="colorAccent">@color/ns_accent</item>
</style>
<style name="AppTheme" parent="AppThemeBase">
</style>
<!-- theme for action-bar -->
<style name="NativeScriptToolbarStyleBase" parent="Widget.AppCompat.Toolbar">
<item name="android:background">@color/ns_primary</item>
<item name="theme">@style/ThemeOverlay.AppCompat.ActionBar</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
</style>
<style name="NativeScriptToolbarStyle" parent="NativeScriptToolbarStyleBase">
</style>
</resources>

View file

@ -13,13 +13,15 @@ $grayL1: #e0e0e0;
$grayL2: #eeeeee; $grayL2: #eeeeee;
$grayL3: #f5f5f5; $grayL3: #f5f5f5;
$grayL4: #fafafa; $grayL4: #fafafa;
$orange400: #ff7043;
$orange: #ff7043; $orange500: #ff5722;
$paleOrange: #fbe9e7;
// Global SCSS styling // Global SCSS styling
// @see https://docs.nativescript.org/ui/styling // @see https://docs.nativescript.org/ui/styling
// * {
// font-size: 16;
// }
Page { Page {
font-family: "Orkney-Regular"; font-family: "Orkney-Regular";
} }
@ -32,13 +34,19 @@ Page {
.bx { .bx {
font-family: "boxicons"; font-family: "boxicons";
font-size: 24; font-size: 24;
vertical-alignment: center;
&.small {
padding: 0;
font-size: 16;
}
} }
.ns-light { .ns-light {
Page, Page,
ActionBar, ActionBar,
SearchBar, SearchBar,
TabView { TabView,
ListPicker {
color: $grayD4; color: $grayD4;
background: $grayL4; background: $grayL4;
} }
@ -53,49 +61,57 @@ Page {
.fieldLabel { .fieldLabel {
background: $grayL4; background: $grayL4;
} }
.view-reference-text, .referenceItem,
.view-reference-container { .recipeText,
.overviewItem,
.recipeItem {
background: white; background: white;
} }
.option-highlight { .option-highlight,
.selected-sd-item {
background: $grayL2; background: $grayL2;
} }
.sd-item, .sd-item,
.sd-group-header, .sd-group-header,
.recipe-time { .time .bx {
color: $grayD2; color: $grayD2;
} }
.fab-button {
color: white;
background-color: $orange500;
}
.option, .option,
.icon-option { .icon-option {
.bx, .bx,
.option-info { .option-info {
color: $grayD2; color: $grayD2;
} }
.option-title {
color: $grayD4;
}
} }
.view-imageHolder, .imageHolder {
.recipeImgContainer {
color: $grayL1; color: $grayL1;
background: $grayL2; background: $grayL2;
} }
.view-other { .count {
color: $grayD2;
}
.view-count {
color: $grayL4; color: $grayL4;
background: $grayD4; background: $grayD4;
} }
.view-instruction { .instruction {
border-color: $grayD4; border-color: $grayD4;
} }
.text-btn,
.group-header,
.category,
ActivityIndicator,
.selected-sd-item {
color: $orange500;
}
} }
.ns-dark { .ns-dark {
Page, Page,
ActionBar, ActionBar,
SearchBar, SearchBar,
TabView { TabView,
ListPicker {
color: $grayL4; color: $grayL4;
background: $grayD4; background: $grayD4;
} }
@ -106,50 +122,57 @@ Page {
.hr { .hr {
border-color: #111; border-color: #111;
} }
.sd, .sd,
.fieldLabel { .fieldLabel {
background: $grayD4; background: $grayD4;
} }
.recipe-li, .referenceItem,
.recipeText,
.overviewItem,
.recipeItem,
.option-highlight, .option-highlight,
.view-reference-text, .appIconContainer {
.view-reference-container {
background: $grayD3; background: $grayD3;
} }
.sd-item, .sd-item,
.sd-group-header, .sd-group-header,
.recipe-time { .time .bx {
color: $grayL2; color: $grayL2;
} }
.selected-sd-item {
background: #111;
}
.fab-button {
color: #111;
background: $orange400;
}
.option, .option,
.icon-option { .icon-option {
.bx, .bx,
.option-info { .option-info {
color: $grayL2; color: $grayL2;
} }
.option-title {
color: $grayL4;
}
} }
.view-imageHolder, .imageHolder {
.recipeImgContainer {
color: $grayD4; color: $grayD4;
background: #111; background: #111;
} }
.view-other { .count {
color: $grayL2;
}
.view-count {
color: $grayD4; color: $grayD4;
background: $grayL4; background: $grayL4;
} }
.view-instruction { .instruction {
border-color: $grayL4; border-color: $grayL4;
} }
.text-btn,
.group-header,
.category,
ActivityIndicator,
.selected-sd-item {
color: $orange400;
}
} }
// -----------------------------
// Elements // Elements
TextField, TextField,
TextView, TextView,
@ -173,131 +196,118 @@ TabView {
margin-top: 16; margin-top: 16;
} }
.fieldLabel { .fieldLabel {
font-size: 13; font-size: 12;
margin-left: 8; margin-left: 8;
padding: 0 8; padding: 0 8;
} }
// DateTimePicker // -----------------------------
.date-time-picker,
.date-time-picker-spinners {
color: $grayD4;
background: $grayL4;
&.ns-dark {
color: $grayL4;
background: $grayD4;
}
}
.ns-dark .date-time-picker-spinners {
color: $grayL4;
background: $grayD4;
}
// ActionBar // ActionBar
ActionBar { ActionBar {
width: 100%;
margin: 24 0 0 0; margin: 24 0 0 0;
padding: 0; padding: 0;
height: 64; height: 64;
.bx { .bx {
padding: 16 12;
vertical-alignment: center;
}
.leftAction {
padding: 16 16 16 4; padding: 16 16 16 4;
margin: 0; margin: 0;
vertical-alignment: center;
}
.title {
padding-left: 8;
text-align: left;
font-size: 18;
} }
} }
.actionBarContainer { // -----------------------------
margin: 0;
padding: 0;
}
SearchBar {
width: 100%;
font-size: 16;
}
.title {
padding-left: 8;
text-align: left;
font-size: 18;
}
// Side Drawer // Side Drawer
.sd { .sd {
margin-top: 24; padding: 32 8 8;
} }
.sd-item { .sd-item {
border-radius: 4; border-radius: 4;
padding: 0 16; padding: 0 16;
height: 48; height: 48;
font-size: 16;
vertical-alignment: center; vertical-alignment: center;
// prettier-ignore .bx {
.bx, Label { font-size: 24;
vertical-alignment: center;
} }
&.selected-sd-item { // prettier-ignore
background: $paleOrange; Label {
color: $orange; font-size: 16;
vertical-alignment: center;
} }
} }
.sd-group-header { .sd-group-header {
width: 100%; width: 100%;
padding: 8 8 16; padding: 8 8 16;
font-size: 12;
} }
// -----------------------------
// Home // HOME
RadListView { .emptyState,
margin: 8 0 128; .noResult {
}
.recipe-li {
background: white;
margin: 8 16;
border-radius: 6;
.recipe-info {
margin: 4;
}
.recipe-cat {
font-size: 12;
padding: 0;
margin: 0;
}
.recipe-title {
font-size: 16;
line-height: 4;
margin: 0;
padding: 4 0;
}
.recipe-time {
padding: 0;
}
.recipe-cat {
color: $orange;
}
}
.emptyState {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.noResults { .noResult {
width: 100%;
height: 100%;
font-size: 16;
line-height: 8; line-height: 8;
padding: 64 16; padding: 64 16;
text-align: center; text-align: center;
font-size: 16;
horizontal-alignment: center; horizontal-alignment: center;
// vertical-alignment: center; vertical-alignment: center;
.title { .icon {
font-size: 20;
text-align: center;
}
.bx {
font-size: 64; font-size: 64;
text-align: center; text-align: center;
color: $gray; color: $gray;
margin-bottom: 16; margin-bottom: 16;
} }
.title {
font-size: 20;
text-align: center;
padding: 0;
margin: 0;
horizontal-alignment: center;
.bx {
font-size: 24;
vertical-alignment: center;
}
}
}
// -----------------------------
// Recipe Items
RadListView {
margin: 0 0 128;
font-size: 16;
}
.recipeItem {
margin: 8 16;
border-radius: 6;
.recipeInfo {
margin: 4;
.category,
.time {
font-size: 12;
padding: 0;
margin: 0;
}
.title {
margin: 0;
padding: 4 0;
}
.timeContainer {
padding: 0;
.time {
padding: 0 0 0 4;
}
}
}
}
.imageHolder {
vertical-alignment: center;
border-radius: 6 0 0 6;
// prettier-ignore
Image {
border-radius: 6 0 0 6;
}
} }
.swipe-item { .swipe-item {
margin: 0 8; margin: 0 8;
@ -306,167 +316,166 @@ RadListView {
height: 128; height: 128;
border-radius: 6; border-radius: 6;
} }
.recipeImgContainer { // -----------------------------
vertical-alignment: center; // SETTINGS
// prettier-ignore
Image {
border-radius: 6 0 0 6;
}
}
// Settings
.group-header { .group-header {
padding: 8; padding: 8;
color: #ff7043;
} }
.main-container { .main-container {
padding: 16 8 128; padding: 16 8 128;
.option { .option {
padding: 16;
border-radius: 4;
font-size: 16; font-size: 16;
padding: 16;
.bx { .bx {
margin: 0 24 0 0; margin: 0 24 0 0;
} }
.option-info { .option-info {
font-size: 12; font-size: 12;
} }
} }
} }
// -----------------------------
// About // ABOUT
.app-icon-container { .appIconContainer {
margin: 32 0; padding: 32 0;
width: 100%; width: 100%;
.app-icon { .appIcon {
width: 56; width: 56;
height: 56; height: 56;
margin: 0 6 0 0; margin: 0 6 0 0;
padding: 0; padding: 0;
} }
.app-name { .appName {
font-size: 24; font-size: 24;
} }
} }
.icon-option { // -----------------------------
padding: 16; // VIEW RECIPE
border-radius: 4; .viewRecipe {
.bx { .category,
margin: 0 24 0 0; .time,
} .ingredient,
.option-title { .ingredient-check {
font-size: 16; font-size: 16;
} }
} .category {
margin: 0 8;
// View Recipe }
.time {
.view-cat { margin: 0 8;
font-size: 16; .bx {
color: #ff7043; vertical-align: top;
} }
.view-other { }
font-size: 16; .title {
} font-size: 22;
.view-title { line-height: 6;
font-size: 22; }
line-height: 6; .overviewContainer {
margin-bottom: 16; margin-top: 12;
} .overviewItem {
.view-ingredient { border-radius: 6;
font-size: 16; padding: 8;
line-height: 6; margin: 8;
padding-bottom: 12; android-elevation: 1;
} .bx {
.activity-indicator { color: $gray;
background: #ff7043; width: 24;
} horizontal-alignment: left;
}
.view-count { .itemCount {
font-size: 12; font-size: 16;
width: 24; padding: 8 4 4;
height: 24; }
padding-top: 4%; }
margin: 0 0 0 8; }
text-align: center; .ingredient,
border-radius: 100; .ingredient-check {
&.note { line-height: 6;
clip-path: polygon( padding-bottom: 12;
5% 0, }
95% 0, .ingredient-check {
100% 5%, margin-bottom: 12;
100% 65%, }
65% 100%, .count {
5% 100%, width: 24;
0 95%, height: 24;
0 5% padding-top: 4%;
); margin: 0 0 0 8;
text-align: center;
border-radius: 100;
&.square {
clip-path: polygon(
5% 0,
95% 0,
100% 5%,
100% 65%,
65% 100%,
5% 100%,
0 95%,
0 5%
);
}
}
.instruction,
.note,
.reference {
font-size: 16;
line-height: 6;
padding: 2 0 14 36;
margin: 0 0 0 19;
border-width: 0 0 0 2;
}
.instruction.noBorder {
border-color: transparent;
}
.note {
border-width: 0;
}
.referenceItem {
padding: 14 16;
margin: 8 16 8;
border-radius: 6;
font-size: 16;
.bx {
font-size: 24;
}
.recipeLink {
padding: 0 16 0 0;
margin: 0;
}
}
.recipeText {
font-size: 16;
line-height: 6;
padding: 16;
margin: 8 16 8;
border-radius: 6;
} }
} }
.view-instruction, // -----------------------------
.view-note, // FAB
.view-reference {
font-size: 16;
line-height: 6;
padding: 2 0 14 36;
margin: 0 0 0 19;
border-width: 0 0 0 2;
}
.view-instruction.instructionWOBorder {
border-color: transparent;
}
.view-note {
border-width: 0;
}
.view-reference-container {
padding: 14 16;
margin: 8 16 8;
border-radius: 6;
}
.view-reference {
padding: 0 16 0 0;
margin: 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%;
} }
// Edit Recipe
.fab-button { .fab-button {
color: #fff;
height: 56; height: 56;
width: 56; width: 56;
background-color: #ff7043;
vertical-alignment: center;
// horizontal-alignment: center;
border-radius: 28; border-radius: 28;
padding: 16; padding: 16;
margin: 16; margin: 16;
vertical-alignment: center;
android-elevation: 6; android-elevation: 6;
&.negative {
background-color: #e53935;
}
} }
// -----------------------------
// EDIT RECIPE
.sectionTitle { .sectionTitle {
font-size: 20; font-size: 20;
} }
.sec-btn { .text-btn {
font-size: 14; font-size: 14;
color: #ff7043; horizontal-alignment: left;
text-align: left;
padding: 16; padding: 16;
margin: 8 0 0 0; margin: 8 0 0 0;
} }
@ -474,14 +483,13 @@ RadListView {
padding: 4; padding: 4;
margin-top: 16; margin-top: 16;
} }
// -----------------------------
// Dialogs // DIALOGS
.dialogContainer { .dialogContainer {
width: 100%; width: 100%;
&.light { color: $grayD4;
color: $grayD4; background: $grayL4;
background: $grayL4; font-size: 16;
}
&.dark { &.dark {
color: $grayL4; color: $grayL4;
background: $grayD4; background: $grayD4;
@ -490,60 +498,62 @@ RadListView {
padding: 24 24 12; padding: 24 24 12;
font-size: 20; font-size: 20;
} }
.dialogInputField { .dialogInput {
padding: 0 24 16; padding: 0 24 16;
} }
.dialogDescription { .dialogDescription {
font-size: 16;
line-height: 4; line-height: 4;
padding: 0 24 16; padding: 0 24 16;
} }
.action {
padding: 24 24 24 8;
font-size: 12;
color: #ff7043;
}
.actionItem { .actionItem {
width: 100%; padding: 8 24;
font-size: 16; margin: 0;
padding: 8 20;
} }
.cancel { .actionsContainer {
padding: 24; padding: 24;
}
.action {
font-size: 12; font-size: 12;
color: #ff7043; color: #ff7043;
} }
} }
ActivityIndicator {
color: #ff7043;
padding: 16 12;
margin: 16 10 16 8;
}
// Vue Transitions // -----------------------------
ActivityIndicator {
width: 24;
height: 24;
margin: 16;
}
// -----------------------------
// Transitions
.bounce-enter-active { .bounce-enter-active {
animation-name: bounce-in; animation-name: bounce-in;
animation-duration: 0.5s; animation-duration: 1s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
animation-timing-function: ease-out; animation-timing-function: ease-in-out;
} }
.bounce-leave-active { .bounce-leave-active {
animation-name: bounce-in; animation-name: bounce-in;
animation-duration: 0.1s; animation-duration: 0.1s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
animation-direction: reverse; animation-direction: reverse;
animation-timing-function: ease-in; animation-timing-function: ease;
} }
@keyframes bounce-in { @keyframes bounce-in {
0% { 0% {
transform: scale(0); transform: scale(0);
opacity: 0; opacity: 0;
} }
25% {
50% {
transform: scale(1.2); transform: scale(1.2);
opacity: 1; opacity: 1;
} }
50% {
transform: scale(0.9);
}
75% {
transform: scale(1.1);
}
100% { 100% {
transform: scale(1); transform: scale(1);
@ -554,7 +564,7 @@ ActivityIndicator {
animation-duration: 1s; animation-duration: 1s;
animation-delay: 0.25s; animation-delay: 0.25s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
animation-timing-function: ease; animation-timing-function: ease-in-out;
} }
.dolly-leave-active { .dolly-leave-active {
opacity: 0; opacity: 0;

File diff suppressed because it is too large Load diff

View file

@ -1,86 +1,81 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="initializePage">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<!-- Settings Actionbar --> <GridLayout rows="*" columns="auto, *">
<GridLayout rows="*" columns="auto, *" class="actionBarContainer">
<Label <Label
class="bx leftAction" class="bx"
:text="icon.menu" :text="icon.menu"
automationText="Menu" automationText="Back"
@tap="showDrawer" @tap="showDrawer"
col="0" col="0"
/> />
<Label class="title orkm" text="About" col="1" /> <Label class="title orkm" text="About" col="1" />
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false" @scroll="onScroll">
<StackLayout class="main-container"> <StackLayout class="main-container">
<StackLayout <StackLayout
horizontalAlignment="center" horizontalAlignment="center"
orientation="horizontal" orientation="horizontal"
class="app-icon-container" class="appIconContainer"
> >
<Image src="res://icon" class="app-icon" stretch="fill" /> <Image src="res://icon" class="appIcon" stretch="fill" />
<Label <Label
text="EnRecipes" text="EnRecipes"
verticalAlignment="center" verticalAlignment="center"
class="app-name orkb" class="appName orkb"
/> />
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" class="icon-option"> <StackLayout orientation="horizontal" class="option">
<Label verticalAlignment="center" class="bx" :text="icon.info" /> <Label class="bx" :text="icon.info" />
<StackLayout> <StackLayout>
<Label text="Version" class="option-title" /> <Label text="Version" />
<Label text="1.0.0" class="option-info" textWrap="true" /> <Label :text="getVersion" class="option-info" textWrap="true" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="icon-option" class="option"
@tap="openURL($event, 'https://github.com/vishnuraghavb/enrecipes')" @tap="openURL($event, 'https://github.com/vishnuraghavb/enrecipes')"
> >
<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" />
</StackLayout> </StackLayout>
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="icon-option" class="option"
@tap="openURL($event, 'https://t.me/enrecipes')" @tap="openURL($event, 'https://t.me/enrecipes')"
> >
<Label class="bx" :text="icon.telegram" /> <Label class="bx" :text="icon.telegram" />
<Label text="Join the telegram group" class="option-title" /> <Label text="Join the Telegram group" />
</StackLayout> </StackLayout>
<!-- <StackLayout orientation="horizontal" class="icon-option">
<Label class="bx" :text="icon.file" />
<Label text="Licenses" class="option-title" />
</StackLayout> -->
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<Label text="Author" class="group-header" /> <Label text="Author" class="group-header" />
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="icon-option" class="option"
@tap="openURL($event, 'https://www.vishnuraghav.com')" @tap="openURL($event, 'https://www.vishnuraghav.com')"
> >
<Label class="bx" :text="icon.user" /> <Label class="bx" :text="icon.user" />
<Label text="Vishnu Raghav" class="option-title" /> <Label text="Vishnu Raghav" />
</StackLayout> </StackLayout>
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="icon-option" class="option"
@tap="openURL($event, 'https://github.com/vishnuraghavb')" @tap="openURL($event, 'https://github.com/vishnuraghavb')"
> >
<Label class="bx" :text="icon.link" /> <Label class="bx" :text="icon.link" />
<Label text="Follow on GitHub" class="option-title" /> <Label text="Follow on GitHub" />
</StackLayout> </StackLayout>
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="icon-option" class="option"
@tap="openURL($event, 'https://mastodon.social/@vishnuraghavb')" @tap="openURL($event, 'https://mastodon.social/@vishnuraghavb')"
> >
<Label class="bx" :text="icon.link" /> <Label class="bx" :text="icon.link" />
<Label text="Follow on Mastodon" class="option-title" /> <Label text="Follow on Mastodon" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
@ -88,18 +83,33 @@
</template> </template>
<script> <script>
import { Utils } from "@nativescript/core" import { Utils, Application } from "@nativescript/core"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
export default { export default {
props: ["highlight", "viewIsScrolled", "showDrawer", "title"], props: ["highlight", "showDrawer", "title"],
computed: { computed: {
...mapState(["icon", "currentComponent"]), ...mapState(["icon", "currentComponent"]),
getVersion() {
let ctx = Application.android.context
return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0)
.versionName
},
},
data() {
return {
viewIsScrolled: false,
}
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction"]), ...mapActions(["setCurrentComponentAction"]),
initializePage() { initializePage() {
this.setCurrentComponentAction("About") this.setCurrentComponentAction("About")
}, },
onScroll(args) {
args.scrollY
? (this.viewIsScrolled = true)
: (this.viewIsScrolled = false)
},
openURL(args, url) { openURL(args, url) {
this.highlight(args) this.highlight(args)
Utils.openUrl(url) Utils.openUrl(url)

View file

@ -8,27 +8,22 @@
gesturesEnabled="true" gesturesEnabled="true"
drawerTransition="SlideInOnTopTransition" drawerTransition="SlideInOnTopTransition"
> >
<GridLayout <GridLayout rows="*, auto" columns="*" ~drawerContent class="sd">
rows="*, auto"
columns="*"
~drawerContent
padding="8"
class="sd"
>
<StackLayout row="0"> <StackLayout row="0">
<StackLayout <GridLayout
rows="48"
columns="auto, 24, *"
v-for="(item, index) in topmenu" v-for="(item, index) in topmenu"
:key="index" :key="index"
@tap="navigateTo(item.component, false, false)" @tap="navigateTo(item.component, false, false)"
orientation="horizontal"
class="sd-item orkm" class="sd-item orkm"
:class="{ :class="{
'selected-sd-item': currentComponent === item.component, 'selected-sd-item': currentComponent === item.component,
}" }"
> >
<Label class="bx" :text="icon[item.icon]" margin="0 24 0 0" /> <Label col="0" row="0" class="bx" :text="icon[item.icon]" />
<Label :text="item.title" /> <Label col="2" row="0" :text="item.title" />
</StackLayout> </GridLayout>
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<GridLayout <GridLayout
class="sd-group-header orkm" class="sd-group-header orkm"
@ -64,7 +59,7 @@
<Label col="1" :text="item" /> <Label col="1" :text="item" />
<Label <Label
v-if="catEditMode" v-if="catEditMode"
@tap="editCategory(item)" @tap="renameCategory(item)"
col="2" col="2"
class="bx" class="bx"
:text="icon.edit" :text="icon.edit"
@ -100,7 +95,7 @@
</GridLayout> </GridLayout>
<GridLayout ~mainContent rows="*" columns="*"> <GridLayout ~mainContent rows="*" columns="*">
<Frame ref="mainFrame" id="main-frame"> <Frame row="0" col="0" ref="mainFrame" id="main-frame">
<!-- Home --> <!-- Home -->
<EnRecipes <EnRecipes
ref="enrecipes" ref="enrecipes"
@ -179,7 +174,13 @@ export default {
} }
}, },
computed: { computed: {
...mapState(["icon", "recipes", "categories", "currentComponent"]), ...mapState([
"icon",
"recipes",
"categories",
"yieldUnits",
"currentComponent",
]),
categoriesWithRecipes() { categoriesWithRecipes() {
let arr = this.recipes.map((e) => { let arr = this.recipes.map((e) => {
return e.category return e.category
@ -192,11 +193,12 @@ export default {
"setCurrentComponentAction", "setCurrentComponentAction",
"initializeRecipes", "initializeRecipes",
"initializeCategories", "initializeCategories",
"initializeYieldUnits",
"renameCategoryAction", "renameCategoryAction",
]), ]),
toggleCatEdit() { toggleCatEdit() {
this.catEditMode = !this.catEditMode this.catEditMode = !this.catEditMode
this.setComponent("EnRecipes") if (this.selectedCategory) this.setComponent("EnRecipes")
this.filterFavorites = this.filterTrylater = false this.filterFavorites = this.filterTrylater = false
this.selectedCategory = null this.selectedCategory = null
this.$refs.enrecipes.updateFilter() this.$refs.enrecipes.updateFilter()
@ -204,7 +206,7 @@ export default {
setComponent(comp) { setComponent(comp) {
this.setCurrentComponentAction(comp) this.setCurrentComponentAction(comp)
}, },
editCategory(category) { renameCategory(category) {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.$showModal(PromptDialog, { this.$showModal(PromptDialog, {
props: { props: {
@ -217,6 +219,7 @@ export default {
if (newCategory.length) { if (newCategory.length) {
this.renameCategoryAction({ current: category, updated: newCategory }) this.renameCategoryAction({ current: category, updated: newCategory })
this.catEditMode = false this.catEditMode = false
this.navigateTo(newCategory, false, true)
} }
}) })
}, },
@ -279,7 +282,6 @@ export default {
frame: "main-frame", frame: "main-frame",
props: { props: {
highlight: this.highlight, highlight: this.highlight,
viewIsScrolled: this.viewIsScrolled,
showDrawer: this.showDrawer, showDrawer: this.showDrawer,
restartApp: this.restartApp, restartApp: this.restartApp,
hijackGlobalBackEvent: this.hijackGlobalBackEvent, hijackGlobalBackEvent: this.hijackGlobalBackEvent,
@ -340,6 +342,7 @@ export default {
setTimeout((e) => Theme.setMode(Theme[themeName]), 50) setTimeout((e) => Theme.setMode(Theme[themeName]), 50)
if (!this.recipes.length) this.initializeRecipes() if (!this.recipes.length) this.initializeRecipes()
if (!this.categories.length) this.initializeCategories() if (!this.categories.length) this.initializeCategories()
if (!this.yieldUnits.length) this.initializeYieldUnits()
}, },
} }
</script> </script>

View file

@ -1,9 +1,9 @@
<template> <template>
<Page @loaded="initialize" @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">
<Label <Label
class="bx leftAction" class="bx"
:text="icon.back" :text="icon.back"
automationText="Back" automationText="Back"
col="0" col="0"
@ -11,19 +11,20 @@
/> />
<Label class="title orkm" :text="title" col="1" /> <Label class="title orkm" :text="title" col="1" />
<Label <Label
v-if="hasEnoughDetails" v-if="hasEnoughDetails && !imageLoading"
class="bx" class="bx"
:text="icon.save" :text="icon.save"
col="2" col="2"
@tap="saveRecipe" @tap="saveOperation"
/> />
<ActivityIndicator col="2" v-if="imageLoading" :busy="imageLoading" />
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<AbsoluteLayout> <GridLayout rows="*" columns="*">
<ScrollView <ScrollView
width="100%" row="0"
height="100%" col="0"
@scroll="onScroll($event)" @scroll="onScroll"
scrollBarIndicatorVisible="false" scrollBarIndicatorVisible="false"
> >
<StackLayout width="100%" padding="0 0 128"> <StackLayout width="100%" padding="0 0 128">
@ -32,7 +33,7 @@
<StackLayout <StackLayout
width="100%" width="100%"
:height="screenWidth" :height="screenWidth"
class="view-imageHolder" class="imageHolder"
verticalAlignment="center" verticalAlignment="center"
> >
<Image <Image
@ -47,7 +48,7 @@
horizontalAlignment="center" horizontalAlignment="center"
class="bx" class="bx"
fontSize="160" fontSize="160"
:text="icon.image" :text="icon.food"
/> />
</StackLayout> </StackLayout>
<StackLayout width="100%" :top="screenWidth - 42"> <StackLayout width="100%" :top="screenWidth - 42">
@ -55,7 +56,7 @@
<Label <Label
v-if="showFab" v-if="showFab"
horizontalAlignment="right" horizontalAlignment="right"
@tap="photoHandler" @tap="imageHandler"
class="bx fab-button" class="bx fab-button"
:text="icon.camera" :text="icon.camera"
androidElevation="6" androidElevation="6"
@ -76,43 +77,41 @@
</AbsoluteLayout> </AbsoluteLayout>
<AbsoluteLayout class="inputField"> <AbsoluteLayout class="inputField">
<TextField <TextField
v-model="recipeContent.category" :text="recipeContent.category"
editable="false" editable="false"
@tap="showCategories()" @tap="showCategories"
/> />
<Label top="0" class="fieldLabel" text="Category" /> <Label top="0" class="fieldLabel" text="Category" />
</AbsoluteLayout> </AbsoluteLayout>
<GridLayout columns="*, 8, *"> <GridLayout columns="*, 8, *">
<AbsoluteLayout class="inputField" col="0"> <AbsoluteLayout class="inputField" col="0">
<TimePickerField <TextField
titleTextColor="red" v-model="recipeContent.yield.quantity"
timeFormat="HH:mm" hint="1"
pickerTitle="Approx. preparation time" keyboardType="number"
@timeChange="onPrepTimeChange" />
:time="recipeContent.prepTime" <Label top="0" class="fieldLabel" text="Yield quantity" />
></TimePickerField>
<Label top="0" class="fieldLabel" text="Preparation time" />
</AbsoluteLayout> </AbsoluteLayout>
<AbsoluteLayout class="inputField" col="2"> <AbsoluteLayout class="inputField" col="2">
<TimePickerField <TextField
timeFormat="HH:mm" :text="recipeContent.yield.unit"
pickerTitle="Approx. cooking time" editable="false"
@timeChange="onCookTimeChange" @tap="showYieldUnits"
:time="recipeContent.cookTime" />
></TimePickerField> <Label top="0" class="fieldLabel" text="Yield measured in" />
<Label top="0" class="fieldLabel" text="Cooking time" />
</AbsoluteLayout> </AbsoluteLayout>
</GridLayout> </GridLayout>
<GridLayout columns="*, 16, *"> <GridLayout columns="*, 8, *">
<AbsoluteLayout class="inputField" col="0"> <AbsoluteLayout class="inputField" col="0">
<TextField <TextField
width="100%" :text="formattedTimeRequired"
keyboardType="number" editable="false"
v-model="recipeContent.portionSize" @tap="setTimeRequired"
/> />
<Label top="0" class="fieldLabel" text="Portion size" /> <Label top="0" class="fieldLabel" text="Time required" />
</AbsoluteLayout> </AbsoluteLayout>
</GridLayout> </GridLayout>
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
@ -125,6 +124,7 @@
:key="index" :key="index"
> >
<TextField <TextField
@loaded="focusField"
col="0" col="0"
v-model="recipeContent.ingredients[index].item" v-model="recipeContent.ingredients[index].item"
:hint="`Item ${index + 1}`" :hint="`Item ${index + 1}`"
@ -153,7 +153,7 @@
/> />
</GridLayout> </GridLayout>
<Label <Label
class="sec-btn pull-left orkm" class="text-btn orkm"
text="+ ADD INGREDIENT" text="+ ADD INGREDIENT"
@tap="addIngredient()" @tap="addIngredient()"
/> />
@ -170,6 +170,7 @@
:key="index" :key="index"
> >
<TextView <TextView
@loaded="focusField"
col="0" col="0"
:hint="`Step ${index + 1}`" :hint="`Step ${index + 1}`"
v-model="recipeContent.instructions[index]" v-model="recipeContent.instructions[index]"
@ -183,7 +184,7 @@
/> />
</GridLayout> </GridLayout>
<Label <Label
class="sec-btn pull-left orkm" class="text-btn orkm"
text="+ ADD STEP" text="+ ADD STEP"
@tap="addInstruction()" @tap="addInstruction()"
/> />
@ -199,6 +200,7 @@
:key="index" :key="index"
> >
<TextView <TextView
@loaded="focusField"
col="0" col="0"
v-model="recipeContent.notes[index]" v-model="recipeContent.notes[index]"
:hint="`Note ${index + 1}`" :hint="`Note ${index + 1}`"
@ -211,11 +213,7 @@
@tap="removeNote(index)" @tap="removeNote(index)"
/> />
</GridLayout> </GridLayout>
<Label <Label class="text-btn orkm" text="+ ADD NOTE" @tap="addNote()" />
class="sec-btn pull-left orkm"
text="+ ADD NOTE"
@tap="addNote()"
/>
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
@ -228,6 +226,7 @@
:key="index" :key="index"
> >
<TextView <TextView
@loaded="focusField"
col="0" col="0"
v-model="recipeContent.references[index]" v-model="recipeContent.references[index]"
hint="Text or Website/Video URL" hint="Text or Website/Video URL"
@ -241,7 +240,7 @@
/> />
</GridLayout> </GridLayout>
<Label <Label
class="sec-btn pull-left orkm" class="text-btn orkm"
text="+ ADD REFERENCE" text="+ ADD REFERENCE"
@tap="addReference()" @tap="addReference()"
/> />
@ -249,11 +248,14 @@
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</AbsoluteLayout> </GridLayout>
</Page> </Page>
</template> </template>
<script> <script>
import { WorkerService } from "../worker.service"
var workerService = new WorkerService()
import { import {
Screen, Screen,
AndroidApplication, AndroidApplication,
@ -261,14 +263,20 @@ import {
path, path,
getFileAccess, getFileAccess,
knownFolders, knownFolders,
Utils,
File,
} from "@nativescript/core" } from "@nativescript/core"
import { Mediafilepicker } from "nativescript-mediafilepicker" import { Mediafilepicker } from "nativescript-mediafilepicker"
import { DateTimePicker, TimePickerField } from "@nativescript/datetimepicker"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
import ActionDialog from "./modal/ActionDialog.vue" import ActionDialog from "./modal/ActionDialog.vue"
import ConfirmDialog from "./modal/ConfirmDialog.vue" import ConfirmDialog from "./modal/ConfirmDialog.vue"
import PromptDialog from "./modal/PromptDialog.vue" import PromptDialog from "./modal/PromptDialog.vue"
import ListPicker from "./modal/ListPicker.vue"
import { create } from "domain"
export default { export default {
props: ["recipeIndex", "recipeID", "selectedCategory"], props: ["recipeIndex", "recipeID", "selectedCategory"],
@ -280,31 +288,36 @@ export default {
imageSrc: null, imageSrc: null,
title: undefined, title: undefined,
category: "Undefined", category: "Undefined",
prepTime: "00:00", timeRequired: "00:00",
cookTime: "00:00", yield: {
portionSize: 1, quantity: undefined,
ingredients: [ unit: "Servings",
{ },
item: "", ingredients: [],
quantity: undefined, instructions: [],
unit: "unit", notes: [],
}, references: [],
],
instructions: [""],
notes: [""],
references: [""],
isFavorite: false, isFavorite: false,
tried: false, tried: false,
lastTried: null,
lastModified: null, lastModified: null,
}, },
tempRecipeContent: {}, tempRecipeContent: {},
blockModal: false, blockModal: false,
newRecipeID: null, newRecipeID: null,
showFab: false, showFab: false,
imageLoading: false,
} }
}, },
computed: { computed: {
...mapState(["icon", "units", "recipes", "categories", "currentComponent"]), ...mapState([
"icon",
"units",
"yieldUnits",
"recipes",
"categories",
"currentComponent",
]),
screenWidth() { screenWidth() {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
@ -314,6 +327,12 @@ export default {
JSON.stringify(this.tempRecipeContent) JSON.stringify(this.tempRecipeContent)
) )
}, },
formattedTimeRequired() {
let t = this.recipeContent.timeRequired.split(":")
let h = parseInt(t[0])
let m = parseInt(t[1])
return h ? (m ? `${h}h ${m}m` : `${h}h`) : `${m}m`
},
}, },
methods: { methods: {
...mapActions([ ...mapActions([
@ -321,10 +340,19 @@ export default {
"addRecipeAction", "addRecipeAction",
"overwriteRecipeAction", "overwriteRecipeAction",
"addCategoryAction", "addCategoryAction",
"addYieldUnitAction",
]), ]),
initialize() { initialize() {
this.showFab = true this.showFab = true
}, },
// HELPERS
focusField(args) {
if (!args.object.text) {
args.object.focus()
setTimeout((e) => Utils.ad.showSoftInput(args.object.android), 1)
}
},
getRandomID() { getRandomID() {
let res = "" let res = ""
let chars = "abcdefghijklmnopqrstuvwxyz0123456789" let chars = "abcdefghijklmnopqrstuvwxyz0123456789"
@ -333,87 +361,41 @@ export default {
} }
return res return res
}, },
setTime(key, time) { setTimeRequired() {
if (Date.parse(time)) { let time = this.recipeContent.timeRequired.split(":")
let date = new Date(time) let hr = time[0]
let h = date.getHours() let min = time[1]
let m = date.getMinutes() this.$showModal(ListPicker, {
props: {
this.recipeContent[key] = title: "Approx. time required",
(h < 10 ? "0" + h : h) + ":" + (m < 10 ? "0" + m : m) action: "SET",
} selectedHr: hr,
}, selectedMin: min,
clearEmptyFields() { },
if (!this.recipeContent.title) { }).then((result) => {
this.recipeContent.title = "Untitled Recipe" if (result) {
} this.recipeContent.timeRequired = result
if (!this.recipeContent.portionSize) {
this.recipeContent.portionSize = 1
}
this.recipeContent.ingredients.forEach((e, i) => {
if (!e.item.length) {
this.recipeContent.ingredients.splice(i, 1)
} }
}) })
let vm = this
function removeEmpty(arr) {
vm.recipeContent[arr].forEach((e, i) => {
if (!e.length) {
vm.recipeContent[arr].splice(i, 1)
}
})
}
removeEmpty("instructions")
removeEmpty("notes")
removeEmpty("references")
},
saveRecipe() {
console.log(
JSON.stringify(this.recipeContent),
JSON.stringify(this.tempRecipeContent)
)
this.clearEmptyFields()
this.recipeContent.lastModified = new Date()
if (this.recipeID) {
this.overwriteRecipeAction({
index: this.recipeIndex,
id: this.recipeID,
recipe: this.recipeContent,
})
} else {
this.recipeContent.id = this.newRecipeID
this.addRecipeAction({
id: this.newRecipeID,
recipe: this.recipeContent,
})
}
if (this.tempRecipeContent.imageSrc && !this.recipeContent.imageSrc) {
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
}
this.$navigateBack()
},
onPrepTimeChange(args) {
this.setTime("prepTime", args.value)
},
onCookTimeChange(args) {
this.setTime("cookTime", args.value)
}, },
onScroll(args) { onScroll(args) {
args.scrollY args.scrollY
? (this.viewIsScrolled = true) ? (this.viewIsScrolled = true)
: (this.viewIsScrolled = false) : (this.viewIsScrolled = false)
}, },
// DATA LIST
showCategories() { showCategories() {
this.releaseBackEvent() this.releaseBackEvent()
this.$showModal(ActionDialog, { this.$showModal(ActionDialog, {
props: { props: {
title: "Category", title: "Category",
list: [...this.categories], list: [...this.categories],
height: "60%", height: "408",
action: "NEW CATEGORY", action: "CREATE NEW",
}, },
}).then((action) => { }).then((action) => {
if (action == "NEW CATEGORY") { if (action == "CREATE NEW") {
this.$showModal(PromptDialog, { this.$showModal(PromptDialog, {
props: { props: {
title: "New category", title: "New category",
@ -434,6 +416,52 @@ export default {
} }
}) })
}, },
showYieldUnits() {
this.releaseBackEvent()
this.$showModal(ActionDialog, {
props: {
title: "Yield measured in",
list: [...this.yieldUnits],
height: "408",
action: "CREATE NEW",
},
}).then((action) => {
if (action == "CREATE NEW") {
this.$showModal(PromptDialog, {
props: {
title: "New yield unit",
action: "ADD",
},
}).then((yieldUnit) => {
this.hijackBackEvent()
if (yieldUnit.length) {
this.recipeContent.yield.unit = yieldUnit
this.addYieldUnitAction(yieldUnit)
}
})
} else if (action) {
this.recipeContent.yield.unit = action
this.hijackBackEvent()
} else {
this.hijackBackEvent()
}
})
},
showUnits(e) {
this.releaseBackEvent()
this.$showModal(ActionDialog, {
props: {
title: "Unit",
list: [...this.units],
height: "408",
},
}).then((action) => {
this.hijackBackEvent()
if (action) e.object.text = action
})
},
// NAVIGATION HANDLERS
navigateBack() { navigateBack() {
if (this.hasEnoughDetails) { if (this.hasEnoughDetails) {
this.blockModal = true this.blockModal = true
@ -448,7 +476,7 @@ export default {
}).then((action) => { }).then((action) => {
this.blockModal = false this.blockModal = false
if (action) { if (action) {
this.saveRecipe() this.saveOperation()
} else if (action != null) { } else if (action != null) {
this.$navigateBack() this.$navigateBack()
this.releaseBackEvent() this.releaseBackEvent()
@ -477,7 +505,9 @@ export default {
this.navigateBack() this.navigateBack()
} }
}, },
photoHandler() {
// DATA HANDLERS
imageHandler() {
if (this.recipeContent.imageSrc) { if (this.recipeContent.imageSrc) {
this.blockModal = true this.blockModal = true
this.$showModal(ConfirmDialog, { this.$showModal(ConfirmDialog, {
@ -489,17 +519,17 @@ export default {
}).then((action) => { }).then((action) => {
this.blockModal = false this.blockModal = false
if (action) { if (action) {
this.takePicture() this.imagePicker()
} else if (action != null) { } else if (action != null) {
this.removePicture() this.recipeContent.imageSrc = null
this.releaseBackEvent() this.releaseBackEvent()
} }
}) })
} else { } else {
this.takePicture() this.imagePicker()
} }
}, },
takePicture() { imagePicker() {
const vm = this const vm = this
const mediafilepicker = new Mediafilepicker() const mediafilepicker = new Mediafilepicker()
mediafilepicker.openImagePicker({ mediafilepicker.openImagePicker({
@ -511,36 +541,7 @@ export default {
}, },
}) })
mediafilepicker.on("getFiles", (image) => { mediafilepicker.on("getFiles", (image) => {
let result = image.object.get("results")[0].file vm.recipeContent.imageSrc = image.object.get("results")[0].file
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
})
})
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() {
confirm({
title: "Delete Photo",
message: "Are you sure you want to delete the recipe photo?",
okButtonText: "Delete",
cancelButtonText: "Cancel",
}).then((e) => {
if (e) {
this.recipeContent.imageSrc = null
}
}) })
}, },
@ -576,18 +577,70 @@ export default {
this.recipeContent.references.splice(index, 1) this.recipeContent.references.splice(index, 1)
}, },
showUnits(e) { clearEmptyFields() {
this.releaseBackEvent() if (!this.recipeContent.title)
this.$showModal(ActionDialog, { this.recipeContent.title = "Untitled Recipe"
props: { if (!this.recipeContent.yield.quantity)
title: "Unit", this.recipeContent.yield.quantity = 1
list: [...this.units], this.recipeContent.ingredients = this.recipeContent.ingredients.filter(
height: "75%", (e) => e.item
}, )
}).then((action) => { let vm = this
this.hijackBackEvent() function clearEmpty(arr) {
if (action) e.object.text = action vm.recipeContent[arr] = vm.recipeContent[arr].filter((e) => e)
}
clearEmpty("instructions")
clearEmpty("notes")
clearEmpty("references")
},
saveOperation() {
this.imageLoading = true
this.clearEmptyFields()
this.recipeContent.lastModified = new Date()
if (this.recipeContent.imageSrc) {
if (this.tempRecipeContent.imageSrc) {
if (this.tempRecipeContent.imageSrc !== this.recipeContent.imageSrc) {
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
this.imageSaveOperation()
} else this.saveRecipe()
} else this.imageSaveOperation()
} else if (this.tempRecipeContent.imageSrc) {
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
this.saveRecipe()
} else this.saveRecipe()
},
imageSaveOperation() {
let imgSavedToPath = path.join(
knownFolders.documents().getFolder("enrecipes").path,
`${this.getRandomID()}.jpg`
)
let workerService = new WorkerService()
let ImageProcessor = workerService.initImageProcessor()
ImageProcessor.postMessage({
imgFile: this.recipeContent.imageSrc,
imgSavedToPath,
}) })
ImageProcessor.onmessage = ({ data }) => {
this.recipeContent.imageSrc = imgSavedToPath
this.saveRecipe()
}
},
saveRecipe() {
if (this.recipeID) {
this.overwriteRecipeAction({
index: this.recipeIndex,
id: this.recipeID,
recipe: this.recipeContent,
})
} else {
this.recipeContent.id = this.newRecipeID
this.addRecipeAction({
id: this.newRecipeID,
recipe: this.recipeContent,
})
}
this.imageLoading = false
this.$navigateBack()
}, },
}, },
created() { created() {
@ -598,7 +651,10 @@ export default {
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, JSON.parse(JSON.stringify(recipe))) Object.assign(this.recipeContent, JSON.parse(JSON.stringify(recipe)))
Object.assign(this.tempRecipeContent, JSON.parse(JSON.stringify(recipe))) Object.assign(
this.tempRecipeContent,
JSON.parse(JSON.stringify(this.recipeContent))
)
} else { } else {
Object.assign( Object.assign(
this.tempRecipeContent, this.tempRecipeContent,

View file

@ -5,11 +5,10 @@
<GridLayout <GridLayout
v-if="showSearch" v-if="showSearch"
columns="auto, *" columns="auto, *"
class="actionBarContainer"
verticalAlignment="center" verticalAlignment="center"
> >
<Label <Label
class="bx leftAction" class="bx"
:text="icon.back" :text="icon.back"
automationText="Back" automationText="Back"
col="0" col="0"
@ -25,15 +24,11 @@
/> />
</GridLayout> </GridLayout>
<!-- Home Actionbar --> <!-- Home Actionbar -->
<GridLayout <GridLayout v-else columns="auto, *, auto, auto">
v-else
columns="auto, *, auto, auto"
class="actionBarContainer"
>
<Label <Label
class="bx leftAction" class="bx"
:text="icon.menu" :text="icon.menu"
automationText="Menu" automationText="Back"
@tap="showDrawer" @tap="showDrawer"
col="0" col="0"
/> />
@ -62,19 +57,19 @@
swipeActions="true" swipeActions="true"
@itemSwipeProgressChanged="onSwiping" @itemSwipeProgressChanged="onSwiping"
@itemSwipeProgressEnded="onSwipeEnded" @itemSwipeProgressEnded="onSwipeEnded"
@scrolled="onScroll($event)" @scrolled="onScroll"
@itemTap="viewRecipe" @itemTap="viewRecipe"
:filteringFunction="filterFunction" :filteringFunction="filterFunction"
:sortingFunction="sortFunction" :sortingFunction="sortFunction"
> >
<v-template> <v-template>
<GridLayout <GridLayout
class="recipe-li" class="recipeItem"
rows="112" rows="112"
columns="112, *" columns="112, *"
androidElevation="1" androidElevation="2"
> >
<GridLayout class="recipeImgContainer" rows="112" columns="112"> <GridLayout class="imageHolder" rows="112" columns="112">
<Image <Image
row="0" row="0"
col="0" col="0"
@ -89,16 +84,19 @@
horizontalAlignment="center" horizontalAlignment="center"
class="bx" class="bx"
fontSize="56" fontSize="56"
:text="icon.image" :text="icon.food"
/> />
</GridLayout> </GridLayout>
<StackLayout class="recipe-info" col="1"> <StackLayout class="recipeInfo" col="1">
<Label :text="recipe.category" class="orkm recipe-cat" /> <Label :text="recipe.category" class="orkm category" />
<Label :text="recipe.title" class="orkm recipe-title" /> <Label :text="recipe.title" class="orkm title" />
<Label <StackLayout class="timeContainer" orientation="horizontal">
:text="recipeTotalTime(recipe.prepTime, recipe.cookTime)" <Label class="bx small" :text="icon.time" />
class="h4 recipe-time" <Label
/> class="time"
:text="`${formattedTime(recipe.timeRequired).time}`"
/>
</StackLayout>
</StackLayout> </StackLayout>
</GridLayout> </GridLayout>
</v-template> </v-template>
@ -117,32 +115,30 @@
<StackLayout <StackLayout
col="0" col="0"
row="0" row="0"
class="noResults" class="noResult"
v-if="!recipes.length && !filterFavorites && !filterTrylater" v-if="!recipes.length && !filterFavorites && !filterTrylater"
verticalAlignment="center"
> >
<Label class="bx icon" :text="icon.plusCircle" textWrap="true" />
<Label <Label
@tap="addRecipe" class="title orkm"
class="bx"
:text="icon.plusCircle"
textWrap="true"
/>
<Label
class="title orkb"
text="Start adding your recipes!" text="Start adding your recipes!"
textWrap="true" textWrap="true"
/> />
<Label text='Tap the "+" icon to add a new recipe' 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 new recipe" textWrap="true" />
</StackLayout>
</StackLayout> </StackLayout>
<StackLayout <StackLayout
col="0" col="0"
row="0" row="0"
class="noResults" class="noResult"
v-if="!filteredRecipes.length && searchQuery" v-if="!filteredRecipes.length && searchQuery"
verticalAlignment="top" verticalAlignment="top"
> >
<Label class="bx" :text="icon.search" textWrap="true" /> <Label class="bx icon" :text="icon.search" textWrap="true" />
<Label class="title orkb" text="No recipes found" textWrap="true" /> <Label class="title orkm" text="No recipes found" textWrap="true" />
<Label <Label
:text=" :text="
`Your search &quot;${searchQuery}&quot; did not match any recipes${ `Your search &quot;${searchQuery}&quot; did not match any recipes${
@ -157,12 +153,11 @@
<StackLayout <StackLayout
col="0" col="0"
row="0" row="0"
class="noResults" class="noResult"
verticalAlignment="center"
v-if="!filteredRecipes.length && filterFavorites && !searchQuery" v-if="!filteredRecipes.length && filterFavorites && !searchQuery"
> >
<Label class="bx" :text="icon.heartOutline" textWrap="true" /> <Label class="bx icon" :text="icon.heartOutline" textWrap="true" />
<Label class="title orkb" text="No favorites yet!" textWrap="true" /> <Label class="title orkm" text="No favorites yet!" textWrap="true" />
<Label <Label
text="Your favorited recipes will be listed here" text="Your favorited recipes will be listed here"
textWrap="true" textWrap="true"
@ -171,19 +166,18 @@
<StackLayout <StackLayout
col="0" col="0"
row="0" row="0"
class="noResults" class="noResult"
verticalAlignment="center"
v-if="!filteredRecipes.length && filterTrylater && !searchQuery" v-if="!filteredRecipes.length && filterTrylater && !searchQuery"
> >
<Label class="bx" :text="icon.trylaterOutline" textWrap="true" /> <Label class="bx icon" :text="icon.trylaterOutline" textWrap="true" />
<Label <Label
class="title orkb" class="title orkm"
text="No recipes here to try!" text="Nothing to try next!"
textWrap="true" textWrap="true"
/> />
<!-- text="Your Try later recipes will be listed here" --> <!-- text="Your Try later recipes will be listed here" -->
<Label <Label
text="Your recipes to try later will be listed here" text="Recipes you wanted to try later will be listed here"
textWrap="true" textWrap="true"
/> />
</StackLayout> </StackLayout>
@ -297,10 +291,10 @@ export default {
this.closeSearch() this.closeSearch()
}, },
closeSearch() { closeSearch() {
if (this.searchQuery) this.updateFilter()
this.searchQuery = "" this.searchQuery = ""
Utils.ad.dismissSoftInput() Utils.ad.dismissSoftInput()
this.showSearch = false this.showSearch = false
this.updateFilter()
this.releaseLocalBackEvent() this.releaseLocalBackEvent()
}, },
sortDialog() { sortDialog() {
@ -309,7 +303,7 @@ export default {
props: { props: {
title: "Sort by", title: "Sort by",
list: ["Natural order", "Title", "Duration", "Last modified"], list: ["Natural order", "Title", "Duration", "Last modified"],
height: "195", // 48*4 + 3 1dip separators height: "216", // 54*4
}, },
}).then((action) => { }).then((action) => {
if (action && action !== "Cancel" && this.sortType !== action) { if (action && action !== "Cancel" && this.sortType !== action) {
@ -330,8 +324,8 @@ export default {
.localeCompare(otherItem.title.toLowerCase(), "en", { .localeCompare(otherItem.title.toLowerCase(), "en", {
ignorePunctuation: true, ignorePunctuation: true,
}) })
let d1 = this.recipeDuration(item.prepTime, item.cookTime) let d1 = this.formattedTime(item.timeRequired).duration
let d2 = this.recipeDuration(otherItem.prepTime, otherItem.cookTime) let d2 = this.formattedTime(otherItem.timeRequired).duration
let ld1 = new Date(item.lastModified) let ld1 = new Date(item.lastModified)
let ld2 = new Date(otherItem.lastModified) let ld2 = new Date(otherItem.lastModified)
switch (this.sortType) { switch (this.sortType) {
@ -411,28 +405,29 @@ export default {
this.deletionDialogActive = false this.deletionDialogActive = false
}) })
}, },
getTotalTime(prepTime, cookTime) { // getTotalTime(prepTime, timeRequired) {
let pT = prepTime.split(":") // let pT = prepTime.split(":")
let cT = cookTime.split(":") // let cT = timeRequired.split(":")
let hrs = parseInt(pT[0]) + parseInt(cT[0]) // let hrs = parseInt(pT[0]) + parseInt(cT[0])
let mins = parseInt(pT[1]) + parseInt(cT[1]) // let mins = parseInt(pT[1]) + parseInt(cT[1])
if (mins > 60) { // if (mins > 60) {
hrs += Math.floor(mins / 60) // hrs += Math.floor(mins / 60)
mins -= 60 // mins -= 60
} // }
// return {
// hrs,
// mins,
// }
// },
formattedTime(time) {
let t = time.split(":")
let h = parseInt(t[0])
let m = parseInt(t[1])
return { return {
hrs, time: h ? (m ? `${h}h ${m}m` : `${h}h`) : `${m}m`,
mins, duration: `${h}${m}`,
} }
}, },
recipeTotalTime(prepTime, cookTime) {
let { hrs, mins } = this.getTotalTime(prepTime, cookTime)
return hrs ? `${hrs}h ${mins}m` : `${mins}m`
},
recipeDuration(prepTime, cookTime) {
let { hrs, mins } = this.getTotalTime(prepTime, cookTime)
return `${hrs}${mins}`
},
onScroll(args) { onScroll(args) {
args.scrollOffset args.scrollOffset
? (this.viewIsScrolled = true) ? (this.viewIsScrolled = true)
@ -448,7 +443,6 @@ export default {
// curve: "easeIn", // curve: "easeIn",
// }, // },
props: { props: {
viewIsScrolled: this.viewIsScrolled,
selectedCategory: this.selectedCategory, selectedCategory: this.selectedCategory,
}, },
}) })

View file

@ -1,56 +1,40 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="initializePage">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<!-- Settings Actionbar --> <GridLayout rows="*" columns="auto, *">
<GridLayout rows="*" columns="auto, *" class="actionBarContainer">
<Label <Label
class="bx leftAction" class="bx"
:text="icon.menu" :text="icon.menu"
automationText="Menu" automationText="Back"
@tap="showDrawer" @tap="showDrawer"
col="0" col="0"
/> />
<Label class="title orkm" text="Settings" col="1" /> <Label class="title orkm" text="Settings" col="1" />
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false" @scroll="onScroll">
<StackLayout class="main-container"> <StackLayout class="main-container">
<Label text="Interface" class="group-header" /> <Label text="Interface" class="group-header" />
<StackLayout <StackLayout
orientation="horizontal" orientation="horizontal"
class="option" class="option"
@tap="selectThemes" @tap="selectThemes"
> >
<!-- @tap="selectThemes" -->
<Label verticalAlignment="center" class="bx" :text="icon.theme" /> <Label verticalAlignment="center" class="bx" :text="icon.theme" />
<StackLayout> <StackLayout>
<Label text="Theme" class="option-title" /> <Label text="Theme" class="option-title" />
<Label :text="themeName" class="option-info" textWrap="true" /> <Label :text="themeName" class="option-info" textWrap="true" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
<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
orientation="horizontal"
class="option"
@tap="selectBackupDir"
>
<Label verticalAlignment="center" class="bx" :text="icon.folder" />
<StackLayout>
<Label text="EnRecipes Backup Directory" class="option-title" />
<Label text="/storage/emulated/0/EnRecipes" class="option-info" />
</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.save" />
<Label text="Backup Data" class="option-title" /> <Label text="Backup data" />
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" class="option" @tap="restoreData"> <StackLayout orientation="horizontal" class="option" @tap="restoreData">
<Label class="bx" :text="icon.restore" /> <Label class="bx" :text="icon.restore" />
<Label text="Restore Data" class="option-title" /> <Label text="Restore data" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
@ -78,7 +62,6 @@ import { mapState, mapActions } from "vuex"
export default { export default {
props: [ props: [
"highlight", "highlight",
"viewIsScrolled",
"showDrawer", "showDrawer",
"restartApp", "restartApp",
"hijackGlobalBackEvent", "hijackGlobalBackEvent",
@ -86,6 +69,7 @@ export default {
], ],
data() { data() {
return { return {
viewIsScrolled: false,
interface: { interface: {
theme: { theme: {
title: "Theme", title: "Theme",
@ -123,9 +107,10 @@ export default {
this.setCurrentComponentAction("Settings") this.setCurrentComponentAction("Settings")
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
}, },
showDialog(args) { onScroll(args) {
this.highlight(args) args.scrollY
this.$showModal(ActionDialog) ? (this.viewIsScrolled = true)
: (this.viewIsScrolled = false)
}, },
selectThemes(args) { selectThemes(args) {
this.highlight(args) this.highlight(args)
@ -133,7 +118,7 @@ export default {
props: { props: {
title: "Theme", title: "Theme",
list: ["Light", "Dark"], list: ["Light", "Dark"],
height: "97", height: "108",
}, },
}).then((action) => { }).then((action) => {
if (action && action !== "Cancel" && this.themeName !== action) { if (action && action !== "Cancel" && this.themeName !== action) {
@ -177,10 +162,11 @@ export default {
android.os.Environment.DIRECTORY_DOWNLOADS android.os.Environment.DIRECTORY_DOWNLOADS
).toString() ).toString()
let date = new Date() let date = new Date()
let formattedDate = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`
let fromPath = path.join(knownFolders.documents().path, "enrecipes") let fromPath = path.join(knownFolders.documents().path, "enrecipes")
let destPath = path.join( let destPath = path.join(
sdDownloadPath, sdDownloadPath,
`enrecipes_${date.toString()}.zip` `EnRecipes-${formattedDate}.zip`
) )
console.log(fromPath, destPath, sdDownloadPath) console.log(fromPath, destPath, sdDownloadPath)
Zip.zip({ Zip.zip({

View file

@ -1,15 +1,11 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="initializePage">
<ActionBar height="152" margin="0" flat="true"> <ActionBar height="152" margin="0" flat="true" visibility="collapse">
<GridLayout <GridLayout rows="24, 64, 64" columns="auto, *, auto,auto, auto">
rows="24, 64, 64"
columns="auto, *, auto,auto, auto"
class="actionBarContainer"
>
<Label <Label
row="1" row="1"
col="0" col="0"
class="bx leftAction" class="bx"
:text="icon.back" :text="icon.back"
automationText="Back" automationText="Back"
@tap="$navigateBack()" @tap="$navigateBack()"
@ -50,11 +46,18 @@
:text="icon.edit" :text="icon.edit"
@tap="editRecipe" @tap="editRecipe"
/> />
<ActivityIndicator v-else row="1" col="4" :busy="busy" /> <ActivityIndicator v-else row="1" col="2" :busy="busy" />
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<AbsoluteLayout> <AbsoluteLayout>
<TabView androidElevation="0" width="100%" height="100%"> <TabView
:selectedIndex="selectedTabIndex"
@selectedIndexChange="selectedIndexChange"
androidElevation="0"
width="100%"
height="100%"
class="viewRecipe"
>
<TabViewItem title="Overview"> <TabViewItem title="Overview">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<StackLayout> <StackLayout>
@ -62,7 +65,7 @@
width="100%" width="100%"
:height="screenWidth" :height="screenWidth"
verticalAlignment="center" verticalAlignment="center"
class="view-imageHolder" class="imageHolder"
> >
<Image <Image
v-if="recipe.imageSrc" v-if="recipe.imageSrc"
@ -76,69 +79,181 @@
horizontalAlignment="center" horizontalAlignment="center"
class="bx" class="bx"
fontSize="160" fontSize="160"
:text="icon.image" :text="icon.food"
/> />
</StackLayout> </StackLayout>
<StackLayout margin="16 16 144"> <StackLayout margin="16 8 144">
<Label class="view-cat orkm" :text="recipe.category" /> <Label class="category orkm" :text="recipe.category" />
<Label <Label
class="view-title orkm" margin="0 8"
class="title orkm"
:text="recipe.title" :text="recipe.title"
textWrap="true" textWrap="true"
/> />
<Label <Label class="time">
class="view-other" <FormattedString>
:text="`Preparation time: ${getTime(recipe.prepTime)}`" <Span text="Time required:"></Span>
/> <Span
<Label :text="` ${formattedTime(recipe.timeRequired)}`"
class="view-other" ></Span>
:text="`Cooking time: ${getTime(recipe.cookTime)}`" </FormattedString>
/> </Label>
<GridLayout
rows="auto, auto"
columns="*, *"
class="overviewContainer"
>
<StackLayout
class="overviewItem"
row="0"
col="0"
@tap="selectedTabIndex = 1"
>
<Label class="bx" :text="icon.item" />
<Label
class="itemCount"
:text="
`${recipe.ingredients.length} ${
recipe.ingredients.length == 1
? 'Ingredient'
: 'Ingredients'
}`
"
textWrap="true"
/>
</StackLayout>
<StackLayout
class="overviewItem"
row="0"
col="1"
@tap="selectedTabIndex = 2"
>
<Label class="bx" :text="icon.step" />
<Label
class="itemCount"
:text="
`${recipe.instructions.length} ${
recipe.instructions.length == 1 ? 'Step' : 'Steps'
}`
"
textWrap="true"
/>
</StackLayout>
<StackLayout
class="overviewItem"
row="1"
col="0"
@tap="selectedTabIndex = 3"
>
<Label class="bx" :text="icon.note" />
<Label
class="itemCount"
:text="
`${recipe.notes.length} ${
recipe.notes.length == 1 ? 'Note' : 'Notes'
}`
"
textWrap="true"
/>
</StackLayout>
<StackLayout
class="overviewItem"
row="1"
col="1"
@tap="selectedTabIndex = 4"
>
<Label class="bx" :text="icon.source" />
<Label
class="itemCount"
:text="
`${recipe.references.length} ${
recipe.references.length == 1
? 'Reference'
: 'References'
}`
"
textWrap="true"
/>
</StackLayout>
</GridLayout>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
<TabViewItem title="Ingredients"> <TabViewItem title="Ingredients">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<Label <GridLayout
v-if="!recipe.ingredients.length" v-if="!recipe.ingredients.length"
class="noResults" rows="*"
text="Click the edit button to add ingredients to this recipe" columns="*"
textWrap="true" class="emptyState"
/> >
<StackLayout v-else padding="16 16 124"> <StackLayout col="0" row="0" class="noResult">
<Label class="bx icon" :text="icon.item" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " />
<Label class="bx" :text="icon.edit" />
<Label text=" button" />
</StackLayout>
<Label text="to add some ingredients" textWrap="true" />
</StackLayout>
</GridLayout>
<StackLayout v-else padding="16 16 134">
<AbsoluteLayout class="inputField"> <AbsoluteLayout class="inputField">
<TextField <TextField
width="165" width="50%"
v-model="portionScale" v-model="yieldMultiplier"
keyboardType="number" keyboardType="number"
/> />
<Label top="0" class="fieldLabel" text="Set portion size" />
</AbsoluteLayout>
<StackLayout margin="24 0 8 0">
<Label <Label
class="view-title orkm" top="0"
class="fieldLabel"
:text="`Required ${recipe.yield.unit.toLowerCase()}`"
/>
</AbsoluteLayout>
<StackLayout margin="24 0 16 0">
<Label
class="title orkm"
:text=" :text="
`Ingredients for ${portionScale}${ `Ingredients for ${
portionScale > 1 yieldMultiplier ? yieldMultiplier : 1
? ' portions' } ${recipe.yield.unit.toLowerCase()}`
: portionScale == 0
? '1 portion'
: ' portion'
}`
" "
textWrap="true" textWrap="true"
/> />
<Label </StackLayout>
class="view-ingredient" <StackLayout
v-for="(item, index) in recipe.ingredients" v-for="(item, index) in recipe.ingredients"
:key="index" :key="index"
>
<check-box
v-if="filterTrylater"
class="ingredient-check"
checkPadding="16"
:fillColor="`${isLightMode ? '#ff5722' : '#ff7043'}`"
:text=" :text="
`${roundedQuantity(item.quantity)}${ `${
item.unit ? ' ' + item.unit : '' roundedQuantity(item.quantity)
} ${item.item}` ? roundedQuantity(item.quantity) + ' '
: ''
}${roundedQuantity(item.quantity) ? item.unit + ' ' : ''}${
item.item
}`
" "
/>
<Label
v-else
class="ingredient"
textWrap="true" textWrap="true"
:text="
`${
roundedQuantity(item.quantity)
? roundedQuantity(item.quantity) + ' '
: ''
}${roundedQuantity(item.quantity) ? item.unit + ' ' : ''}${
item.item
}`
"
/> />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
@ -146,12 +261,22 @@
</TabViewItem> </TabViewItem>
<TabViewItem title="Instructions"> <TabViewItem title="Instructions">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<Label <GridLayout
v-if="!recipe.instructions.length" v-if="!recipe.instructions.length"
class="noResults" rows="*"
text="Click the edit button to add instructions to this recipe" columns="*"
textWrap="true" class="emptyState"
/> >
<StackLayout col="0" row="0" class="noResult">
<Label class="bx icon" :text="icon.step" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " />
<Label class="bx" :text="icon.edit" />
<Label text=" button" />
</StackLayout>
<Label text="to add some instructions" textWrap="true" />
</StackLayout>
</GridLayout>
<StackLayout v-else padding="32 16 132"> <StackLayout v-else padding="32 16 132">
<GridLayout <GridLayout
columns="auto ,*" columns="auto ,*"
@ -161,10 +286,9 @@
<Label <Label
col="0" col="0"
colSpan="2" colSpan="2"
class="view-instruction" class="instruction"
:class="{ :class="{
instructionWOBorder: noBorder: index === recipe.instructions.length - 1,
index === recipe.instructions.length - 1,
}" }"
:text="instruction" :text="instruction"
textWrap="true" textWrap="true"
@ -172,7 +296,7 @@
<Label <Label
verticalAlignment="top" verticalAlignment="top"
horizontalAlignment="center" horizontalAlignment="center"
class="view-count orkb" class="count orkb"
col="0" col="0"
:text="index + 1" :text="index + 1"
/> />
@ -182,12 +306,22 @@
</TabViewItem> </TabViewItem>
<TabViewItem title="Notes"> <TabViewItem title="Notes">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<Label <GridLayout
v-if="!recipe.notes.length" v-if="!recipe.notes.length"
class="noResults" rows="*"
text="Click the edit button to add notes to this recipe" columns="*"
textWrap="true" class="emptyState"
/> >
<StackLayout col="0" row="0" class="noResult">
<Label class="bx icon" :text="icon.note" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " />
<Label class="bx" :text="icon.edit" />
<Label text=" button" />
</StackLayout>
<Label text="to add some notes" textWrap="true" />
</StackLayout>
</GridLayout>
<StackLayout v-else padding="32 16 132"> <StackLayout v-else padding="32 16 132">
<GridLayout <GridLayout
columns="auto ,*" columns="auto ,*"
@ -197,14 +331,14 @@
<Label <Label
col="0" col="0"
colSpan="2" colSpan="2"
class="view-note" class="note"
:text="note" :text="note"
textWrap="true" textWrap="true"
/> />
<Label <Label
verticalAlignment="top" verticalAlignment="top"
horizontalAlignment="center" horizontalAlignment="center"
class="view-count note orkb" class="count square orkb"
col="0" col="0"
:text="index + 1" :text="index + 1"
/> />
@ -214,12 +348,22 @@
</TabViewItem> </TabViewItem>
<TabViewItem title="References"> <TabViewItem title="References">
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<Label <GridLayout
v-if="!recipe.references.length" v-if="!recipe.references.length"
class="noResults" rows="*"
text="Click the edit button to add references to this recipe" columns="*"
textWrap="true" class="emptyState"
/> >
<StackLayout col="0" row="0" class="noResult">
<Label class="bx icon" :text="icon.source" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " />
<Label class="bx" :text="icon.edit" />
<Label text=" button" />
</StackLayout>
<Label text="to add some references" textWrap="true" />
</StackLayout>
</GridLayout>
<StackLayout v-else padding="10 0 132"> <StackLayout v-else padding="10 0 132">
<StackLayout <StackLayout
v-for="(reference, index) in recipe.references" v-for="(reference, index) in recipe.references"
@ -228,27 +372,27 @@
<GridLayout <GridLayout
v-if="isValidURL(reference)" v-if="isValidURL(reference)"
columns="*, auto" columns="*, auto"
class="view-reference-container" class="referenceItem"
androidElevation="1" androidElevation="1"
@longPress="copyURL($event, reference)"
> >
<Label <Label
col="0" col="0"
verticalAlignment="center" verticalAlignment="center"
class="view-reference" class="recipeLink"
:text="reference" :text="reference"
textWrap="false" textWrap="false"
@tap="openURL($event, reference)"
/> />
<Label <Label
col="1" col="1"
class="view-copyReference bx" class="bx"
:text="icon.copy" :text="icon.source"
@tap="copyURL($event, reference)" @tap="openURL($event, reference)"
/> />
</GridLayout> </GridLayout>
<Label <Label
v-else v-else
class="view-reference-text" class="recipeText"
:text="reference" :text="reference"
textWrap="true" textWrap="true"
/> />
@ -262,7 +406,7 @@
row="1" row="1"
col="1" col="1"
class="bx fab-button" class="bx fab-button"
:text="icon.unchecked" :text="icon.check"
@tap="recipeTried" @tap="recipeTried"
v-if="filterTrylater" v-if="filterTrylater"
/> />
@ -282,12 +426,19 @@
</template> </template>
<script> <script>
import { Screen, Utils, ImageSource, Device, File } from "@nativescript/core" import {
Screen,
Utils,
ImageSource,
Device,
File,
Color,
} from "@nativescript/core"
import { Feedback, FeedbackType, FeedbackPosition } from "nativescript-feedback" 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-ns-7" import * as SocialShare from "nativescript-social-share-ns-7"
import { setText } from "nativescript-clipboard" import { setText } from "nativescript-clipboard"
import { Application } from "@nativescript/core"
import { mapState, mapActions } from "vuex" import { mapState, mapActions } from "vuex"
@ -304,9 +455,10 @@ export default {
data() { data() {
return { return {
busy: false, busy: false,
portionScale: 1, yieldMultiplier: 1,
recipe: null, recipe: null,
showFab: false, showFab: false,
selectedTabIndex: 0,
} }
}, },
computed: { computed: {
@ -314,11 +466,15 @@ export default {
screenWidth() { screenWidth() {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
isPortionScalePositive() { isYieldMultiplierPositive() {
return this.portionScale && !isNaN(this.portionScale) return this.yieldMultiplier && !isNaN(this.yieldMultiplier)
? parseFloat(this.portionScale) ? parseFloat(this.yieldMultiplier)
: 1 : 1
}, },
isLightMode() {
console.log(Application.systemAppearance())
return Application.systemAppearance() === "light"
},
}, },
methods: { methods: {
...mapActions(["toggleStateAction", "setCurrentComponentAction"]), ...mapActions(["toggleStateAction", "setCurrentComponentAction"]),
@ -328,42 +484,38 @@ export default {
setTimeout((e) => { setTimeout((e) => {
this.setCurrentComponentAction("ViewRecipe") this.setCurrentComponentAction("ViewRecipe")
}, 500) }, 500)
this.portionScale = this.recipe.portionSize this.yieldMultiplier = this.recipe.yield.quantity
this.showFab = true this.showFab = true
this.showInfo()
}, },
niceDates(time) { niceDates(time) {
let date = new Date(time) let lastTried = new Date(time).getTime()
let diff = (new Date().getTime() - date.getTime()) / 1000 let now = new Date().getTime()
console.log(diff) let midnight = new Date().setHours(0, 0, 0, 0)
let diff = (now - lastTried) / 1000
let dayDiff = Math.ceil(diff / 86400) let dayDiff = Math.ceil(diff / 86400)
console.log(dayDiff)
if (isNaN(dayDiff) || dayDiff < 0) return "" if (isNaN(dayDiff) || dayDiff < 0) return ""
return ( return (
(dayDiff == 0 && (diff < 86400 && lastTried > midnight && "today") ||
((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 == 1 && "yesterday") ||
(dayDiff < 7 && dayDiff + " days ago") || (dayDiff < 7 && dayDiff + " days ago") ||
(dayDiff < 31 && Math.ceil(dayDiff / 7) + " week(s) ago") || (dayDiff < 31 && Math.round(dayDiff / 7) + " week(s) ago") ||
(dayDiff > 30 && (dayDiff < 366 && Math.round(dayDiff / 30) + " month(s) ago") ||
dayDiff < 365 && (dayDiff > 365 && "long time ago")
Math.round(dayDiff / 30) + " month(s) ago") ||
(dayDiff > 364 && Math.round(dayDiff / 365) + " year(s) ago")
) )
}, },
selectedIndexChange(args) {
this.selectedTabIndex = args.object.selectedIndex
},
showInfo() { showInfo() {
let feedback = new Feedback() let feedback = new Feedback()
feedback.show({ feedback.show({
type: FeedbackType.Info, title: `You tried this recipe ${this.niceDates(
message: `You tried this recipe ${this.niceDates( this.recipe.lastTried
this.recipe.triedOn
)}!`, )}!`,
titleColor: new Color(`${this.isLightMode ? "#fff" : "#111"}`),
backgroundColor: new Color(
`${this.isLightMode ? "#ff5722" : "#ff7043"}`
),
}) })
}, },
highlight(args) { highlight(args) {
@ -374,8 +526,8 @@ export default {
roundedQuantity(quantity) { roundedQuantity(quantity) {
return ( return (
Math.round( Math.round(
(quantity / this.recipe.portionSize) * (quantity / this.recipe.yield.quantity) *
this.isPortionScalePositive * this.isYieldMultiplierPositive *
100 100
) / 100 ) / 100
) )
@ -399,14 +551,14 @@ export default {
shareRecipe() { shareRecipe() {
let overview = `${ let overview = `${
this.recipe.title this.recipe.title
} Recipe\n\nPreparation time: ${this.getTime( } Recipe\n\nApprox. cooking time: ${this.formattedTime(
this.recipe.prepTime this.recipe.timeRequired
)}\nCooking time: ${this.getTime(this.recipe.cookTime)}\n` )}\n`
let shareContent = overview let shareContent = overview
if (this.recipe.ingredients.length) { if (this.recipe.ingredients.length) {
let ingredients = `\n\nIngredients for ${this.recipe.portionSize} ${ let ingredients = `\n\nIngredients for ${
this.recipe.portionSize === 1 ? "postion:" : "portions:" this.yieldMultiplier
}\n\n` } ${this.recipe.yield.unit.toLowerCase()}\n\n`
this.recipe.ingredients.forEach((e) => { this.recipe.ingredients.forEach((e) => {
ingredients += `- ${this.roundedQuantity(e.quantity)} ${e.unit} ${ ingredients += `- ${this.roundedQuantity(e.quantity)} ${e.unit} ${
e.item e.item
@ -417,7 +569,7 @@ export default {
if (this.recipe.instructions.length) { if (this.recipe.instructions.length) {
let instructions = `\n\nInstructions:\n\n` let instructions = `\n\nInstructions:\n\n`
this.recipe.instructions.forEach((e, i) => { this.recipe.instructions.forEach((e, i) => {
instructions += `${i + 1}. ${e}\n\n` instructions += `Step ${i + 1}: ${e}\n\n`
}) })
shareContent += instructions shareContent += instructions
} }
@ -445,12 +597,13 @@ export default {
"How would you like to share this recipe?" "How would you like to share this recipe?"
) )
}, },
toggle(key) { toggle(key, setDate) {
this.toggleStateAction({ this.toggleStateAction({
index: this.recipeIndex, index: this.recipeIndex,
id: this.recipeID, id: this.recipeID,
recipe: this.recipe, recipe: this.recipe,
key, key,
setDate,
}) })
}, },
toggleFavorite() { toggleFavorite() {
@ -471,14 +624,14 @@ export default {
this.toggle("tried") this.toggle("tried")
}, },
recipeTried() { recipeTried() {
this.toggle("tried") this.toggle("tried", true)
this.$navigateBack() this.$navigateBack()
}, },
getTime(time) { formattedTime(time) {
let t = time.split(":") let t = time.split(":")
let h = t[0] let h = parseInt(t[0])
let m = t[1] let m = parseInt(t[1])
return h !== "00" ? `${h}h ${m}m` : `${m}m` return h ? (m ? `${h}h ${m}m` : `${h}h`) : `${m}m`
}, },
isValidURL(string) { isValidURL(string) {
let pattern = new RegExp("^https?|www", "ig") let pattern = new RegExp("^https?|www", "ig")
@ -499,6 +652,7 @@ export default {
}, },
mounted() { mounted() {
this.showFab = true this.showFab = true
setTimeout((e) => this.recipe.tried && this.showInfo(), 2000)
}, },
} }
</script> </script>

View file

@ -1,7 +1,6 @@
<template> <template>
<Page> <Page>
<StackLayout class="dialogContainer" :class="isLightMode"> <StackLayout class="dialogContainer" :class="isLightMode">
<!-- :class="isLightTheme ? 'light' : 'dark'" -->
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<ListView <ListView
width="100%" width="100%"
@ -11,22 +10,20 @@
separatorColor="transparent" separatorColor="transparent"
> >
<v-template> <v-template>
<StackLayout class="actionItem"> <Label class="actionItem" :text="item" />
<Label :text="item" />
</StackLayout>
</v-template> </v-template>
</ListView> </ListView>
<GridLayout rows="auto" columns="auto, *, auto"> <GridLayout rows="auto" columns="auto, *, auto" class="actionsContainer">
<Label <Label
v-if="action" v-if="action"
col="0" col="0"
class="cancel orkm pull-left" class="action orkm pull-left"
:text="action" :text="action"
@tap="$modal.close(action)" @tap="$modal.close(action)"
/> />
<Label <Label
col="2" col="2"
class="cancel orkm pull-right" class="action orkm pull-right"
text="CANCEL" text="CANCEL"
@tap="$modal.close(false)" @tap="$modal.close(false)"
/> />

View file

@ -2,23 +2,30 @@
<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 v-if="description" class="dialogDescription" :text="description" textWrap="true" /> <Label
<StackLayout v-if="description"
orientation="horizontal" class="dialogDescription"
:text="description"
textWrap="true"
/>
<GridLayout
rows="auto"
columns="*, auto, 32, auto"
class="actionsContainer" class="actionsContainer"
horizontalAlignment="right"
> >
<Label <Label
col="1"
class="action orkm" class="action orkm"
:text="cancelButtonText" :text="cancelButtonText"
@tap="$modal.close(false)" @tap="$modal.close(false)"
/> />
<Label <Label
col="3"
class="action orkm" class="action orkm"
:text="okButtonText" :text="okButtonText"
@tap="$modal.close(true)" @tap="$modal.close(true)"
/> />
</StackLayout> </GridLayout>
</StackLayout> </StackLayout>
</Page> </Page>
</template> </template>

View file

@ -0,0 +1,132 @@
<template>
<Page>
<StackLayout class="dialogContainer" :class="isLightMode">
<Label class="dialogTitle orkm" :text="title" />
<StackLayout
class="dialogListPicker"
orientation="horizontal"
horizontalAlignment="center"
>
<ListPicker
ref="hrPicker"
:items="hrs"
:selectedIndex="hrIndex"
@selectedIndexChange="setHrs"
></ListPicker>
<Label
verticalAlignment="center"
class="okrb"
text=":"
textWrap="false"
/>
<ListPicker
ref="minPicker"
:items="mins"
:selectedIndex="minIndex"
@selectedIndexChange="setMins"
></ListPicker>
</StackLayout>
<GridLayout
rows="auto"
columns="*, auto, 32, auto"
class="actionsContainer"
>
<Label
col="1"
class="action orkm"
text="CANCEL"
@tap="$modal.close(false)"
/>
<Label
col="3"
class="action orkm"
:text="action"
@tap="$modal.close(selectedTime)"
/>
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import { Application } from "@nativescript/core"
export default {
props: ["title", "selectedHr", "selectedMin", "action"],
data() {
return {
hrs: [
"00",
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
],
mins: [
"00",
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"10",
"15",
"20",
"25",
"30",
"35",
"40",
"45",
"50",
"55",
],
selectedHrs: "00",
selectedMins: "00",
}
},
computed: {
hrIndex() {
return this.hrs.indexOf(this.selectedHr)
},
minIndex() {
return this.mins.indexOf(this.selectedMin)
},
isLightMode() {
return Application.systemAppearance()
},
selectedTime() {
return this.selectedHrs + ":" + this.selectedMins
},
},
methods: {
setHrs(args) {
this.selectedHrs = this.hrs[args.object.selectedIndex]
},
setMins(args) {
this.selectedMins = this.mins[args.object.selectedIndex]
},
},
}
</script>

View file

@ -2,27 +2,38 @@
<Page> <Page>
<StackLayout class="dialogContainer" :class="isLightMode"> <StackLayout class="dialogContainer" :class="isLightMode">
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<StackLayout class="dialogInputField"> <StackLayout class="dialogInput">
<TextField <TextField
@loaded="focusField"
:hint="hint" :hint="hint"
v-model="category" v-model="category"
autocapitalizationType="words" autocapitalizationType="words"
/> />
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" horizontalAlignment="right"> <GridLayout
<Label class="action orkm" text="CANCEL" @tap="$modal.close(false)" /> rows="auto"
columns="*, auto, 32, auto"
class="actionsContainer"
>
<Label <Label
col="1"
class="action orkm"
text="CANCEL"
@tap="$modal.close(false)"
/>
<Label
col="3"
class="action orkm" class="action orkm"
:text="action" :text="action"
@tap="$modal.close(category)" @tap="$modal.close(category)"
/> />
</StackLayout> </GridLayout>
</StackLayout> </StackLayout>
</Page> </Page>
</template> </template>
<script> <script>
import { Application } from "@nativescript/core" import { Application, Utils } from "@nativescript/core"
export default { export default {
props: ["title", "hint", "action"], props: ["title", "hint", "action"],
data() { data() {
@ -35,5 +46,11 @@ export default {
return Application.systemAppearance() return Application.systemAppearance()
}, },
}, },
methods: {
focusField(args) {
args.object.focus()
setTimeout((e) => Utils.ad.showSoftInput(args.object.android), 1)
},
},
} }
</script> </script>

Binary file not shown.

View file

@ -12,11 +12,13 @@ Vue.registerElement(
"RadSideDrawer", "RadSideDrawer",
() => require("nativescript-ui-sidedrawer").RadSideDrawer () => require("nativescript-ui-sidedrawer").RadSideDrawer
) )
import { CheckBox } from "@nstudio/nativescript-checkbox"
// Vue.registerElement( Vue.registerElement("CheckBox", () => CheckBox, {
// 'Fab', model: {
// () => require('@nstudio/nativescript-floatingactionbutton').Fab prop: "checked",
// ); event: "checkedChange",
},
})
if (TNS_ENV !== "production") { if (TNS_ENV !== "production") {
// Vue.use(VueDevtools) // Vue.use(VueDevtools)

View file

@ -2,124 +2,182 @@ import Vue from "vue"
import Vuex from "vuex" import Vuex from "vuex"
import { Couchbase } from "nativescript-couchbase-plugin" import { Couchbase } from "nativescript-couchbase-plugin"
const recipesDB = new Couchbase("enrecipes") const recipesDB = new Couchbase("enrecipes")
const categoriesDB = new Couchbase("categories") const categoriesDB = new Couchbase("userCategories")
const yieldUnitsDB = new Couchbase("userYieldUnits")
Vue.use(Vuex) Vue.use(Vuex)
let defaultCategories = [
"Appetizers",
"BBQ",
"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",
]
let defaultYieldUnits = [
"Serving",
"Piece",
"Teaspoon",
"Tablespoon",
"Fluid Ounce",
"Ounce",
"Pound",
"Gram",
"Kilogram",
"Cup",
"Gallon",
"Millilitre",
"Litre",
"Roll",
"Pattie",
"Loaf",
]
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
recipes: [ recipes: [
{ // {
imageSrc: null, // imageSrc: null,
title: "Mushroom & Spinach Risotto", // title: "Mushroom & Spinach Risotto",
category: "Lunch", // category: "Lunch",
prepTime: "00:25", // timeRequired: "01:45",
cookTime: "00:45", // yield: {
portionSize: "8", // quantity: 1,
ingredients: [ // unit: "Serving",
{ // },
item: "reduced-sodium chicken broth", // ingredients: [
unit: "cup", // {
quantity: "5.25", // item: "reduced-sodium chicken broth",
}, // unit: "cup",
{ item: "sliced fresh mushrooms", unit: "cup", quantity: "2.5" }, // quantity: "5.25",
{ item: "medium onion, finely chopped", unit: "unit", quantity: "1" }, // },
{ item: "butter", unit: "Tbsp", quantity: "3" }, // { item: "sliced fresh mushrooms", unit: "cup", quantity: "2.5" },
{ item: "Garlic", unit: "unit", quantity: "3" }, // { item: "medium onion, finely chopped", unit: "unit", quantity: "1" },
{ // { item: "butter", unit: "Tbsp", quantity: "3" },
item: "white wine or reduced-sodium chicken broth", // { item: "Garlic", unit: "unit", quantity: "3" },
unit: "cup", // {
quantity: ".75", // item: "white wine or reduced-sodium chicken broth",
}, // unit: "l",
{ item: "heavy whipping cream", unit: "cup", quantity: "1" }, // quantity: ".75",
{ item: "uncooked arborio rice", unit: "cup", quantity: "1.75" }, // },
{ item: "olive oil", unit: "Tbsp", quantity: "2" }, // { item: "heavy whipping cream", unit: "cup", quantity: "1" },
{ // { item: "uncooked arborio rice", unit: "cup", quantity: "1.75" },
item: "frozen chopped spinach, thawed and squeezed dry", // { item: "olive oil", unit: "Tbsp", quantity: "2" },
unit: "cup", // {
quantity: "1.5", // item: "frozen chopped spinach, thawed and squeezed dry",
}, // unit: "cup",
{ item: "pepper", unit: "tsp", quantity: ".5" }, // quantity: "1.5",
{ item: "Salt", unit: "tsp", quantity: ".25" }, // },
{ item: "grated Parmesan cheese", unit: "cup", quantity: "1" }, // { item: "pepper", unit: "tsp", quantity: ".5" },
], // { item: "Salt", unit: "tsp", quantity: ".25" },
instructions: [ // { item: "grated Parmesan cheese", unit: "cup", quantity: "1" },
"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.", // instructions: [
"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.", // "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.",
notes: [ // "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.",
"Nutrition Facts\n3/4 cup: 409 calories, 22g fat (12g saturated fat), 61mg cholesterol, 667mg sodium, 41g carbohydrate (3g sugars, 2g fiber), 11g protein.", // ],
], // notes: [
references: [ // "Nutrition Facts\n3/4 cup: 409 calories, 22g fat (12g saturated fat), 61mg cholesterol, 667mg sodium, 41g carbohydrate (3g sugars, 2g fiber), 11g protein.",
"https://www.tasteofhome.com/recipes/mushroom-spinach-risotto/", // ],
], // references: [
isFavorite: false, // "https://www.tasteofhome.com/recipes/mushroom-spinach-risotto/",
tried: true, // ],
triedOn: "2020-10-28T18:19:06.528Z", // isFavorite: false,
lastModified: "2020-10-28T06:19:06.528Z", // tried: false,
id: "57qm8oqxdr", // lastTried: "2020-10-28T18:19:06.528Z",
}, // lastModified: "2020-10-28T06:19:06.528Z",
// id: "57qm8oqxdr",
// },
], ],
categories: [], categories: [],
userCategories: [],
units: [ units: [
"unit", "unit",
"tsp", "tsp",
"Tbsp", "tbsp",
"oz", "fl oz",
"cup", "cup",
"pt", "pt",
"qt", "qt",
"lb",
"gal", "gal",
"ml", "ml",
"L", "l",
"oz",
"lb",
"mg", "mg",
"g", "g",
"kg", "kg",
"mm",
"cm", "cm",
"m",
"in", "in",
"°C", "clove",
"°F", "pinch",
"drop",
"dozen",
"stick",
"small",
"medium",
"large",
], ],
yieldUnits: [],
userYieldUnits: [],
icon: { icon: {
home: "\ued99", home: "\ued3b",
heart: "\ued94", heart: "\ued36",
heartOutline: "\uead6", heartOutline: "\uea6c",
label: "\uedaf", label: "\ued51",
cog: "\ued05", cog: "\ueca6",
info: "\ueda7", info: "\ued49",
menu: "\ueb2a", menu: "\ueac1",
search: "\uebbc", search: "\ueb54",
sort: "\ueb2b", sort: "\ueac2",
plus: "\ueb89", plus: "\ueb21",
close: "\uec4e", plusCircle: "\ueb22",
image: "\ueae9", close: "\uebe9",
addImage: "\ueae8", image: "\uea7f",
back: "\ue988", food: "\ueb3e",
save: "\uee48", back: "\uea95",
camera: "\uecc2", save: "\uedeb",
share: "\uee50", camera: "\uec61",
edit: "\uee17", share: "\uedf3",
theme: "\ued09", edit: "\uedba",
folder: "\ued7c", theme: "\uecaa",
backup: "\uee48", restore: "\uea72",
restore: "\ueadc", link: "\ueaa0",
link: "\ueb09", file: "\ued02",
file: "\ued60", user: "\uee33",
user: "\uee8e", trash: "\uee26",
trash: "\uee83", donate: "\ueb4f",
donate: "\ued41", trylater: "\uec31",
trylater: "\uec96", trylaterOutline: "\ue94a",
trylaterOutline: "\ue9bb", note: "\ueb04",
note: "\uee0a", copy: "\ue9e6",
copy: "\uea51", check: "\ue9ab",
plusCircle: "\ueb8a", telegram: "\ueec7",
unchecked: "\uea16", time: "\ueba2",
checked: "\uece6", item: "\ue99d",
telegram: "\ue95e", step: "\ue948",
source: "\ueaa0",
}, },
currentComponent: "EnRecipes", currentComponent: "EnRecipes",
}, },
@ -132,58 +190,58 @@ export default new Vuex.Store({
}, },
initializeCategories(state) { initializeCategories(state) {
let isCategoriesStored = categoriesDB.query({ select: [] }).length let isCategoriesStored = categoriesDB.query({ select: [] }).length
let cats
if (isCategoriesStored) { if (isCategoriesStored) {
cats = categoriesDB.getDocument("categories").categories state.userCategories = categoriesDB.getDocument(
} else { "userCategories"
categoriesDB.createDocument( ).userCategories
{ let categoriesWithRecipes = state.recipes.map((e) => e.category)
categories: [ state.userCategories = state.userCategories.filter((e) =>
"Appetizers", categoriesWithRecipes.includes(e)
"BBQ",
"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",
],
},
"categories"
) )
cats = categoriesDB.getDocument("categories").categories } else {
categoriesDB.createDocument({ userCategories: [] }, "userCategories")
} }
cats.forEach((e) => state.categories.push(e)) state.categories = [...defaultCategories, ...state.userCategories]
state.categories.sort()
},
initializeYieldUnits(state) {
let isYieldUnitsStored = yieldUnitsDB.query({ select: [] }).length
if (isYieldUnitsStored) {
state.userYieldUnits = yieldUnitsDB.getDocument(
"userYieldUnits"
).userYieldUnits
let yieldUnitsWithRecipes = state.recipes.map((e) => e.yield.unit)
state.userYieldUnits = state.userYieldUnits.filter((e) =>
yieldUnitsWithRecipes.includes(e)
)
} else {
yieldUnitsDB.createDocument({ userYieldUnits: [] }, "userYieldUnits")
}
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
}, },
addRecipe(state, { id, recipe }) { addRecipe(state, { id, recipe }) {
state.recipes.push(recipe) state.recipes.push(recipe)
recipesDB.createDocument(recipe, id) recipesDB.createDocument(recipe, id)
}, },
addCategory(state, category) { addCategory(state, category) {
let a = state.categories.filter((e) => e === category).length let lowercase = state.categories.map((e) => e.toLowerCase())
if (a == 0) { if (lowercase.indexOf(category.toLowerCase()) == -1) {
state.categories.push(category) state.userCategories.push(category)
state.categories.sort() categoriesDB.updateDocument("userCategories", {
categoriesDB.updateDocument("categories", { userCategories: [...state.userCategories],
categories: [...state.categories],
}) })
state.categories = [...defaultCategories, ...state.userCategories]
state.categories.sort()
}
},
addYieldUnit(state, unit) {
let lowercase = state.yieldUnits.map((e) => e.toLowerCase())
if (lowercase.indexOf(unit.toLowerCase()) == -1) {
state.userYieldUnits.push(unit)
yieldUnitsDB.updateDocument("userYieldUnits", {
userYieldUnits: [...state.userYieldUnits],
})
state.yieldUnits = [...defaultYieldUnits, ...state.userYieldUnits]
} }
}, },
overwriteRecipe(state, { index, id, recipe }) { overwriteRecipe(state, { index, id, recipe }) {
@ -194,25 +252,27 @@ export default new Vuex.Store({
state.recipes.splice(index, 1) state.recipes.splice(index, 1)
recipesDB.deleteDocument(id) recipesDB.deleteDocument(id)
}, },
toggleState(state, { index, id, recipe, key }) { toggleState(state, { index, id, recipe, key, setDate }) {
state.recipes[index][key] = !state.recipes[index][key] state.recipes[index][key] = !state.recipes[index][key]
if (setDate) state.recipes[index].lastTried = new Date()
recipesDB.updateDocument(id, recipe) recipesDB.updateDocument(id, recipe)
}, },
setLastTriedDate(state, index) {
state.recipes[index].lastTried = new Date()
recipesDB.updateDocument(state.recipes[index].id, state.recipes[index])
},
setCurrentComponent(state, comp) { setCurrentComponent(state, comp) {
state.currentComponent = comp state.currentComponent = comp
}, },
renameCategory(state, { current, updated }) { renameCategory(state, { current, updated }) {
let exists = state.categories.filter((e) => e === updated).length let lowercase = state.categories.map((e) => e.toLowerCase())
if (lowercase.indexOf(updated.toLowerCase()) == -1) {
state.categories.splice(state.categories.indexOf(current), 1) state.userCategories.push(updated)
categoriesDB.updateDocument("userCategories", {
// update recipes with updated category userCategories: [...state.userCategories],
if (!exists) {
state.categories.push(updated)
state.categories.sort()
categoriesDB.updateDocument("categories", {
categories: [...state.categories],
}) })
state.categories = [...defaultCategories, ...state.userCategories]
state.categories.sort()
} }
state.recipes.forEach((e, i) => { state.recipes.forEach((e, i) => {
if (e.category == current) { if (e.category == current) {
@ -231,12 +291,18 @@ export default new Vuex.Store({
initializeCategories({ commit }) { initializeCategories({ commit }) {
commit("initializeCategories") commit("initializeCategories")
}, },
initializeYieldUnits({ commit }) {
commit("initializeYieldUnits")
},
addRecipeAction({ commit }, recipe) { addRecipeAction({ commit }, recipe) {
commit("addRecipe", recipe) commit("addRecipe", recipe)
}, },
addCategoryAction({ commit }, category) { addCategoryAction({ commit }, category) {
commit("addCategory", category) commit("addCategory", category)
}, },
addYieldUnitAction({ commit }, yieldUnit) {
commit("addYieldUnit", yieldUnit)
},
overwriteRecipeAction({ commit }, updatedRecipe) { overwriteRecipeAction({ commit }, updatedRecipe) {
commit("overwriteRecipe", updatedRecipe) commit("overwriteRecipe", updatedRecipe)
}, },
@ -246,6 +312,9 @@ export default new Vuex.Store({
toggleStateAction({ commit }, toggledRecipe) { toggleStateAction({ commit }, toggledRecipe) {
commit("toggleState", toggledRecipe) commit("toggleState", toggledRecipe)
}, },
setLastTriedDateAction({ commit }, index) {
commit("setLastTriedDate", index)
},
setCurrentComponentAction({ commit }, comp) { setCurrentComponentAction({ commit }, comp) {
commit("setCurrentComponent", comp) commit("setCurrentComponent", comp)
}, },

22
app/worker.service.js Normal file
View file

@ -0,0 +1,22 @@
const workers = []
export class WorkerService {
constructor() {}
initImageProcessor() {
if (this.imageProcessor) {
return this.imageProcessor
}
const ImageProcessor = require("nativescript-worker-loader!./workers/ImageProcessor.worker.js")
this.imageProcessor = new ImageProcessor()
workers.push(this.imageProcessor)
return this.imageProcessor
}
}
if (module.hot) {
module.hot.dispose(() => {
workers.forEach((w) => {
w.terminate()
})
})
}

View file

@ -0,0 +1,12 @@
require("tns-core-modules/globals")
import { ImageSource } from "@nativescript/core"
global.onmessage = function({ data }) {
let imgFile = data.imgFile
let imgSavedToPath = data.imgSavedToPath
ImageSource.fromFile(imgFile).then((imgData) => {
if (imgData.saveToFile(imgSavedToPath, "jpg")) {
global.postMessage("savedToFile")
}
})
}

View file

@ -1,11 +1,12 @@
import { NativeScriptConfig } from '@nativescript/core' import { NativeScriptConfig } from "@nativescript/core"
export default { export default {
id: 'com.vishnuraghav.enrecipes', id: "com.vishnuraghav.enrecipes",
appResourcesPath: 'app/App_Resources', appResourcesPath: "App_Resources",
android: { android: {
v8Flags: '--expose_gc', v8Flags: "--expose_gc",
markingMode: 'none', markingMode: "none",
codeCache: true,
}, },
appPath: 'app', appPath: "app",
} as NativeScriptConfig } as NativeScriptConfig

5
package-lock.json generated
View file

@ -1203,6 +1203,11 @@
"mkdirp": "^1.0.4" "mkdirp": "^1.0.4"
} }
}, },
"@nstudio/nativescript-checkbox": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@nstudio/nativescript-checkbox/-/nativescript-checkbox-2.0.4.tgz",
"integrity": "sha512-ypIGAHxDE/2o3CzYohSdypdhiw4GjMcZ3H/qtF4z97HMcMqj+g5bYPDC9cRH97qgAez8jf/z3UX5OzOtnrNxug=="
},
"@types/anymatch": { "@types/anymatch": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",

View file

@ -12,6 +12,7 @@
"@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",
"@nstudio/nativescript-checkbox": "^2.0.4",
"nativescript-clipboard": "^2.0.0", "nativescript-clipboard": "^2.0.0",
"nativescript-couchbase-plugin": "^0.9.6", "nativescript-couchbase-plugin": "^0.9.6",
"nativescript-feedback": "^2.0.0", "nativescript-feedback": "^2.0.0",