This commit is contained in:
Vishnu Raghav B 2020-11-10 23:58:48 +05:30
parent 16935b62d2
commit cff2dea334
61 changed files with 12239 additions and 1572 deletions

8595
.update_backup/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
{
"name": "enrecipes",
"version": "1.0.0",
"description": "A native application built with NativeScript-Vue",
"author": "Vishnu Raghav <design@vishnuraghav.com>",
"license": "GPL",
"scripts": {
"run": "ns run android"
},
"dependencies": {
"@nativescript-community/perms": "^2.1.1",
"@nativescript-community/ui-material-activityindicator": "^5.0.30",
"@nativescript-community/ui-material-button": "^5.1.0",
"@nativescript-community/ui-material-floatingactionbutton": "^5.0.30",
"@nativescript-community/ui-material-progress": "^5.0.30",
"@nativescript-community/ui-material-ripple": "^5.0.30",
"@nativescript/core": "~7.0.0",
"@nativescript/imagepicker": "^1.0.0",
"@nativescript/social-share": "^2.0.1",
"@nativescript/theme": "^3.0.0",
"@nativescript/webpack": "3.0.0",
"@nativescript/zip": "^5.0.0",
"@nstudio/nativescript-checkbox": "^2.0.4",
"nativescript-clipboard": "^2.0.0",
"nativescript-couchbase-plugin": "^0.9.6",
"nativescript-feedback": "^2.0.0",
"nativescript-imagecropper": "^4.0.1",
"nativescript-plugin-filepicker": "^1.0.0",
"nativescript-toast": "^2.0.0",
"nativescript-ui-listview": "^9.0.4",
"nativescript-ui-sidedrawer": "^9.0.3",
"nativescript-vue": "^2.6.1",
"vuex": "^3.3.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@nativescript/android": "7.0.1",
"@types/node": "^14.0.27",
"babel-loader": "^8.1.0",
"nativescript-vue-template-compiler": "^2.6.0",
"node-sass": "^4.13.1",
"vue-loader": "^15.9.1"
},
"main": "main"
}

View file

@ -0,0 +1,369 @@
const { join, relative, resolve, sep } = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const TerserPlugin = require("terser-webpack-plugin");
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const NsVueTemplateCompiler = require("nativescript-vue-template-compiler");
const nsWebpack = require("@nativescript/webpack");
const nativescriptTarget = require("@nativescript/webpack/nativescript-target");
const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin");
const hashSalt = Date.now().toString();
module.exports = env => {
// Add your custom Activities, Services and other android app components here.
const appComponents = env.appComponents || [];
appComponents.push(...[
"@nativescript/core/ui/frame",
"@nativescript/core/ui/frame/activity",
]);
const platform = env && (env.android && "android" || env.ios && "ios" || env.platform);
if (!platform) {
throw new Error("You need to provide a target platform!");
}
const platforms = ["ios", "android"];
const projectRoot = __dirname;
if (env.platform) {
platforms.push(env.platform);
}
// Default destination inside platforms/<platform>/...
const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
const {
// The 'appPath' and 'appResourcesPath' values are fetched from
// the nsconfig.json configuration file.
appPath = "app",
appResourcesPath = "app/App_Resources",
// You can provide the following flags when running 'tns run android|ios'
snapshot, // --env.snapshot
production, // --env.production
report, // --env.report
hmr, // --env.hmr
sourceMap, // --env.sourceMap
hiddenSourceMap, // --env.hiddenSourceMap
unitTesting, // --env.unitTesting
testing, // --env.testing
verbose, // --env.verbose
snapshotInDocker, // --env.snapshotInDocker
skipSnapshotTools, // --env.skipSnapshotTools
compileSnapshot // --env.compileSnapshot
} = env;
const useLibs = compileSnapshot;
const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
const externals = nsWebpack.getConvertedExternals(env.externals);
const mode = production ? "production" : "development"
const appFullPath = resolve(projectRoot, appPath);
const hasRootLevelScopedModules = nsWebpack.hasRootLevelScopedModules({ projectDir: projectRoot });
let coreModulesPackageName = "tns-core-modules";
const alias = env.alias || {};
alias['~/package.json'] = resolve(projectRoot, 'package.json');
alias['~'] = appFullPath;
alias['@'] = appFullPath;
alias['vue'] = 'nativescript-vue';
if (hasRootLevelScopedModules) {
coreModulesPackageName = "@nativescript/core";
alias["tns-core-modules"] = coreModulesPackageName;
}
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
const copyIgnore = { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] };
const entryModule = nsWebpack.getEntryModule(appFullPath, platform);
const entryPath = `.${sep}${entryModule}`;
const entries = env.entries || {};
entries.bundle = entryPath;
const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("@nativescript") > -1);
if (platform === "ios" && !areCoreModulesExternal && !testing) {
entries["tns_modules/@nativescript/core/inspector_modules"] = "inspector_modules";
};
console.log(`Bundling application for entryPath ${entryPath}...`);
let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist);
const itemsToClean = [`${dist}/**/*`];
if (platform === "android") {
itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "src", "main", "assets", "snapshots")}`);
itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
}
nsWebpack.processAppComponents(appComponents, platform);
const config = {
mode: mode,
context: appFullPath,
externals,
watchOptions: {
ignored: [
appResourcesFullPath,
// Don't watch hidden files
"**/.*",
],
},
target: nativescriptTarget,
// target: nativeScriptVueTarget,
entry: entries,
output: {
pathinfo: false,
path: dist,
sourceMapFilename,
libraryTarget: "commonjs2",
filename: "[name].js",
globalObject: "global",
hashSalt
},
resolve: {
extensions: [".vue", ".ts", ".js", ".scss", ".css"],
// Resolve {N} system modules from @nativescript/core
modules: [
resolve(__dirname, `node_modules/${coreModulesPackageName}`),
resolve(__dirname, "node_modules"),
`node_modules/${coreModulesPackageName}`,
"node_modules",
],
alias,
// resolve symlinks to symlinked modules
symlinks: true,
},
resolveLoader: {
// don't resolve symlinks to symlinked loaders
symlinks: false,
},
node: {
// Disable node shims that conflict with NativeScript
"http": false,
"timers": false,
"setImmediate": false,
"fs": "empty",
"__dirname": false,
},
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
optimization: {
runtimeChunk: "single",
noEmitOnErrors: true,
splitChunks: {
cacheGroups: {
vendor: {
name: "vendor",
chunks: "all",
test: (module) => {
const moduleName = module.nameForCondition ? module.nameForCondition() : '';
return /[\\/]node_modules[\\/]/.test(moduleName) ||
appComponents.some(comp => comp === moduleName);
},
enforce: true,
},
},
},
minimize: Boolean(production),
minimizer: [
new TerserPlugin({
parallel: true,
cache: true,
sourceMap: isAnySourceMapEnabled,
terserOptions: {
output: {
comments: false,
semicolons: !isAnySourceMapEnabled
},
compress: {
// The Android SBG has problems parsing the output
// when these options are enabled
'collapse_vars': platform !== "android",
sequences: platform !== "android",
},
keep_fnames: true,
},
}),
],
},
module: {
rules: [{
include: [join(appFullPath, entryPath + ".js"), join(appFullPath, entryPath + ".ts")],
use: [
// Require all Android app components
platform === "android" && {
loader: "@nativescript/webpack/helpers/android-app-components-loader",
options: { modules: appComponents },
},
{
loader: "@nativescript/webpack/bundle-config-loader",
options: {
registerPages: true, // applicable only for non-angular apps
loadCss: !snapshot, // load the application css if in debug mode
unitTesting,
appFullPath,
projectRoot,
ignoredFiles: nsWebpack.getUserDefinedEntries(entries, platform)
},
},
].filter(loader => Boolean(loader)),
},
{
test: /[\/|\\]app\.css$/,
use: [
'@nativescript/webpack/helpers/style-hot-loader',
{
loader: "@nativescript/webpack/helpers/css2json-loader",
options: { useForImports: true }
},
],
},
{
test: /[\/|\\]app\.scss$/,
use: [
'@nativescript/webpack/helpers/style-hot-loader',
{
loader: "@nativescript/webpack/helpers/css2json-loader",
options: { useForImports: true }
},
'sass-loader',
],
},
{
test: /\.css$/,
exclude: /[\/|\\]app\.css$/,
use: [
'@nativescript/webpack/helpers/style-hot-loader',
'@nativescript/webpack/helpers/apply-css-loader.js',
{ loader: "css-loader", options: { url: false } },
],
},
{
test: /\.scss$/,
exclude: /[\/|\\]app\.scss$/,
use: [
'@nativescript/webpack/helpers/style-hot-loader',
'@nativescript/webpack/helpers/apply-css-loader.js',
{ loader: "css-loader", options: { url: false } },
'sass-loader',
],
},
{
test: /\.js$/,
loader: 'babel-loader',
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
allowTsInNodeModules: true,
compilerOptions: {
declaration: false
},
getCustomTransformers: (program) => ({
before: [
require("@nativescript/webpack/transformers/ns-transform-native-classes").default
]
})
},
},
{
test: /\.vue$/,
loader: "vue-loader",
options: {
compiler: NsVueTemplateCompiler,
},
},
],
},
plugins: [
// ... Vue Loader plugin omitted
// make sure to include the plugin!
new VueLoaderPlugin(),
// Define useful constants like TNS_WEBPACK
new webpack.DefinePlugin({
"global.TNS_WEBPACK": "true",
"global.isAndroid": platform === 'android',
"global.isIOS": platform === 'ios',
"TNS_ENV": JSON.stringify(mode),
"process": "global.process"
}),
// Remove all files from the out dir.
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: itemsToClean,
verbose: !!verbose
}),
// Copy assets
new CopyWebpackPlugin({
patterns: [
{ from: 'assets/**', noErrorOnMissing: true, globOptions: { dot: false, ...copyIgnore } },
{ from: 'fonts/**', noErrorOnMissing: true, globOptions: { dot: false, ...copyIgnore } },
{ from: '**/*.+(jpg|png)', noErrorOnMissing: true, globOptions: { dot: false, ...copyIgnore } }
],
}),
new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),
// For instructions on how to set up workers with webpack
// check out https://github.com/nativescript/worker-loader
new NativeScriptWorkerPlugin(),
new nsWebpack.PlatformFSPlugin({
platform,
platforms,
}),
// Does IPC communication with the {N} CLI to notify events when running in watch mode.
new nsWebpack.WatchStateLoggerPlugin()
],
};
if (unitTesting) {
config.module.rules.push(
{
test: /-page\.js$/,
use: "@nativescript/webpack/helpers/script-hot-loader"
},
{
test: /\.(html|xml)$/,
use: "@nativescript/webpack/helpers/markup-hot-loader"
},
{ test: /\.(html|xml)$/, use: "@nativescript/webpack/helpers/xml-namespace-loader" }
);
}
if (report) {
// Generate report files for bundles content
config.plugins.push(new BundleAnalyzerPlugin({
analyzerMode: "static",
openAnalyzer: false,
generateStatsFile: true,
reportFilename: resolve(projectRoot, "report", `report.html`),
statsFilename: resolve(projectRoot, "report", `stats.json`),
}));
}
if (snapshot) {
config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({
chunk: "vendor",
requireModules: [
"@nativescript/core/bundle-entry-points",
],
projectRoot,
webpackConfig: config,
snapshotInDocker,
skipSnapshotTools,
useLibs
}));
}
if (hmr) {
config.plugins.push(new webpack.HotModuleReplacementPlugin());
}
return config;
};

View file

@ -1,4 +1,4 @@
# NativeScript-Vue Application # EnRecipes
> A native application built with NativeScript-Vue > A native application built with NativeScript-Vue

View file

@ -15,14 +15,14 @@
android { android {
defaultConfig { defaultConfig {
versionCode 2 versionCode 1
versionName '1.0.0' versionName '1.0.0'
applicationId 'com.vishnuraghav.enrecipes' applicationId 'com.vishnuraghav.enrecipes'
minSdkVersion 21 minSdkVersion 21
generatedDensities = [] generatedDensities = []
ndk { ndk {
abiFilters.clear() abiFilters.clear()
abiFilters.addAll(['arm64-v8a','x86']) abiFilters.addAll(['arm64-v8a'])
} }
} }
aaptOptions { aaptOptions {

View file

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="__PACKAGE__" android:versionCode="10000" android:versionName="1.0"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="__PACKAGE__" android:versionCode="10000" android:versionName="1.0">
<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" /> <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" tools:node="remove" />
<uses-feature android:name="android.hardware.camera" android:required="false" /> <!-- <uses-feature android:name="android.hardware.camera" /> -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove" />
<uses-permission android:name="android.permission.INTERNET" tools:node="remove" /> <uses-permission android:name="android.permission.INTERNET" tools:node="remove" />
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" /> <uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
<application android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:hardwareAccelerated="true" android:largeHeap="true" android:theme="@style/AppTheme" android:requestLegacyExternalStorage="true"> <application android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:hardwareAccelerated="true" android:largeHeap="true" android:theme="@style/AppTheme" android:requestLegacyExternalStorage="true">
<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"> <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="adjustResize">
<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" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ns_primary"> <color name="ns_primary">
#fafafa #f1f3f5
</color> </color>
<color name="ns_primaryDark"> <color name="ns_primaryDark">
#ff5200 #ff5200

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"> <resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- theme to use FOR launch screen --> <!-- theme to use FOR launch screen -->
<style name="LaunchScreenThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> <style name="LaunchScreenThemeBase" parent="Theme.MaterialComponents.NoActionBar">
<item name="toolbarStyle"> <item name="toolbarStyle">
@style/NativeScriptToolbarStyle @style/NativeScriptToolbarStyle
</item> </item>
@ -27,7 +27,10 @@
<style name="LaunchScreenTheme" parent="LaunchScreenThemeBase"> <style name="LaunchScreenTheme" parent="LaunchScreenThemeBase">
</style> </style>
<!-- theme to use AFTER launch screen is loaded --> <!-- theme to use AFTER launch screen is loaded -->
<style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppThemeBase" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="searchViewStyle">
@style/SearchViewMy
</item>
<item name="toolbarStyle"> <item name="toolbarStyle">
@style/NativeScriptToolbarStyle @style/NativeScriptToolbarStyle
</item> </item>
@ -40,21 +43,42 @@
<item name="colorAccent"> <item name="colorAccent">
@color/ns_accent @color/ns_accent
</item> </item>
<item name="colorControlNormal">
#868e96
</item>
</style> </style>
<style name="AppTheme" parent="AppThemeBase"> <style name="AppTheme" parent="AppThemeBase">
</style> </style>
<!-- theme for action-bar --> <!-- theme for action-bar -->
<style name="NativeScriptToolbarStyleBase" parent="Widget.AppCompat.Toolbar"> <style name="NativeScriptToolbarStyleBase" parent="Widget.MaterialComponents.Toolbar">
<item name="contentInsetStart">
0dp
</item>
<item name="contentInsetEnd">
0dp
</item>
<item name="android:divider">
#ff0000
</item>
<item name="android:background"> <item name="android:background">
@color/ns_primary @color/ns_primary
</item> </item>
<item name="theme"> <item name="theme">
@style/ThemeOverlay.AppCompat.ActionBar @style/ThemeOverlay.MaterialComponents.ActionBar
</item> </item>
<item name="popupTheme"> <item name="popupTheme">
@style/ThemeOverlay.AppCompat @style/ThemeOverlay.MaterialComponents
</item> </item>
</style> </style>
<style name="NativeScriptToolbarStyle" parent="NativeScriptToolbarStyleBase"> <style name="NativeScriptToolbarStyle" parent="NativeScriptToolbarStyleBase">
</style> </style>
<!-- theme for searchview -->
<style name="SearchViewMy" parent="Widget.AppCompat.Light.SearchView">
<item name="searchHintIcon">
@null
</item>
<!-- <item name="closeIcon">
@null
</item> -->
</style>
</resources> </resources>

View file

@ -1,27 +1,24 @@
// NativeScript core theme // NativeScript core theme
// @see https://docs.nativescript.org/ui/theme // @see https://docs.nativescript.org/ui/theme
@import "~@nativescript/theme/core"; @import "~@nativescript/theme/core";
// Override variables here // Override variables here
$gray1: #f1f3f5;
$grayD4: #212121; $gray2: #e9ecef;
$grayD3: #424242; $gray3: #dee2e6;
$grayD2: #616161; $gray4: #ced4da;
$grayD1: #757575; $gray5: #adb5bd; // rgb(173,181,189)
$gray: #9e9e9e; $gray6: #868e96;
$grayL1: #e0e0e0; $gray7: #495057;
$grayL2: #eeeeee; $gray8: #343a40;
$grayL3: #f5f5f5; $gray9: #212529;
$grayL4: #fafafa;
$orange: #ff5200; $orange: #ff5200;
$fabRipple: #ff864d;
$red: #c92a2a;
// Global SCSS styling // Global SCSS styling
// @see https://docs.nativescript.org/ui/styling // @see https://docs.nativescript.org/ui/styling
// * { Page,
// font-size: 16; .ns-modal {
// }
Page {
font-family: "Orkney-Regular"; font-family: "Orkney-Regular";
} }
.orkm { .orkm {
@ -36,26 +33,30 @@ Page {
font-size: 16; font-size: 16;
} }
} }
.ns-light { .ns-light {
Page, Page,
ActionBar, ActionBar,
SearchBar, SearchBar,
TabView, TabView,
ListPicker { ListPicker {
color: $grayD4; color: $gray9;
background: $grayL4; background: $gray1;
} }
MDRipple,
MDButton {
ripple-color: rgba($gray6, 0.2);
}
TabView { TabView {
tab-background-color: $grayL4; tab-background-color: $gray1;
selected-tab-text-color: $grayD4; selected-tab-text-color: $gray9;
} }
.hr { .hr {
border-color: $grayL2; border-color: $gray3;
} }
.sd, .sd,
.fieldLabel { .fieldLabel {
background: $grayL4; background: $gray1;
} }
.referenceItem, .referenceItem,
.recipeText, .recipeText,
@ -63,36 +64,32 @@ Page {
.recipeItem { .recipeItem {
background: white; background: white;
} }
.option-highlight,
.selected-sd-item {
background: $grayL2;
}
.sd-item, .sd-item,
.sd-group-header, .sd-group-header,
.time .bx { .time .bx {
color: $grayD2; color: $gray8;
} }
.fab-button { .option .bx,
color: white; .option .info {
background-color: $orange; color: $gray7;
}
.option,
.icon-option {
.bx,
.option-info {
color: $grayD2;
}
} }
.imageHolder { .imageHolder {
color: $gray; color: $gray4;
background: $grayL2; background: $gray3;
} }
.count { .count {
color: $grayL4; color: $gray1;
background: $grayD4; background: $gray9;
} }
.instruction { .instruction {
border-color: $grayD4; border-color: $gray9;
}
MDProgress {
progress-background-color: $gray4;
}
MDFloatingActionButton {
color: white;
} }
} }
@ -102,56 +99,56 @@ Page {
SearchBar, SearchBar,
TabView, TabView,
ListPicker { ListPicker {
color: $grayL4; color: $gray1;
background: $grayD4; background: $gray9;
} }
TabView { TabView {
tab-background-color: $grayD4; tab-background-color: $gray9;
selected-tab-text-color: $grayL4; selected-tab-text-color: $gray1;
} }
MDRipple,
MDButton {
ripple-color: rgba($gray4, 0.1);
}
.hr { .hr {
border-color: #111; border-color: #111;
} }
.sd, .sd,
.fieldLabel { .fieldLabel {
background: $grayD4; background: $gray9;
} }
.referenceItem, .referenceItem,
.recipeText, .recipeText,
.overviewItem, .overviewItem,
.recipeItem, .recipeItem {
.option-highlight { background: $gray8;
background: $grayD3;
} }
.sd-item, .sd-item,
.sd-group-header, .sd-group-header,
.time .bx { .time .bx {
color: $grayL2; color: $gray3;
} }
.selected-sd-item { .option .bx,
background: #111; .option .info {
} color: $gray5;
.fab-button {
color: #111;
background: $orange;
}
.option,
.icon-option {
.bx,
.option-info {
color: $gray;
}
} }
.imageHolder { .imageHolder {
color: $gray; color: $gray8;
background: #111; background: #111;
} }
.count { .count {
color: $grayD4; color: $gray9;
background: $grayL4; background: $gray1;
} }
.instruction { .instruction {
border-color: $grayL4; border-color: $gray1;
}
MDProgress {
progress-background-color: $gray6;
}
MDFloatingActionButton {
color: $gray9;
} }
} }
// ----------------------------- // -----------------------------
@ -165,48 +162,56 @@ TimePickerField {
padding: 14 14 13; padding: 14 14 13;
margin: 8 0 0 0; margin: 8 0 0 0;
border-radius: 4; border-radius: 4;
border-color: $gray; border-color: $gray6;
placeholder-color: $gray; placeholder-color: $gray6;
} }
TextView { TextView {
line-height: 12; line-height: 12;
} }
SearchBar {
font-family: "Orkney-Regular";
font-size: 16;
text-field-hint-color: $gray6;
}
TabView { TabView {
tab-text-color: $gray; tab-text-color: $gray6;
} }
.inputField { .inputField {
margin-top: 16; margin-bottom: 14;
} }
.fieldLabel { .fieldLabel {
font-size: 12; font-size: 12;
margin-left: 8; margin-left: 12;
padding: 0 8; padding: 0 4;
} }
// prettier-ignore .progressContainer {
.progressContainer{
width: 100%; width: 100%;
} }
// prettier-ignore
.text-btn, .text-btn,
.group-header, .group-header,
.category, .category,
ActivityIndicator, MDActivityIndicator {
Progress {
color: $orange; color: $orange;
} }
MDProgress {
progress-color: $orange;
}
// ----------------------------- // -----------------------------
// ActionBar // ActionBar
ActionBar { ActionBar {
margin: 0; margin: 0;
padding: 0; padding: 0 8;
height: 64; height: 64;
.bx { GridLayout {
padding: 16 16 16 4; padding: 0;
margin: 0;
}
MDButton.bx {
padding: 0;
margin: 0; margin: 0;
vertical-alignment: center;
} }
.title { .title {
padding-left: 8; padding-left: 12;
text-align: left; text-align: left;
font-size: 18; font-size: 18;
} }
@ -218,7 +223,6 @@ ActionBar {
} }
.sd-item { .sd-item {
border-radius: 4; border-radius: 4;
padding: 0 16;
height: 48; height: 48;
vertical-alignment: center; vertical-alignment: center;
.bx { .bx {
@ -226,25 +230,50 @@ ActionBar {
} }
&.selected-sd-item { &.selected-sd-item {
color: $orange; color: $orange;
background: rgba($orange, 0.1);
MDRipple {
ripple-color: transparent;
}
} }
// prettier-ignore // prettier-ignore
Label { Label {
padding: 0 16 0 0;
font-size: 16; font-size: 16;
vertical-alignment: center; vertical-alignment: center;
&.bx{
padding: 0 0 0 16;
}
}
MDRipple {
padding: 0 16;
} }
} }
.sd-group-header { .sd-group-header {
width: 100%; width: 100%;
padding: 8 8 16; padding: 8 0 8 8;
}
MDRipple {
border-radius: 4;
}
MDButton {
padding: 8;
min-width: 0;
min-height: 0;
&.bx {
padding: 0;
width: 48;
height: 48;
margin: 0 8 0 0;
border-radius: 99;
}
} }
// ----------------------------- // -----------------------------
// HOME // HOME
.emptyStateContainer {
.emptyState {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.noResult { .emptyState {
line-height: 8; line-height: 8;
padding: 0 32; padding: 0 32;
text-align: center; text-align: center;
@ -253,7 +282,7 @@ ActionBar {
.icon { .icon {
font-size: 64; font-size: 64;
text-align: center; text-align: center;
color: $gray; color: $gray5;
margin-bottom: 16; margin-bottom: 16;
} }
.logo { .logo {
@ -271,6 +300,9 @@ ActionBar {
vertical-alignment: center; vertical-alignment: center;
} }
} }
&.noResult {
margin-top: 32;
}
} }
// ----------------------------- // -----------------------------
// Recipe Items // Recipe Items
@ -313,7 +345,7 @@ RadListView {
} }
.swipe-item { .swipe-item {
margin: 0 8; margin: 0 8;
background: #c62828; background: $red;
color: #fff; color: #fff;
height: 128; height: 128;
border-radius: 4; border-radius: 4;
@ -324,17 +356,19 @@ RadListView {
padding: 8; padding: 8;
} }
.main-container { .main-container {
padding: 16 8 128; padding: 8 8 128;
.option { .option {
font-size: 16; font-size: 16;
padding: 16;
.bx { .bx {
margin: 0 24 0 0; margin: 16 24 16 16;
} }
.option-info { .info {
font-size: 12; font-size: 12;
line-height: 4; line-height: 4;
} }
StackLayout {
margin: 16 24 16 0;
}
} }
} }
// ----------------------------- // -----------------------------
@ -374,17 +408,16 @@ RadListView {
margin-top: 12; margin-top: 12;
.overviewItem { .overviewItem {
border-radius: 4; border-radius: 4;
padding: 8;
margin: 8; margin: 8;
android-elevation: 1; android-elevation: 1;
.bx { .bx {
color: $gray; padding: 12 0 0 12;
width: 24; color: $gray6;
horizontal-alignment: left; horizontal-alignment: left;
} }
.itemCount { .itemCount {
font-size: 16; font-size: 16;
padding: 8 4 4; padding: 8 12 12;
} }
} }
} }
@ -394,7 +427,7 @@ RadListView {
padding-bottom: 12; padding-bottom: 12;
} }
.ingredient-check { .ingredient-check {
margin-bottom: 12; padding-bottom: 0;
} }
.count { .count {
width: 24; width: 24;
@ -432,15 +465,14 @@ RadListView {
border-width: 0; border-width: 0;
} }
.referenceItem { .referenceItem {
padding: 14 16; margin: 8 16;
margin: 8 16 8;
border-radius: 4; border-radius: 4;
font-size: 16; font-size: 16;
.bx { .bx {
font-size: 24; font-size: 24;
} }
.recipeLink { .recipeLink {
padding: 0 16 0 0; padding: 16;
margin: 0; margin: 0;
} }
} }
@ -448,7 +480,7 @@ RadListView {
font-size: 16; font-size: 16;
line-height: 6; line-height: 6;
padding: 16; padding: 16;
margin: 8 16 8; margin: 8 16;
border-radius: 4; border-radius: 4;
} }
} }
@ -458,14 +490,10 @@ RadListView {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.fab-button { MDFloatingActionButton {
height: 56;
width: 56;
border-radius: 28;
padding: 16;
margin: 16; margin: 16;
vertical-alignment: center; background: $orange;
android-elevation: 6; ripple-color: $fabRipple;
} }
// ----------------------------- // -----------------------------
// EDIT RECIPE // EDIT RECIPE
@ -475,51 +503,67 @@ RadListView {
.text-btn { .text-btn {
font-size: 14; font-size: 14;
horizontal-alignment: left; horizontal-alignment: left;
padding: 16; padding: 12;
margin: 8 0 0 0; margin: 8 0 0 0;
min-width: 0;
} }
.closeBtn { MDButton.closeBtn {
padding: 4; padding: 4;
margin-top: 16; margin-top: 16;
min-width: 0;
vertical-alignment: top; vertical-alignment: top;
} }
// ----------------------------- // -----------------------------
// DIALOGS // DIALOGS
.dialogContainer { .dialogContainer {
width: 100%; width: 100%;
color: $grayD4; color: $gray9;
background: $grayL4; background: $gray1;
font-size: 16; font-size: 16;
&.dark { &.dark {
color: $grayL4; color: $gray1;
background: $grayD4; background: $gray9;
} }
.dialogTitle { .dialogTitle {
padding: 24 24 12; padding: 24 24 16;
font-size: 20; font-size: 20;
} }
.dialogInput { .dialogInput {
padding: 0 24 16; padding: 0 24 16;
} }
.dialogDescription { .dialogDescription {
line-height: 4; line-height: 6;
padding: 0 24 16; padding: 0 24 8;
} }
.actionItem { .actionItem {
padding: 8 24; letter-spacing: 0;
text-transform: none;
padding: 16 24;
margin: 0; margin: 0;
} }
.actionsContainer { .actionsContainer {
padding: 24; padding: 8;
} }
.action { .action {
font-size: 12; font-size: 12;
padding: 12;
min-width: 0;
color: #ff7043; color: #ff7043;
} }
MDButton.actionIcon {
width: auto;
height: auto;
min-width: 0;
padding: 16 24;
border-radius: 4;
letter-spacing: 0;
text-transform: none;
margin: 0 16 16;
}
} }
// ----------------------------- // -----------------------------
ActivityIndicator { MDActivityIndicator {
width: 24; width: 24;
height: 24; height: 24;
margin: 16; margin: 16;

View file

@ -1,8 +1,9 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="onPageLoad">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<GridLayout rows="*" columns="auto, *"> <GridLayout rows="*" columns="auto, *">
<Label <MDButton
variant="text"
class="bx" class="bx"
:text="icon.menu" :text="icon.menu"
automationText="Back" automationText="Back"
@ -21,67 +22,73 @@
> >
<Image src="res://logo_light" class="appIcon" stretch="aspectFit" /> <Image src="res://logo_light" class="appIcon" stretch="aspectFit" />
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" class="option"> <StackLayout class="m-8"></StackLayout>
<Label class="bx" :text="icon.info" /> <GridLayout columns="auto, *" class="option">
<StackLayout> <Label col="0" class="bx" :text="icon.info" />
<StackLayout col="1">
<Label text="Version" /> <Label text="Version" />
<Label :text="getVersion" class="option-info" textWrap="true" /> <Label :text="getVersion" class="info" textWrap="true" />
</StackLayout> </StackLayout>
</StackLayout> </GridLayout>
<StackLayout <GridLayout columns="auto, *" class="option">
orientation="horizontal" <MDRipple
class="option" colSpan="2"
@tap="openURL($event, 'https://github.com/vishnuraghavb/enrecipes')" @tap="openURL('https://github.com/vishnuraghavb/enrecipes')"
> />
<Label class="bx" :text="icon.link" /> <Label col="0" class="bx" :text="icon.link" />
<Label text="View project on GitHub" /> <Label
</StackLayout> verticalAlignment="center"
<StackLayout col="1"
orientation="horizontal" text="View project on GitHub"
class="option" />
@tap="openURL($event, 'https://t.me/enrecipes')" </GridLayout>
> <GridLayout columns="auto, *" class="option">
<Label class="bx" :text="icon.telegram" /> <MDRipple colSpan="2" @tap="openURL('https://t.me/enrecipes')" />
<Label text="Join the Telegram group" /> <Label col="0" class="bx" :text="icon.telegram" />
</StackLayout> <Label
verticalAlignment="center"
col="1"
text="Join the Telegram group"
/>
</GridLayout>
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<Label text="Author" class="group-header" /> <Label text="Author" class="group-header orkm" />
<StackLayout <GridLayout columns="auto, *" class="option">
orientation="horizontal" <MDRipple
class="option" colSpan="2"
@tap="openURL($event, 'https://www.vishnuraghav.com')" @tap="openURL('https://www.vishnuraghav.com')"
> />
<Label class="bx" :text="icon.user" /> <Label col="0" class="bx" :text="icon.user" />
<Label text="Vishnu Raghav" /> <Label verticalAlignment="center" col="1" text="Vishnu Raghav" />
</StackLayout> </GridLayout>
<StackLayout <GridLayout columns="auto, *" class="option">
orientation="horizontal" <MDRipple
class="option" colSpan="2"
@tap="openURL($event, 'https://github.com/vishnuraghavb')" @tap="openURL('https://github.com/vishnuraghavb')"
> />
<Label class="bx" :text="icon.link" /> <Label col="0" class="bx" :text="icon.link" />
<Label text="Follow on GitHub" /> <Label verticalAlignment="center" col="1" text="Follow on GitHub" />
</StackLayout> </GridLayout>
<StackLayout <GridLayout columns="auto, *" class="option">
orientation="horizontal" <MDRipple
class="option" colSpan="2"
@tap="openURL($event, 'https://mastodon.social/@vishnuraghavb')" @tap="openURL('https://mastodon.social/@vishnuraghavb')"
> />
<Label class="bx" :text="icon.link" /> <Label col="0" class="bx" :text="icon.link" />
<Label text="Follow on Mastodon" /> <Label verticalAlignment="center" col="1" text="Follow on Mastodon" />
</StackLayout> </GridLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</Page> </Page>
</template> </template>
<script> <script>
import { Utils, Application } from "@nativescript/core" import { Application, Utils } from "@nativescript/core"
import { mapState, mapActions } from "vuex" import { mapActions, mapState } from "vuex"
export default { export default {
props: ["highlight", "showDrawer", "title"], props: ["showDrawer", "title"],
computed: { computed: {
...mapState(["icon", "currentComponent"]), ...mapState(["icon", "currentComponent"]),
getVersion() { getVersion() {
@ -98,16 +105,16 @@ export default {
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction"]), ...mapActions(["setCurrentComponentAction"]),
initializePage() { onPageLoad() {
this.setCurrentComponentAction("About") this.setCurrentComponentAction("About")
}, },
// HELPERS
onScroll(args) { onScroll(args) {
args.scrollY args.scrollY
? (this.viewIsScrolled = true) ? (this.viewIsScrolled = true)
: (this.viewIsScrolled = false) : (this.viewIsScrolled = false)
}, },
openURL(args, url) { openURL(url) {
this.highlight(args)
Utils.openUrl(url) Utils.openUrl(url)
}, },
}, },

View file

@ -1,8 +1,8 @@
<template> <template>
<Page <Page
@loaded="initialize" @loaded="onPageLoad"
actionBarHidden="true" actionBarHidden="true"
:androidStatusBarBackground="themeName == 'Light' ? '#fafafa' : '#212121'" :androidStatusBarBackground="appTheme == 'Light' ? '#f1f3f5' : '#212529'"
> >
<RadSideDrawer <RadSideDrawer
ref="drawer" ref="drawer"
@ -19,12 +19,16 @@
columns="auto, 24, *" 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)"
class="sd-item orkm" class="sd-item orkm"
:class="{ :class="{
'selected-sd-item': currentComponent === item.component, 'selected-sd-item': currentComponent === item.component,
}" }"
> >
<MDRipple
row="0"
colSpan="3"
@tap="navigateTo(item.component, false, false)"
/>
<Label col="0" row="0" class="bx" :text="icon[item.icon]" /> <Label col="0" row="0" class="bx" :text="icon[item.icon]" />
<Label col="2" row="0" :text="item.title" /> <Label col="2" row="0" :text="item.title" />
</GridLayout> </GridLayout>
@ -35,17 +39,17 @@
columns="*, auto" columns="*, auto"
v-if="categoriesWithRecipes.length" v-if="categoriesWithRecipes.length"
> >
<Label col="0" text="Categories" /> <Label verticalAlignment="center" col="0" text="Categories" />
<Label <MDButton
variant="text"
@tap="toggleCatEdit" @tap="toggleCatEdit"
col="2" col="2"
:text="catEditMode ? 'DONE' : 'RENAME'" :text="editCategory ? 'DONE' : 'RENAME'"
/> />
</GridLayout> </GridLayout>
<ScrollView height="100%"> <ScrollView height="100%">
<StackLayout> <StackLayout>
<GridLayout <GridLayout
@tap="navigateTo(item, false, true)"
v-for="(item, index) in categoriesWithRecipes" v-for="(item, index) in categoriesWithRecipes"
:key="index" :key="index"
class="sd-item orkm" class="sd-item orkm"
@ -54,6 +58,11 @@
}" }"
columns="auto, *, auto" columns="auto, *, auto"
> >
<MDRipple
row="0"
colSpan="3"
@tap="navigateTo(item, false, true)"
/>
<Label <Label
col="0" col="0"
class="bx" class="bx"
@ -61,8 +70,9 @@
margin="0 24 0 0" margin="0 24 0 0"
/> />
<Label col="1" :text="item" /> <Label col="1" :text="item" />
<Label <MDButton
v-if="catEditMode" variant="text"
v-if="editCategory"
@tap="renameCategory(item)" @tap="renameCategory(item)"
col="2" col="2"
class="bx" class="bx"
@ -74,30 +84,25 @@
</StackLayout> </StackLayout>
<StackLayout row="1"> <StackLayout row="1">
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<!-- <StackLayout <GridLayout
orientation="horizontal"
class="sd-item orkm"
@tap="donate"
>
<Label class="bx" :text="icon.donate" margin="0 24 0 0" />
<Label text="Donate" />
</StackLayout> -->
<StackLayout
@tap="navigateTo(item.component, true, false)"
v-for="(item, index) in bottommenu"
:key="index"
orientation="horizontal"
class="sd-item orkm" class="sd-item orkm"
:class="{ :class="{
'selected-sd-item': currentComponent == item.title, 'selected-sd-item': currentComponent == item.title,
}" }"
v-for="(item, index) in bottommenu"
:key="index"
rows="48"
columns="auto, 24, *"
> >
<Label class="bx" :text="icon[item.icon]" margin="0 24 0 0" /> <MDRipple
<Label :text="item.title" /> colSpan="3"
</StackLayout> @tap="navigateTo(item.component, true, false)"
/>
<Label class="bx" col="0" :text="icon[item.icon]" />
<Label col="2" :text="item.title" />
</GridLayout>
</StackLayout> </StackLayout>
</GridLayout> </GridLayout>
<GridLayout ~mainContent rows="*" columns="*"> <GridLayout ~mainContent rows="*" columns="*">
<Frame row="0" col="0" ref="mainFrame" id="main-frame"> <Frame row="0" col="0" ref="mainFrame" id="main-frame">
<!-- Home --> <!-- Home -->
@ -119,29 +124,28 @@
<script> <script>
import { import {
Utils,
ApplicationSettings, ApplicationSettings,
AndroidApplication, AndroidApplication,
ApplicationEventData,
Application, Application,
Color,
Utils,
} from "@nativescript/core" } from "@nativescript/core"
import Theme from "@nativescript/theme" import Theme from "@nativescript/theme"
import * as Toast from "nativescript-toast" import * as Toast from "nativescript-toast"
import * as application from "tns-core-modules/application" import * as application from "tns-core-modules/application"
import { mapActions, mapState } from "vuex"
import EnRecipes from "./EnRecipes.vue" import EnRecipes from "./EnRecipes.vue"
import Settings from "./Settings.vue" import Settings from "./Settings.vue"
import About from "./About.vue" import About from "./About.vue"
import PromptDialog from "./modal/PromptDialog.vue"
import { mapState, mapActions } from "vuex"
let page import PromptDialog from "./modal/PromptDialog.vue"
export default { export default {
components: { components: {
EnRecipes, EnRecipes,
Settings,
About,
}, },
data() { data() {
return { return {
selectedCategory: null, selectedCategory: null,
@ -176,8 +180,8 @@ export default {
icon: "info", icon: "info",
}, },
], ],
catEditMode: false, editCategory: false,
themeName: "Light", appTheme: "Light",
} }
}, },
computed: { computed: {
@ -203,16 +207,19 @@ export default {
"initializeYieldUnits", "initializeYieldUnits",
"renameCategoryAction", "renameCategoryAction",
]), ]),
initialize() { onPageLoad() {
if (this.themeName === "Light") { if (this.appTheme === "Light") {
const View = android.view.View const View = android.view.View
const window = Application.android.startActivity.getWindow() const window = Application.android.startActivity.getWindow()
const decorView = window.getDecorView() const decorView = window.getDecorView()
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
// window.setNavigationBarColor(new Color("#e0e0e0").android)
} }
}, },
// HELPERS
toggleCatEdit() { toggleCatEdit() {
this.catEditMode = !this.catEditMode this.editCategory = !this.editCategory
if (this.selectedCategory) 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
@ -226,99 +233,22 @@ export default {
this.$showModal(PromptDialog, { this.$showModal(PromptDialog, {
props: { props: {
title: `Rename category`, title: `Rename category`,
hint: category, text: category,
action: "RENAME", action: "RENAME",
}, },
}).then((newCategory) => { }).then((newCategory) => {
this.hijackGlobalBackEvent() this.hijackGlobalBackEvent()
if (newCategory.length) { if (newCategory.length) {
this.renameCategoryAction({ current: category, updated: newCategory }) this.renameCategoryAction({ current: category, updated: newCategory })
this.catEditMode = false this.editCategory = false
this.navigateTo(newCategory, false, true) this.navigateTo(newCategory, false, true)
} }
}) })
}, },
highlight(args) {
let temp = args.object.className
args.object.className = `${temp} option-highlight`
setTimeout(() => (args.object.className = temp), 100)
},
// Navigation
setSelectedCategory(e) { setSelectedCategory(e) {
this.selectedCategory = e.item this.selectedCategory = e.item
this.closeDrawer() this.closeDrawer()
}, },
hijackGlobalBackEvent() {
AndroidApplication.on(
AndroidApplication.activityBackPressedEvent,
this.globalBackEvent
)
},
releaseGlobalBackEvent() {
AndroidApplication.off(
AndroidApplication.activityBackPressedEvent,
this.globalBackEvent
)
},
globalBackEvent(args) {
function preventDefault() {
args.cancel = true
}
let vm = this
function isFiltered() {
vm.filterFavorites
? vm.setComponent("Favorites")
: vm.filterTrylater
? vm.setComponent("Try later")
: vm.selectedCategory
? vm.setComponent(vm.selectedCategory)
: vm.setComponent("EnRecipes")
}
if (this.$refs.drawer && this.$refs.drawer.nativeView.getIsOpen()) {
preventDefault()
this.closeDrawer()
this.catEditMode = false
} else if (
["Favorites", "Try later", this.selectedCategory].includes(
this.currentComponent
)
) {
preventDefault()
this.setComponent("EnRecipes")
this.filterFavorites = this.filterTrylater = false
this.selectedCategory = null
this.$refs.enrecipes.updateFilter()
this.releaseGlobalBackEvent()
}
},
navigateTo(to, isTrueComponent, isCategory) {
if (isTrueComponent) {
this.$navigateTo(to, {
frame: "main-frame",
props: {
highlight: this.highlight,
showDrawer: this.showDrawer,
restartApp: this.restartApp,
hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent,
openAppSettingsPage: this.openAppSettingsPage,
},
backstackVisible: false,
})
this.closeDrawer()
} else if (!this.catEditMode || !isCategory) {
this.releaseGlobalBackEvent()
this.hijackGlobalBackEvent()
this.setComponent(to)
this.$navigateBack({ frame: "main-frame", backstackVisible: false })
this.filterFavorites = to === "Favorites" ? true : false
this.filterTrylater = to === "Try later" ? true : false
this.selectedCategory = isCategory ? to : null
this.$refs.enrecipes.updateFilter()
this.closeDrawer()
}
this.catEditMode = false
},
restartApp() { restartApp() {
// Code from nativescript-master-technology // Code from nativescript-master-technology
const mStartActivity = new android.content.Intent( const mStartActivity = new android.content.Intent(
@ -360,19 +290,91 @@ export default {
closeDrawer() { closeDrawer() {
this.$refs.drawer.nativeView.closeDrawer() this.$refs.drawer.nativeView.closeDrawer()
}, },
donate(args) {
this.highlight(args) // NAVIGATION HANDLERS
hijackGlobalBackEvent() {
AndroidApplication.on(
AndroidApplication.activityBackPressedEvent,
this.globalBackEvent
)
},
releaseGlobalBackEvent() {
AndroidApplication.off(
AndroidApplication.activityBackPressedEvent,
this.globalBackEvent
)
},
globalBackEvent(args) {
function preventDefault() {
args.cancel = true
}
let vm = this
function isFiltered() {
vm.filterFavorites
? vm.setComponent("Favorites")
: vm.filterTrylater
? vm.setComponent("Try later")
: vm.selectedCategory
? vm.setComponent(vm.selectedCategory)
: vm.setComponent("EnRecipes")
}
if (this.$refs.drawer && this.$refs.drawer.nativeView.getIsOpen()) {
preventDefault()
this.closeDrawer()
this.editCategory = false
} else if (
["Favorites", "Try later", this.selectedCategory].includes(
this.currentComponent
)
) {
preventDefault()
this.setComponent("EnRecipes")
this.filterFavorites = this.filterTrylater = false
this.selectedCategory = null
this.$refs.enrecipes.updateFilter()
this.releaseGlobalBackEvent()
}
},
navigateTo(to, isTrueComponent, isCategory) {
if (isTrueComponent) {
this.$navigateTo(to, {
frame: "main-frame",
props: {
showDrawer: this.showDrawer,
restartApp: this.restartApp,
hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent,
openAppSettingsPage: this.openAppSettingsPage,
},
backstackVisible: false,
})
this.closeDrawer()
} else if (!this.editCategory || !isCategory) {
this.releaseGlobalBackEvent()
this.hijackGlobalBackEvent()
this.setComponent(to)
this.$navigateBack({ frame: "main-frame", backstackVisible: false })
this.filterFavorites = to === "Favorites" ? true : false
this.filterTrylater = to === "Try later" ? true : false
this.selectedCategory = isCategory ? to : null
this.$refs.enrecipes.updateFilter()
this.closeDrawer()
}
this.editCategory = false
},
donate() {
Utils.openUrl("https://www.vishnuraghav.com/donate/") Utils.openUrl("https://www.vishnuraghav.com/donate/")
}, },
}, },
created() { created() {
this.themeName = ApplicationSettings.getString("appTheme", "Light") this.appTheme = ApplicationSettings.getString("appTheme", "Light")
setTimeout((e) => { setTimeout((e) => {
Theme.setMode(Theme[this.themeName]) Theme.setMode(Theme[this.appTheme])
}, 50) }, 50)
if (!this.recipes.length) this.initializeRecipes() this.initializeRecipes()
if (!this.categories.length) this.initializeCategories() this.initializeCategories()
if (!this.yieldUnits.length) this.initializeYieldUnits() this.initializeYieldUnits()
}, },
} }
</script> </script>

View file

@ -1,8 +1,9 @@
<template> <template>
<Page @loaded="initialize" @unloaded="releaseBackEvent"> <Page @loaded="onPageLoad" @unloaded="releaseBackEvent">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<GridLayout rows="*" columns="auto, *, auto"> <GridLayout rows="*" columns="auto, *, auto">
<Label <MDButton
variant="text"
class="bx" class="bx"
:text="icon.back" :text="icon.back"
automationText="Back" automationText="Back"
@ -10,14 +11,15 @@
@tap="navigateBack" @tap="navigateBack"
/> />
<Label class="title orkm" :text="title" col="1" /> <Label class="title orkm" :text="title" col="1" />
<Label <MDButton
v-if="hasEnoughDetails && !imageLoading" variant="text"
v-if="hasChanges && !imageLoading"
class="bx" class="bx"
:text="icon.save" :text="icon.save"
col="2" col="2"
@tap="saveOperation" @tap="saveOperation"
/> />
<ActivityIndicator col="2" v-if="imageLoading" :busy="imageLoading" /> <MDActivityIndicator col="2" v-if="imageLoading" :busy="imageLoading" />
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<GridLayout rows="*" columns="*"> <GridLayout rows="*" columns="*">
@ -51,18 +53,16 @@
:text="icon.image" :text="icon.image"
/> />
</StackLayout> </StackLayout>
<StackLayout width="100%" :top="screenWidth - 42"> <transition :name="recipeContent.imageSrc ? 'null' : 'bounce'">
<transition :name="recipeContent.imageSrc ? 'null' : 'bounce'"> <MDFloatingActionButton
<Label v-if="showFab"
v-if="showFab" :top="screenWidth - 44"
horizontalAlignment="right" :left="screenWidth - 88"
@tap="imageHandler" class="bx"
class="bx fab-button" src="res://camera"
:text="icon.camera" @tap="imageHandler"
androidElevation="6" />
/> </transition>
</transition>
</StackLayout>
</AbsoluteLayout> </AbsoluteLayout>
<StackLayout margin="0 16"> <StackLayout margin="0 16">
@ -71,6 +71,7 @@
hint="My Healthy Recipe" hint="My Healthy Recipe"
v-model="recipeContent.title" v-model="recipeContent.title"
autocapitalizationType="words" autocapitalizationType="words"
autocorrect="true"
/> />
<Label top="0" class="fieldLabel" text="Title" /> <Label top="0" class="fieldLabel" text="Title" />
</AbsoluteLayout> </AbsoluteLayout>
@ -103,7 +104,7 @@
<GridLayout columns="*, 8, *"> <GridLayout columns="*, 8, *">
<AbsoluteLayout class="inputField" col="0"> <AbsoluteLayout class="inputField" col="0">
<TextField <TextField
:text="formattedTimeRequired" :text="timeRequired"
editable="false" editable="false"
@tap="setTimeRequired" @tap="setTimeRequired"
/> />
@ -127,30 +128,33 @@
v-model="recipeContent.ingredients[index].item" v-model="recipeContent.ingredients[index].item"
:hint="`Item ${index + 1}`" :hint="`Item ${index + 1}`"
autocapitalizationType="words" autocapitalizationType="words"
autocorrect="true"
/> />
<TextField <TextField
width="72" width="68"
col="2" col="2"
v-model="recipeContent.ingredients[index].quantity" v-model="recipeContent.ingredients[index].quantity"
hint="1.000" hint="1.00"
keyboardType="number" keyboardType="number"
/> />
<TextField <TextField
width="64" width="68"
col="4" col="4"
v-model="recipeContent.ingredients[index].unit" v-model="recipeContent.ingredients[index].unit"
hint="Unit" hint="Unit"
editable="false" editable="false"
@tap="showUnits($event)" @tap="showUnits($event)"
/> />
<Label <MDButton
variant="text"
col="6" col="6"
class="bx closeBtn" class="bx closeBtn"
:text="icon.close" :text="icon.close"
@tap="removeIngredient(index)" @tap="removeIngredient(index)"
/> />
</GridLayout> </GridLayout>
<Label <MDButton
variant="text"
class="text-btn orkm" class="text-btn orkm"
text="+ ADD INGREDIENT" text="+ ADD INGREDIENT"
@tap="addIngredient()" @tap="addIngredient()"
@ -172,15 +176,18 @@
:hint="`Step ${index + 1}`" :hint="`Step ${index + 1}`"
v-model="recipeContent.instructions[index]" v-model="recipeContent.instructions[index]"
editable="true" editable="true"
autocorrect="true"
/> />
<Label <MDButton
variant="text"
col="2" col="2"
class="bx closeBtn" class="bx closeBtn"
:text="icon.close" :text="icon.close"
@tap="removeInstruction(index)" @tap="removeInstruction(index)"
/> />
</GridLayout> </GridLayout>
<Label <MDButton
variant="text"
class="text-btn orkm" class="text-btn orkm"
text="+ ADD STEP" text="+ ADD STEP"
@tap="addInstruction()" @tap="addInstruction()"
@ -201,15 +208,22 @@
v-model="recipeContent.notes[index]" v-model="recipeContent.notes[index]"
:hint="`Note ${index + 1}`" :hint="`Note ${index + 1}`"
editable="true" editable="true"
autocorrect="true"
/> />
<Label <MDButton
variant="text"
col="2" col="2"
class="bx closeBtn" class="bx closeBtn"
:text="icon.close" :text="icon.close"
@tap="removeNote(index)" @tap="removeNote(index)"
/> />
</GridLayout> </GridLayout>
<Label class="text-btn orkm" text="+ ADD NOTE" @tap="addNote()" /> <MDButton
variant="text"
class="text-btn orkm"
text="+ ADD NOTE"
@tap="addNote()"
/>
<StackLayout class="hr" margin="24 16"></StackLayout> <StackLayout class="hr" margin="24 16"></StackLayout>
</StackLayout> </StackLayout>
@ -226,15 +240,18 @@
v-model="recipeContent.references[index]" v-model="recipeContent.references[index]"
hint="Text or Website/Video URL" hint="Text or Website/Video URL"
editable="true" editable="true"
autocorrect="true"
/> />
<Label <MDButton
variant="text"
col="2" col="2"
class="bx closeBtn" class="bx closeBtn"
:text="icon.close" :text="icon.close"
@tap="removeReference(index)" @tap="removeReference(index)"
/> />
</GridLayout> </GridLayout>
<Label <MDButton
variant="text"
class="text-btn orkm" class="text-btn orkm"
text="+ ADD REFERENCE" text="+ ADD REFERENCE"
@tap="addReference()" @tap="addReference()"
@ -248,31 +265,30 @@
</template> </template>
<script> <script>
import { WorkerService } from "../worker.service" // import { WorkerService } from "../worker.service"
const workerService = new WorkerService() // const workerService = new WorkerService()
import { import {
Screen,
AndroidApplication, AndroidApplication,
ImageSource, Application,
path,
getFileAccess,
knownFolders,
Utils,
File,
ApplicationSettings, ApplicationSettings,
File,
getFileAccess,
ImageSource,
knownFolders,
path,
Screen,
Utils,
} from "@nativescript/core" } from "@nativescript/core"
import * as Permissions from "@nativescript-community/perms"
import { Mediafilepicker } from "nativescript-mediafilepicker" import * as Toast from "nativescript-toast"
import * as ImagePicker from "@nativescript/imagepicker"
import { ImageCropper } from "nativescript-imagecropper"
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 ListPicker from "./modal/ListPicker.vue"
import * as Permissions from "@nativescript-community/perms"
import * as Toast from "nativescript-toast"
export default { export default {
props: [ props: [
@ -310,6 +326,7 @@ export default {
newRecipeID: null, newRecipeID: null,
showFab: false, showFab: false,
imageLoading: false, imageLoading: false,
cacheImagePath: null,
} }
}, },
computed: { computed: {
@ -324,13 +341,16 @@ export default {
screenWidth() { screenWidth() {
return Screen.mainScreen.widthDIPs return Screen.mainScreen.widthDIPs
}, },
hasEnoughDetails() { appTheme() {
return Application.systemAppearance()
},
hasChanges() {
return ( return (
JSON.stringify(this.recipeContent) !== JSON.stringify(this.recipeContent) !==
JSON.stringify(this.tempRecipeContent) JSON.stringify(this.tempRecipeContent)
) )
}, },
formattedTimeRequired() { timeRequired() {
let t = this.recipeContent.timeRequired.split(":") let t = this.recipeContent.timeRequired.split(":")
let h = parseInt(t[0]) let h = parseInt(t[0])
let m = parseInt(t[1]) let m = parseInt(t[1])
@ -345,7 +365,7 @@ export default {
"addCategoryAction", "addCategoryAction",
"addYieldUnitAction", "addYieldUnitAction",
]), ]),
initialize() { onPageLoad() {
this.showFab = true this.showFab = true
}, },
@ -394,11 +414,11 @@ export default {
props: { props: {
title: "Category", title: "Category",
list: [...this.categories], list: [...this.categories],
height: "408", // height: "420",
action: "CREATE NEW", action: "ADD NEW",
}, },
}).then((action) => { }).then((action) => {
if (action == "CREATE NEW") { if (action == "ADD NEW") {
this.$showModal(PromptDialog, { this.$showModal(PromptDialog, {
props: { props: {
title: "New category", title: "New category",
@ -425,11 +445,11 @@ export default {
props: { props: {
title: "Yield measured in", title: "Yield measured in",
list: [...this.yieldUnits], list: [...this.yieldUnits],
height: "408", // height: "420",
action: "CREATE NEW", action: "ADD NEW",
}, },
}).then((action) => { }).then((action) => {
if (action == "CREATE NEW") { if (action == "ADD NEW") {
this.$showModal(PromptDialog, { this.$showModal(PromptDialog, {
props: { props: {
title: "New yield unit", title: "New yield unit",
@ -456,7 +476,7 @@ export default {
props: { props: {
title: "Unit", title: "Unit",
list: [...this.units], list: [...this.units],
height: "408", // height: "420",
}, },
}).then((action) => { }).then((action) => {
this.hijackBackEvent() this.hijackBackEvent()
@ -466,21 +486,19 @@ export default {
// NAVIGATION HANDLERS // NAVIGATION HANDLERS
navigateBack() { navigateBack() {
if (this.hasEnoughDetails) { if (this.hasChanges) {
this.blockModal = true this.blockModal = true
this.$showModal(ConfirmDialog, { this.$showModal(ConfirmDialog, {
props: { props: {
title: "Unsaved changes", title: "Unsaved changes",
description: description:
"Do you want to save the changes you made in this recipe?", "Are you sure you want to discard unsaved changes to this recipe?",
cancelButtonText: "DISCARD", cancelButtonText: "DISCARD",
okButtonText: "SAVE", okButtonText: "KEEP EDITING",
}, },
}).then((action) => { }).then((action) => {
this.blockModal = false this.blockModal = false
if (action) { if (action != null && !action) {
this.saveOperation()
} else if (action != null) {
this.$navigateBack() this.$navigateBack()
this.releaseBackEvent() this.releaseBackEvent()
} }
@ -503,7 +521,7 @@ export default {
) )
}, },
backEvent(args) { backEvent(args) {
if (this.hasEnoughDetails && !this.blockModal) { if (this.hasChanges && !this.blockModal) {
args.cancel = true args.cancel = true
this.navigateBack() this.navigateBack()
} }
@ -522,28 +540,22 @@ export default {
}).then((action) => { }).then((action) => {
this.blockModal = false this.blockModal = false
if (action) { if (action) {
this.permissionCheck( this.permissionCheck(this.permissionConfirmation, this.imagePicker)
this.imagePickerPermissionConfirmation,
this.imagePicker
)
} else if (action != null) { } else if (action != null) {
this.recipeContent.imageSrc = null this.recipeContent.imageSrc = null
this.releaseBackEvent() this.releaseBackEvent()
} }
}) })
} else { } else {
this.permissionCheck( this.permissionCheck(this.permissionConfirmation, this.imagePicker)
this.imagePickerPermissionConfirmation,
this.imagePicker
)
} }
}, },
imagePickerPermissionConfirmation() { permissionConfirmation() {
return this.$showModal(ConfirmDialog, { return this.$showModal(ConfirmDialog, {
props: { props: {
title: "Grant permission", title: "Grant permission",
description: description:
"EnRecipes requires storage and camera permission in order to set recipe photo.", "EnRecipes requires storage permission in order to set recipe photo.",
cancelButtonText: "NOT NOW", cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE", okButtonText: "CONTINUE",
}, },
@ -553,52 +565,82 @@ export default {
if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) { if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) {
confirmation().then((e) => { confirmation().then((e) => {
if (e) { if (e) {
Permissions.request("camera").then((res) => { Permissions.request("photo").then((status) => {
let status = res[Object.keys(res)[0]] switch (status[0]) {
if (status === "authorized") action() case "authorized":
if (status === "never_ask_again") action()
ApplicationSettings.setBoolean("storagePermissionAsked", true) break
if (status === "denied") case "never_ask_again":
Toast.makeText("Permission denied").show() ApplicationSettings.setBoolean("storagePermissionAsked", true)
break
case "denied":
Toast.makeText("Permission denied").show()
break
default:
break
}
}) })
} }
}) })
} else { } else {
Permissions.check("camera").then((res) => { Permissions.check("photo").then((res) => {
if (res[0] !== "authorized") { res[0] !== "authorized"
confirmation().then((e) => { ? confirmation().then((e) => e && this.openAppSettingsPage())
e && this.openAppSettingsPage() : action()
})
} else {
Permissions.request("storage").then((res) => {
let status = res[Object.keys(res)[0]]
if (status !== "authorized") {
confirmation().then((e) => {
e && this.openAppSettingsPage()
})
} else action()
})
}
}) })
} }
}, },
imagePicker() { imagePicker() {
const vm = this ApplicationSettings.setBoolean("storagePermissionAsked", true)
const mediafilepicker = new Mediafilepicker() this.cacheImagePath = path.join(
mediafilepicker.openImagePicker({ knownFolders.temp().path,
android: { `${this.getRandomID()}.jpg`
isCaptureMood: false, // if true then camera will open directly. )
isNeedCamera: true, let screenWidth = Math.round(this.screenWidth * 2)
maxNumberFiles: 1, ImagePicker.create({
isNeedFolderList: true, mode: "single",
}, mediaType: ImagePicker.ImagePickerMediaType.Image,
})
mediafilepicker.on("getFiles", (image) => {
ApplicationSettings.setBoolean("storagePermissionAsked", true)
vm.recipeContent.imageSrc = image.object.get("results")[0].file
}) })
.present()
.then((selection) => {
let imgPath = selection[0]._android
ImageSource.fromFile(imgPath).then((image) => {
ImageCropper.prototype
.show(
image,
{
width: screenWidth,
height: screenWidth,
},
{
hideBottomControls: true,
toolbarTitle: "Crop photo",
statusBarColor: "#ff5200",
toolbarTextColor:
this.appTheme == "light" ? "#212529" : "#f1f3f5",
toolbarColor:
this.appTheme == "light" ? "#f1f3f5" : "#212529",
cropFrameColor: "#ff5200",
}
)
.then((cropped) => {
cropped.image.saveToFile(this.cacheImagePath, "jpg", 75)
this.recipeContent.imageSrc = this.cacheImagePath
})
})
})
}, },
// INPUT FIELD HANDLERS
fieldDeletionConfirm(item) {
return this.$showModal(ConfirmDialog, {
props: {
title: `Delete ${item}?`,
cancelButtonText: "CANCEL",
okButtonText: "DELETE",
},
})
},
addIngredient() { addIngredient() {
this.recipeContent.ingredients.push({ this.recipeContent.ingredients.push({
item: "", item: "",
@ -607,30 +649,55 @@ export default {
}) })
}, },
removeIngredient(index) { removeIngredient(index) {
this.recipeContent.ingredients.splice(index, 1) if (this.recipeContent.ingredients[index].item.length) {
this.fieldDeletionConfirm("ingredient").then((res) => {
if (res) {
this.recipeContent.ingredients.splice(index, 1)
}
})
} else this.recipeContent.ingredients.splice(index, 1)
}, },
addInstruction() { addInstruction() {
this.recipeContent.instructions.push("") this.recipeContent.instructions.push("")
}, },
removeInstruction(index) { removeInstruction(index) {
this.recipeContent.instructions.splice(index, 1) if (this.recipeContent.instructions[index].length) {
this.fieldDeletionConfirm("instruction").then((res) => {
if (res) {
this.recipeContent.instructions.splice(index, 1)
}
})
} else this.recipeContent.instructions.splice(index, 1)
}, },
addNote() { addNote() {
this.recipeContent.notes.push("") this.recipeContent.notes.push("")
}, },
removeNote(index) { removeNote(index) {
this.recipeContent.notes.splice(index, 1) if (this.recipeContent.notes[index].length) {
this.fieldDeletionConfirm("note").then((res) => {
if (res) {
this.recipeContent.notes.splice(index, 1)
}
})
} else this.recipeContent.notes.splice(index, 1)
}, },
addReference() { addReference() {
this.recipeContent.references.push("") this.recipeContent.references.push("")
}, },
removeReference(index) { removeReference(index) {
this.recipeContent.references.splice(index, 1) if (this.recipeContent.references[index].length) {
this.fieldDeletionConfirm("reference").then((res) => {
if (res) {
this.recipeContent.references.splice(index, 1)
}
})
} else this.recipeContent.references.splice(index, 1)
}, },
// SAVE OPERATION
clearEmptyFields() { clearEmptyFields() {
if (!this.recipeContent.title) if (!this.recipeContent.title)
this.recipeContent.title = "Untitled Recipe" this.recipeContent.title = "Untitled Recipe"
@ -651,37 +718,50 @@ export default {
this.imageLoading = true this.imageLoading = true
this.clearEmptyFields() this.clearEmptyFields()
this.recipeContent.lastModified = new Date() this.recipeContent.lastModified = new Date()
if (this.cacheImagePath) {
let recipeImage = path.join(
knownFolders
.documents()
.getFolder("EnRecipes")
.getFolder("Images").path,
`${this.getRandomID()}.jpg`
)
let binarySource = File.fromPath(this.cacheImagePath).readSync()
File.fromPath(recipeImage).writeSync(binarySource)
this.recipeContent.imageSrc = recipeImage
knownFolders.temp().clear()
}
if (this.recipeContent.imageSrc) { if (this.recipeContent.imageSrc) {
if (this.tempRecipeContent.imageSrc) { if (
if (this.tempRecipeContent.imageSrc !== this.recipeContent.imageSrc) { this.tempRecipeContent.imageSrc &&
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc) this.tempRecipeContent.imageSrc !== this.recipeContent.imageSrc
this.imageSaveOperation() ) {
} else this.saveRecipe() getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
} else this.imageSaveOperation() }
} else if (this.tempRecipeContent.imageSrc) { } else if (this.tempRecipeContent.imageSrc) {
getFileAccess().deleteFile(this.tempRecipeContent.imageSrc) getFileAccess().deleteFile(this.tempRecipeContent.imageSrc)
this.saveRecipe()
} else this.saveRecipe()
},
imageSaveOperation() {
let imgSavedToPath = path.join(
knownFolders
.documents()
.getFolder("EnRecipes")
.getFolder("Images").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()
} }
this.saveRecipe()
}, },
// imageSaveOperation() {
// let imgSavedToPath = path.join(
// knownFolders
// .documents()
// .getFolder("EnRecipes")
// .getFolder("Images").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() { saveRecipe() {
if (this.recipeID) { if (this.recipeID) {
this.overwriteRecipeAction({ this.overwriteRecipeAction({
@ -705,6 +785,7 @@ export default {
this.setCurrentComponentAction("EditRecipe") this.setCurrentComponentAction("EditRecipe")
}, 500) }, 500)
this.title = this.recipeID ? "Edit recipe" : "New recipe" this.title = this.recipeID ? "Edit recipe" : "New recipe"
if (this.recipeID) { if (this.recipeID) {
let recipe = this.recipes.filter((e) => e.id === this.recipeID)[0] let recipe = this.recipes.filter((e) => e.id === this.recipeID)[0]
Object.assign(this.recipeContent, JSON.parse(JSON.stringify(recipe))) Object.assign(this.recipeContent, JSON.parse(JSON.stringify(recipe)))
@ -713,13 +794,14 @@ export default {
JSON.parse(JSON.stringify(this.recipeContent)) JSON.parse(JSON.stringify(this.recipeContent))
) )
} else { } else {
if (this.selectedCategory)
this.recipeContent.category = this.selectedCategory
if (this.filterFavorites) this.recipeContent.isFavorite = true
Object.assign( Object.assign(
this.tempRecipeContent, this.tempRecipeContent,
JSON.parse(JSON.stringify(this.recipeContent)) JSON.parse(JSON.stringify(this.recipeContent))
) )
if (this.selectedCategory)
this.recipeContent.category = this.selectedCategory
if (this.filterFavorites) this.recipeContent.isFavorite = true
this.newRecipeID = this.getRandomID() this.newRecipeID = this.getRandomID()
} }
this.hijackBackEvent() this.hijackBackEvent()

View file

@ -1,5 +1,5 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="onPageLoad">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<!-- Search Actionbar --> <!-- Search Actionbar -->
<GridLayout <GridLayout
@ -7,9 +7,10 @@
columns="auto, *" columns="auto, *"
verticalAlignment="center" verticalAlignment="center"
> >
<Label <MDButton
class="bx" class="bx"
:text="icon.back" :text="icon.back"
variant="text"
automationText="Back" automationText="Back"
col="0" col="0"
@tap="closeSearch" @tap="closeSearch"
@ -17,33 +18,35 @@
<SearchBar <SearchBar
col="1" col="1"
hint="Search" hint="Search"
textFieldHintColor="#bdbdbd"
v-model="searchQuery" v-model="searchQuery"
@textChange="updateFilter" @textChange="updateFilter"
@clear="updateFilter" @clear="clearSearch"
/> />
</GridLayout> </GridLayout>
<!-- Home Actionbar --> <!-- Home Actionbar -->
<GridLayout v-else columns="auto, *, auto, auto"> <GridLayout v-else columns="auto, *, auto, auto">
<Label <MDButton
class="bx" class="bx"
col="0"
variant="text"
@tap="showDrawer"
:text="icon.menu" :text="icon.menu"
automationText="Back" automationText="Back"
@tap="showDrawer"
col="0"
/> />
<Label class="title orkm" :text="currentComponent" col="1" /> <Label class="title orkm" :text="currentComponent" col="1" />
<Label <MDButton
v-if="recipes.length" v-if="recipes.length"
class="bx" class="bx"
:text="icon.search" :text="icon.search"
variant="text"
col="2" col="2"
@tap="openSearch" @tap="openSearch"
/> />
<Label <MDButton
v-if="recipes.length" v-if="recipes.length"
class="bx" class="bx"
:text="icon.sort" :text="icon.sort"
variant="text"
col="3" col="3"
@tap="sortDialog" @tap="sortDialog"
/> />
@ -58,10 +61,10 @@
@itemSwipeProgressChanged="onSwiping" @itemSwipeProgressChanged="onSwiping"
@itemSwipeProgressEnded="onSwipeEnded" @itemSwipeProgressEnded="onSwipeEnded"
@scrolled="onScroll" @scrolled="onScroll"
@itemTap="viewRecipe"
:filteringFunction="filterFunction" :filteringFunction="filterFunction"
:sortingFunction="sortFunction" :sortingFunction="sortFunction"
> >
<!-- @itemTap="viewRecipe" -->
<v-template> <v-template>
<GridLayout <GridLayout
class="recipeItem" class="recipeItem"
@ -69,6 +72,7 @@
columns="112, *" columns="112, *"
androidElevation="1" androidElevation="1"
> >
<MDRipple colSpan="2" @tap="viewRecipe(recipe)" />
<GridLayout class="imageHolder card" rows="112" columns="112"> <GridLayout class="imageHolder card" rows="112" columns="112">
<Image <Image
row="0" row="0"
@ -114,31 +118,85 @@
<StackLayout height="128"></StackLayout> <StackLayout height="128"></StackLayout>
</v-template> </v-template>
</RadListView> </RadListView>
<GridLayout rows="96, auto, *" columns="*" class="emptyState"> <GridLayout rows="*, auto, *, 88" columns="*" class="emptyStateContainer">
<StackLayout <StackLayout
col="0" col="0"
row="1" row="1"
class="noResult" class="emptyState"
v-if="!recipes.length && !filterFavorites && !filterTrylater" v-if="
!recipes.length &&
!filterFavorites &&
!filterTrylater &&
!selectedCategory
"
@tap="addRecipe"
> >
<Image class="logo" src="res://icon_gray" stretch="aspectFit" /> <Label class="bx icon" :text="icon.plusCircle" />
<Label <Label
class="title orkm" class="title orkm"
text="Start adding your recipes!" text="Start adding your recipes!"
textWrap="true" textWrap="true"
/> />
<StackLayout orientation="horizontal" horizontalAlignment="center"> <StackLayout orientation="horizontal" horizontalAlignment="center">
<Label text="Use the " textWrap="true" /> <Label text="Use the " />
<Label class="bx" :text="icon.plus" /> <Label class="bx" :text="icon.plus" />
<Label text=" button to add a new recipe" textWrap="true" /> <Label text=" button to add one" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
<StackLayout <StackLayout
col="0" col="0"
row="1" row="1"
class="noResult" class="emptyState"
v-if="!filteredRecipes.length && filterFavorites && !searchQuery"
>
<Label class="bx icon" :text="icon.heartOutline" textWrap="true" />
<Label class="title orkm" text="No favorites yet" textWrap="true" />
<Label
text="Recipes you mark as favorite will be listed here"
textWrap="true"
/>
</StackLayout>
<StackLayout
col="0"
row="1"
class="emptyState"
v-if="!filteredRecipes.length && filterTrylater && !searchQuery"
>
<Label class="bx icon" :text="icon.trylaterOutline" textWrap="true" />
<Label class="title orkm" text="All done!" textWrap="true" />
<Label
text="Recipes you mark as try later will be listed here"
textWrap="true"
/>
</StackLayout>
<StackLayout
col="0"
row="1"
class="emptyState"
v-if="
!filteredRecipes.length &&
!filterFavorites &&
!filterTrylater &&
selectedCategory
"
>
<Label class="bx icon" :text="icon.labelOutline" textWrap="true" />
<Label
class="title orkm"
text="Category looks empty"
textWrap="true"
/>
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Label text="Use the " textWrap="true" />
<Label class="bx" :text="icon.plus" />
<Label text=" button to add a recipe" textWrap="true" />
</StackLayout>
</StackLayout>
<StackLayout
col="0"
row="0"
class="emptyState noResult"
v-if="!filteredRecipes.length && searchQuery" v-if="!filteredRecipes.length && searchQuery"
verticalAlignment="top"
> >
<Label class="bx icon" :text="icon.search" textWrap="true" /> <Label class="bx icon" :text="icon.search" textWrap="true" />
<Label class="title orkm" text="No recipes found" textWrap="true" /> <Label class="title orkm" text="No recipes found" textWrap="true" />
@ -153,45 +211,15 @@
textWrap="true" textWrap="true"
/> />
</StackLayout> </StackLayout>
<StackLayout
col="0"
row="1"
class="noResult"
v-if="!filteredRecipes.length && filterFavorites && !searchQuery"
>
<Label class="bx icon" :text="icon.heartOutline" textWrap="true" />
<Label class="title orkm" text="No favorites yet!" textWrap="true" />
<Label
text="Your favorited recipes will be listed here"
textWrap="true"
/>
</StackLayout>
<StackLayout
col="0"
row="1"
class="noResult"
v-if="!filteredRecipes.length && filterTrylater && !searchQuery"
>
<Label class="bx icon" :text="icon.trylaterOutline" textWrap="true" />
<Label
class="title orkm"
text="Nothing to try next!"
textWrap="true"
/>
<Label
text="Recipes you wanted to try later will be listed here"
textWrap="true"
/>
</StackLayout>
</GridLayout> </GridLayout>
<GridLayout id="btnFabContainer" rows="*,auto" columns="*,auto"> <GridLayout id="btnFabContainer" rows="*, auto" columns="*, auto">
<transition name="bounce"> <transition name="bounce">
<Label <MDFloatingActionButton
v-if="showFAB" v-if="showFAB"
row="1" row="1"
col="1" col="1"
class="bx fab-button" class="bx fab-button"
:text="icon.plus" src="res://plus"
@tap="addRecipe" @tap="addRecipe"
/> />
</transition> </transition>
@ -201,13 +229,14 @@
</template> </template>
<script> <script>
import { Utils, AndroidApplication } from "@nativescript/core" import { AndroidApplication, Utils } from "@nativescript/core"
import { mapActions, mapState } from "vuex"
import EditRecipe from "./EditRecipe.vue" import EditRecipe from "./EditRecipe.vue"
import ViewRecipe from "./ViewRecipe.vue" import ViewRecipe from "./ViewRecipe.vue"
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 { mapState, mapActions } from "vuex"
export default { export default {
props: [ props: [
@ -261,7 +290,7 @@ export default {
}, },
methods: { methods: {
...mapActions(["setCurrentComponentAction", "deleteRecipeAction"]), ...mapActions(["setCurrentComponentAction", "deleteRecipeAction"]),
initializePage() { onPageLoad() {
this.filterFavorites this.filterFavorites
? this.setComponent("Favorites") ? this.setComponent("Favorites")
: this.filterTrylater : this.filterTrylater
@ -271,10 +300,46 @@ export default {
: this.setComponent("EnRecipes") : this.setComponent("EnRecipes")
this.showFAB = true this.showFAB = true
}, },
// HELPERS
openSearch() { openSearch() {
this.showSearch = true this.showSearch = true
this.showFAB = false
this.hijackLocalBackEvent() this.hijackLocalBackEvent()
}, },
closeSearch() {
if (this.searchQuery) this.updateFilter()
this.searchQuery = ""
Utils.ad.dismissSoftInput()
this.showSearch = false
this.showFAB = true
this.releaseLocalBackEvent()
},
setComponent(comp) {
this.setCurrentComponentAction(comp)
this.hijackGlobalBackEvent()
},
clearSearch() {
if (this.searchQuery !== "") {
this.updateFilter()
}
},
formattedTime(time) {
let t = time.split(":")
let h = parseInt(t[0])
let m = parseInt(t[1])
return {
time: h ? (m ? `${h}h ${m}m` : `${h}h`) : `${m}m`,
duration: `${h}${m}`,
}
},
onScroll(args) {
args.scrollOffset
? (this.viewIsScrolled = true)
: (this.viewIsScrolled = false)
},
// NAVIGATION HANDLERS
hijackLocalBackEvent() { hijackLocalBackEvent() {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
AndroidApplication.on( AndroidApplication.on(
@ -293,20 +358,49 @@ export default {
args.cancel = true args.cancel = true
this.closeSearch() this.closeSearch()
}, },
closeSearch() { addRecipe() {
if (this.searchQuery) this.updateFilter() this.showFAB = false
this.searchQuery = "" this.releaseGlobalBackEvent()
Utils.ad.dismissSoftInput() this.$navigateTo(EditRecipe, {
this.showSearch = false // transition: {
this.releaseLocalBackEvent() // name: "fade",
// duration: 200,
// curve: "easeOut",
// },
props: {
selectedCategory: this.selectedCategory,
openAppSettingsPage: this.openAppSettingsPage,
filterFavorites: this.filterFavorites,
},
})
}, },
viewRecipe(item) {
let index = this.recipes.indexOf(item)
this.showFAB = false
this.$navigateTo(ViewRecipe, {
// transition: {
// name: "fade",
// duration: 200,
// curve: "easeOut",
// },
props: {
filterTrylater: this.filterTrylater,
recipeIndex: index,
recipeID: item.id,
hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent,
},
})
},
// LIST HANDLERS
sortDialog() { sortDialog() {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.$showModal(ActionDialog, { this.$showModal(ActionDialog, {
props: { props: {
title: "Sort by", title: "Sort by",
list: ["Natural order", "Title", "Duration", "Last modified"], list: ["Natural order", "Title", "Duration", "Last modified"],
height: "216", // 54*4 height: "225",
}, },
}).then((action) => { }).then((action) => {
if (action && action !== "Cancel" && this.sortType !== action) { if (action && action !== "Cancel" && this.sortType !== action) {
@ -346,10 +440,6 @@ export default {
break break
} }
}, },
setComponent(comp) {
this.setCurrentComponentAction(comp)
this.hijackGlobalBackEvent()
},
updateFilter() { updateFilter() {
let listView = this.$refs.listView.nativeView let listView = this.$refs.listView.nativeView
setTimeout((e) => { setTimeout((e) => {
@ -392,6 +482,8 @@ export default {
this.deleteRecipe(index, recipeID) this.deleteRecipe(index, recipeID)
this.rightAction = false this.rightAction = false
}, },
// DATA HANDLERS
deleteRecipe(index, recipeID) { deleteRecipe(index, recipeID) {
this.deletionDialogActive = true this.deletionDialogActive = true
this.$showModal(ConfirmDialog, { this.$showModal(ConfirmDialog, {
@ -408,54 +500,6 @@ export default {
this.deletionDialogActive = false this.deletionDialogActive = false
}) })
}, },
formattedTime(time) {
let t = time.split(":")
let h = parseInt(t[0])
let m = parseInt(t[1])
return {
time: h ? (m ? `${h}h ${m}m` : `${h}h`) : `${m}m`,
duration: `${h}${m}`,
}
},
onScroll(args) {
args.scrollOffset
? (this.viewIsScrolled = true)
: (this.viewIsScrolled = false)
},
addRecipe() {
this.showFAB = false
this.releaseGlobalBackEvent()
this.$navigateTo(EditRecipe, {
// transition: {
// name: "fade",
// duration: 200,
// curve: "easeOut",
// },
props: {
selectedCategory: this.selectedCategory,
openAppSettingsPage: this.openAppSettingsPage,
filterFavorites: this.filterFavorites,
},
})
},
viewRecipe({ item, index }) {
this.showFAB = false
this.$navigateTo(ViewRecipe, {
// transition: {
// name: "fade",
// duration: 200,
// curve: "easeOut",
// },
props: {
filterTrylater: this.filterTrylater,
recipeIndex: index,
recipeID: item.id,
hijackGlobalBackEvent: this.hijackGlobalBackEvent,
releaseGlobalBackEvent: this.releaseGlobalBackEvent,
},
})
},
}, },
mounted() { mounted() {
this.showFAB = true this.showFAB = true

View file

@ -1,9 +1,10 @@
<template> <template>
<Page @loaded="initializePage"> <Page @loaded="onPageLoad">
<ActionBar :flat="viewIsScrolled ? false : true"> <ActionBar :flat="viewIsScrolled ? false : true">
<GridLayout rows="*" columns="auto, *"> <GridLayout rows="*" columns="auto, *">
<Label <MDButton
class="bx" class="bx left"
variant="text"
:text="icon.menu" :text="icon.menu"
automationText="Back" automationText="Back"
@tap="showDrawer" @tap="showDrawer"
@ -14,55 +15,59 @@
</ActionBar> </ActionBar>
<ScrollView scrollBarIndicatorVisible="false" @scroll="onScroll"> <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 orkm" />
<StackLayout <GridLayout columns="auto, *" class="option">
orientation="horizontal" <MDRipple colSpan="2" @tap="selectThemes" />
class="option" <Label
@tap="selectThemes" col="0"
> verticalAlignment="center"
<Label verticalAlignment="center" class="bx" :text="icon.theme" /> class="bx"
<StackLayout> :text="icon.theme"
/>
<StackLayout col="1">
<Label text="Theme" /> <Label text="Theme" />
<Label :text="appTheme" class="option-info" textWrap="true" /> <Label :text="appTheme" class="info" textWrap="true" />
</StackLayout> </StackLayout>
</StackLayout> </GridLayout>
<StackLayout class="hr m-10"></StackLayout> <StackLayout class="hr m-10"></StackLayout>
<Label text="Database" class="group-header" /> <Label text="Database" class="group-header orkm" />
<StackLayout orientation="horizontal" class="option" @tap="backupCheck"> <GridLayout columns="auto, *" class="option">
<Label class="bx" :text="icon.export" /> <MDRipple colSpan="2" @tap="exportCheck" />
<StackLayout> <Label col="0" class="bx" :text="icon.export" />
<StackLayout col="1">
<Label text="Export a full backup" /> <Label text="Export a full backup" />
<GridLayout <GridLayout
class="progressContainer" class="progressContainer"
v-if="backupInProgress" v-if="backupInProgress"
columns="*, 64" columns="*, 64"
> >
<Progress col="0" :value="backupProgress" /> <MDProgress
col="0"
:value="backupProgress"
maxValue="100"
></MDProgress>
<Label col="1" :text="` ${backupProgress}%`" /> <Label col="1" :text="` ${backupProgress}%`" />
</GridLayout> </GridLayout>
<Label <Label
v-else v-else
text="Generates a zip file that contains all your data. This file can be imported back." text="Generates a zip file that contains all your data. This file can be imported back."
class="option-info" class="info"
textWrap="true" textWrap="true"
/> />
</StackLayout> </StackLayout>
</StackLayout> </GridLayout>
<StackLayout <GridLayout columns="auto, *" class="option"
orientation="horizontal" ><MDRipple colSpan="2" @tap="importCheck" />
class="option" <Label col="0" class="bx" :text="icon.import" />
@tap="restoreCheck" <StackLayout col="1">
>
<Label class="bx" :text="icon.import" />
<StackLayout>
<Label text="Import from backup" /> <Label text="Import from backup" />
<Label <Label
text="Supports full backups exported by this app." text="Supports full backups exported by this app."
class="option-info" class="info"
textWrap="true" textWrap="true"
/> />
</StackLayout> </StackLayout>
</StackLayout> </GridLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</Page> </Page>
@ -83,14 +88,13 @@ import { Zip } from "@nativescript/zip"
import * as Toast from "nativescript-toast" import * as Toast from "nativescript-toast"
import * as Filepicker from "nativescript-plugin-filepicker" import * as Filepicker from "nativescript-plugin-filepicker"
import Theme from "@nativescript/theme" import Theme from "@nativescript/theme"
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 { Couchbase } from "nativescript-couchbase-plugin"
import { mapState, mapActions } from "vuex"
export default { export default {
props: [ props: [
"highlight",
"showDrawer", "showDrawer",
"restartApp", "restartApp",
"hijackGlobalBackEvent", "hijackGlobalBackEvent",
@ -121,22 +125,25 @@ export default {
"importYieldUnitsAction", "importYieldUnitsAction",
"importRecipesAction", "importRecipesAction",
]), ]),
initializePage() { onPageLoad() {
this.setCurrentComponentAction("Settings") this.setCurrentComponentAction("Settings")
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
}, },
// HELPERS
onScroll(args) { onScroll(args) {
args.scrollY args.scrollY
? (this.viewIsScrolled = true) ? (this.viewIsScrolled = true)
: (this.viewIsScrolled = false) : (this.viewIsScrolled = false)
}, },
selectThemes(args) {
this.highlight(args) // THEME SELECTION
selectThemes() {
this.$showModal(ActionDialog, { this.$showModal(ActionDialog, {
props: { props: {
title: "Theme", title: "Theme",
list: ["Light", "Dark"], list: ["Light", "Dark"],
height: "108", height: "113",
}, },
}).then((action) => { }).then((action) => {
if (action && action !== "Cancel" && this.appTheme !== action) { if (action && action !== "Cancel" && this.appTheme !== action) {
@ -159,53 +166,23 @@ export default {
}) })
}, },
writeFile(file, data) { // EXPORT HANDLERS
file.writeText(JSON.stringify(data)) exportCheck() {
}, if (!this.recipes.length) {
BackupDataFiles(option) { Toast.makeText(
const folder = path.join(knownFolders.documents().path, "EnRecipes") "Add at least one recipe to perform a backup",
const EnRecipesFile = File.fromPath(path.join(folder, "EnRecipes.json")) "long"
let userCategoriesFile, userYieldUnitsFile ).show()
if (this.userCategories.length) { } else {
userCategoriesFile = File.fromPath( this.permissionCheck(
path.join(folder, "userCategories.json") this.permissionConfirmation,
"EnRecipes requires storage permission in order to backup your data to this device.",
this.exportBackup
) )
} }
if (this.userYieldUnits.length) {
userYieldUnitsFile = File.fromPath(
path.join(folder, "userYieldUnits.json")
)
}
switch (option) {
case "create":
this.writeFile(EnRecipesFile, this.recipes)
this.userCategories.length &&
this.writeFile(userCategoriesFile, this.userCategories)
this.userYieldUnits.length &&
this.writeFile(userYieldUnitsFile, this.userYieldUnits)
break
case "delete":
EnRecipesFile.remove()
this.userCategories.length && userCategoriesFile.remove()
this.userYieldUnits.length && userYieldUnitsFile.remove()
break
default:
break
}
}, },
backupPermissionConfirmation() { exportBackup() {
return this.$showModal(ConfirmDialog, { this.exportFiles("create")
props: {
title: "Grant permission",
description:
"EnRecipes requires storage permission in order to backup your data to this device",
cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE",
},
})
},
backupData() {
this.BackupDataFiles("create")
let date = new Date() let date = new Date()
let formattedDate = let formattedDate =
date.getFullYear() + date.getFullYear() +
@ -242,59 +219,70 @@ export default {
"Backup file successfully saved to Downloads", "Backup file successfully saved to Downloads",
"long" "long"
).show() ).show()
this.BackupDataFiles("delete") this.exportFiles("delete")
}) })
}, },
backupCheck(args) { exportFiles(option) {
let btn = args.object const folder = path.join(knownFolders.documents().path, "EnRecipes")
this.highlight(args) const EnRecipesFile = File.fromPath(path.join(folder, "EnRecipes.json"))
if (!this.recipes.length) { let userCategoriesFile, userYieldUnitsFile
Toast.makeText( if (this.userCategories.length) {
"Add at least one recipe to perform a backup", userCategoriesFile = File.fromPath(
"long" path.join(folder, "userCategories.json")
).show() )
} else { }
this.permissionCheck(this.backupPermissionConfirmation, this.backupData) if (this.userYieldUnits.length) {
userYieldUnitsFile = File.fromPath(
path.join(folder, "userYieldUnits.json")
)
}
switch (option) {
case "create":
this.writeDataToFile(EnRecipesFile, this.recipes)
this.userCategories.length &&
this.writeDataToFile(userCategoriesFile, this.userCategories)
this.userYieldUnits.length &&
this.writeDataToFile(userYieldUnitsFile, this.userYieldUnits)
break
case "delete":
EnRecipesFile.remove()
this.userCategories.length && userCategoriesFile.remove()
this.userYieldUnits.length && userYieldUnitsFile.remove()
break
default:
break
} }
}, },
writeDataToFile(file, data) {
restorePermissionConfirmation() { file.writeText(JSON.stringify(data))
return this.$showModal(ConfirmDialog, {
props: {
title: "Grant permission",
description:
"EnRecipes requires storage permission in order to restore your data from a previous backup.",
cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE",
},
})
}, },
restoreCheck(args) {
let btn = args.object
this.highlight(args)
// IMPORT HANDLERS
importCheck() {
this.permissionCheck( this.permissionCheck(
this.restorePermissionConfirmation, this.permissionConfirmation,
"EnRecipes requires storage permission in order to restore your data from a previous backup.",
this.openFilePicker this.openFilePicker
) )
}, },
openFilePicker() { openFilePicker() {
let context = Filepicker.create({ Filepicker.create({
mode: "single", // use "multiple" for multiple selection mode: "single",
extensions: ["zip"], extensions: ["zip"],
}) })
context.present().then((selection) => { .present()
Toast.makeText("Processing...").show() .then((selection) => {
let result = selection[0] Toast.makeText("Processing...").show()
let zipPath = result let result = selection[0]
let dest = knownFolders.documents().path let zipPath = result
this.validateZipContent(zipPath) let dest = knownFolders.documents().path
}) this.validateZipContent(zipPath)
})
}, },
importDataToDB(data, db, zipPath) { importDataToDB(data, db, zipPath) {
switch (db) { switch (db) {
case "EnRecipesDB": case "EnRecipesDB":
this.copyImages(zipPath) this.importImages(zipPath)
this.importRecipesAction(data) this.importRecipesAction(data)
break break
case "userCategoriesDB": case "userCategoriesDB":
@ -307,7 +295,7 @@ export default {
break break
} }
}, },
isImportedDataValid(file) { isFileDataValid(file) {
file.forEach((file, i) => { file.forEach((file, i) => {
if (File.exists(file.path)) { if (File.exists(file.path)) {
File.fromPath(file.path) File.fromPath(file.path)
@ -329,7 +317,7 @@ export default {
const userCategoriesFilePath = cacheFolderPath + "/userCategories.json" const userCategoriesFilePath = cacheFolderPath + "/userCategories.json"
const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json" const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json"
if (Folder.exists(cacheFolderPath)) { if (Folder.exists(cacheFolderPath)) {
this.isImportedDataValid([ this.isFileDataValid([
{ {
zipPath, zipPath,
path: EnRecipesFilePath, path: EnRecipesFilePath,
@ -341,30 +329,33 @@ export default {
} else { } else {
Folder.fromPath(extractedFolderPath).remove() Folder.fromPath(extractedFolderPath).remove()
Toast.makeText( Toast.makeText(
"Zip modified externally or incorrect file", "Import failed. Backup file is incorrect or corrupt",
"long" "long"
).show() ).show()
} }
if (Folder.exists(cacheFolderPath + "/Images")) { if (Folder.exists(cacheFolderPath + "/Images")) {
this.copyImages(cacheFolderPath + "/Images") this.importImages(cacheFolderPath + "/Images")
} }
}) })
}, },
copyImages(sourcePath) { importImages(sourcePath) {
let dest = knownFolders.documents().path let dest = knownFolders.documents().path
Zip.unzip({ Zip.unzip({
archive: sourcePath, archive: sourcePath,
directory: dest, directory: dest,
overwrite: true, overwrite: true,
}).then((res) => { }).then((res) => {
Toast.makeText("Import successful!", "long").show() Toast.makeText("Import successful", "long").show()
this.$navigateBack()
}) })
}, },
permissionCheck(confirmation, action) {
// PERMISSIONS HANDLER
permissionCheck(confirmation, description, action) {
if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) { if (!ApplicationSettings.getBoolean("storagePermissionAsked", false)) {
confirmation().then((e) => { confirmation(description).then((e) => {
if (e) { if (e) {
Permissions.request("storage").then((res) => { Permissions.request("photo").then((res) => {
let status = res[Object.keys(res)[0]] let status = res[Object.keys(res)[0]]
if (status === "authorized") action() if (status === "authorized") action()
if (status !== "denied") if (status !== "denied")
@ -374,16 +365,26 @@ export default {
} }
}) })
} else { } else {
Permissions.request("storage").then((res) => { Permissions.check("photo").then((res) => {
let status = res[Object.keys(res)[0]] let status = res[Object.keys(res)[0]]
if (status !== "authorized") { if (status !== "authorized") {
confirmation().then((e) => { confirmation(description).then((e) => {
e && this.openAppSettingsPage() e && this.openAppSettingsPage()
}) })
} else action() } else action()
}) })
} }
}, },
permissionConfirmation(description) {
return this.$showModal(ConfirmDialog, {
props: {
title: "Grant permission",
description,
cancelButtonText: "NOT NOW",
okButtonText: "CONTINUE",
},
})
},
}, },
created() { created() {
this.appTheme = ApplicationSettings.getString("appTheme", "Light") this.appTheme = ApplicationSettings.getString("appTheme", "Light")

View file

@ -1,8 +1,9 @@
<template> <template>
<Page @loaded="initializePage" @unloaded="unLoad"> <Page @loaded="onPageLoad" @unloaded="onPageUnload">
<ActionBar height="112" margin="0" flat="true"> <ActionBar height="112" margin="0" flat="true">
<GridLayout rows="64, 48" columns="auto, *, auto,auto, auto"> <GridLayout rows="64, 48" columns="auto, *, auto,auto, auto">
<Label <MDButton
variant="text"
row="0" row="0"
col="0" col="0"
class="bx" class="bx"
@ -23,30 +24,36 @@
verticalAlignment="bottom" verticalAlignment="bottom"
/> />
</ScrollView> </ScrollView>
<Label <FlexboxLayout row="0" col="2" alignItems="center">
row="0" <MDButton
col="3" variant="text"
class="bx" v-if="!busy"
:text="recipe.isFavorite ? icon.heart : icon.heartOutline" class="bx"
@tap="toggleFavorite" :text="icon.edit"
/> @tap="editRecipe"
<Label />
v-if="!filterTrylater" <MDActivityIndicator v-else :busy="busy" />
row="0" <MDButton
col="4" variant="text"
class="bx" class="bx"
:text="recipe.tried ? icon.trylaterOutline : icon.trylater" :text="recipe.isFavorite ? icon.heart : icon.heartOutline"
@tap="toggleTrylater" @tap="toggleFavorite"
/> />
<Label <MDButton
v-if="!busy" variant="text"
row="0" v-if="!filterTrylater"
col="2" class="bx"
class="bx" :text="recipe.tried ? icon.trylaterOutline : icon.trylater"
:text="icon.edit" @tap="toggleTrylater"
@tap="editRecipe" />
/> <MDButton
<ActivityIndicator v-else row="0" col="2" :busy="busy" /> variant="text"
v-else
class="bx"
:text="icon.share"
@tap="shareHandler"
/>
</FlexboxLayout>
</GridLayout> </GridLayout>
</ActionBar> </ActionBar>
<AbsoluteLayout> <AbsoluteLayout>
@ -91,27 +98,26 @@
:text="recipe.title" :text="recipe.title"
textWrap="true" textWrap="true"
/> />
<Label class="time"> <StackLayout orientation="horizontal" class="time">
<FormattedString> <Label text="Time required:" />
<Span text="Time required:"></Span> <Label :text="` ${formattedTime(recipe.timeRequired)}`" />
<Span </StackLayout>
:text="` ${formattedTime(recipe.timeRequired)}`"
></Span>
</FormattedString>
</Label>
<GridLayout <GridLayout
rows="auto, auto" rows="auto, auto"
columns="*, *" columns="*, *"
class="overviewContainer" class="overviewContainer"
> >
<StackLayout <GridLayout
class="overviewItem" class="overviewItem"
row="0" row="0"
col="0" col="0"
@tap="selectedTabIndex = 1" rows="auto, auto"
columns="*"
> >
<Label class="bx" :text="icon.item" /> <MDRipple rowSpan="2" @tap="selectedTabIndex = 1" />
<Label row="0" class="bx" :text="icon.item" />
<Label <Label
row="1"
class="itemCount" class="itemCount"
:text=" :text="
`${recipe.ingredients.length} ${ `${recipe.ingredients.length} ${
@ -122,15 +128,18 @@
" "
textWrap="true" textWrap="true"
/> />
</StackLayout> </GridLayout>
<StackLayout <GridLayout
class="overviewItem" class="overviewItem"
row="0" row="0"
col="1" col="1"
@tap="selectedTabIndex = 2" rows="auto, auto"
columns="*"
> >
<Label class="bx" :text="icon.step" /> <MDRipple rowSpan="2" @tap="selectedTabIndex = 2" />
<Label row="0" class="bx" :text="icon.step" />
<Label <Label
row="1"
class="itemCount" class="itemCount"
:text=" :text="
`${recipe.instructions.length} ${ `${recipe.instructions.length} ${
@ -139,15 +148,18 @@
" "
textWrap="true" textWrap="true"
/> />
</StackLayout> </GridLayout>
<StackLayout <GridLayout
class="overviewItem" class="overviewItem"
row="1" row="1"
col="0" col="0"
@tap="selectedTabIndex = 3" rows="auto, auto"
columns="*"
> >
<Label class="bx" :text="icon.note" /> <MDRipple rowSpan="2" @tap="selectedTabIndex = 3" />
<Label row="0" class="bx" :text="icon.note" />
<Label <Label
row="1"
class="itemCount" class="itemCount"
:text=" :text="
`${recipe.notes.length} ${ `${recipe.notes.length} ${
@ -156,15 +168,18 @@
" "
textWrap="true" textWrap="true"
/> />
</StackLayout> </GridLayout>
<StackLayout <GridLayout
class="overviewItem" class="overviewItem"
row="1" row="1"
col="1" col="1"
@tap="selectedTabIndex = 4" rows="auto, auto"
columns="*"
> >
<Label class="bx" :text="icon.source" /> <MDRipple rowSpan="2" @tap="selectedTabIndex = 4" />
<Label row="0" class="bx" :text="icon.source" />
<Label <Label
row="1"
class="itemCount" class="itemCount"
:text=" :text="
`${recipe.references.length} ${ `${recipe.references.length} ${
@ -175,7 +190,7 @@
" "
textWrap="true" textWrap="true"
/> />
</StackLayout> </GridLayout>
</GridLayout> </GridLayout>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
@ -185,11 +200,11 @@
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<GridLayout <GridLayout
v-if="!recipe.ingredients.length" v-if="!recipe.ingredients.length"
rows="96, auto, *" rows="*, auto, *, 88"
columns="*" columns="*"
class="emptyState" class="emptyStateContainer"
> >
<StackLayout col="0" row="1" class="noResult"> <StackLayout col="0" row="1" class="emptyState">
<Label class="bx icon" :text="icon.item" textWrap="true" /> <Label class="bx icon" :text="icon.item" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm"> <StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " /> <Label text="Use the " />
@ -199,7 +214,7 @@
<Label text="to add some ingredients" textWrap="true" /> <Label text="to add some ingredients" textWrap="true" />
</StackLayout> </StackLayout>
</GridLayout> </GridLayout>
<StackLayout v-else padding="16 16 134"> <StackLayout v-else padding="32 16 134">
<AbsoluteLayout class="inputField"> <AbsoluteLayout class="inputField">
<TextField <TextField
width="50%" width="50%"
@ -212,7 +227,7 @@
:text="`Required ${recipe.yield.unit.toLowerCase()}`" :text="`Required ${recipe.yield.unit.toLowerCase()}`"
/> />
</AbsoluteLayout> </AbsoluteLayout>
<StackLayout margin="24 0 16 0"> <StackLayout margin="16 0">
<Label <Label
class="title orkm" class="title orkm"
:text=" :text="
@ -231,7 +246,7 @@
v-if="filterTrylater" v-if="filterTrylater"
class="ingredient-check" class="ingredient-check"
checkPadding="16" checkPadding="16"
:fillColor="`${isLightMode ? '#ff5200' : '#ff7043'}`" fillColor="#ff5200"
:text=" :text="
`${ `${
roundedQuantity(item.quantity) roundedQuantity(item.quantity)
@ -264,11 +279,11 @@
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<GridLayout <GridLayout
v-if="!recipe.instructions.length" v-if="!recipe.instructions.length"
rows="96, auto, *" rows="*, auto, *, 88"
columns="*" columns="*"
class="emptyState" class="emptyStateContainer"
> >
<StackLayout col="0" row="1" class="noResult"> <StackLayout col="0" row="1" class="emptyState">
<Label class="bx icon" :text="icon.step" textWrap="true" /> <Label class="bx icon" :text="icon.step" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm"> <StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " /> <Label text="Use the " />
@ -309,11 +324,11 @@
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<GridLayout <GridLayout
v-if="!recipe.notes.length" v-if="!recipe.notes.length"
rows="96, auto, *" rows="*, auto, *, 88"
columns="*" columns="*"
class="emptyState" class="emptyStateContainer"
> >
<StackLayout col="0" row="1" class="noResult"> <StackLayout col="0" row="1" class="emptyState">
<Label class="bx icon" :text="icon.note" textWrap="true" /> <Label class="bx icon" :text="icon.note" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm"> <StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " /> <Label text="Use the " />
@ -351,11 +366,11 @@
<ScrollView scrollBarIndicatorVisible="false"> <ScrollView scrollBarIndicatorVisible="false">
<GridLayout <GridLayout
v-if="!recipe.references.length" v-if="!recipe.references.length"
rows="96, auto, *" rows="*, auto, *, 88"
columns="*" columns="*"
class="emptyState" class="emptyStateContainer"
> >
<StackLayout col="0" row="1" class="noResult"> <StackLayout col="0" row="1" class="emptyState">
<Label class="bx icon" :text="icon.source" textWrap="true" /> <Label class="bx icon" :text="icon.source" textWrap="true" />
<StackLayout orientation="horizontal" class="title orkm"> <StackLayout orientation="horizontal" class="title orkm">
<Label text="Use the " /> <Label text="Use the " />
@ -375,8 +390,8 @@
columns="*, auto" columns="*, auto"
class="referenceItem" class="referenceItem"
androidElevation="1" androidElevation="1"
@longPress="copyURL($event, reference)"
> >
<MDRipple colSpan="3" @tap="copyURL(reference)" />
<Label <Label
col="0" col="0"
verticalAlignment="center" verticalAlignment="center"
@ -384,11 +399,13 @@
:text="reference" :text="reference"
textWrap="false" textWrap="false"
/> />
<Label <MDButton
variant="text"
automationText="openURL"
col="1" col="1"
class="bx" class="bx"
:text="icon.source" :text="icon.source"
@tap="openURL($event, reference)" @tap="openURL(reference)"
/> />
</GridLayout> </GridLayout>
<Label <Label
@ -402,22 +419,20 @@
</ScrollView> </ScrollView>
</TabViewItem> </TabViewItem>
</TabView> </TabView>
<GridLayout id="btnFabContainer" rows="*,auto" columns="*,auto"> <GridLayout id="btnFabContainer" rows="*, auto" columns="*, auto">
<Label <MDFloatingActionButton
row="1" row="1"
col="1" col="1"
class="bx fab-button" src="res://check"
:text="icon.check"
@tap="recipeTried" @tap="recipeTried"
v-if="filterTrylater" v-if="filterTrylater"
/> />
<transition name="dolly" appear> <transition name="dolly" appear>
<Label <MDFloatingActionButton
row="1" row="1"
col="1" col="1"
class="bx fab-button" src="res://share"
:text="icon.share" @tap="shareHandler"
@tap="shareRecipe"
v-if="!filterTrylater && showFab" v-if="!filterTrylater && showFab"
/> />
</transition> </transition>
@ -428,22 +443,24 @@
<script> <script>
import { import {
Screen, Color,
Utils,
ImageSource,
Device, Device,
File, File,
Color, knownFolders,
path,
ImageSource,
Screen,
Utils,
} from "@nativescript/core" } from "@nativescript/core"
import { Feedback, FeedbackType, FeedbackPosition } from "nativescript-feedback" import { Feedback, FeedbackType, FeedbackPosition } from "nativescript-feedback"
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"
import { setText } from "nativescript-clipboard" import { setText } from "nativescript-clipboard"
import { Application } from "@nativescript/core" import { Application } from "@nativescript/core"
import { mapActions, mapState } from "vuex"
import { mapState, mapActions } from "vuex"
import EditRecipe from "./EditRecipe.vue" import EditRecipe from "./EditRecipe.vue"
import ShareChooser from "./modal/ShareChooser.vue"
let feedback = new Feedback() let feedback = new Feedback()
@ -480,7 +497,7 @@ export default {
}, },
methods: { methods: {
...mapActions(["toggleStateAction", "setCurrentComponentAction"]), ...mapActions(["toggleStateAction", "setCurrentComponentAction"]),
initializePage() { onPageLoad() {
this.releaseGlobalBackEvent() this.releaseGlobalBackEvent()
this.busy = false this.busy = false
setTimeout((e) => { setTimeout((e) => {
@ -488,8 +505,14 @@ export default {
}, 500) }, 500)
this.yieldMultiplier = this.recipe.yield.quantity this.yieldMultiplier = this.recipe.yield.quantity
this.showFab = true this.showFab = true
this.toggleScreenOn(true) this.keepScreenOn(true)
}, },
onPageUnload() {
feedback.hide()
this.keepScreenOn(false)
},
// HELPERS
niceDates(time) { niceDates(time) {
let lastTried = new Date(time).getTime() let lastTried = new Date(time).getTime()
let now = new Date().getTime() let now = new Date().getTime()
@ -509,26 +532,15 @@ export default {
selectedIndexChange(args) { selectedIndexChange(args) {
this.selectedTabIndex = args.object.selectedIndex this.selectedTabIndex = args.object.selectedIndex
}, },
showInfo() { showLastTried() {
feedback.show({ feedback.show({
title: `You tried this recipe ${this.niceDates( title: `You tried this recipe ${this.niceDates(
this.recipe.lastTried this.recipe.lastTried
)}!`, )}!`,
titleColor: new Color(`${this.isLightMode ? "#fff" : "#111"}`), titleColor: new Color(`${this.isLightMode ? "#f1f3f5" : "#212529"}`),
backgroundColor: new Color( backgroundColor: new Color("#ff5200"),
`${this.isLightMode ? "#ff5200" : "#ff7043"}`
),
}) })
}, },
unLoad() {
feedback.hide()
this.toggleScreenOn(false)
},
highlight(args) {
let temp = args.object.className
args.object.className = `${temp} option-highlight`
setTimeout(() => (args.object.className = temp), 100)
},
roundedQuantity(quantity) { roundedQuantity(quantity) {
return ( return (
Math.round( Math.round(
@ -538,6 +550,32 @@ export default {
) / 100 ) / 100
) )
}, },
keepScreenOn(boolean) {
let activity =
Application.android.foregroundActivity ||
Application.android.startActivity
let window = activity.getWindow()
if (boolean)
window.addFlags(
android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
else
window.clearFlags(
android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
},
formattedTime(time) {
let t = time.split(":")
let h = parseInt(t[0])
let m = parseInt(t[1])
return h ? (m ? `${h}h ${m}m` : `${h}h`) : `${m}m`
},
isValidURL(string) {
let pattern = new RegExp("^https?|www", "ig")
return pattern.test(string)
},
// NAVIGATION HANDLERS
editRecipe() { editRecipe() {
this.showFab = false this.showFab = false
this.busy = true this.busy = true
@ -554,6 +592,47 @@ export default {
// backstackVisible: false, // backstackVisible: false,
}) })
}, },
// SHARE ACTION
shareHandler() {
if (this.recipe.imageSrc) {
this.$showModal(ShareChooser, {
props: {
title: "Share",
},
}).then((result) => {
switch (result) {
case "photo":
// let cacheFilePath = path.join(
// knownFolders.temp().path,
// `${this.recipe.title}.jpg`
// )
// if (!File.exists(cacheFilePath)) {
// File.fromPath(cacheFilePath).writeSync(
// File.fromPath(this.recipe.imageSrc).readSync()
// )
// }
// let shareFile = new ShareFile()
// shareFile.open({
// path: cacheFilePath,
// title: "Share recipe photo using",
// })
ImageSource.fromFile(this.recipe.imageSrc).then((res) => {
SocialShare.shareImage(res, "Share recipe photo using")
})
break
case "recipe":
this.shareRecipe()
break
default:
break
}
})
} else {
this.shareRecipe()
}
},
shareRecipe() { shareRecipe() {
let overview = `${ let overview = `${
this.recipe.title this.recipe.title
@ -564,9 +643,11 @@ export default {
this.yieldMultiplier this.yieldMultiplier
} ${this.recipe.yield.unit.toLowerCase()}\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 += `- ${
e.item e.quantity
}\n` ? this.roundedQuantity(e.quantity) + " " + e.unit + " "
: ""
}${e.item}\n`
}) })
shareContent += ingredients shareContent += ingredients
} }
@ -592,15 +673,14 @@ export default {
shareContent += references shareContent += references
} }
let sharenote = let sharenote =
"\nCreated and shared via EnRecipes.\nDownload the app on f-droid: https://www.vishnuraghav.com/" "\nCreated and shared via EnRecipes.\nGet it on F-Droid."
shareContent += sharenote shareContent += sharenote
SocialShare.shareText( SocialShare.shareText(shareContent, "Share recipe using")
shareContent,
"How would you like to share this recipe?"
)
}, },
// DATA HANDLERS
toggle(key, setDate) { toggle(key, setDate) {
this.toggleStateAction({ this.toggleStateAction({
index: this.recipeIndex, index: this.recipeIndex,
@ -624,53 +704,32 @@ export default {
: this.filterTrylater : this.filterTrylater
? Toast.makeText("You tried this recipe").show() ? Toast.makeText("You tried this recipe").show()
: Toast.makeText("Removed from Try later").show() : Toast.makeText("Removed from Try later").show()
// : Toast.makeText("You tried this recipe").show()
this.toggle("tried") this.toggle("tried")
}, },
recipeTried() { recipeTried() {
this.toggle("tried", true) this.toggle("tried", true)
this.$navigateBack() this.$navigateBack()
}, },
formattedTime(time) {
let t = time.split(":") // URL ACTION
let h = parseInt(t[0]) openURL(url) {
let m = parseInt(t[1])
return h ? (m ? `${h}h ${m}m` : `${h}h`) : `${m}m`
},
isValidURL(string) {
let pattern = new RegExp("^https?|www", "ig")
return pattern.test(string)
},
openURL(args, url) {
// this.highlight(args)
Utils.openUrl(url) Utils.openUrl(url)
}, },
copyURL(args, url) { copyURL(url) {
setText(url).then((e) => { setText(url).then((e) => {
Toast.makeText("URL Copied").show() Toast.makeText("URL Copied").show()
}) })
}, },
toggleScreenOn(boolean) {
let activity =
Application.android.foregroundActivity ||
Application.android.startActivity
let window = activity.getWindow()
if (boolean)
window.addFlags(
android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
else
window.clearFlags(
android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
},
}, },
created() { created() {
this.recipe = this.recipes.filter((e) => e.id === this.recipeID)[0] this.recipe = this.recipes.filter((e) => e.id === this.recipeID)[0]
}, },
mounted() { mounted() {
this.showFab = true this.showFab = true
setTimeout((e) => this.recipe.tried && this.showInfo(), 500) setTimeout(
(e) => this.recipe.tried && this.recipe.lastTried && this.showLastTried(),
500
)
}, },
} }
</script> </script>

View file

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

View file

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

View file

@ -1,6 +1,6 @@
<template> <template>
<Page> <Page>
<StackLayout class="dialogContainer" :class="isLightMode"> <StackLayout class="dialogContainer" :class="appTheme">
<Label class="dialogTitle orkm" :text="title" /> <Label class="dialogTitle orkm" :text="title" />
<StackLayout <StackLayout
class="dialogListPicker" class="dialogListPicker"
@ -13,12 +13,6 @@
:selectedIndex="hrIndex" :selectedIndex="hrIndex"
@selectedIndexChange="setHrs" @selectedIndexChange="setHrs"
></ListPicker> ></ListPicker>
<Label
verticalAlignment="center"
class="okrb"
text=":"
textWrap="false"
/>
<ListPicker <ListPicker
ref="minPicker" ref="minPicker"
:items="mins" :items="mins"
@ -26,19 +20,19 @@
@selectedIndexChange="setMins" @selectedIndexChange="setMins"
></ListPicker> ></ListPicker>
</StackLayout> </StackLayout>
<GridLayout <GridLayout rows="auto" columns="*, auto, auto" class="actionsContainer">
rows="auto" <MDButton
columns="*, auto, 32, auto" :rippleColor="rippleColor"
class="actionsContainer" variant="text"
>
<Label
col="1" col="1"
class="action orkm" class="action orkm"
text="CANCEL" text="CANCEL"
@tap="$modal.close(false)" @tap="$modal.close(false)"
/> />
<Label <MDButton
col="3" :rippleColor="rippleColor"
variant="text"
col="2"
class="action orkm" class="action orkm"
:text="action" :text="action"
@tap="$modal.close(selectedTime)" @tap="$modal.close(selectedTime)"
@ -55,52 +49,52 @@ export default {
data() { data() {
return { return {
hrs: [ hrs: [
"00", "0h",
"01", "1h",
"02", "2h",
"03", "3h",
"04", "4h",
"05", "5h",
"06", "6h",
"07", "7h",
"08", "8h",
"09", "9h",
"10", "10h",
"11", "11h",
"12", "12h",
"13", "13h",
"14", "14h",
"15", "15h",
"16", "16h",
"17", "17h",
"18", "18h",
"19", "19h",
"20", "20h",
"21", "21h",
"22", "22h",
"23", "23h",
], ],
mins: [ mins: [
"00", "0m",
"01", "1m",
"02", "2m",
"03", "3m",
"04", "4m",
"05", "5m",
"06", "6m",
"07", "7m",
"08", "8m",
"09", "9m",
"10", "10m",
"15", "15m",
"20", "20m",
"25", "25m",
"30", "30m",
"35", "35m",
"40", "40m",
"45", "45m",
"50", "50m",
"55", "55m",
], ],
selectedHrs: "00", selectedHrs: "00",
selectedMins: "00", selectedMins: "00",
@ -108,24 +102,37 @@ export default {
}, },
computed: { computed: {
hrIndex() { hrIndex() {
return this.hrs.indexOf(this.selectedHr) let hr = this.selectedHr
if (hr.charAt(0) == "0") hr = hr.slice(-1) + "h"
else hr = hr + "h"
return this.hrs.indexOf(hr)
}, },
minIndex() { minIndex() {
return this.mins.indexOf(this.selectedMin) let min = this.selectedMin
if (min.charAt(0) == "0") min = min.slice(-1) + "m"
else min = min + "m"
return this.mins.indexOf(min)
}, },
isLightMode() { appTheme() {
return Application.systemAppearance() return Application.systemAppearance()
}, },
rippleColor() {
return this.appTheme == "light"
? "rgba(134,142,150,0.2)"
: "rgba(206,212,218,0.1)"
},
selectedTime() { selectedTime() {
return this.selectedHrs + ":" + this.selectedMins return this.selectedHrs + ":" + this.selectedMins
}, },
}, },
methods: { methods: {
setHrs(args) { setHrs(args) {
this.selectedHrs = this.hrs[args.object.selectedIndex] let hr = "0" + this.hrs[args.object.selectedIndex]
this.selectedHrs = hr.slice(-3).slice(0, -1)
}, },
setMins(args) { setMins(args) {
this.selectedMins = this.mins[args.object.selectedIndex] let min = "0" + this.mins[args.object.selectedIndex]
this.selectedMins = min.slice(-3).slice(0, -1)
}, },
}, },
} }

View file

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

View file

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

View file

@ -5,6 +5,24 @@ import store from "./store"
import RadListView from "nativescript-ui-listview/vue" import RadListView from "nativescript-ui-listview/vue"
Vue.use(RadListView) Vue.use(RadListView)
import ButtonPlugin from "@nativescript-community/ui-material-button/vue"
Vue.use(ButtonPlugin)
import ActivityIndicatorPlugin from "@nativescript-community/ui-material-activityindicator/vue"
Vue.use(ActivityIndicatorPlugin)
import RipplePlugin from "@nativescript-community/ui-material-ripple/vue"
Vue.use(RipplePlugin)
import FloatingActionButtonPlugin from "@nativescript-community/ui-material-floatingactionbutton/vue"
Vue.use(FloatingActionButtonPlugin)
import ProgressPlugin from "@nativescript-community/ui-material-progress/vue"
Vue.use(ProgressPlugin)
// import SpeedDialPlugin from "@nativescript-community/ui-material-speeddial/vue"
// Vue.use(SpeedDialPlugin)
Vue.registerElement( Vue.registerElement(
"RadSideDrawer", "RadSideDrawer",
() => require("nativescript-ui-sidedrawer").RadSideDrawer () => require("nativescript-ui-sidedrawer").RadSideDrawer

View file

@ -147,6 +147,7 @@ export default new Vuex.Store({
heart: "\ued36", heart: "\ued36",
heartOutline: "\uea6c", heartOutline: "\uea6c",
label: "\ued51", label: "\ued51",
labelOutline: "\uea8b",
cog: "\ueca6", cog: "\ueca6",
info: "\ued49", info: "\ued49",
menu: "\ueac1", menu: "\ueac1",
@ -165,6 +166,7 @@ export default new Vuex.Store({
theme: "\uecaa", theme: "\uecaa",
link: "\ueaa0", link: "\ueaa0",
file: "\ued02", file: "\ued02",
detail: "\ue9f9",
user: "\uee33", user: "\uee33",
trash: "\uee26", trash: "\uee26",
donate: "\ueb4f", donate: "\ueb4f",
@ -185,9 +187,8 @@ export default new Vuex.Store({
}, },
mutations: { mutations: {
initializeRecipes(state) { initializeRecipes(state) {
let a = EnRecipesDB.query({ select: [] }) EnRecipesDB.query({ select: [] }).forEach((recipe) => {
a.forEach((e) => { state.recipes.push(recipe)
state.recipes.push(e)
}) })
}, },
initializeCategories(state) { initializeCategories(state) {

View file

@ -1,17 +1,51 @@
require("tns-core-modules/globals") require("tns-core-modules/globals")
import { ImageSource, ImageAsset } from "@nativescript/core" import { ImageSource } from "@nativescript/core"
var ImageCropper = require("nativescript-imagecropper").ImageCropper
global.onmessage = function({ data }) { global.onmessage = function({ data }) {
console.log(data)
let imgSavedToPath = data.imgSavedToPath let imgSavedToPath = data.imgSavedToPath
let imgAsset = new ImageAsset(data.imgFile) let imgPath = data.imgPath
imgAsset.options = { let screenWidth = data.screenWidth
width: 1200, let toolbarTextColor = data.toolbarTextColor
height: 1200, let toolbarColor = data.toolbarColor
keepAspectRatio: true,
} ImageSource.fromFile(imgPath).then((image) => {
ImageSource.fromAsset(imgAsset).then((imgData) => { ImageCropper.prototype
if (imgData.saveToFile(imgSavedToPath, "jpg", 75)) { .show(
global.postMessage("savedToFile") image,
} {
width: screenWidth,
height: screenWidth,
// compressionQuality: 75,
},
{
hideBottomControls: true,
toolbarTitle: "Crop photo",
statusBarColor: "#ff5200",
toolbarTextColor,
toolbarColor,
cropFrameColor: "#ff5200",
}
)
.then((cropped) => {
if (cropped.image.saveToFile(imgSavedToPath, "jpg", 75))
global.postMessage("savedToFile")
})
}) })
} }
// global.onmessage = function({ data }) {
// let imgSavedToPath = data.imgSavedToPath
// let imgAsset = new ImageAsset(data.imgFile)
// imgAsset.options = {
// width: 1200,
// height: 1200,
// keepAspectRatio: true,
// }
// ImageSource.fromAsset(imgAsset).then((imgData) => {
// if (imgData.saveToFile(imgSavedToPath, "jpg", 75)) {
// global.postMessage("savedToFile")
// }
// })
// }

2490
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,17 +9,22 @@
}, },
"dependencies": { "dependencies": {
"@nativescript-community/perms": "^2.1.1", "@nativescript-community/perms": "^2.1.1",
"@nativescript-community/ui-material-activityindicator": "^5.0.30",
"@nativescript-community/ui-material-button": "^5.1.0",
"@nativescript-community/ui-material-floatingactionbutton": "^5.0.30",
"@nativescript-community/ui-material-progress": "^5.0.30",
"@nativescript-community/ui-material-ripple": "^5.0.30",
"@nativescript/core": "~7.0.0", "@nativescript/core": "~7.0.0",
"@nativescript/imagepicker": "^1.0.0",
"@nativescript/social-share": "^2.0.1",
"@nativescript/theme": "^3.0.0", "@nativescript/theme": "^3.0.0",
"@nativescript/webpack": "3.0.0",
"@nativescript/zip": "^5.0.0", "@nativescript/zip": "^5.0.0",
"@nstudio/nativescript-checkbox": "^2.0.4", "@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",
"nativescript-mediafilepicker": "^4.0.1", "nativescript-imagecropper": "^4.0.1",
"nativescript-plugin-filepicker": "^1.0.0", "nativescript-plugin-filepicker": "^1.0.0",
"nativescript-social-share-ns-7": "^11.6.0",
"nativescript-toast": "^2.0.0", "nativescript-toast": "^2.0.0",
"nativescript-ui-listview": "^9.0.4", "nativescript-ui-listview": "^9.0.4",
"nativescript-ui-sidedrawer": "^9.0.3", "nativescript-ui-sidedrawer": "^9.0.3",
@ -34,7 +39,8 @@
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"nativescript-vue-template-compiler": "^2.6.0", "nativescript-vue-template-compiler": "^2.6.0",
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
"vue-loader": "^15.9.1" "vue-loader": "^15.9.1",
"@nativescript/webpack": "~3.0.0"
}, },
"main": "main" "main": "main"
} }